Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c32481cc13 | |||
| a462047004 | |||
| 13711a710a | |||
| 01701b4d86 | |||
| be0d1fc84d | |||
| 2b07a718f7 | |||
| 59345b5d83 | |||
| 4db2517018 | |||
| 95573408d5 | |||
| d46e96b844 | |||
| 4d204a9c45 | |||
| 5c5ec70784 | |||
| 5564af7ba9 | |||
| b0991a8bf6 | |||
| de47d41874 | |||
| 3f7a4b3c38 | |||
| d0312af3d9 | |||
| 6972cefb1f | |||
| 0485c187ec | |||
| a1c268e469 | |||
| 387a683ff0 | |||
| 9a520637e9 | |||
| d37c72de80 | |||
| 0ab1fd27a6 | |||
| 652a437d14 | |||
| b1ea2cae30 | |||
| ba5e300ff6 | |||
| af25ef731b | |||
| d3111e1cad | |||
| b61b10856d | |||
| dab6548230 | |||
| 3f9256ce81 | |||
| 39ae15d9aa | |||
| e683b3c91a | |||
| b92b00fc4a | |||
| 89347bbdbe | |||
| 244c4977c6 | |||
| aa29a229ea | |||
| 2271b33cdd | |||
| 857a0c3fae | |||
| bd4b10f871 | |||
| e1ac05e540 | |||
| 629e82353a | |||
| 9d189b2550 | |||
| 5c32fc9b8c | |||
| fd470c97f7 | |||
| 823ef428c1 | |||
| 67b4d1a2e7 | |||
| f13188f812 | |||
| 64d308402d | |||
| 6b477a6b92 | |||
| 574a4f5b34 | |||
| 6313515291 | |||
| c3abc515dc | |||
| d9fb1db793 | |||
| c19aec52a3 | |||
| cc383eb5de | |||
| e9d27cb7cc | |||
| 6a0cc560f5 | |||
| ac7b0a2806 | |||
| 71dd94b76f | |||
| a7f748bbb5 | |||
| 4b577b0644 | |||
| d6005ac502 | |||
| e07f6ae46b | |||
| 363c9b01d4 | |||
| 0356d837e0 | |||
| af71952313 | |||
| 789bf60fbd | |||
| bd8f8941bc | |||
| eec5647b4b | |||
| be15092a8a | |||
| 0d49766425 | |||
| 3e0cffdfbc | |||
| c6cb66b42a | |||
| 8cf35dd34d | |||
| 691f3ccc97 | |||
| 2d660efb78 | |||
| a8a5303f7b | |||
| 3b3db58a20 | |||
| bcffc1b1e3 | |||
| 9ba90f1bd5 | |||
| 9686bdca95 | |||
| ad9ee60a5b | |||
| 68252ce102 | |||
| afd776e836 |
@@ -12,4 +12,8 @@
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
|
||||
*.bundle
|
||||
|
||||
.DS_Store
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.6
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 170
|
||||
|
||||
Metrics/BlockLength:
|
||||
Max: 250
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 1000
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 70
|
||||
|
||||
Metrics/MethodLength:
|
||||
Max: 100
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Max: 1000
|
||||
|
||||
Metrics/ParameterLists:
|
||||
Max: 50
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 80
|
||||
|
||||
Layout/SpaceInsideBlockBraces:
|
||||
EnforcedStyle: no_space
|
||||
SpaceBeforeBlockParameters: false
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/NumericPredicate:
|
||||
EnforcedStyle: comparison
|
||||
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
|
||||
Style/SpecialGlobalVars:
|
||||
EnforcedStyle: use_perl_names
|
||||
@@ -1,2 +1,2 @@
|
||||
source "https://rubygems.org"
|
||||
source 'https://rubygems.org'
|
||||
gemspec
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# mikunyan
|
||||
|
||||
Library to deserialize Unity AssetBundle files (\*.unity3d) and asset files.
|
||||
A Ruby library to deserialize AssetBundle files (\*.unity3d) and asset files of Unity.
|
||||
|
||||
The name “Mikunyan” is derived from [Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
|
||||
|
||||
Ruby-Doc: http://www.rubydoc.info/gems/mikunyan/
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -33,67 +37,67 @@ If you want to install development build:
|
||||
```ruby
|
||||
require 'mikunyan'
|
||||
|
||||
# load AssetBundle
|
||||
# load an AssetBundle file
|
||||
bundle = Mikunyan::AssetBundle.file(filename)
|
||||
|
||||
# you can load AssetBundle from blob
|
||||
# you can also load a bundle from blob
|
||||
# bundle = Mikunyan::AssetBundle.load(blob)
|
||||
|
||||
# select asset (normaly only one asset)
|
||||
# select asset (a bundle normally contains only one asset)
|
||||
asset = bundle.assets[0]
|
||||
|
||||
# or you can directly load asset
|
||||
# or you can directly load an asset from an asset file
|
||||
# asset = Mikunyan::Asset.file(filename)
|
||||
|
||||
# object list
|
||||
list = asset.objects
|
||||
# get a list of objects
|
||||
objects = asset.objects
|
||||
|
||||
# object PathIds
|
||||
# get PathIds of objects
|
||||
path_ids = asset.path_ids
|
||||
|
||||
# object container table (if available)
|
||||
# get an container table of objects (if available)
|
||||
containers = asset.containers
|
||||
|
||||
# load object (Mikunyan::ObjectValue)
|
||||
obj = asset.parse_object(path_ids[0])
|
||||
# load an object (Mikunyan::ObjectValue)
|
||||
obj = asset.parse_object(objects[0])
|
||||
|
||||
# simplified structure (based on Hash)
|
||||
obj_hash = asset.parse_object_simple(path_ids[0])
|
||||
# load an object to Ruby data structures
|
||||
obj_hash = asset.parse_object_simple(objects[0])
|
||||
|
||||
# hash can be easily serialized to json
|
||||
# a Hash can be serialized to JSON
|
||||
require 'json'
|
||||
obj_hash.to_json
|
||||
```
|
||||
|
||||
### Mikunyan::ObjectValue
|
||||
|
||||
`Mikunyan::ObjectValue` can be 3 types. Value, array and map.
|
||||
`Mikunyan::ObjectValue` is formed in 3 types: value, array, and key-value table.
|
||||
|
||||
```ruby
|
||||
# You can get whether obj is value or not
|
||||
# get whether obj is value or not
|
||||
obj.value?
|
||||
|
||||
# get value
|
||||
# get a value
|
||||
obj.value
|
||||
|
||||
# same as obj.value
|
||||
obj[]
|
||||
|
||||
|
||||
# You can get whether obj is array or not
|
||||
# get whether obj is array or not
|
||||
obj.array?
|
||||
|
||||
# get array
|
||||
# get an array
|
||||
obj.value
|
||||
|
||||
# you can directly access by index
|
||||
obj[0]
|
||||
|
||||
|
||||
# If obj is map, you can get keys
|
||||
# get keys (if obj is key-value table)
|
||||
obj.keys
|
||||
|
||||
# get child object
|
||||
# get child objects
|
||||
obj[key]
|
||||
|
||||
# same as obj[key]
|
||||
@@ -102,59 +106,156 @@ obj.key
|
||||
|
||||
### Unpack Texture2D
|
||||
|
||||
You can get png file directly from Texture2D asset. Output object's class is `ChunkyPNG::Image`.
|
||||
Mikunyan generates `ChunkyPNG::Image` images directly from Texture2D objects.
|
||||
|
||||
Some basic texture formats (1--5, 7, 9, 13--20, 22, 62, and 63), ETC_RGB4 (34), ETC2 (45, 47), and ASTC (48--59) are available.
|
||||
Mikunyan can decode images in basic texture formats (1–5, 7, 9, 13–20, 22, 62, 63), DXT1 (10), DXT5 (12), PVRTC1 (30–33), ETC (34), EAC (41–44), ETC2 (45–47), ASTC (48–59), HDR ASTC (66–71), or Crunched format (28, 29, 64, 65).
|
||||
|
||||
```ruby
|
||||
require 'mikunyan/decoders'
|
||||
|
||||
# get some Texture2D asset
|
||||
obj = asset.parse_object(path_ids[1])
|
||||
|
||||
# you can get Image object
|
||||
img = Mikunyan::ImageDecoder.decode_object(obj)
|
||||
# you can get image data
|
||||
img = obj.generate_png
|
||||
|
||||
# save it!
|
||||
img.save('mikunyan.png')
|
||||
```
|
||||
|
||||
Mikunyan cannot decode ASTC with HDR data. Use `Mikunyan::ImageDecoder.create_astc_file` instead.
|
||||
### JSON / YAML Outputter
|
||||
|
||||
### Json / YAML Outputer
|
||||
|
||||
`mikunyan-json` is an executable command for converting unity3d to json.
|
||||
`mikunyan-json` is an executable command for converting unity3d to JSON.
|
||||
|
||||
$ mikunyan-json bundle.unity3d > bundle.json
|
||||
|
||||
Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
|
||||
- `--pretty` (`-p`): prettify output json (`mikunyan-json` only)
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBundle but Asset
|
||||
- `--pretty` (`-p`): prettify output JSON
|
||||
- `--yaml` (`-y`): YAML mode
|
||||
|
||||
### Image Outputter
|
||||
|
||||
`mikunyan-image` is an executable command for unpacking images from unity3d.
|
||||
|
||||
$ mikunyan-image bundle.unity3d
|
||||
|
||||
The command generates JSON text, which contains information about unpacked images.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "bg_x",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": 56,
|
||||
"path_id": -8556635666641176453
|
||||
},
|
||||
{
|
||||
"name": "bg",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"format": 56,
|
||||
"path_id": -1848302546424191165
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If the option `--sprite` is specified, `mikunyan-image` will output sprites. The logged JSON also contains information about sprites.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "bg_x",
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"format": 56,
|
||||
"path_id": -8556635666641176453,
|
||||
"sprites": [
|
||||
{
|
||||
"name": "bg_4",
|
||||
"x": 171.0,
|
||||
"y": 1.0,
|
||||
"width": 168.0,
|
||||
"height": 510.0,
|
||||
"path_id": -9129589624490902606
|
||||
},
|
||||
{
|
||||
"name": "bg_5",
|
||||
"x": 341.0,
|
||||
"y": 45.0,
|
||||
"width": 168.0,
|
||||
"height": 210.0,
|
||||
"path_id": -4692216110975580946
|
||||
},
|
||||
{
|
||||
"name": "bg_2",
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"width": 168.0,
|
||||
"height": 510.0,
|
||||
"path_id": 5129117526830897711
|
||||
},
|
||||
{
|
||||
"name": "bg_3",
|
||||
"x": 341.0,
|
||||
"y": 301.0,
|
||||
"width": 168.0,
|
||||
"height": 210.0,
|
||||
"path_id": 8564534684796303817
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bg",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"format": 56,
|
||||
"path_id": -1848302546424191165,
|
||||
"sprites": [
|
||||
{
|
||||
"name": "bg_1",
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"width": 720.0,
|
||||
"height": 258.0,
|
||||
"path_id": -3411127056098763138
|
||||
},
|
||||
{
|
||||
"name": "bg_0",
|
||||
"x": 1.0,
|
||||
"y": 303.0,
|
||||
"width": 1022.0,
|
||||
"height": 720.0,
|
||||
"path_id": 7486118431221564872
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBundle but Asset
|
||||
- `--outputdir` (`-o`): specify an output directory (default is a basename of input file without an extension)
|
||||
- `--sprite` (`-s`): output sprites instead of textures
|
||||
- `--pretty` (`-p`): prettify output JSON
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [json](https://rubygems.org/gems/json)
|
||||
- [extlz4](https://rubygems.org/gems/extlz4)
|
||||
- [extlzma2](https://rubygems.org/gems/extlzma2)
|
||||
- extlzma2 requires liblzma. You may need a `--with-liblzma-dir=` argument to install extlzma2.
|
||||
- [bin_utils](https://rubygems.org/gems/bin_utils)
|
||||
- [chunky_png](https://rubygems.org/gems/chunky_png)
|
||||
|
||||
Mikunyan uses [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
||||
|
||||
## FAQ
|
||||
Note: extlz4 0.3 (current version) has a fatal bug because of LZ4 v1.9.0. In case of SIGSEGV in extlz4, you need to build extlz4 with the latest LZ4.
|
||||
|
||||
### Sometimes unpacking fails
|
||||
## Implementation in other languages
|
||||
|
||||
I'm sorry...
|
||||
|
||||
### Can I unpack Mesh files?
|
||||
|
||||
It's hard work for me...
|
||||
|
||||
### What mikunyan comes from?
|
||||
|
||||
[Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
|
||||
- TypeScript: [shibunyan](https://github.com/AnemoneStar/shibunyan)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -163,3 +264,11 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Ishoti
|
||||
## License
|
||||
|
||||
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This project contains following softwares.
|
||||
|
||||
- [Unity's crunch/crnlib](https://github.com/Unity-Technologies/crunch) (zlib License)
|
||||
- [FP16](https://github.com/Maratyszcza/FP16/) (MIT License)
|
||||
- [endianness.h](https://gist.github.com/jtbr/7a43e6281e6cca353b33ee501421860c) (MIT License)
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
require "bundler/gem_tasks"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rake/extensiontask'
|
||||
|
||||
task :scream do
|
||||
puts "みくは自分を曲げないよ!"
|
||||
puts 'みくは自分を曲げないよ!'
|
||||
end
|
||||
|
||||
task build: :compile
|
||||
|
||||
ext_dirs = %w[decoders/native decoders/crunch]
|
||||
|
||||
ext_dirs.each do |dir|
|
||||
Rake::ExtensionTask.new(dir) do |ext|
|
||||
ext.lib_dir = "lib/mikunyan/#{File.dirname(dir)}"
|
||||
end
|
||||
end
|
||||
|
||||
task compile: ext_dirs.map {|e| "compile:#{e}".to_sym}
|
||||
|
||||
task default: %i[clobber compile spec]
|
||||
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan'
|
||||
require 'fileutils'
|
||||
begin
|
||||
require 'usamin'
|
||||
require 'usamin/overwrite'
|
||||
rescue LoadError
|
||||
require 'json'
|
||||
end
|
||||
|
||||
opts = { as_asset: false, outputdir: nil, sprite: false, pretty: false }
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
if ARGV[i].start_with?('-')
|
||||
case ARGV[i]
|
||||
when '--as-asset', '-a'
|
||||
opts[:as_asset] = true
|
||||
when '--outputdir', '-o'
|
||||
i += 1
|
||||
opts[:outputdir] = ARGV[i]
|
||||
when '--sprite', '-s'
|
||||
opts[:sprite] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
end
|
||||
else
|
||||
arg ||= ARGV[i]
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
unless arg
|
||||
warn('Input file is not specified')
|
||||
exit(1)
|
||||
end
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
if opts[:as_asset]
|
||||
assets = [Mikunyan::Asset.file(arg)]
|
||||
else
|
||||
bundle = Mikunyan::AssetBundle.file(arg)
|
||||
assets = bundle.assets
|
||||
end
|
||||
|
||||
outdir = opts[:outputdir] || File.basename(arg, '.*')
|
||||
FileUtils.mkpath(outdir)
|
||||
|
||||
if opts[:sprite]
|
||||
textures = {}
|
||||
textures_meta = {}
|
||||
assets.each do |asset|
|
||||
json = {}
|
||||
|
||||
asset.each_object do |obj|
|
||||
next unless obj.type == 'Sprite'
|
||||
next unless obj.klass
|
||||
obj = obj.parse
|
||||
next unless obj&.m_RD&.texture
|
||||
file_id = obj.m_RD.texture.m_FileID.value
|
||||
texture_asset = file_id == 0 ? asset : bundle && bundle[asset.references[file_id - 1].file_path]
|
||||
texture_id = obj.m_RD.texture.m_PathID.value
|
||||
next unless texture_asset && texture_id
|
||||
|
||||
unless textures.dig(texture_asset, texture_id)
|
||||
texture_obj = texture_asset.parse_object(texture_id)
|
||||
if texture_obj.is_a?(Mikunyan::CustomTypes::Texture2D)
|
||||
textures[texture_asset] ||= {}
|
||||
textures[texture_asset][texture_id] = texture_obj.generate_png
|
||||
textures_meta[texture_asset] ||= {}
|
||||
textures_meta[texture_asset][texture_id] = {
|
||||
name: texture_obj.m_Name&.value, width: texture_obj.m_Width&.value, height: texture_obj.m_Height&.value,
|
||||
format: texture_obj.m_TextureFormat&.value, asset: texture_asset.name, path_id: texture_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
next unless textures_meta[texture_asset][texture_id]
|
||||
|
||||
unless json.key?([file_id, texture_id])
|
||||
json[[file_id, texture_id]] = textures_meta[texture_asset][texture_id].dup
|
||||
json[[file_id, texture_id]][:sprites] = []
|
||||
end
|
||||
|
||||
x = obj.m_Rect&.x&.value
|
||||
y = obj.m_Rect&.y&.value
|
||||
width = obj.m_Rect&.width&.value
|
||||
height = obj.m_Rect&.height&.value
|
||||
|
||||
json[[file_id, texture_id]][:sprites] << { name: obj.object_name, x: x, y: y, width: width, height: height, path_id: obj.path_id }
|
||||
|
||||
texture = textures[texture_asset][texture_id]
|
||||
next unless texture && x && y && width && height
|
||||
texture.crop(x.round, (texture.height - height - y).round, width.round, height.round).save("#{outdir}/#{obj.object_name}.png")
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json.values) : JSON.generate(json.values)
|
||||
end
|
||||
else
|
||||
assets.each do |asset|
|
||||
json = []
|
||||
asset.each_object do |obj|
|
||||
next unless obj.type == 'Texture2D'
|
||||
next unless obj.klass
|
||||
obj = obj.parse
|
||||
next unless obj.is_a?(Mikunyan::CustomTypes::Texture2D)
|
||||
json << {
|
||||
name: obj.object_name, width: obj.width, height: obj.height,
|
||||
format: obj.texture_format, path_id: obj.path_id
|
||||
}
|
||||
obj.generate_png&.save("#{outdir}/#{obj.object_name}.png")
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json) : JSON.generate(json)
|
||||
end
|
||||
end
|
||||
+45
-59
@@ -1,84 +1,70 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan'
|
||||
require 'base64'
|
||||
|
||||
def obj64(obj)
|
||||
if obj.class == Hash
|
||||
obj.map{|k, v| [k, obj64(v)]}.to_h
|
||||
elsif obj.class == Array
|
||||
obj.map{|e| obj64(e)}
|
||||
elsif obj.class == String
|
||||
if obj.encoding == Encoding::UTF_8
|
||||
obj
|
||||
else
|
||||
Base64::strict_encode64(obj)
|
||||
end
|
||||
if obj.is_a?(Hash)
|
||||
obj.map{|k, v| [k, obj64(v)]}.to_h
|
||||
elsif obj.is_a?(Array)
|
||||
obj.map{|e| obj64(e)}
|
||||
elsif obj.is_a?(String)
|
||||
if obj.encoding == Encoding::UTF_8
|
||||
obj
|
||||
else
|
||||
obj
|
||||
Base64.strict_encode64(obj)
|
||||
end
|
||||
else
|
||||
obj
|
||||
end
|
||||
end
|
||||
|
||||
opts = {:as_asset => false, :pretty => false, :yaml => false}
|
||||
opts = { as_asset: false, pretty: false, yaml: false }
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
if ARGV[i].start_with?('-')
|
||||
case ARGV[i]
|
||||
when '--as-asset', '-a'
|
||||
opts[:as_asset] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
when '--yaml', '-y'
|
||||
opts[:yaml] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
end
|
||||
if ARGV[i].start_with?('-')
|
||||
case ARGV[i]
|
||||
when '--as-asset', '-a'
|
||||
opts[:as_asset] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
when '--yaml', '-y'
|
||||
opts[:yaml] = true
|
||||
else
|
||||
arg = ARGV[i] unless arg
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
end
|
||||
i += 1
|
||||
else
|
||||
arg ||= ARGV[i]
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
if opts[:pretty] && opts[:yaml]
|
||||
warn("Option --pretty is ignored if --yaml is specified.")
|
||||
end
|
||||
warn('Option --pretty is ignored if --yaml is specified.') if opts[:pretty] && opts[:yaml]
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
assets = {}
|
||||
|
||||
if opts[:as_asset]
|
||||
asset = Mikunyan::Asset.file(arg, arg.match(/([^\/]*?)(\.[^.]*)?\z/)[1])
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
else
|
||||
bundle = Mikunyan::AssetBundle.file(arg)
|
||||
bundle.assets.each do |asset|
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
end
|
||||
end
|
||||
assets = opts[:as_asset] ? [Mikunyan::Asset.file(arg)] : Mikunyan::AssetBundle.file(arg).assets
|
||||
assets = assets.map{|asset| [asset.name, asset.each_object.map(&:parse_simple)]}.to_h
|
||||
|
||||
if opts[:yaml]
|
||||
require 'yaml'
|
||||
puts YAML.dump(assets)
|
||||
require 'yaml'
|
||||
puts YAML.dump(assets)
|
||||
else
|
||||
begin
|
||||
require 'usamin'
|
||||
require 'usamin/overwrite'
|
||||
rescue LoadError
|
||||
require 'json'
|
||||
assets = assets.map{|k, v| [k, obj64(v)]}.to_h
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
end
|
||||
end
|
||||
assets = assets.map{|k, v| [k, obj64(v)]}.to_h
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
end
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,286 @@
|
||||
#ifndef CRND_INCLUDE_CRN_DEFS_H
|
||||
#define CRND_INCLUDE_CRN_DEFS_H
|
||||
|
||||
// Include crnlib.h (only to bring in some basic CRN-related types).
|
||||
#include "crnlib.h"
|
||||
#include <cstdint>
|
||||
|
||||
#define CRND_LIB_VERSION 104
|
||||
#define CRND_VERSION_STRING "01.04"
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define CRND_BUILD_DEBUG
|
||||
#else
|
||||
#define CRND_BUILD_RELEASE
|
||||
#endif
|
||||
|
||||
// CRN decompression API
|
||||
namespace crnd {
|
||||
typedef uint8_t uint8;
|
||||
typedef int8_t int8;
|
||||
typedef uint16_t uint16;
|
||||
typedef int16_t int16;
|
||||
typedef uint32_t uint32;
|
||||
typedef int32_t int32;
|
||||
typedef uint64_t uint64;
|
||||
typedef int64_t int64;
|
||||
typedef unsigned int uint;
|
||||
|
||||
// The crnd library assumes all allocation blocks have at least CRND_MIN_ALLOC_ALIGNMENT alignment.
|
||||
const uint32 CRND_MIN_ALLOC_ALIGNMENT = sizeof(uint32) * 2U;
|
||||
|
||||
// realloc callback:
|
||||
// Used to allocate, resize, or free memory blocks.
|
||||
// If p is NULL, the realloc function attempts to allocate a block of at least size bytes. Returns NULL on out of memory.
|
||||
// *pActual_size must be set to the actual size of the allocated block, which must be greater than or equal to the requested size.
|
||||
// If p is not NULL, and size is 0, the realloc function frees the specified block, and always returns NULL. *pActual_size should be set to 0.
|
||||
// If p is not NULL, and size is non-zero, the realloc function attempts to resize the specified block:
|
||||
// If movable is false, the realloc function attempts to shrink or expand the block in-place. NULL is returned if the block cannot be resized in place, or if the
|
||||
// underlying heap implementation doesn't support in-place resizing. Otherwise, the pointer to the original block is returned.
|
||||
// If movable is true, it is permissible to move the block's contents if it cannot be resized in place. NULL is returned if the block cannot be resized in place, and there
|
||||
// is not enough memory to relocate the block.
|
||||
// In all cases, *pActual_size must be set to the actual size of the allocated block, whether it was successfully resized or not.
|
||||
typedef void* (*crnd_realloc_func)(void* p, size_t size, size_t* pActual_size, bool movable, void* pUser_data);
|
||||
|
||||
// msize callback: Returns the size of the memory block in bytes, or 0 if the pointer or block is invalid.
|
||||
typedef size_t (*crnd_msize_func)(void* p, void* pUser_data);
|
||||
|
||||
// crnd_set_memory_callbacks() - Use to override the crnd library's memory allocation functions.
|
||||
// If any input parameters are NULL, the memory callback functions are reset to the default functions.
|
||||
// The default functions call malloc(), free(), _msize(), _expand(), etc.
|
||||
void crnd_set_memory_callbacks(crnd_realloc_func pRealloc, crnd_msize_func pMSize, void* pUser_data);
|
||||
|
||||
struct crn_file_info {
|
||||
inline crn_file_info()
|
||||
: m_struct_size(sizeof(crn_file_info)) {}
|
||||
|
||||
uint32 m_struct_size;
|
||||
uint32 m_actual_data_size;
|
||||
uint32 m_header_size;
|
||||
uint32 m_total_palette_size;
|
||||
uint32 m_tables_size;
|
||||
uint32 m_levels;
|
||||
uint32 m_level_compressed_size[cCRNMaxLevels];
|
||||
uint32 m_color_endpoint_palette_entries;
|
||||
uint32 m_color_selector_palette_entries;
|
||||
uint32 m_alpha_endpoint_palette_entries;
|
||||
uint32 m_alpha_selector_palette_entries;
|
||||
};
|
||||
|
||||
struct crn_texture_info {
|
||||
inline crn_texture_info()
|
||||
: m_struct_size(sizeof(crn_texture_info)) {}
|
||||
|
||||
uint32 m_struct_size;
|
||||
uint32 m_width;
|
||||
uint32 m_height;
|
||||
uint32 m_levels;
|
||||
uint32 m_faces;
|
||||
uint32 m_bytes_per_block;
|
||||
uint32 m_userdata0;
|
||||
uint32 m_userdata1;
|
||||
crn_format m_format;
|
||||
};
|
||||
|
||||
struct crn_level_info {
|
||||
inline crn_level_info()
|
||||
: m_struct_size(sizeof(crn_level_info)) {}
|
||||
|
||||
uint32 m_struct_size;
|
||||
uint32 m_width;
|
||||
uint32 m_height;
|
||||
uint32 m_faces;
|
||||
uint32 m_blocks_x;
|
||||
uint32 m_blocks_y;
|
||||
uint32 m_bytes_per_block;
|
||||
crn_format m_format;
|
||||
};
|
||||
|
||||
// Returns the FOURCC format code corresponding to the specified CRN format.
|
||||
uint32 crnd_crn_format_to_fourcc(crn_format fmt);
|
||||
|
||||
// Returns the fundamental GPU format given a potentially swizzled DXT5 crn_format.
|
||||
crn_format crnd_get_fundamental_dxt_format(crn_format fmt);
|
||||
|
||||
// Returns the size of the crn_format in bits/texel (either 4 or 8).
|
||||
uint32 crnd_get_crn_format_bits_per_texel(crn_format fmt);
|
||||
|
||||
// Returns the number of bytes per DXTn block (8 or 16).
|
||||
uint32 crnd_get_bytes_per_dxt_block(crn_format fmt);
|
||||
|
||||
// Validates the entire file by checking the header and data CRC's.
|
||||
// This is not something you want to be doing much!
|
||||
// The crn_file_info.m_struct_size field must be set before calling this function.
|
||||
bool crnd_validate_file(const void* pData, uint32 data_size, crn_file_info* pFile_info);
|
||||
|
||||
// Retrieves texture information from the CRN file.
|
||||
// The crn_texture_info.m_struct_size field must be set before calling this function.
|
||||
bool crnd_get_texture_info(const void* pData, uint32 data_size, crn_texture_info* pTexture_info);
|
||||
|
||||
// Retrieves mipmap level specific information from the CRN file.
|
||||
// The crn_level_info.m_struct_size field must be set before calling this function.
|
||||
bool crnd_get_level_info(const void* pData, uint32 data_size, uint32 level_index, crn_level_info* pLevel_info);
|
||||
|
||||
// Transcode/unpack context handle.
|
||||
typedef void* crnd_unpack_context;
|
||||
|
||||
// crnd_unpack_begin() - Decompresses the texture's decoder tables and endpoint/selector palettes.
|
||||
// Once you call this function, you may call crnd_unpack_level() to unpack one or more mip levels.
|
||||
// Don't call this once per mip level (unless you absolutely must)!
|
||||
// This function allocates enough memory to hold: Huffman decompression tables, and the endpoint/selector palettes (color and/or alpha).
|
||||
// Worst case allocation is approx. 200k, assuming all palettes contain 8192 entries.
|
||||
// pData must point to a buffer holding all of the compressed .CRN file data.
|
||||
// This buffer must be stable until crnd_unpack_end() is called.
|
||||
// Returns NULL if out of memory, or if any of the input parameters are invalid.
|
||||
crnd_unpack_context crnd_unpack_begin(const void* pData, uint32 data_size);
|
||||
|
||||
// Returns a pointer to the compressed .CRN data associated with a crnd_unpack_context.
|
||||
// Returns false if any of the input parameters are invalid.
|
||||
bool crnd_get_data(crnd_unpack_context pContext, const void** ppData, uint32* pData_size);
|
||||
|
||||
// crnd_unpack_level() - Transcodes the specified mipmap level to a destination buffer in cached or write combined memory.
|
||||
// pContext - Context created by a call to crnd_unpack_begin().
|
||||
// ppDst - A pointer to an array of 1 or 6 destination buffer pointers. Cubemaps require an array of 6 pointers, 2D textures require an array of 1 pointer.
|
||||
// dst_size_in_bytes - Optional size of each destination buffer. Only used for debugging - OK to set to UINT32_MAX.
|
||||
// row_pitch_in_bytes - The pitch in bytes from one row of DXT blocks to the next. Must be a multiple of 4.
|
||||
// level_index - mipmap level index, where 0 is the largest/first level.
|
||||
// Returns false if any of the input parameters, or the compressed stream, are invalid.
|
||||
// This function does not allocate any memory.
|
||||
bool crnd_unpack_level(
|
||||
crnd_unpack_context pContext,
|
||||
void** ppDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes,
|
||||
uint32 level_index);
|
||||
|
||||
// crnd_unpack_level_segmented() - Unpacks the specified mipmap level from a "segmented" CRN file.
|
||||
// See the crnd_create_segmented_file() API below.
|
||||
// Segmented files allow the user to control where the compressed mipmap data is stored.
|
||||
bool crnd_unpack_level_segmented(
|
||||
crnd_unpack_context pContext,
|
||||
const void* pSrc, uint32 src_size_in_bytes,
|
||||
void** ppDst, uint32 dst_size_in_bytes, uint32 row_pitch_in_bytes,
|
||||
uint32 level_index);
|
||||
|
||||
// crnd_unpack_end() - Frees the decompress tables and unpacked palettes associated with the specified unpack context.
|
||||
// Returns false if the context is NULL, or if it points to an invalid context.
|
||||
// This function frees all memory associated with the context.
|
||||
bool crnd_unpack_end(crnd_unpack_context pContext);
|
||||
|
||||
// The following API's allow the user to create "segmented" CRN files. A segmented file contains multiple pieces:
|
||||
// - Base data: Header + compression tables
|
||||
// - Level data: Individual mipmap levels
|
||||
// This allows mipmap levels from multiple CRN files to be tightly packed together into single files.
|
||||
|
||||
// Returns a pointer to the level's compressed data, and optionally returns the level's compressed data size if pSize is not NULL.
|
||||
const void* crnd_get_level_data(const void* pData, uint32 data_size, uint32 level_index, uint32* pSize);
|
||||
|
||||
// Returns the compressed size of the texture's header and compression tables (but no levels).
|
||||
uint32 crnd_get_segmented_file_size(const void* pData, uint32 data_size);
|
||||
|
||||
// Creates a "segmented" CRN texture from a normal CRN texture. The new texture will be created at pBase_data, and will be crnd_get_base_data_size() bytes long.
|
||||
// base_data_size must be >= crnd_get_base_data_size().
|
||||
// The base data will contain the CRN header and compression tables, but no mipmap data.
|
||||
bool crnd_create_segmented_file(const void* pData, uint32 data_size, void* pBase_data, uint base_data_size);
|
||||
|
||||
} // namespace crnd
|
||||
|
||||
// Low-level CRN file header cracking.
|
||||
namespace crnd {
|
||||
template <unsigned int N>
|
||||
struct crn_packed_uint {
|
||||
inline crn_packed_uint() {}
|
||||
|
||||
inline crn_packed_uint(unsigned int val) { *this = val; }
|
||||
|
||||
inline crn_packed_uint(const crn_packed_uint& other) { *this = other; }
|
||||
|
||||
inline crn_packed_uint& operator=(const crn_packed_uint& rhs) {
|
||||
if (this != &rhs)
|
||||
memcpy(m_buf, rhs.m_buf, sizeof(m_buf));
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline crn_packed_uint& operator=(unsigned int val) {
|
||||
//CRND_ASSERT((N == 4U) || (val < (1U << (N * 8U))));
|
||||
|
||||
val <<= (8U * (4U - N));
|
||||
|
||||
for (unsigned int i = 0; i < N; i++) {
|
||||
m_buf[i] = static_cast<unsigned char>(val >> 24U);
|
||||
val <<= 8U;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline operator unsigned int() const {
|
||||
switch (N) {
|
||||
case 1:
|
||||
return m_buf[0];
|
||||
case 2:
|
||||
return (m_buf[0] << 8U) | m_buf[1];
|
||||
case 3:
|
||||
return (m_buf[0] << 16U) | (m_buf[1] << 8U) | (m_buf[2]);
|
||||
default:
|
||||
return (m_buf[0] << 24U) | (m_buf[1] << 16U) | (m_buf[2] << 8U) | (m_buf[3]);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char m_buf[N];
|
||||
};
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
struct crn_palette {
|
||||
crn_packed_uint<3> m_ofs;
|
||||
crn_packed_uint<3> m_size;
|
||||
crn_packed_uint<2> m_num;
|
||||
};
|
||||
|
||||
enum crn_header_flags {
|
||||
// If set, the compressed mipmap level data is not located after the file's base data - it will be separately managed by the user instead.
|
||||
cCRNHeaderFlagSegmented = 1
|
||||
};
|
||||
|
||||
struct crn_header {
|
||||
enum { cCRNSigValue = ('H' << 8) | 'x' };
|
||||
|
||||
crn_packed_uint<2> m_sig;
|
||||
crn_packed_uint<2> m_header_size;
|
||||
crn_packed_uint<2> m_header_crc16;
|
||||
|
||||
crn_packed_uint<4> m_data_size;
|
||||
crn_packed_uint<2> m_data_crc16;
|
||||
|
||||
crn_packed_uint<2> m_width;
|
||||
crn_packed_uint<2> m_height;
|
||||
|
||||
crn_packed_uint<1> m_levels;
|
||||
crn_packed_uint<1> m_faces;
|
||||
|
||||
crn_packed_uint<1> m_format;
|
||||
crn_packed_uint<2> m_flags;
|
||||
|
||||
crn_packed_uint<4> m_reserved;
|
||||
crn_packed_uint<4> m_userdata0;
|
||||
crn_packed_uint<4> m_userdata1;
|
||||
|
||||
crn_palette m_color_endpoints;
|
||||
crn_palette m_color_selectors;
|
||||
|
||||
crn_palette m_alpha_endpoints;
|
||||
crn_palette m_alpha_selectors;
|
||||
|
||||
crn_packed_uint<2> m_tables_size;
|
||||
crn_packed_uint<3> m_tables_ofs;
|
||||
|
||||
// m_level_ofs[] is actually an array of offsets: m_level_ofs[m_levels]
|
||||
crn_packed_uint<4> m_level_ofs[1];
|
||||
};
|
||||
|
||||
const unsigned int cCRNHeaderMinSize = 62U;
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace crnd
|
||||
|
||||
#endif // CRND_INCLUDE_CRN_DEFS_H
|
||||
@@ -0,0 +1,642 @@
|
||||
// File: crnlib.h - Advanced DXTn texture compression library.
|
||||
// Copyright (c) 2010-2016 Richard Geldreich, Jr. and Binomial LLC
|
||||
// See copyright notice and license at the end of this file.
|
||||
//
|
||||
// This header file contains the public crnlib declarations for DXTn,
|
||||
// clustered DXTn, and CRN compression/decompression.
|
||||
//
|
||||
// Note: This library does NOT need to be linked into your game executable if
|
||||
// all you want to do is transcode .CRN files to raw DXTn bits at run-time.
|
||||
// The crn_decomp.h header file library contains all the code necessary for
|
||||
// decompression.
|
||||
//
|
||||
// Important: If compiling with gcc, be sure strict aliasing is disabled: -fno-strict-aliasing
|
||||
#ifndef CRNLIB_H
|
||||
#define CRNLIB_H
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4127) // conditional expression is constant
|
||||
#endif
|
||||
|
||||
#define CRNLIB_VERSION 104
|
||||
|
||||
#define CRNLIB_SUPPORT_ATI_COMPRESS 0
|
||||
#define CRNLIB_SUPPORT_SQUISH 0
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
typedef uint8_t crn_uint8;
|
||||
typedef uint16_t crn_uint16;
|
||||
typedef uint32_t crn_uint32;
|
||||
typedef int8_t crn_int8;
|
||||
typedef int16_t crn_int16;
|
||||
typedef int32_t crn_int32;
|
||||
typedef unsigned int crn_bool;
|
||||
|
||||
// crnlib can compress to these file types.
|
||||
enum crn_file_type {
|
||||
// .CRN
|
||||
cCRNFileTypeCRN = 0,
|
||||
|
||||
// .DDS using regular DXT or clustered DXT
|
||||
cCRNFileTypeDDS,
|
||||
|
||||
cCRNFileTypeForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// Supported compressed pixel formats.
|
||||
// Basically all the standard DX9 formats, with some swizzled DXT5 formats
|
||||
// (most of them supported by ATI's Compressonator), along with some ATI/X360 GPU specific formats.
|
||||
enum crn_format {
|
||||
cCRNFmtInvalid = -1,
|
||||
|
||||
cCRNFmtDXT1 = 0,
|
||||
|
||||
cCRNFmtFirstValid = cCRNFmtDXT1,
|
||||
|
||||
// cCRNFmtDXT3 is not currently supported when writing to CRN - only DDS.
|
||||
cCRNFmtDXT3,
|
||||
|
||||
cCRNFmtDXT5,
|
||||
|
||||
// Various DXT5 derivatives
|
||||
cCRNFmtDXT5_CCxY, // Luma-chroma
|
||||
cCRNFmtDXT5_xGxR, // Swizzled 2-component
|
||||
cCRNFmtDXT5_xGBR, // Swizzled 3-component
|
||||
cCRNFmtDXT5_AGBR, // Swizzled 4-component
|
||||
|
||||
// ATI 3DC and X360 DXN
|
||||
cCRNFmtDXN_XY,
|
||||
cCRNFmtDXN_YX,
|
||||
|
||||
// DXT5 alpha blocks only
|
||||
cCRNFmtDXT5A,
|
||||
|
||||
cCRNFmtETC1,
|
||||
cCRNFmtETC2,
|
||||
cCRNFmtETC2A,
|
||||
cCRNFmtETC1S,
|
||||
cCRNFmtETC2AS,
|
||||
|
||||
cCRNFmtTotal,
|
||||
|
||||
cCRNFmtForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// Various library/file format limits.
|
||||
enum crn_limits {
|
||||
// Max. mipmap level resolution on any axis.
|
||||
cCRNMaxLevelResolution = 4096,
|
||||
|
||||
cCRNMinPaletteSize = 8,
|
||||
cCRNMaxPaletteSize = 8192,
|
||||
|
||||
cCRNMaxFaces = 6,
|
||||
cCRNMaxLevels = 16,
|
||||
|
||||
cCRNMaxHelperThreads = 15,
|
||||
|
||||
cCRNMinQualityLevel = 0,
|
||||
cCRNMaxQualityLevel = 255
|
||||
};
|
||||
|
||||
// CRN/DDS compression flags.
|
||||
// See the m_flags member in the crn_comp_params struct, below.
|
||||
enum crn_comp_flags {
|
||||
// Enables perceptual colorspace distance metrics if set.
|
||||
// Important: Be sure to disable this when compressing non-sRGB colorspace images, like normal maps!
|
||||
// Default: Set
|
||||
cCRNCompFlagPerceptual = 1,
|
||||
|
||||
// Enables (up to) 8x8 macroblock usage if set. If disabled, only 4x4 blocks are allowed.
|
||||
// Compression ratio will be lower when disabled, but may cut down on blocky artifacts because the process used to determine
|
||||
// where large macroblocks can be used without artifacts isn't perfect.
|
||||
// Default: Set.
|
||||
cCRNCompFlagHierarchical = 2,
|
||||
|
||||
// cCRNCompFlagQuick disables several output file optimizations - intended for things like quicker previews.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagQuick = 4,
|
||||
|
||||
// DXT1: OK to use DXT1 alpha blocks for better quality or DXT1A transparency.
|
||||
// DXT5: OK to use both DXT5 block types.
|
||||
// Currently only used when writing to .DDS files, as .CRN uses only a subset of the possible DXTn block types.
|
||||
// Default: Set.
|
||||
cCRNCompFlagUseBothBlockTypes = 8,
|
||||
|
||||
// OK to use DXT1A transparent indices to encode black (assumes pixel shader ignores fetched alpha).
|
||||
// Currently only used when writing to .DDS files, .CRN never uses alpha blocks.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagUseTransparentIndicesForBlack = 16,
|
||||
|
||||
// Disables endpoint caching, for more deterministic output.
|
||||
// Currently only used when writing to .DDS files.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagDisableEndpointCaching = 32,
|
||||
|
||||
// If enabled, use the cCRNColorEndpointPaletteSize, etc. params to control the CRN palette sizes. Only useful when writing to .CRN files.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagManualPaletteSizes = 64,
|
||||
|
||||
// If enabled, DXT1A alpha blocks are used to encode single bit transparency.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagDXT1AForTransparency = 128,
|
||||
|
||||
// If enabled, the DXT1 compressor's color distance metric assumes the pixel shader will be converting the fetched RGB results to luma (Y part of YCbCr).
|
||||
// This increases quality when compressing grayscale images, because the compressor can spread the luma error amoung all three channels (i.e. it can generate blocks
|
||||
// with some chroma present if doing so will ultimately lead to lower luma error).
|
||||
// Only enable on grayscale source images.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagGrayscaleSampling = 256,
|
||||
|
||||
// If enabled, debug information will be output during compression.
|
||||
// Default: Not set.
|
||||
cCRNCompFlagDebugging = 0x80000000,
|
||||
|
||||
cCRNCompFlagForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// Controls DXTn quality vs. speed control - only used when compressing to .DDS.
|
||||
enum crn_dxt_quality {
|
||||
cCRNDXTQualitySuperFast,
|
||||
cCRNDXTQualityFast,
|
||||
cCRNDXTQualityNormal,
|
||||
cCRNDXTQualityBetter,
|
||||
cCRNDXTQualityUber,
|
||||
|
||||
cCRNDXTQualityTotal,
|
||||
|
||||
cCRNDXTQualityForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// Which DXTn compressor to use when compressing to plain (non-clustered) .DDS.
|
||||
enum crn_dxt_compressor_type {
|
||||
cCRNDXTCompressorCRN, // Use crnlib's ETC1 or DXTc block compressor (default, highest quality, comparable or better than ati_compress or squish, and crnlib's ETC1 is a lot fasterw with similiar quality to Erricson's)
|
||||
cCRNDXTCompressorCRNF, // Use crnlib's "fast" DXTc block compressor
|
||||
cCRNDXTCompressorRYG, // Use RYG's DXTc block compressor (low quality, but very fast)
|
||||
|
||||
#if CRNLIB_SUPPORT_ATI_COMPRESS
|
||||
cCRNDXTCompressorATI,
|
||||
#endif
|
||||
|
||||
#if CRNLIB_SUPPORT_SQUISH
|
||||
cCRNDXTCompressorSquish,
|
||||
#endif
|
||||
|
||||
cCRNTotalDXTCompressors,
|
||||
|
||||
cCRNDXTCompressorForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
// Progress callback function.
|
||||
// Processing will stop prematurely (and fail) if the callback returns false.
|
||||
// phase_index, total_phases - high level progress
|
||||
// subphase_index, total_subphases - progress within current phase
|
||||
typedef crn_bool (*crn_progress_callback_func)(crn_uint32 phase_index, crn_uint32 total_phases, crn_uint32 subphase_index, crn_uint32 total_subphases, void* pUser_data_ptr);
|
||||
|
||||
// CRN/DDS compression parameters struct.
|
||||
struct crn_comp_params {
|
||||
inline crn_comp_params() { clear(); }
|
||||
|
||||
// Clear struct to default parameters.
|
||||
inline void clear() {
|
||||
m_size_of_obj = sizeof(*this);
|
||||
m_file_type = cCRNFileTypeCRN;
|
||||
m_faces = 1;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_levels = 1;
|
||||
m_format = cCRNFmtDXT1;
|
||||
m_flags = cCRNCompFlagPerceptual | cCRNCompFlagHierarchical | cCRNCompFlagUseBothBlockTypes;
|
||||
|
||||
for (crn_uint32 f = 0; f < cCRNMaxFaces; f++)
|
||||
for (crn_uint32 l = 0; l < cCRNMaxLevels; l++)
|
||||
m_pImages[f][l] = NULL;
|
||||
|
||||
m_target_bitrate = 0.0f;
|
||||
m_quality_level = cCRNMaxQualityLevel;
|
||||
m_dxt1a_alpha_threshold = 128;
|
||||
m_dxt_quality = cCRNDXTQualityUber;
|
||||
m_dxt_compressor_type = cCRNDXTCompressorCRN;
|
||||
m_alpha_component = 3;
|
||||
|
||||
m_crn_adaptive_tile_color_psnr_derating = 2.0f;
|
||||
m_crn_adaptive_tile_alpha_psnr_derating = 2.0f;
|
||||
m_crn_color_endpoint_palette_size = 0;
|
||||
m_crn_color_selector_palette_size = 0;
|
||||
m_crn_alpha_endpoint_palette_size = 0;
|
||||
m_crn_alpha_selector_palette_size = 0;
|
||||
|
||||
m_num_helper_threads = 0;
|
||||
m_userdata0 = 0;
|
||||
m_userdata1 = 0;
|
||||
m_pProgress_func = NULL;
|
||||
m_pProgress_func_data = NULL;
|
||||
}
|
||||
|
||||
inline bool operator==(const crn_comp_params& rhs) const {
|
||||
#define CRNLIB_COMP(x) \
|
||||
do { \
|
||||
if ((x) != (rhs.x)) \
|
||||
return false; \
|
||||
} while (0)
|
||||
CRNLIB_COMP(m_size_of_obj);
|
||||
CRNLIB_COMP(m_file_type);
|
||||
CRNLIB_COMP(m_faces);
|
||||
CRNLIB_COMP(m_width);
|
||||
CRNLIB_COMP(m_height);
|
||||
CRNLIB_COMP(m_levels);
|
||||
CRNLIB_COMP(m_format);
|
||||
CRNLIB_COMP(m_flags);
|
||||
CRNLIB_COMP(m_target_bitrate);
|
||||
CRNLIB_COMP(m_quality_level);
|
||||
CRNLIB_COMP(m_dxt1a_alpha_threshold);
|
||||
CRNLIB_COMP(m_dxt_quality);
|
||||
CRNLIB_COMP(m_dxt_compressor_type);
|
||||
CRNLIB_COMP(m_alpha_component);
|
||||
CRNLIB_COMP(m_crn_adaptive_tile_color_psnr_derating);
|
||||
CRNLIB_COMP(m_crn_adaptive_tile_alpha_psnr_derating);
|
||||
CRNLIB_COMP(m_crn_color_endpoint_palette_size);
|
||||
CRNLIB_COMP(m_crn_color_selector_palette_size);
|
||||
CRNLIB_COMP(m_crn_alpha_endpoint_palette_size);
|
||||
CRNLIB_COMP(m_crn_alpha_selector_palette_size);
|
||||
CRNLIB_COMP(m_num_helper_threads);
|
||||
CRNLIB_COMP(m_userdata0);
|
||||
CRNLIB_COMP(m_userdata1);
|
||||
CRNLIB_COMP(m_pProgress_func);
|
||||
CRNLIB_COMP(m_pProgress_func_data);
|
||||
|
||||
for (crn_uint32 f = 0; f < cCRNMaxFaces; f++)
|
||||
for (crn_uint32 l = 0; l < cCRNMaxLevels; l++)
|
||||
CRNLIB_COMP(m_pImages[f][l]);
|
||||
|
||||
#undef CRNLIB_COMP
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if the input parameters are reasonable.
|
||||
inline bool check() const {
|
||||
if ((m_file_type > cCRNFileTypeDDS) ||
|
||||
(((int)m_quality_level < (int)cCRNMinQualityLevel) || ((int)m_quality_level > (int)cCRNMaxQualityLevel)) ||
|
||||
(m_dxt1a_alpha_threshold > 255) ||
|
||||
((m_faces != 1) && (m_faces != 6)) ||
|
||||
((m_width < 1) || (m_width > cCRNMaxLevelResolution)) ||
|
||||
((m_height < 1) || (m_height > cCRNMaxLevelResolution)) ||
|
||||
((m_levels < 1) || (m_levels > cCRNMaxLevels)) ||
|
||||
((m_format < cCRNFmtDXT1) || (m_format >= cCRNFmtTotal)) ||
|
||||
((m_crn_color_endpoint_palette_size) && ((m_crn_color_endpoint_palette_size < cCRNMinPaletteSize) || (m_crn_color_endpoint_palette_size > cCRNMaxPaletteSize))) ||
|
||||
((m_crn_color_selector_palette_size) && ((m_crn_color_selector_palette_size < cCRNMinPaletteSize) || (m_crn_color_selector_palette_size > cCRNMaxPaletteSize))) ||
|
||||
((m_crn_alpha_endpoint_palette_size) && ((m_crn_alpha_endpoint_palette_size < cCRNMinPaletteSize) || (m_crn_alpha_endpoint_palette_size > cCRNMaxPaletteSize))) ||
|
||||
((m_crn_alpha_selector_palette_size) && ((m_crn_alpha_selector_palette_size < cCRNMinPaletteSize) || (m_crn_alpha_selector_palette_size > cCRNMaxPaletteSize))) ||
|
||||
(m_alpha_component > 3) ||
|
||||
(m_num_helper_threads > cCRNMaxHelperThreads) ||
|
||||
(m_dxt_quality > cCRNDXTQualityUber) ||
|
||||
(m_dxt_compressor_type >= cCRNTotalDXTCompressors)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper to set/get flags from m_flags member.
|
||||
inline bool get_flag(crn_comp_flags flag) const { return (m_flags & flag) != 0; }
|
||||
inline void set_flag(crn_comp_flags flag, bool val) {
|
||||
m_flags &= ~flag;
|
||||
if (val)
|
||||
m_flags |= flag;
|
||||
}
|
||||
|
||||
crn_uint32 m_size_of_obj;
|
||||
|
||||
crn_file_type m_file_type; // Output file type: cCRNFileTypeCRN or cCRNFileTypeDDS.
|
||||
|
||||
crn_uint32 m_faces; // 1 (2D map) or 6 (cubemap)
|
||||
crn_uint32 m_width; // [1,cCRNMaxLevelResolution], non-power of 2 OK, non-square OK
|
||||
crn_uint32 m_height; // [1,cCRNMaxLevelResolution], non-power of 2 OK, non-square OK
|
||||
crn_uint32 m_levels; // [1,cCRNMaxLevelResolution], non-power of 2 OK, non-square OK
|
||||
|
||||
crn_format m_format; // Output pixel format.
|
||||
|
||||
crn_uint32 m_flags; // see crn_comp_flags enum
|
||||
|
||||
// Array of pointers to 32bpp input images.
|
||||
const crn_uint32* m_pImages[cCRNMaxFaces][cCRNMaxLevels];
|
||||
|
||||
// Target bitrate - if non-zero, the compressor will use an interpolative search to find the
|
||||
// highest quality level that is <= the target bitrate. If it fails to find a bitrate high enough, it'll
|
||||
// try disabling adaptive block sizes (cCRNCompFlagHierarchical flag) and redo the search. This process can be pretty slow.
|
||||
float m_target_bitrate;
|
||||
|
||||
// Desired quality level.
|
||||
// Currently, CRN and DDS quality levels are not compatible with eachother from an image quality standpoint.
|
||||
crn_uint32 m_quality_level; // [cCRNMinQualityLevel, cCRNMaxQualityLevel]
|
||||
|
||||
// DXTn compression parameters.
|
||||
crn_uint32 m_dxt1a_alpha_threshold;
|
||||
crn_dxt_quality m_dxt_quality;
|
||||
crn_dxt_compressor_type m_dxt_compressor_type;
|
||||
|
||||
// Alpha channel's component. Defaults to 3.
|
||||
crn_uint32 m_alpha_component;
|
||||
|
||||
// Various low-level CRN specific parameters.
|
||||
float m_crn_adaptive_tile_color_psnr_derating;
|
||||
float m_crn_adaptive_tile_alpha_psnr_derating;
|
||||
|
||||
crn_uint32 m_crn_color_endpoint_palette_size; // [cCRNMinPaletteSize,cCRNMaxPaletteSize]
|
||||
crn_uint32 m_crn_color_selector_palette_size; // [cCRNMinPaletteSize,cCRNMaxPaletteSize]
|
||||
|
||||
crn_uint32 m_crn_alpha_endpoint_palette_size; // [cCRNMinPaletteSize,cCRNMaxPaletteSize]
|
||||
crn_uint32 m_crn_alpha_selector_palette_size; // [cCRNMinPaletteSize,cCRNMaxPaletteSize]
|
||||
|
||||
// Number of helper threads to create during compression. 0=no threading.
|
||||
crn_uint32 m_num_helper_threads;
|
||||
|
||||
// CRN userdata0 and userdata1 members, which are written directly to the header of the output file.
|
||||
crn_uint32 m_userdata0;
|
||||
crn_uint32 m_userdata1;
|
||||
|
||||
// User provided progress callback.
|
||||
crn_progress_callback_func m_pProgress_func;
|
||||
void* m_pProgress_func_data;
|
||||
};
|
||||
|
||||
// Mipmap generator's mode.
|
||||
enum crn_mip_mode {
|
||||
cCRNMipModeUseSourceOrGenerateMips, // Use source texture's mipmaps if it has any, otherwise generate new mipmaps
|
||||
cCRNMipModeUseSourceMips, // Use source texture's mipmaps if it has any, otherwise the output has no mipmaps
|
||||
cCRNMipModeGenerateMips, // Always generate new mipmaps
|
||||
cCRNMipModeNoMips, // Output texture has no mipmaps
|
||||
|
||||
cCRNMipModeTotal,
|
||||
|
||||
cCRNModeForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
const char* crn_get_mip_mode_desc(crn_mip_mode m);
|
||||
const char* crn_get_mip_mode_name(crn_mip_mode m);
|
||||
|
||||
// Mipmap generator's filter kernel.
|
||||
enum crn_mip_filter {
|
||||
cCRNMipFilterBox,
|
||||
cCRNMipFilterTent,
|
||||
cCRNMipFilterLanczos4,
|
||||
cCRNMipFilterMitchell,
|
||||
cCRNMipFilterKaiser, // Kaiser=default mipmap filter
|
||||
|
||||
cCRNMipFilterTotal,
|
||||
|
||||
cCRNMipFilterForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
const char* crn_get_mip_filter_name(crn_mip_filter f);
|
||||
|
||||
// Mipmap generator's scale mode.
|
||||
enum crn_scale_mode {
|
||||
cCRNSMDisabled,
|
||||
cCRNSMAbsolute,
|
||||
cCRNSMRelative,
|
||||
cCRNSMLowerPow2,
|
||||
cCRNSMNearestPow2,
|
||||
cCRNSMNextPow2,
|
||||
|
||||
cCRNSMTotal,
|
||||
|
||||
cCRNSMForceDWORD = 0xFFFFFFFF
|
||||
};
|
||||
|
||||
const char* crn_get_scale_mode_desc(crn_scale_mode sm);
|
||||
|
||||
// Mipmap generator parameters.
|
||||
struct crn_mipmap_params {
|
||||
inline crn_mipmap_params() { clear(); }
|
||||
|
||||
inline void clear() {
|
||||
m_size_of_obj = sizeof(*this);
|
||||
m_mode = cCRNMipModeUseSourceOrGenerateMips;
|
||||
m_filter = cCRNMipFilterKaiser;
|
||||
m_gamma_filtering = true;
|
||||
m_gamma = 2.2f;
|
||||
// Default "blurriness" factor of .9 actually sharpens the output a little.
|
||||
m_blurriness = .9f;
|
||||
m_renormalize = false;
|
||||
m_tiled = false;
|
||||
m_max_levels = cCRNMaxLevels;
|
||||
m_min_mip_size = 1;
|
||||
|
||||
m_scale_mode = cCRNSMDisabled;
|
||||
m_scale_x = 1.0f;
|
||||
m_scale_y = 1.0f;
|
||||
|
||||
m_window_left = 0;
|
||||
m_window_top = 0;
|
||||
m_window_right = 0;
|
||||
m_window_bottom = 0;
|
||||
|
||||
m_clamp_scale = false;
|
||||
m_clamp_width = 0;
|
||||
m_clamp_height = 0;
|
||||
}
|
||||
|
||||
inline bool check() const { return true; }
|
||||
|
||||
inline bool operator==(const crn_mipmap_params& rhs) const {
|
||||
#define CRNLIB_COMP(x) \
|
||||
do { \
|
||||
if ((x) != (rhs.x)) \
|
||||
return false; \
|
||||
} while (0)
|
||||
CRNLIB_COMP(m_size_of_obj);
|
||||
CRNLIB_COMP(m_mode);
|
||||
CRNLIB_COMP(m_filter);
|
||||
CRNLIB_COMP(m_gamma_filtering);
|
||||
CRNLIB_COMP(m_gamma);
|
||||
CRNLIB_COMP(m_blurriness);
|
||||
CRNLIB_COMP(m_renormalize);
|
||||
CRNLIB_COMP(m_tiled);
|
||||
CRNLIB_COMP(m_max_levels);
|
||||
CRNLIB_COMP(m_min_mip_size);
|
||||
CRNLIB_COMP(m_scale_mode);
|
||||
CRNLIB_COMP(m_scale_x);
|
||||
CRNLIB_COMP(m_scale_y);
|
||||
CRNLIB_COMP(m_window_left);
|
||||
CRNLIB_COMP(m_window_top);
|
||||
CRNLIB_COMP(m_window_right);
|
||||
CRNLIB_COMP(m_window_bottom);
|
||||
CRNLIB_COMP(m_clamp_scale);
|
||||
CRNLIB_COMP(m_clamp_width);
|
||||
CRNLIB_COMP(m_clamp_height);
|
||||
return true;
|
||||
#undef CRNLIB_COMP
|
||||
}
|
||||
crn_uint32 m_size_of_obj;
|
||||
|
||||
crn_mip_mode m_mode;
|
||||
crn_mip_filter m_filter;
|
||||
|
||||
crn_bool m_gamma_filtering;
|
||||
float m_gamma;
|
||||
|
||||
float m_blurriness;
|
||||
|
||||
crn_uint32 m_max_levels;
|
||||
crn_uint32 m_min_mip_size;
|
||||
|
||||
crn_bool m_renormalize;
|
||||
crn_bool m_tiled;
|
||||
|
||||
crn_scale_mode m_scale_mode;
|
||||
float m_scale_x;
|
||||
float m_scale_y;
|
||||
|
||||
crn_uint32 m_window_left;
|
||||
crn_uint32 m_window_top;
|
||||
crn_uint32 m_window_right;
|
||||
crn_uint32 m_window_bottom;
|
||||
|
||||
crn_bool m_clamp_scale;
|
||||
crn_uint32 m_clamp_width;
|
||||
crn_uint32 m_clamp_height;
|
||||
};
|
||||
|
||||
// -------- High-level helper function definitions for CDN/DDS compression.
|
||||
|
||||
#ifndef CRNLIB_MIN_ALLOC_ALIGNMENT
|
||||
#define CRNLIB_MIN_ALLOC_ALIGNMENT sizeof(size_t) * 2
|
||||
#endif
|
||||
|
||||
// Function to set an optional user provided memory allocation/reallocation/msize routines.
|
||||
// By default, crnlib just uses malloc(), free(), etc. for all allocations.
|
||||
typedef void* (*crn_realloc_func)(void* p, size_t size, size_t* pActual_size, bool movable, void* pUser_data);
|
||||
typedef size_t (*crn_msize_func)(void* p, void* pUser_data);
|
||||
void crn_set_memory_callbacks(crn_realloc_func pRealloc, crn_msize_func pMSize, void* pUser_data);
|
||||
|
||||
// Frees memory blocks allocated by crn_compress(), crn_decompress_crn_to_dds(), or crn_decompress_dds_to_images().
|
||||
void crn_free_block(void* pBlock);
|
||||
|
||||
// Compresses a 32-bit/pixel texture to either: a regular DX9 DDS file, a "clustered" (or reduced entropy) DX9 DDS file, or a CRN file in memory.
|
||||
// Input parameters:
|
||||
// comp_params is the compression parameters struct, defined above.
|
||||
// compressed_size will be set to the size of the returned memory block containing the output file.
|
||||
// The returned block must be freed by calling crn_free_block().
|
||||
// *pActual_quality_level will be set to the actual quality level used to compress the image. May be NULL.
|
||||
// *pActual_bitrate will be set to the output file's effective bitrate, possibly taking into account LZMA compression. May be NULL.
|
||||
// Return value:
|
||||
// The compressed file data, or NULL on failure.
|
||||
// compressed_size will be set to the size of the returned memory buffer.
|
||||
// Notes:
|
||||
// A "regular" DDS file is compressed using normal DXTn compression at the specified DXT quality level.
|
||||
// A "clustered" DDS file is compressed using clustered DXTn compression to either the target bitrate or the specified integer quality factor.
|
||||
// The output file is a standard DX9 format DDS file, except the compressor assumes you will be later losslessly compressing the DDS output file using the LZMA algorithm.
|
||||
// A texture is defined as an array of 1 or 6 "faces" (6 faces=cubemap), where each "face" consists of between [1,cCRNMaxLevels] mipmap levels.
|
||||
// Mipmap levels are simple 32-bit 2D images with a pitch of width*sizeof(uint32), arranged in the usual raster order (top scanline first).
|
||||
// The image pixels may be grayscale (YYYX bytes in memory), grayscale/alpha (YYYA in memory), 24-bit (RGBX in memory), or 32-bit (RGBA) colors (where "X"=don't care).
|
||||
// RGB color data is generally assumed to be in the sRGB colorspace. If not, be sure to clear the "cCRNCompFlagPerceptual" in the crn_comp_params struct!
|
||||
void* crn_compress(const crn_comp_params& comp_params, crn_uint32& compressed_size, crn_uint32* pActual_quality_level = NULL, float* pActual_bitrate = NULL);
|
||||
|
||||
// Like the above function, except this function can also do things like generate mipmaps, and resize or crop the input texture before compression.
|
||||
// The actual operations performed are controlled by the crn_mipmap_params struct members.
|
||||
// Be sure to set the "m_gamma_filtering" member of crn_mipmap_params to false if the input texture is not sRGB.
|
||||
void* crn_compress(const crn_comp_params& comp_params, const crn_mipmap_params& mip_params, crn_uint32& compressed_size, crn_uint32* pActual_quality_level = NULL, float* pActual_bitrate = NULL);
|
||||
|
||||
// Transcodes an entire CRN file to DDS using the crn_decomp.h header file library to do most of the heavy lifting.
|
||||
// The output DDS file's format is guaranteed to be one of the DXTn formats in the crn_format enum.
|
||||
// This is a fast operation, because the CRN format is explicitly designed to be efficiently transcodable to DXTn.
|
||||
// For more control over decompression, see the lower-level helper functions in crn_decomp.h, which do not depend at all on crnlib.
|
||||
void* crn_decompress_crn_to_dds(const void* pCRN_file_data, crn_uint32& file_size);
|
||||
|
||||
// Decompresses an entire DDS file in any supported format to uncompressed 32-bit/pixel image(s).
|
||||
// See the crnlib::pixel_format enum in inc/dds_defs.h for a list of the supported DDS formats.
|
||||
// You are responsible for freeing each image block, either by calling crn_free_all_images() or manually calling crn_free_block() on each image pointer.
|
||||
struct crn_texture_desc {
|
||||
crn_uint32 m_faces;
|
||||
crn_uint32 m_width;
|
||||
crn_uint32 m_height;
|
||||
crn_uint32 m_levels;
|
||||
crn_uint32 m_fmt_fourcc; // Same as crnlib::pixel_format
|
||||
};
|
||||
bool crn_decompress_dds_to_images(const void* pDDS_file_data, crn_uint32 dds_file_size, crn_uint32** ppImages, crn_texture_desc& tex_desc);
|
||||
|
||||
// Frees all images allocated by crn_decompress_dds_to_images().
|
||||
void crn_free_all_images(crn_uint32** ppImages, const crn_texture_desc& desc);
|
||||
|
||||
// -------- crn_format related helpers functions.
|
||||
|
||||
// Returns the FOURCC format equivalent to the specified crn_format.
|
||||
crn_uint32 crn_get_format_fourcc(crn_format fmt);
|
||||
|
||||
// Returns the crn_format's bits per texel.
|
||||
crn_uint32 crn_get_format_bits_per_texel(crn_format fmt);
|
||||
|
||||
// Returns the crn_format's number of bytes per block.
|
||||
crn_uint32 crn_get_bytes_per_dxt_block(crn_format fmt);
|
||||
|
||||
// Returns the non-swizzled, basic DXTn version of the specified crn_format.
|
||||
// This is the format you would supply D3D or OpenGL.
|
||||
crn_format crn_get_fundamental_dxt_format(crn_format fmt);
|
||||
|
||||
// -------- String helpers.
|
||||
|
||||
// Converts a crn_file_type to a string.
|
||||
const char* crn_get_file_type_ext(crn_file_type file_type);
|
||||
|
||||
// Converts a crn_format to a string.
|
||||
const char* crn_get_format_string(crn_format fmt);
|
||||
|
||||
// Converts a crn_dxt_quality to a string.
|
||||
const char* crn_get_dxt_quality_string(crn_dxt_quality q);
|
||||
|
||||
// -------- Low-level DXTn 4x4 block compressor API
|
||||
|
||||
// crnlib's DXTn endpoint optimizer actually supports any number of source pixels (i.e. from 1 to thousands, not just 16),
|
||||
// but for simplicity this API only supports 4x4 texel blocks.
|
||||
typedef void* crn_block_compressor_context_t;
|
||||
|
||||
// Create a DXTn block compressor.
|
||||
// This function only supports the basic/nonswizzled "fundamental" formats: DXT1, DXT3, DXT5, DXT5A, DXN_XY and DXN_YX.
|
||||
// Avoid calling this multiple times if you intend on compressing many blocks, because it allocates some memory.
|
||||
crn_block_compressor_context_t crn_create_block_compressor(const crn_comp_params& params);
|
||||
|
||||
// Compresses a block of 16 pixels to the destination DXTn block.
|
||||
// pDst_block should be 8 (for DXT1/DXT5A) or 16 bytes (all the others).
|
||||
// pPixels should be an array of 16 crn_uint32's. Each crn_uint32 must be r,g,b,a (r is always first) in memory.
|
||||
void crn_compress_block(crn_block_compressor_context_t pContext, const crn_uint32* pPixels, void* pDst_block);
|
||||
|
||||
// Frees a DXTn block compressor.
|
||||
void crn_free_block_compressor(crn_block_compressor_context_t pContext);
|
||||
|
||||
// Unpacks a compressed block to pDst_pixels.
|
||||
// pSrc_block should be 8 (for DXT1/DXT5A) or 16 bytes (all the others).
|
||||
// pDst_pixel should be an array of 16 crn_uint32's. Each uint32 will be r,g,b,a (r is always first) in memory.
|
||||
// crn_fmt should be one of the "fundamental" formats: DXT1, DXT3, DXT5, DXT5A, DXN_XY and DXN_YX.
|
||||
// The various swizzled DXT5 formats (such as cCRNFmtDXT5_xGBR, etc.) will be unpacked as if they where plain DXT5.
|
||||
// Returns false if the crn_fmt is invalid.
|
||||
bool crn_decompress_block(const void* pSrc_block, crn_uint32* pDst_pixels, crn_format crn_fmt);
|
||||
|
||||
#endif // CRNLIB_H
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// crnlib uses the ZLIB license:
|
||||
// http://opensource.org/licenses/Zlib
|
||||
//
|
||||
// Copyright (c) 2010-2016 Richard Geldreich, Jr. and Binomial LLC
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would be
|
||||
// appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not be
|
||||
// misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source distribution.
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,149 @@
|
||||
// File: dds_defs.h
|
||||
// DX9 .DDS file header definitions.
|
||||
#ifndef CRNLIB_DDS_DEFS_H
|
||||
#define CRNLIB_DDS_DEFS_H
|
||||
|
||||
#include "crnlib.h"
|
||||
|
||||
#define CRNLIB_PIXEL_FMT_FOURCC(a, b, c, d) ((a) | ((b) << 8U) | ((c) << 16U) | ((d) << 24U))
|
||||
|
||||
namespace crnlib {
|
||||
enum pixel_format {
|
||||
PIXEL_FMT_INVALID = 0,
|
||||
|
||||
PIXEL_FMT_DXT1 = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', 'T', '1'),
|
||||
PIXEL_FMT_DXT2 = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', 'T', '2'),
|
||||
PIXEL_FMT_DXT3 = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', 'T', '3'),
|
||||
PIXEL_FMT_DXT4 = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', 'T', '4'),
|
||||
PIXEL_FMT_DXT5 = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', 'T', '5'),
|
||||
PIXEL_FMT_3DC = CRNLIB_PIXEL_FMT_FOURCC('A', 'T', 'I', '2'), // DXN_YX
|
||||
PIXEL_FMT_DXN = CRNLIB_PIXEL_FMT_FOURCC('A', '2', 'X', 'Y'), // DXN_XY
|
||||
PIXEL_FMT_DXT5A = CRNLIB_PIXEL_FMT_FOURCC('A', 'T', 'I', '1'), // ATI1N, http://developer.amd.com/media/gpu_assets/Radeon_X1x00_Programming_Guide.pdf
|
||||
|
||||
// Non-standard, crnlib-specific pixel formats (some of these are supported by ATI's Compressonator)
|
||||
PIXEL_FMT_DXT5_CCxY = CRNLIB_PIXEL_FMT_FOURCC('C', 'C', 'x', 'Y'),
|
||||
PIXEL_FMT_DXT5_xGxR = CRNLIB_PIXEL_FMT_FOURCC('x', 'G', 'x', 'R'),
|
||||
PIXEL_FMT_DXT5_xGBR = CRNLIB_PIXEL_FMT_FOURCC('x', 'G', 'B', 'R'),
|
||||
PIXEL_FMT_DXT5_AGBR = CRNLIB_PIXEL_FMT_FOURCC('A', 'G', 'B', 'R'),
|
||||
|
||||
PIXEL_FMT_DXT1A = CRNLIB_PIXEL_FMT_FOURCC('D', 'X', '1', 'A'),
|
||||
PIXEL_FMT_ETC1 = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', 'C', '1'),
|
||||
PIXEL_FMT_ETC2 = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', 'C', '2'),
|
||||
PIXEL_FMT_ETC2A = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', '2', 'A'),
|
||||
PIXEL_FMT_ETC1S = CRNLIB_PIXEL_FMT_FOURCC('E', 'T', '1', 'S'),
|
||||
PIXEL_FMT_ETC2AS = CRNLIB_PIXEL_FMT_FOURCC('E', '2', 'A', 'S'),
|
||||
|
||||
PIXEL_FMT_R8G8B8 = CRNLIB_PIXEL_FMT_FOURCC('R', 'G', 'B', 'x'),
|
||||
PIXEL_FMT_L8 = CRNLIB_PIXEL_FMT_FOURCC('L', 'x', 'x', 'x'),
|
||||
PIXEL_FMT_A8 = CRNLIB_PIXEL_FMT_FOURCC('x', 'x', 'x', 'A'),
|
||||
PIXEL_FMT_A8L8 = CRNLIB_PIXEL_FMT_FOURCC('L', 'x', 'x', 'A'),
|
||||
PIXEL_FMT_A8R8G8B8 = CRNLIB_PIXEL_FMT_FOURCC('R', 'G', 'B', 'A')
|
||||
};
|
||||
|
||||
const crn_uint32 cDDSMaxImageDimensions = 8192U;
|
||||
|
||||
// Total size of header is sizeof(uint32)+cDDSSizeofDDSurfaceDesc2;
|
||||
const crn_uint32 cDDSSizeofDDSurfaceDesc2 = 124;
|
||||
|
||||
// "DDS "
|
||||
const crn_uint32 cDDSFileSignature = 0x20534444;
|
||||
|
||||
struct DDCOLORKEY {
|
||||
crn_uint32 dwUnused0;
|
||||
crn_uint32 dwUnused1;
|
||||
};
|
||||
|
||||
struct DDPIXELFORMAT {
|
||||
crn_uint32 dwSize;
|
||||
crn_uint32 dwFlags;
|
||||
crn_uint32 dwFourCC;
|
||||
crn_uint32 dwRGBBitCount; // ATI compressonator and crnlib will place a FOURCC code here for swizzled/cooked DXTn formats
|
||||
crn_uint32 dwRBitMask;
|
||||
crn_uint32 dwGBitMask;
|
||||
crn_uint32 dwBBitMask;
|
||||
crn_uint32 dwRGBAlphaBitMask;
|
||||
};
|
||||
|
||||
struct DDSCAPS2 {
|
||||
crn_uint32 dwCaps;
|
||||
crn_uint32 dwCaps2;
|
||||
crn_uint32 dwCaps3;
|
||||
crn_uint32 dwCaps4;
|
||||
};
|
||||
|
||||
struct DDSURFACEDESC2 {
|
||||
crn_uint32 dwSize;
|
||||
crn_uint32 dwFlags;
|
||||
crn_uint32 dwHeight;
|
||||
crn_uint32 dwWidth;
|
||||
union {
|
||||
crn_int32 lPitch;
|
||||
crn_uint32 dwLinearSize;
|
||||
};
|
||||
crn_uint32 dwBackBufferCount;
|
||||
crn_uint32 dwMipMapCount;
|
||||
crn_uint32 dwAlphaBitDepth;
|
||||
crn_uint32 dwUnused0;
|
||||
crn_uint32 lpSurface;
|
||||
DDCOLORKEY unused0;
|
||||
DDCOLORKEY unused1;
|
||||
DDCOLORKEY unused2;
|
||||
DDCOLORKEY unused3;
|
||||
DDPIXELFORMAT ddpfPixelFormat;
|
||||
DDSCAPS2 ddsCaps;
|
||||
crn_uint32 dwUnused1;
|
||||
};
|
||||
|
||||
const crn_uint32 DDSD_CAPS = 0x00000001;
|
||||
const crn_uint32 DDSD_HEIGHT = 0x00000002;
|
||||
const crn_uint32 DDSD_WIDTH = 0x00000004;
|
||||
const crn_uint32 DDSD_PITCH = 0x00000008;
|
||||
|
||||
const crn_uint32 DDSD_BACKBUFFERCOUNT = 0x00000020;
|
||||
const crn_uint32 DDSD_ZBUFFERBITDEPTH = 0x00000040;
|
||||
const crn_uint32 DDSD_ALPHABITDEPTH = 0x00000080;
|
||||
|
||||
const crn_uint32 DDSD_LPSURFACE = 0x00000800;
|
||||
|
||||
const crn_uint32 DDSD_PIXELFORMAT = 0x00001000;
|
||||
const crn_uint32 DDSD_CKDESTOVERLAY = 0x00002000;
|
||||
const crn_uint32 DDSD_CKDESTBLT = 0x00004000;
|
||||
const crn_uint32 DDSD_CKSRCOVERLAY = 0x00008000;
|
||||
|
||||
const crn_uint32 DDSD_CKSRCBLT = 0x00010000;
|
||||
const crn_uint32 DDSD_MIPMAPCOUNT = 0x00020000;
|
||||
const crn_uint32 DDSD_REFRESHRATE = 0x00040000;
|
||||
const crn_uint32 DDSD_LINEARSIZE = 0x00080000;
|
||||
|
||||
const crn_uint32 DDSD_TEXTURESTAGE = 0x00100000;
|
||||
const crn_uint32 DDSD_FVF = 0x00200000;
|
||||
const crn_uint32 DDSD_SRCVBHANDLE = 0x00400000;
|
||||
const crn_uint32 DDSD_DEPTH = 0x00800000;
|
||||
|
||||
const crn_uint32 DDSD_ALL = 0x00fff9ee;
|
||||
|
||||
const crn_uint32 DDPF_ALPHAPIXELS = 0x00000001;
|
||||
const crn_uint32 DDPF_ALPHA = 0x00000002;
|
||||
const crn_uint32 DDPF_FOURCC = 0x00000004;
|
||||
const crn_uint32 DDPF_PALETTEINDEXED8 = 0x00000020;
|
||||
const crn_uint32 DDPF_RGB = 0x00000040;
|
||||
const crn_uint32 DDPF_LUMINANCE = 0x00020000;
|
||||
|
||||
const crn_uint32 DDSCAPS_COMPLEX = 0x00000008;
|
||||
const crn_uint32 DDSCAPS_TEXTURE = 0x00001000;
|
||||
const crn_uint32 DDSCAPS_MIPMAP = 0x00400000;
|
||||
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP = 0x00000200;
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400;
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800;
|
||||
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000;
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000;
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000;
|
||||
const crn_uint32 DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000;
|
||||
|
||||
const crn_uint32 DDSCAPS2_VOLUME = 0x00200000;
|
||||
|
||||
} // namespace crnlib
|
||||
|
||||
#endif // CRNLIB_DDS_DEFS_H
|
||||
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mkmf'
|
||||
|
||||
have_library('stdc++')
|
||||
append_cppflags('-std=c++11')
|
||||
append_cppflags('-O2')
|
||||
append_cppflags('-Wall')
|
||||
append_cppflags('-Wextra')
|
||||
append_cppflags('-Wvla')
|
||||
|
||||
create_makefile('mikunyan/decoders/crunch')
|
||||
@@ -0,0 +1,174 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include "crn_decomp.h"
|
||||
#include <ruby.h>
|
||||
|
||||
ID sym_at_data, sym_new;
|
||||
VALUE stFileInfo, stTextureInfo, stLevelInfo;
|
||||
|
||||
static void set_format_constant(VALUE module) {
|
||||
rb_const_set(module, rb_intern("INVALID"), LONG2NUM(cCRNFmtInvalid));
|
||||
rb_const_set(module, rb_intern("FIRST_VALID"), LONG2NUM(cCRNFmtFirstValid));
|
||||
rb_const_set(module, rb_intern("DXT1"), LONG2NUM(cCRNFmtDXT1));
|
||||
rb_const_set(module, rb_intern("DXT3"), LONG2NUM(cCRNFmtDXT3));
|
||||
rb_const_set(module, rb_intern("DXT5"), LONG2NUM(cCRNFmtDXT5));
|
||||
rb_const_set(module, rb_intern("DXT5_CCXY"), LONG2NUM(cCRNFmtDXT5_CCxY));
|
||||
rb_const_set(module, rb_intern("DXT5_XGXR"), LONG2NUM(cCRNFmtDXT5_xGxR));
|
||||
rb_const_set(module, rb_intern("DXT5_XGBR"), LONG2NUM(cCRNFmtDXT5_xGBR));
|
||||
rb_const_set(module, rb_intern("DXT5_AGBR"), LONG2NUM(cCRNFmtDXT5_AGBR));
|
||||
rb_const_set(module, rb_intern("DXN_XY"), LONG2NUM(cCRNFmtDXN_XY));
|
||||
rb_const_set(module, rb_intern("DXN_YX"), LONG2NUM(cCRNFmtDXN_YX));
|
||||
rb_const_set(module, rb_intern("DXT5A"), LONG2NUM(cCRNFmtDXT5A));
|
||||
rb_const_set(module, rb_intern("ETC1"), LONG2NUM(cCRNFmtETC1));
|
||||
rb_const_set(module, rb_intern("ETC2"), LONG2NUM(cCRNFmtETC2));
|
||||
rb_const_set(module, rb_intern("ETC2A"), LONG2NUM(cCRNFmtETC2A));
|
||||
rb_const_set(module, rb_intern("ETC1S"), LONG2NUM(cCRNFmtETC1S));
|
||||
rb_const_set(module, rb_intern("ETC2AS"), LONG2NUM(cCRNFmtETC2AS));
|
||||
rb_const_set(module, rb_intern("TOTAL"), LONG2NUM(cCRNFmtTotal));
|
||||
}
|
||||
|
||||
static VALUE rb_cCrunchStream_file_info(VALUE self) {
|
||||
VALUE str = rb_ivar_get(self, sym_at_data);
|
||||
crnd::crn_file_info file_info;
|
||||
if (!crnd::crnd_validate_file(RSTRING_PTR(str), RSTRING_LENINT(str), &file_info)) {
|
||||
rb_raise(rb_eRuntimeError, "cannot get file info (invalid file?)");
|
||||
return Qnil;
|
||||
}
|
||||
VALUE level_compressed_size = rb_ary_new2(file_info.m_levels);
|
||||
for (uint32_t i = 0; i < file_info.m_levels; i++)
|
||||
rb_ary_push(level_compressed_size, UINT2NUM(file_info.m_level_compressed_size[i]));
|
||||
VALUE args[] = {
|
||||
UINT2NUM(file_info.m_struct_size),
|
||||
UINT2NUM(file_info.m_actual_data_size),
|
||||
UINT2NUM(file_info.m_header_size),
|
||||
UINT2NUM(file_info.m_total_palette_size),
|
||||
UINT2NUM(file_info.m_tables_size),
|
||||
UINT2NUM(file_info.m_levels),
|
||||
level_compressed_size,
|
||||
UINT2NUM(file_info.m_color_endpoint_palette_entries),
|
||||
UINT2NUM(file_info.m_color_selector_palette_entries),
|
||||
UINT2NUM(file_info.m_alpha_endpoint_palette_entries),
|
||||
UINT2NUM(file_info.m_alpha_selector_palette_entries)
|
||||
};
|
||||
return rb_class_new_instance(sizeof(args) / sizeof(VALUE), args, stFileInfo);
|
||||
}
|
||||
|
||||
static VALUE rb_cCrunchStream_texture_info(VALUE self) {
|
||||
VALUE str = rb_ivar_get(self, sym_at_data);
|
||||
crnd::crn_texture_info texture_info;
|
||||
if (!crnd::crnd_get_texture_info(RSTRING_PTR(str), RSTRING_LENINT(str), &texture_info)) {
|
||||
rb_raise(rb_eRuntimeError, "cannot get texture info (invalid file?)");
|
||||
return Qnil;
|
||||
}
|
||||
VALUE args[] = {
|
||||
UINT2NUM(texture_info.m_struct_size),
|
||||
UINT2NUM(texture_info.m_width),
|
||||
UINT2NUM(texture_info.m_height),
|
||||
UINT2NUM(texture_info.m_levels),
|
||||
UINT2NUM(texture_info.m_faces),
|
||||
UINT2NUM(texture_info.m_bytes_per_block),
|
||||
UINT2NUM(texture_info.m_userdata0),
|
||||
UINT2NUM(texture_info.m_userdata1),
|
||||
UINT2NUM(texture_info.m_format)
|
||||
};
|
||||
return rb_class_new_instance(sizeof(args) / sizeof(VALUE), args, stTextureInfo);
|
||||
}
|
||||
|
||||
static VALUE rb_cCrunchStream_level_info(VALUE self, VALUE rb_level) {
|
||||
VALUE str = rb_ivar_get(self, sym_at_data);
|
||||
crnd::crn_level_info level_info;
|
||||
if (!crnd::crnd_get_level_info(RSTRING_PTR(str), RSTRING_LENINT(str), NUM2UINT(rb_level), &level_info)) {
|
||||
rb_raise(rb_eRuntimeError, "cannot get level info (invalid file or invalid level?)");
|
||||
return Qnil;
|
||||
}
|
||||
VALUE args[] = {
|
||||
UINT2NUM(level_info.m_struct_size),
|
||||
UINT2NUM(level_info.m_width),
|
||||
UINT2NUM(level_info.m_height),
|
||||
UINT2NUM(level_info.m_faces),
|
||||
UINT2NUM(level_info.m_blocks_x),
|
||||
UINT2NUM(level_info.m_blocks_y),
|
||||
UINT2NUM((level_info.m_width + level_info.m_blocks_x - 1) / level_info.m_blocks_x),
|
||||
UINT2NUM((level_info.m_height + level_info.m_blocks_y - 1) / level_info.m_blocks_y),
|
||||
UINT2NUM(level_info.m_bytes_per_block),
|
||||
UINT2NUM(level_info.m_format)
|
||||
};
|
||||
return rb_class_new_instance(sizeof(args) / sizeof(VALUE), args, stLevelInfo);
|
||||
}
|
||||
|
||||
static VALUE rb_cCrunchStream_unpack_level(VALUE self, VALUE rb_level) {
|
||||
VALUE str = rb_ivar_get(self, sym_at_data);
|
||||
crnd::crn_level_info level_info;
|
||||
if (!crnd::crnd_get_level_info(RSTRING_PTR(str), RSTRING_LENINT(str), NUM2UINT(rb_level), &level_info)) {
|
||||
rb_raise(rb_eRuntimeError, "cannot get level info (invalid file or invalid level?)");
|
||||
return Qnil;
|
||||
}
|
||||
uint32_t pitch_size = level_info.m_blocks_x * level_info.m_bytes_per_block;
|
||||
uint32_t size = pitch_size * level_info.m_blocks_y;
|
||||
VALUE ret = rb_str_buf_new(size);
|
||||
void *ret_ptr = (void*)RSTRING_PTR(ret);
|
||||
crnd::crnd_unpack_context context = crnd::crnd_unpack_begin(RSTRING_PTR(str), RSTRING_LENINT(str));
|
||||
if (context == nullptr) {
|
||||
rb_raise(rb_eRuntimeError, "context creation error");
|
||||
return Qnil;
|
||||
}
|
||||
if (!crnd::crnd_unpack_level(context, &ret_ptr, size, pitch_size, 0)) {
|
||||
rb_raise(rb_eRuntimeError, "unpack error");
|
||||
return Qnil;
|
||||
}
|
||||
crnd::crnd_unpack_end(context);
|
||||
rb_str_set_len(ret, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static VALUE create_rb_struct(const int argc, const char **argv) {
|
||||
VALUE *argv_values = (VALUE*)malloc(sizeof(VALUE*) * argc);
|
||||
for (int i = 0; i < argc; i++)
|
||||
argv_values[i] = ID2SYM(rb_intern(argv[i]));
|
||||
VALUE ret = rb_funcall2(rb_cStruct, sym_new, argc, argv_values);
|
||||
free(argv_values);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static VALUE rb_cCrunchStream_initialize(VALUE self, VALUE rb_data) {
|
||||
Check_Type(rb_data, T_STRING);
|
||||
rb_ivar_set(self, sym_at_data, rb_data);
|
||||
return self;
|
||||
}
|
||||
|
||||
void Init_crunch()
|
||||
{
|
||||
sym_new = rb_intern("new");
|
||||
sym_at_data = rb_intern("@data");
|
||||
|
||||
VALUE mMikunyan = rb_define_module("Mikunyan");
|
||||
VALUE mDecodeHelper = rb_define_module_under(mMikunyan, "DecodeHelper");
|
||||
VALUE cCrunchStream = rb_define_class_under(mDecodeHelper, "CrunchStream", rb_cObject);
|
||||
rb_attr(cCrunchStream, rb_intern("data"), 1, 0, 1);
|
||||
|
||||
const char* stFileInfoStr[] = {"struct_size", "actual_data_size", "header_size", "total_palette_size", "tables_size", "levels", "level_compressed_size", "color_endpoint_palette_entries", "color_selector_palette_entries", "alpha_endpoint_palette_entries", "alpha_selector_palette_entries"};
|
||||
stFileInfo = create_rb_struct(sizeof(stFileInfoStr) / sizeof(char*), stFileInfoStr);
|
||||
rb_const_set(cCrunchStream, rb_intern("FileInfo"), stFileInfo);
|
||||
|
||||
const char* stTextureInfoStr[] = {"struct_size", "width", "height", "levels", "faces", "bytes_per_block", "userdata0", "userdata1", "format"};
|
||||
stTextureInfo = create_rb_struct(sizeof(stTextureInfoStr) / sizeof(char*), stTextureInfoStr);
|
||||
rb_const_set(cCrunchStream, rb_intern("TextureInfo"), stTextureInfo);
|
||||
|
||||
const char* stLevelInfoStr[] = {"struct_size", "width", "height", "faces", "blocks_x", "blocks_y", "block_width", "block_height", "bytes_per_block", "format"};
|
||||
stLevelInfo = create_rb_struct(sizeof(stLevelInfoStr) / sizeof(char*), stLevelInfoStr);
|
||||
rb_const_set(cCrunchStream, rb_intern("LevelInfo"), stLevelInfo);
|
||||
|
||||
rb_define_method(cCrunchStream, "initialize", RUBY_METHOD_FUNC(rb_cCrunchStream_initialize), 1);
|
||||
rb_define_method(cCrunchStream, "file_info", RUBY_METHOD_FUNC(rb_cCrunchStream_file_info), 0);
|
||||
rb_define_method(cCrunchStream, "texture_info", RUBY_METHOD_FUNC(rb_cCrunchStream_texture_info), 0);
|
||||
rb_define_method(cCrunchStream, "level_info", RUBY_METHOD_FUNC(rb_cCrunchStream_level_info), 1);
|
||||
rb_define_method(cCrunchStream, "unpack_level", RUBY_METHOD_FUNC(rb_cCrunchStream_unpack_level), 1);
|
||||
|
||||
VALUE mFormat = rb_define_module_under(cCrunchStream, "Format");
|
||||
set_format_constant(mFormat);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
#ifndef ASTC_H
|
||||
#define ASTC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int decode_astc(const uint8_t *, const long, const long, const int, const int, uint32_t *);
|
||||
|
||||
#endif /* end of include guard: ASTC_H */
|
||||
@@ -0,0 +1,87 @@
|
||||
#ifndef COLOR_H
|
||||
#define COLOR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "endianness.h"
|
||||
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
static const uint_fast32_t TRANSPARENT_MASK = 0x00ffffff;
|
||||
#else
|
||||
static const uint_fast32_t TRANSPARENT_MASK = 0xffffff00;
|
||||
#endif
|
||||
|
||||
static inline uint_fast32_t color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
return r | g << 8 | b << 16 | a << 24;
|
||||
#else
|
||||
return a | b << 8 | g << 16 | r << 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint_fast32_t alpha_mask(uint8_t a) {
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
return TRANSPARENT_MASK | a << 24;
|
||||
#else
|
||||
return TRANSPARENT_MASK | a;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void rgb565_le(const uint16_t d, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
*r = (d >> 8 & 0xf8) | (d >> 13);
|
||||
*g = (d >> 3 & 0xfc) | (d >> 9 & 3);
|
||||
*b = (d << 3) | (d >> 2 & 7);
|
||||
#else
|
||||
*r = (d & 0xf8) | (d >> 5 & 7);
|
||||
*g = (d << 5 & 0xe0) | (d >> 11 & 0x1c) | (d >> 1 & 3);
|
||||
*b = (d >> 5 & 0xf8) | (d >> 10 & 0x7);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void rgb565_be(const uint16_t d, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
#ifdef __BIG_ENDIAN__
|
||||
*r = (d >> 8 & 0xf8) | (d >> 13);
|
||||
*g = (d >> 3 & 0xfc) | (d >> 9 & 3);
|
||||
*b = (d << 3) | (d >> 2 & 7);
|
||||
#else
|
||||
*r = (d & 0xf8) | (d >> 5 & 7);
|
||||
*g = (d << 5 & 0xe0) | (d >> 11 & 0x1c) | (d >> 1 & 3);
|
||||
*b = (d >> 5 & 0xf8) | (d >> 10 & 0x7);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void rgb565_lep(const uint16_t d, uint8_t *c) {
|
||||
#ifdef __LITTLE_ENDIAN__
|
||||
*(c++) = (d >> 8 & 0xf8) | (d >> 13);
|
||||
*(c++) = (d >> 3 & 0xfc) | (d >> 9 & 3);
|
||||
*(c++) = (d << 3) | (d >> 2 & 7);
|
||||
#else
|
||||
*(c++) = (d & 0xf8) | (d >> 5 & 7);
|
||||
*(c++) = (d << 5 & 0xe0) | (d >> 11 & 0x1c) | (d >> 1 & 3);
|
||||
*(c++) = (d >> 5 & 0xf8) | (d >> 10 & 0x7);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void rgb565_bep(const uint16_t d, uint8_t *c) {
|
||||
#ifdef __BIG_ENDIAN__
|
||||
*(c++) = (d >> 8 & 0xf8) | (d >> 13);
|
||||
*(c++) = (d >> 3 & 0xfc) | (d >> 9 & 3);
|
||||
*(c++) = (d << 3) | (d >> 2 & 7);
|
||||
#else
|
||||
*(c++) = (d & 0xf8) | (d >> 5 & 7);
|
||||
*(c++) = (d << 5 & 0xe0) | (d >> 11 & 0x1c) | (d >> 1 & 3);
|
||||
*(c++) = (d >> 5 & 0xf8) | (d >> 10 & 0x7);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void copy_block_buffer(const long bx, const long by, const long w, const long h, const long bw,
|
||||
const long bh, const uint32_t *buffer, uint32_t *image) {
|
||||
long x = bw * bx;
|
||||
long xl = (bw * (bx + 1) > w ? w - bw * bx : bw) * 4;
|
||||
const uint32_t *buffer_end = buffer + bw * bh;
|
||||
for (long y = h - by * bh; buffer < buffer_end && y-- > 0; buffer += bw)
|
||||
memcpy(image + y * w + x, buffer, xl);
|
||||
}
|
||||
|
||||
#endif /* end of include guard: COLOR_H */
|
||||
@@ -0,0 +1,77 @@
|
||||
#include "dxtc.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "color.h"
|
||||
#include "endianness.h"
|
||||
|
||||
static inline void decode_dxt1_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint8_t r0, g0, b0, r1, g1, b1;
|
||||
int q0 = *(uint16_t *)(data);
|
||||
int q1 = *(uint16_t *)(data + 2);
|
||||
rgb565_le(q0, &r0, &g0, &b0);
|
||||
rgb565_le(q1, &r1, &g1, &b1);
|
||||
uint_fast32_t c[4] = {color(r0, g0, b0, 255), color(r1, g1, b1, 255)};
|
||||
if (q0 > q1) {
|
||||
c[2] = color((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 255);
|
||||
c[3] = color((r0 + r1 * 2) / 3, (g0 + g1 * 2) / 3, (b0 + b1 * 2) / 3, 255);
|
||||
} else {
|
||||
c[2] = color((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 255);
|
||||
c[3] = color(0, 0, 0, 255);
|
||||
}
|
||||
uint_fast32_t d = lton32(*(uint32_t *)(data + 4));
|
||||
for (int i = 0; i < 16; i++, d >>= 2)
|
||||
outbuf[i] = c[d & 3];
|
||||
}
|
||||
|
||||
int decode_dxt1(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
const uint8_t *d = data;
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, d += 8) {
|
||||
decode_dxt1_block(d, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void decode_dxt5_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint_fast32_t a[8] = {data[0], data[1]};
|
||||
if (a[0] > a[1]) {
|
||||
a[2] = (a[0] * 6 + a[1]) / 7;
|
||||
a[3] = (a[0] * 5 + a[1] * 2) / 7;
|
||||
a[4] = (a[0] * 4 + a[1] * 3) / 7;
|
||||
a[5] = (a[0] * 3 + a[1] * 4) / 7;
|
||||
a[6] = (a[0] * 2 + a[1] * 5) / 7;
|
||||
a[7] = (a[0] + a[1] * 6) / 7;
|
||||
} else {
|
||||
a[2] = (a[0] * 4 + a[1]) / 5;
|
||||
a[3] = (a[0] * 3 + a[1] * 2) / 5;
|
||||
a[4] = (a[0] * 2 + a[1] * 3) / 5;
|
||||
a[5] = (a[0] + a[1] * 4) / 5;
|
||||
a[6] = 0;
|
||||
a[7] = 255;
|
||||
}
|
||||
for (int i = 0; i < 8; i++)
|
||||
a[i] = alpha_mask(a[i]);
|
||||
decode_dxt1_block(data + 8, outbuf);
|
||||
uint_fast64_t d = lton64(*(uint64_t *)data) >> 16;
|
||||
for (int i = 0; i < 16; i++, d >>= 3)
|
||||
outbuf[i] &= a[d & 7];
|
||||
}
|
||||
|
||||
int decode_dxt5(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
const uint8_t *d = data;
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, d += 16) {
|
||||
decode_dxt5_block(d, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef DXTC_H
|
||||
#define DXTC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int decode_dxt1(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_dxt5(const uint8_t *, const long, const long, uint32_t *);
|
||||
|
||||
#endif /* end of include guard: DXTC_H */
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
*
|
||||
* License Information
|
||||
*
|
||||
* endianness.h is derived from https://gist.github.com/jtbr/7a43e6281e6cca353b33ee501421860c
|
||||
* The file is licensed under the MIT License shown below.
|
||||
*
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENDIANNESS_H
|
||||
#define _ENDIANNESS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Detect platform endianness at compile time */
|
||||
|
||||
// If boost were available on all platforms, could use this instead to detect endianness
|
||||
// #include <boost/predef/endian.h>
|
||||
|
||||
// When available, these headers can improve platform endianness detection
|
||||
#ifdef __has_include // C++17, supported as extension to C++11 in clang, GCC 5+, vs2015
|
||||
#if __has_include(<endian.h>)
|
||||
#include <endian.h> // gnu libc normally provides, linux
|
||||
#elif __has_include(<machine/endian.h>)
|
||||
#include <machine/endian.h> //open bsd, macos
|
||||
#elif __has_include(<sys/param.h>)
|
||||
#include <sys/param.h> // mingw, some bsd (not open/macos)
|
||||
#elif __has_include(<sys/isadefs.h>)
|
||||
#include <sys/isadefs.h> // solaris
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \
|
||||
(defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) || (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) || \
|
||||
(defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || (defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || \
|
||||
defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \
|
||||
defined(__MIBSEB__) || defined(_M_PPC)
|
||||
#define __BIG_ENDIAN__
|
||||
#elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */ \
|
||||
(defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) /* linux header */ || \
|
||||
(defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) || \
|
||||
(defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /* mingw header */ || \
|
||||
(defined(__sun) && defined(__SVR4) && defined(_LITTLE_ENDIAN)) || /* solaris */ \
|
||||
defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || \
|
||||
defined(__MIPSEL__) || defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) || /* msvc for intel processors */ \
|
||||
defined(_M_ARM) /* msvc code on arm executes in little endian mode */
|
||||
#define __LITTLE_ENDIAN__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef bswap16
|
||||
#undef bswap16
|
||||
#endif
|
||||
#ifdef bswap32
|
||||
#undef bswap32
|
||||
#endif
|
||||
#ifdef bswap64
|
||||
#undef bswap64
|
||||
#endif
|
||||
|
||||
/* Define byte-swap functions, using fast processor-native built-ins where possible */
|
||||
// needs to be first because msvc doesn't short-circuit after failing defined(__has_builtin)
|
||||
#if defined(_MSC_VER)
|
||||
#define bswap16(x) _byteswap_ushort((x))
|
||||
#define bswap32(x) _byteswap_ulong((x))
|
||||
#define bswap64(x) _byteswap_uint64((x))
|
||||
#elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
|
||||
#define bswap16(x) __builtin_bswap16((x))
|
||||
#define bswap32(x) __builtin_bswap32((x))
|
||||
#define bswap64(x) __builtin_bswap64((x))
|
||||
#elif defined(__has_builtin) && __has_builtin(__builtin_bswap64)
|
||||
/* for clang; gcc 5 fails on this and && shortcircuit fails; must be after GCC check */
|
||||
#define bswap16(x) __builtin_bswap16((x))
|
||||
#define bswap32(x) __builtin_bswap32((x))
|
||||
#define bswap64(x) __builtin_bswap64((x))
|
||||
#else
|
||||
/* even in this case, compilers often optimize by using native instructions */
|
||||
static inline uint16_t bswap16(uint16_t x) {
|
||||
return (((x >> 8) & 0xffu) | ((x & 0xffu) << 8));
|
||||
}
|
||||
static inline uint32_t bswap32(uint32_t x) {
|
||||
return (((x & 0xff000000u) >> 24) | ((x & 0x00ff0000u) >> 8) | ((x & 0x0000ff00u) << 8) |
|
||||
((x & 0x000000ffu) << 24));
|
||||
}
|
||||
static inline uint64_t bswap64(uint64_t x) {
|
||||
return (((x & 0xff00000000000000ull) >> 56) | ((x & 0x00ff000000000000ull) >> 40) |
|
||||
((x & 0x0000ff0000000000ull) >> 24) | ((x & 0x000000ff00000000ull) >> 8) |
|
||||
((x & 0x00000000ff000000ull) << 8) | ((x & 0x0000000000ff0000ull) << 24) |
|
||||
((x & 0x000000000000ff00ull) << 40) | ((x & 0x00000000000000ffull) << 56));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* Defines network - host byte swaps as needed depending upon platform endianness */
|
||||
// note that network order is big endian)
|
||||
|
||||
#if defined(__LITTLE_ENDIAN__)
|
||||
#define ntoh16(x) bswap16((x))
|
||||
#define hton16(x) bswap16((x))
|
||||
#define ntoh32(x) bswap32((x))
|
||||
#define hton32(x) bswap32((x))
|
||||
#define ntoh64(x) bswap64((x))
|
||||
#define hton64(x) bswap64((x))
|
||||
#define lton16(x) (x)
|
||||
#define lton32(x) (x)
|
||||
#define lton64(x) (x)
|
||||
#define ltonf(x) (x)
|
||||
#define ltond(x) (x)
|
||||
#define bton16(x) bswap16((x))
|
||||
#define bton32(x) bswap32((x))
|
||||
#define bton64(x) bswap64((x))
|
||||
#define btonf(x) htonf((x))
|
||||
#define btond(x) htond((x))
|
||||
#elif defined(__BIG_ENDIAN__)
|
||||
#define ntoh16(x) (x)
|
||||
#define hton16(x) (x)
|
||||
#define ntoh32(x) (x)
|
||||
#define hton32(x) (x)
|
||||
#define ntoh64(x) (x)
|
||||
#define hton64(x) (x)
|
||||
#define bton16(x) (x)
|
||||
#define bton32(x) (x)
|
||||
#define bton64(x) (x)
|
||||
#define btonf(x) (x)
|
||||
#define btond(x) (x)
|
||||
#define lton16(x) bswap16((x))
|
||||
#define lton32(x) bswap32((x))
|
||||
#define lton64(x) bswap64((x))
|
||||
#define ltonf(x) htonf((x))
|
||||
#define ltond(x) htond((x))
|
||||
#else
|
||||
#warning "UNKNOWN Platform / endianness; network / host byte swaps not defined."
|
||||
#endif
|
||||
|
||||
|
||||
//! Convert 32-bit float from host to network byte order
|
||||
static inline float htonf(float f) {
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(float) == sizeof(uint32_t), "Unexpected float format");
|
||||
uint32_t val = hton32(*(reinterpret_cast<const uint32_t *>(&f)));
|
||||
return *(reinterpret_cast<float *>(&val));
|
||||
#else
|
||||
uint32_t val = hton32(*(const uint32_t *)(&f));
|
||||
return *((float *)(&val));
|
||||
#endif
|
||||
}
|
||||
#define ntohf(x) htonf((x))
|
||||
|
||||
//! Convert 64-bit double from host to network byte order
|
||||
static inline double htond(double f) {
|
||||
#ifdef __cplusplus
|
||||
static_assert(sizeof(double) == sizeof(uint64_t), "Unexpected double format");
|
||||
uint64_t val = hton64(*(reinterpret_cast<const uint64_t *>(&f)));
|
||||
return *(reinterpret_cast<double *>(&val));
|
||||
#else
|
||||
uint64_t val = hton64(*(const uint64_t *)(&f));
|
||||
return *((double *)(&val));
|
||||
#endif
|
||||
}
|
||||
#define ntohd(x) htond((x))
|
||||
|
||||
#endif //_ENDIANNESS_H
|
||||
@@ -0,0 +1,443 @@
|
||||
#include "etc.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "color.h"
|
||||
|
||||
const uint_fast8_t WriteOrderTable[16] = {0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15};
|
||||
const uint_fast8_t WriteOrderTableRev[16] = {15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0};
|
||||
const uint_fast8_t Etc1ModifierTable[8][2] = {{2, 8}, {5, 17}, {9, 29}, {13, 42},
|
||||
{18, 60}, {24, 80}, {33, 106}, {47, 183}};
|
||||
const uint_fast8_t Etc2aModifierTable[2][8][2] = {
|
||||
{{0, 8}, {0, 17}, {0, 29}, {0, 42}, {0, 60}, {0, 80}, {0, 106}, {0, 183}},
|
||||
{{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}}};
|
||||
const uint_fast8_t Etc1SubblockTable[2][16] = {{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1},
|
||||
{0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}};
|
||||
const uint_fast8_t Etc2DistanceTable[8] = {3, 6, 11, 16, 23, 32, 41, 64};
|
||||
const int_fast8_t Etc2AlphaModTable[16][8] = {
|
||||
{-3, -6, -9, -15, 2, 5, 8, 14}, {-3, -7, -10, -13, 2, 6, 9, 12}, {-2, -5, -8, -13, 1, 4, 7, 12},
|
||||
{-2, -4, -6, -13, 1, 3, 5, 12}, {-3, -6, -8, -12, 2, 5, 7, 11}, {-3, -7, -9, -11, 2, 6, 8, 10},
|
||||
{-4, -7, -8, -11, 3, 6, 7, 10}, {-3, -5, -8, -11, 2, 4, 7, 10}, {-2, -6, -8, -10, 1, 5, 7, 9},
|
||||
{-2, -5, -8, -10, 1, 4, 7, 9}, {-2, -4, -8, -10, 1, 3, 7, 9}, {-2, -5, -7, -10, 1, 4, 6, 9},
|
||||
{-3, -4, -7, -10, 2, 3, 6, 9}, {-1, -2, -3, -10, 0, 1, 2, 9}, {-4, -6, -8, -9, 3, 5, 7, 8},
|
||||
{-3, -5, -7, -9, 2, 4, 6, 8}};
|
||||
|
||||
static inline uint_fast8_t clamp(const int n) {
|
||||
return n < 0 ? 0 : n > 255 ? 255 : n;
|
||||
}
|
||||
|
||||
static inline uint32_t applicate_color(uint_fast8_t c[3], int_fast16_t m) {
|
||||
return color(clamp(c[0] + m), clamp(c[1] + m), clamp(c[2] + m), 255);
|
||||
}
|
||||
|
||||
static inline uint32_t applicate_color_alpha(uint_fast8_t c[3], int_fast16_t m, int transparent) {
|
||||
return color(clamp(c[0] + m), clamp(c[1] + m), clamp(c[2] + m), transparent ? 0 : 255);
|
||||
}
|
||||
|
||||
static inline uint32_t applicate_color_raw(uint_fast8_t c[3]) {
|
||||
return color(c[0], c[1], c[2], 255);
|
||||
}
|
||||
|
||||
static void decode_etc1_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
const uint_fast8_t code[2] = {data[3] >> 5, data[3] >> 2 & 7}; // Table codewords
|
||||
const uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
uint_fast8_t c[2][3];
|
||||
if (data[3] & 2) {
|
||||
// diff bit == 1
|
||||
c[0][0] = data[0] & 0xf8;
|
||||
c[0][1] = data[1] & 0xf8;
|
||||
c[0][2] = data[2] & 0xf8;
|
||||
c[1][0] = c[0][0] + (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20);
|
||||
c[1][1] = c[0][1] + (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20);
|
||||
c[1][2] = c[0][2] + (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20);
|
||||
c[0][0] |= c[0][0] >> 5;
|
||||
c[0][1] |= c[0][1] >> 5;
|
||||
c[0][2] |= c[0][2] >> 5;
|
||||
c[1][0] |= c[1][0] >> 5;
|
||||
c[1][1] |= c[1][1] >> 5;
|
||||
c[1][2] |= c[1][2] >> 5;
|
||||
} else {
|
||||
// diff bit == 0
|
||||
c[0][0] = (data[0] & 0xf0) | data[0] >> 4;
|
||||
c[1][0] = (data[0] & 0x0f) | data[0] << 4;
|
||||
c[0][1] = (data[1] & 0xf0) | data[1] >> 4;
|
||||
c[1][1] = (data[1] & 0x0f) | data[1] << 4;
|
||||
c[0][2] = (data[2] & 0xf0) | data[2] >> 4;
|
||||
c[1][2] = (data[2] & 0x0f) | data[2] << 4;
|
||||
}
|
||||
|
||||
uint_fast16_t j = data[6] << 8 | data[7]; // less significant pixel index bits
|
||||
uint_fast16_t k = data[4] << 8 | data[5]; // more significant pixel index bits
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint_fast16_t j = data[6] << 8 | data[7]; // 15 -> 0
|
||||
uint_fast32_t k = data[4] << 8 | data[5]; // 31 -> 16
|
||||
uint_fast8_t c[3][3] = {};
|
||||
|
||||
if (data[3] & 2) {
|
||||
// diff bit == 1
|
||||
uint_fast8_t r = data[0] & 0xf8;
|
||||
int_fast16_t dr = (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20);
|
||||
uint_fast8_t g = data[1] & 0xf8;
|
||||
int_fast16_t dg = (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20);
|
||||
uint_fast8_t b = data[2] & 0xf8;
|
||||
int_fast16_t db = (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20);
|
||||
if (r + dr < 0 || r + dr > 255) {
|
||||
// T
|
||||
c[0][0] = (data[0] << 3 & 0xc0) | (data[0] << 4 & 0x30) | (data[0] >> 1 & 0xc) | (data[0] & 3);
|
||||
c[0][1] = (data[1] & 0xf0) | data[1] >> 4;
|
||||
c[0][2] = (data[1] & 0x0f) | data[1] << 4;
|
||||
c[1][0] = (data[2] & 0xf0) | data[2] >> 4;
|
||||
c[1][1] = (data[2] & 0x0f) | data[2] << 4;
|
||||
c[1][2] = (data[3] & 0xf0) | data[3] >> 4;
|
||||
const uint_fast8_t d = Etc2DistanceTable[(data[3] >> 1 & 6) | (data[3] & 1)];
|
||||
uint_fast32_t color_set[4] = {applicate_color_raw(c[0]), applicate_color(c[1], d),
|
||||
applicate_color_raw(c[1]), applicate_color(c[1], -d)};
|
||||
k <<= 1;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
outbuf[WriteOrderTable[i]] = color_set[(k & 2) | (j & 1)];
|
||||
} else if (g + dg < 0 || g + dg > 255) {
|
||||
// H
|
||||
c[0][0] = (data[0] << 1 & 0xf0) | (data[0] >> 3 & 0xf);
|
||||
c[0][1] = (data[0] << 5 & 0xe0) | (data[1] & 0x10);
|
||||
c[0][1] |= c[0][1] >> 4;
|
||||
c[0][2] = (data[1] & 8) | (data[1] << 1 & 6) | data[2] >> 7;
|
||||
c[0][2] |= c[0][2] << 4;
|
||||
c[1][0] = (data[2] << 1 & 0xf0) | (data[2] >> 3 & 0xf);
|
||||
c[1][1] = (data[2] << 5 & 0xe0) | (data[3] >> 3 & 0x10);
|
||||
c[1][1] |= c[1][1] >> 4;
|
||||
c[1][2] = (data[3] << 1 & 0xf0) | (data[3] >> 3 & 0xf);
|
||||
uint_fast8_t d = (data[3] & 4) | (data[3] << 1 & 2);
|
||||
if (c[0][0] > c[1][0] ||
|
||||
(c[0][0] == c[1][0] && (c[0][1] > c[1][1] || (c[0][1] == c[1][1] && c[0][2] >= c[1][2]))))
|
||||
++d;
|
||||
d = Etc2DistanceTable[d];
|
||||
uint_fast32_t color_set[4] = {applicate_color(c[0], d), applicate_color(c[0], -d), applicate_color(c[1], d),
|
||||
applicate_color(c[1], -d)};
|
||||
k <<= 1;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1)
|
||||
outbuf[WriteOrderTable[i]] = color_set[(k & 2) | (j & 1)];
|
||||
} else if (b + db < 0 || b + db > 255) {
|
||||
// planar
|
||||
c[0][0] = (data[0] << 1 & 0xfc) | (data[0] >> 5 & 3);
|
||||
c[0][1] = (data[0] << 7 & 0x80) | (data[1] & 0x7e) | (data[0] & 1);
|
||||
c[0][2] = (data[1] << 7 & 0x80) | (data[2] << 2 & 0x60) | (data[2] << 3 & 0x18) | (data[3] >> 5 & 4);
|
||||
c[0][2] |= c[0][2] >> 6;
|
||||
c[1][0] = (data[3] << 1 & 0xf8) | (data[3] << 2 & 4) | (data[3] >> 5 & 3);
|
||||
c[1][1] = (data[4] & 0xfe) | data[4] >> 7;
|
||||
c[1][2] = (data[4] << 7 & 0x80) | (data[5] >> 1 & 0x7c);
|
||||
c[1][2] |= c[1][2] >> 6;
|
||||
c[2][0] = (data[5] << 5 & 0xe0) | (data[6] >> 3 & 0x1c) | (data[5] >> 1 & 3);
|
||||
c[2][1] = (data[6] << 3 & 0xf8) | (data[7] >> 5 & 0x6) | (data[6] >> 4 & 1);
|
||||
c[2][2] = data[7] << 2 | (data[7] >> 4 & 3);
|
||||
for (int y = 0, i = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++, i++) {
|
||||
uint8_t r = clamp((x * (c[1][0] - c[0][0]) + y * (c[2][0] - c[0][0]) + 4 * c[0][0] + 2) >> 2);
|
||||
uint8_t g = clamp((x * (c[1][1] - c[0][1]) + y * (c[2][1] - c[0][1]) + 4 * c[0][1] + 2) >> 2);
|
||||
uint8_t b = clamp((x * (c[1][2] - c[0][2]) + y * (c[2][2] - c[0][2]) + 4 * c[0][2] + 2) >> 2);
|
||||
outbuf[i] = color(r, g, b, 255);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// differential
|
||||
const uint_fast8_t code[2] = {data[3] >> 5, data[3] >> 2 & 7};
|
||||
const uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
c[0][0] = r | r >> 5;
|
||||
c[0][1] = g | g >> 5;
|
||||
c[0][2] = b | b >> 5;
|
||||
c[1][0] = r + dr;
|
||||
c[1][1] = g + dg;
|
||||
c[1][2] = b + db;
|
||||
c[1][0] |= c[1][0] >> 5;
|
||||
c[1][1] |= c[1][1] >> 5;
|
||||
c[1][2] |= c[1][2] >> 5;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// individual (diff bit == 0)
|
||||
const uint_fast8_t code[2] = {data[3] >> 5, data[3] >> 2 & 7};
|
||||
const uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
c[0][0] = (data[0] & 0xf0) | data[0] >> 4;
|
||||
c[1][0] = (data[0] & 0x0f) | data[0] << 4;
|
||||
c[0][1] = (data[1] & 0xf0) | data[1] >> 4;
|
||||
c[1][1] = (data[1] & 0x0f) | data[1] << 4;
|
||||
c[0][2] = (data[2] & 0xf0) | data[2] >> 4;
|
||||
c[1][2] = (data[2] & 0x0f) | data[2] << 4;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc1ModifierTable[code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color(c[s], k & 1 ? -m : m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_etc2a1_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
uint_fast16_t j = data[6] << 8 | data[7]; // 15 -> 0
|
||||
uint_fast32_t k = data[4] << 8 | data[5]; // 31 -> 16
|
||||
uint_fast8_t c[3][3] = {};
|
||||
|
||||
int obaq = data[3] >> 1 & 1;
|
||||
|
||||
// diff bit == 1
|
||||
uint_fast8_t r = data[0] & 0xf8;
|
||||
int_fast16_t dr = (data[0] << 3 & 0x18) - (data[0] << 3 & 0x20);
|
||||
uint_fast8_t g = data[1] & 0xf8;
|
||||
int_fast16_t dg = (data[1] << 3 & 0x18) - (data[1] << 3 & 0x20);
|
||||
uint_fast8_t b = data[2] & 0xf8;
|
||||
int_fast16_t db = (data[2] << 3 & 0x18) - (data[2] << 3 & 0x20);
|
||||
if (r + dr < 0 || r + dr > 255) {
|
||||
// T
|
||||
c[0][0] = (data[0] << 3 & 0xc0) | (data[0] << 4 & 0x30) | (data[0] >> 1 & 0xc) | (data[0] & 3);
|
||||
c[0][1] = (data[1] & 0xf0) | data[1] >> 4;
|
||||
c[0][2] = (data[1] & 0x0f) | data[1] << 4;
|
||||
c[1][0] = (data[2] & 0xf0) | data[2] >> 4;
|
||||
c[1][1] = (data[2] & 0x0f) | data[2] << 4;
|
||||
c[1][2] = (data[3] & 0xf0) | data[3] >> 4;
|
||||
const uint_fast8_t d = Etc2DistanceTable[(data[3] >> 1 & 6) | (data[3] & 1)];
|
||||
uint_fast32_t color_set[4] = {applicate_color_raw(c[0]), applicate_color(c[1], d), applicate_color_raw(c[1]),
|
||||
applicate_color(c[1], -d)};
|
||||
k <<= 1;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
int index = (k & 2) | (j & 1);
|
||||
outbuf[WriteOrderTable[i]] = color_set[index];
|
||||
if (!obaq && index == 2)
|
||||
outbuf[WriteOrderTable[i]] &= TRANSPARENT_MASK;
|
||||
}
|
||||
} else if (g + dg < 0 || g + dg > 255) {
|
||||
// H
|
||||
c[0][0] = (data[0] << 1 & 0xf0) | (data[0] >> 3 & 0xf);
|
||||
c[0][1] = (data[0] << 5 & 0xe0) | (data[1] & 0x10);
|
||||
c[0][1] |= c[0][1] >> 4;
|
||||
c[0][2] = (data[1] & 8) | (data[1] << 1 & 6) | data[2] >> 7;
|
||||
c[0][2] |= c[0][2] << 4;
|
||||
c[1][0] = (data[2] << 1 & 0xf0) | (data[2] >> 3 & 0xf);
|
||||
c[1][1] = (data[2] << 5 & 0xe0) | (data[3] >> 3 & 0x10);
|
||||
c[1][1] |= c[1][1] >> 4;
|
||||
c[1][2] = (data[3] << 1 & 0xf0) | (data[3] >> 3 & 0xf);
|
||||
uint_fast8_t d = (data[3] & 4) | (data[3] << 1 & 2);
|
||||
if (c[0][0] > c[1][0] ||
|
||||
(c[0][0] == c[1][0] && (c[0][1] > c[1][1] || (c[0][1] == c[1][1] && c[0][2] >= c[1][2]))))
|
||||
++d;
|
||||
d = Etc2DistanceTable[d];
|
||||
uint_fast32_t color_set[4] = {applicate_color(c[0], d), applicate_color(c[0], -d), applicate_color(c[1], d),
|
||||
applicate_color(c[1], -d)};
|
||||
k <<= 1;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
int index = (k & 2) | (j & 1);
|
||||
outbuf[WriteOrderTable[i]] = color_set[index];
|
||||
if (!obaq && index == 2)
|
||||
outbuf[WriteOrderTable[i]] &= TRANSPARENT_MASK;
|
||||
}
|
||||
} else if (b + db < 0 || b + db > 255) {
|
||||
// planar
|
||||
c[0][0] = (data[0] << 1 & 0xfc) | (data[0] >> 5 & 3);
|
||||
c[0][1] = (data[0] << 7 & 0x80) | (data[1] & 0x7e) | (data[0] & 1);
|
||||
c[0][2] = (data[1] << 7 & 0x80) | (data[2] << 2 & 0x60) | (data[2] << 3 & 0x18) | (data[3] >> 5 & 4);
|
||||
c[0][2] |= c[0][2] >> 6;
|
||||
c[1][0] = (data[3] << 1 & 0xf8) | (data[3] << 2 & 4) | (data[3] >> 5 & 3);
|
||||
c[1][1] = (data[4] & 0xfe) | data[4] >> 7;
|
||||
c[1][2] = (data[4] << 7 & 0x80) | (data[5] >> 1 & 0x7c);
|
||||
c[1][2] |= c[1][2] >> 6;
|
||||
c[2][0] = (data[5] << 5 & 0xe0) | (data[6] >> 3 & 0x1c) | (data[5] >> 1 & 3);
|
||||
c[2][1] = (data[6] << 3 & 0xf8) | (data[7] >> 5 & 0x6) | (data[6] >> 4 & 1);
|
||||
c[2][2] = data[7] << 2 | (data[7] >> 4 & 3);
|
||||
for (int y = 0, i = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++, i++) {
|
||||
uint8_t r = clamp((x * (c[1][0] - c[0][0]) + y * (c[2][0] - c[0][0]) + 4 * c[0][0] + 2) >> 2);
|
||||
uint8_t g = clamp((x * (c[1][1] - c[0][1]) + y * (c[2][1] - c[0][1]) + 4 * c[0][1] + 2) >> 2);
|
||||
uint8_t b = clamp((x * (c[1][2] - c[0][2]) + y * (c[2][2] - c[0][2]) + 4 * c[0][2] + 2) >> 2);
|
||||
outbuf[i] = color(r, g, b, 255);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// differential
|
||||
const uint_fast8_t code[2] = {data[3] >> 5, data[3] >> 2 & 7};
|
||||
const uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
|
||||
c[0][0] = r | r >> 5;
|
||||
c[0][1] = g | g >> 5;
|
||||
c[0][2] = b | b >> 5;
|
||||
c[1][0] = r + dr;
|
||||
c[1][1] = g + dg;
|
||||
c[1][2] = b + db;
|
||||
c[1][0] |= c[1][0] >> 5;
|
||||
c[1][1] |= c[1][1] >> 5;
|
||||
c[1][2] |= c[1][2] >> 5;
|
||||
for (int i = 0; i < 16; i++, j >>= 1, k >>= 1) {
|
||||
uint_fast8_t s = table[i];
|
||||
uint_fast8_t m = Etc2aModifierTable[obaq][code[s]][j & 1];
|
||||
outbuf[WriteOrderTable[i]] = applicate_color_alpha(c[s], k & 1 ? -m : m, !obaq && (k & 1) && !(j & 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_etc2a8_block(const uint8_t *data, uint32_t *outbuf) {
|
||||
if (data[1] & 0xf0) {
|
||||
// multiplier != 0
|
||||
const uint_fast8_t multiplier = data[1] >> 4;
|
||||
const int_fast8_t *table = Etc2AlphaModTable[data[1] & 0xf];
|
||||
uint_fast64_t l = bton64(*(uint64_t*)data);
|
||||
for (int i = 0; i < 16; i++, l >>= 3)
|
||||
((uint8_t *)(outbuf + WriteOrderTableRev[i]))[3] = clamp(data[0] + multiplier * table[l & 7]);
|
||||
} else {
|
||||
// multiplier == 0 (always same as base codeword)
|
||||
for (int i = 0; i < 16; i++, outbuf++)
|
||||
((uint8_t *)outbuf)[3] = data[0];
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_eac_block(const uint8_t *data, int color, uint32_t *outbuf) {
|
||||
uint_fast8_t multiplier = data[1] >> 1 & 0x78;
|
||||
if (multiplier == 0)
|
||||
multiplier = 1;
|
||||
const int_fast8_t *table = Etc2AlphaModTable[data[1] & 0xf];
|
||||
uint_fast64_t l = bton64(*(uint64_t*)data);
|
||||
for (int i = 0; i < 16; i++, l >>= 3) {
|
||||
int_fast16_t val = data[0] * 8 + multiplier * table[l & 7] + 4;
|
||||
((uint8_t *)(outbuf + WriteOrderTableRev[i]))[color] = val < 0 ? 0 : val >= 2048 ? 0xff : val >> 3;
|
||||
}
|
||||
}
|
||||
|
||||
static void decode_eac_signed_block(const uint8_t *data, int color, uint32_t *outbuf) {
|
||||
int8_t base = (int8_t)data[0];
|
||||
uint_fast8_t multiplier = data[1] >> 1 & 0x78;
|
||||
if (multiplier == 0)
|
||||
multiplier = 1;
|
||||
const int_fast8_t *table = Etc2AlphaModTable[data[1] & 0xf];
|
||||
uint_fast64_t l = bton64(*(uint64_t*)data);
|
||||
for (int i = 0; i < 16; i++, l >>= 3) {
|
||||
int_fast16_t val = base * 8 + multiplier * table[l & 7] + 1023;
|
||||
((uint8_t *)(outbuf + WriteOrderTableRev[i]))[color] = val < 0 ? 0 : val >= 2048 ? 0xff : val >> 3;
|
||||
}
|
||||
}
|
||||
|
||||
int decode_etc1(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 8) {
|
||||
decode_etc1_block(data, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_etc2(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 8) {
|
||||
decode_etc2_block(data, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_etc2a1(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 8) {
|
||||
decode_etc2a1_block(data, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_etc2a8(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 16) {
|
||||
decode_etc2_block(data + 8, buffer);
|
||||
decode_etc2a8_block(data, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_eacr(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
uint32_t base_buffer[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
base_buffer[i] = color(0, 0, 0, 255);
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 8) {
|
||||
memcpy(buffer, base_buffer, sizeof(buffer));
|
||||
decode_eac_block(data, 0, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_eacr_signed(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
uint32_t base_buffer[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
base_buffer[i] = color(0, 0, 0, 255);
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 8) {
|
||||
memcpy(buffer, base_buffer, sizeof(buffer));
|
||||
decode_eac_signed_block(data, 0, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_eacrg(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
uint32_t base_buffer[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
base_buffer[i] = color(0, 0, 0, 255);
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 16) {
|
||||
memcpy(buffer, base_buffer, sizeof(buffer));
|
||||
decode_eac_block(data, 0, buffer);
|
||||
decode_eac_block(data + 8, 1, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_eacrg_signed(const uint8_t *data, const long w, const long h, uint32_t *image) {
|
||||
long num_blocks_x = (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
uint32_t buffer[16];
|
||||
uint32_t base_buffer[16];
|
||||
for (int i = 0; i < 16; i++)
|
||||
base_buffer[i] = color(0, 0, 0, 255);
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
for (long bx = 0; bx < num_blocks_x; bx++, data += 16) {
|
||||
memcpy(buffer, base_buffer, sizeof(buffer));
|
||||
decode_eac_signed_block(data, 0, buffer);
|
||||
decode_eac_signed_block(data + 8, 1, buffer);
|
||||
copy_block_buffer(bx, by, w, h, 4, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef ETC_H
|
||||
#define ETC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int decode_etc1(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_etc2(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_etc2a1(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_etc2a8(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_eacr(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_eacr_signed(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_eacrg(const uint8_t *, const long, const long, uint32_t *);
|
||||
int decode_eacrg_signed(const uint8_t *, const long, const long, uint32_t *);
|
||||
|
||||
#endif /* end of include guard: ETC_H */
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mkmf'
|
||||
|
||||
append_cppflags('-std=c11')
|
||||
append_cppflags('-O2')
|
||||
append_cppflags('-Wall')
|
||||
append_cppflags('-Wextra')
|
||||
append_cppflags('-Wvla')
|
||||
|
||||
create_makefile('mikunyan/decoders/native')
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#ifndef FP16_H
|
||||
#define FP16_H
|
||||
|
||||
#include "fp16/fp16.h"
|
||||
|
||||
#endif /* FP16_H */
|
||||
|
||||
/*
|
||||
*
|
||||
* License Information
|
||||
*
|
||||
* FP16 library is derived from https://github.com/Maratyszcza/FP16.
|
||||
* The library is licensed under the MIT License shown below.
|
||||
*
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017 Facebook Inc.
|
||||
* Copyright (c) 2017 Georgia Institute of Technology
|
||||
* Copyright 2019 Google LLC
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
||||
* Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#ifndef FP16_BITCASTS_H
|
||||
#define FP16_BITCASTS_H
|
||||
|
||||
#if defined(__cplusplus) && (__cplusplus >= 201103L)
|
||||
#include <cstdint>
|
||||
#elif !defined(__OPENCL_VERSION__)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
|
||||
static inline float fp32_from_bits(uint32_t w) {
|
||||
#if defined(__OPENCL_VERSION__)
|
||||
return as_float(w);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
return __uint_as_float((unsigned int) w);
|
||||
#elif defined(__INTEL_COMPILER)
|
||||
return _castu32_f32(w);
|
||||
#else
|
||||
union {
|
||||
uint32_t as_bits;
|
||||
float as_value;
|
||||
} fp32 = { w };
|
||||
return fp32.as_value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t fp32_to_bits(float f) {
|
||||
#if defined(__OPENCL_VERSION__)
|
||||
return as_uint(f);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
return (uint32_t) __float_as_uint(f);
|
||||
#elif defined(__INTEL_COMPILER)
|
||||
return _castf32_u32(f);
|
||||
#else
|
||||
union {
|
||||
float as_value;
|
||||
uint32_t as_bits;
|
||||
} fp32 = { f };
|
||||
return fp32.as_bits;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline double fp64_from_bits(uint64_t w) {
|
||||
#if defined(__OPENCL_VERSION__)
|
||||
return as_double(w);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
return __longlong_as_double((long long) w);
|
||||
#elif defined(__INTEL_COMPILER)
|
||||
return _castu64_f64(w);
|
||||
#else
|
||||
union {
|
||||
uint64_t as_bits;
|
||||
double as_value;
|
||||
} fp64 = { w };
|
||||
return fp64.as_value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint64_t fp64_to_bits(double f) {
|
||||
#if defined(__OPENCL_VERSION__)
|
||||
return as_ulong(f);
|
||||
#elif defined(__CUDA_ARCH__)
|
||||
return (uint64_t) __double_as_longlong(f);
|
||||
#elif defined(__INTEL_COMPILER)
|
||||
return _castf64_u64(f);
|
||||
#else
|
||||
union {
|
||||
double as_value;
|
||||
uint64_t as_bits;
|
||||
} fp64 = { f };
|
||||
return fp64.as_bits;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* FP16_BITCASTS_H */
|
||||
@@ -0,0 +1,451 @@
|
||||
#pragma once
|
||||
#ifndef FP16_FP16_H
|
||||
#define FP16_FP16_H
|
||||
|
||||
#if defined(__cplusplus) && (__cplusplus >= 201103L)
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#elif !defined(__OPENCL_VERSION__)
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "fp16/bitcasts.h"
|
||||
|
||||
|
||||
/*
|
||||
* Convert a 16-bit floating-point number in IEEE half-precision format, in bit representation, to
|
||||
* a 32-bit floating-point number in IEEE single-precision format, in bit representation.
|
||||
*
|
||||
* @note The implementation doesn't use any floating-point operations.
|
||||
*/
|
||||
static inline uint32_t fp16_ieee_to_fp32_bits(uint16_t h) {
|
||||
/*
|
||||
* Extend the half-precision floating-point number to 32 bits and shift to the upper part of the 32-bit word:
|
||||
* +---+-----+------------+-------------------+
|
||||
* | S |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 31 26-30 16-25 0-15
|
||||
*
|
||||
* S - sign bit, E - bits of the biased exponent, M - bits of the mantissa, 0 - zero bits.
|
||||
*/
|
||||
const uint32_t w = (uint32_t) h << 16;
|
||||
/*
|
||||
* Extract the sign of the input number into the high bit of the 32-bit word:
|
||||
*
|
||||
* +---+----------------------------------+
|
||||
* | S |0000000 00000000 00000000 00000000|
|
||||
* +---+----------------------------------+
|
||||
* Bits 31 0-31
|
||||
*/
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
/*
|
||||
* Extract mantissa and biased exponent of the input number into the bits 0-30 of the 32-bit word:
|
||||
*
|
||||
* +---+-----+------------+-------------------+
|
||||
* | 0 |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 30 27-31 17-26 0-16
|
||||
*/
|
||||
const uint32_t nonsign = w & UINT32_C(0x7FFFFFFF);
|
||||
/*
|
||||
* Renorm shift is the number of bits to shift mantissa left to make the half-precision number normalized.
|
||||
* If the initial number is normalized, some of its high 6 bits (sign == 0 and 5-bit exponent) equals one.
|
||||
* In this case renorm_shift == 0. If the number is denormalize, renorm_shift > 0. Note that if we shift
|
||||
* denormalized nonsign by renorm_shift, the unit bit of mantissa will shift into exponent, turning the
|
||||
* biased exponent into 1, and making mantissa normalized (i.e. without leading 1).
|
||||
*/
|
||||
#ifdef _MSC_VER
|
||||
unsigned long nonsign_bsr;
|
||||
_BitScanReverse(&nonsign_bsr, (unsigned long) nonsign);
|
||||
uint32_t renorm_shift = (uint32_t) nonsign_bsr ^ 31;
|
||||
#else
|
||||
uint32_t renorm_shift = __builtin_clz(nonsign);
|
||||
#endif
|
||||
renorm_shift = renorm_shift > 5 ? renorm_shift - 5 : 0;
|
||||
/*
|
||||
* Iff half-precision number has exponent of 15, the addition overflows it into bit 31,
|
||||
* and the subsequent shift turns the high 9 bits into 1. Thus
|
||||
* inf_nan_mask ==
|
||||
* 0x7F800000 if the half-precision number had exponent of 15 (i.e. was NaN or infinity)
|
||||
* 0x00000000 otherwise
|
||||
*/
|
||||
const int32_t inf_nan_mask = ((int32_t) (nonsign + 0x04000000) >> 8) & INT32_C(0x7F800000);
|
||||
/*
|
||||
* Iff nonsign is 0, it overflows into 0xFFFFFFFF, turning bit 31 into 1. Otherwise, bit 31 remains 0.
|
||||
* The signed shift right by 31 broadcasts bit 31 into all bits of the zero_mask. Thus
|
||||
* zero_mask ==
|
||||
* 0xFFFFFFFF if the half-precision number was zero (+0.0h or -0.0h)
|
||||
* 0x00000000 otherwise
|
||||
*/
|
||||
const int32_t zero_mask = (int32_t) (nonsign - 1) >> 31;
|
||||
/*
|
||||
* 1. Shift nonsign left by renorm_shift to normalize it (if the input was denormal)
|
||||
* 2. Shift nonsign right by 3 so the exponent (5 bits originally) becomes an 8-bit field and 10-bit mantissa
|
||||
* shifts into the 10 high bits of the 23-bit mantissa of IEEE single-precision number.
|
||||
* 3. Add 0x70 to the exponent (starting at bit 23) to compensate the different in exponent bias
|
||||
* (0x7F for single-precision number less 0xF for half-precision number).
|
||||
* 4. Subtract renorm_shift from the exponent (starting at bit 23) to account for renormalization. As renorm_shift
|
||||
* is less than 0x70, this can be combined with step 3.
|
||||
* 5. Binary OR with inf_nan_mask to turn the exponent into 0xFF if the input was NaN or infinity.
|
||||
* 6. Binary ANDNOT with zero_mask to turn the mantissa and exponent into zero if the input was zero.
|
||||
* 7. Combine with the sign of the input number.
|
||||
*/
|
||||
return sign | ((((nonsign << renorm_shift >> 3) + ((0x70 - renorm_shift) << 23)) | inf_nan_mask) & ~zero_mask);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a 16-bit floating-point number in IEEE half-precision format, in bit representation, to
|
||||
* a 32-bit floating-point number in IEEE single-precision format.
|
||||
*
|
||||
* @note The implementation relies on IEEE-like (no assumption about rounding mode and no operations on denormals)
|
||||
* floating-point operations and bitcasts between integer and floating-point variables.
|
||||
*/
|
||||
static inline float fp16_ieee_to_fp32_value(uint16_t h) {
|
||||
/*
|
||||
* Extend the half-precision floating-point number to 32 bits and shift to the upper part of the 32-bit word:
|
||||
* +---+-----+------------+-------------------+
|
||||
* | S |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 31 26-30 16-25 0-15
|
||||
*
|
||||
* S - sign bit, E - bits of the biased exponent, M - bits of the mantissa, 0 - zero bits.
|
||||
*/
|
||||
const uint32_t w = (uint32_t) h << 16;
|
||||
/*
|
||||
* Extract the sign of the input number into the high bit of the 32-bit word:
|
||||
*
|
||||
* +---+----------------------------------+
|
||||
* | S |0000000 00000000 00000000 00000000|
|
||||
* +---+----------------------------------+
|
||||
* Bits 31 0-31
|
||||
*/
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
/*
|
||||
* Extract mantissa and biased exponent of the input number into the high bits of the 32-bit word:
|
||||
*
|
||||
* +-----+------------+---------------------+
|
||||
* |EEEEE|MM MMMM MMMM|0 0000 0000 0000 0000|
|
||||
* +-----+------------+---------------------+
|
||||
* Bits 27-31 17-26 0-16
|
||||
*/
|
||||
const uint32_t two_w = w + w;
|
||||
|
||||
/*
|
||||
* Shift mantissa and exponent into bits 23-28 and bits 13-22 so they become mantissa and exponent
|
||||
* of a single-precision floating-point number:
|
||||
*
|
||||
* S|Exponent | Mantissa
|
||||
* +-+---+-----+------------+----------------+
|
||||
* |0|000|EEEEE|MM MMMM MMMM|0 0000 0000 0000|
|
||||
* +-+---+-----+------------+----------------+
|
||||
* Bits | 23-31 | 0-22
|
||||
*
|
||||
* Next, there are some adjustments to the exponent:
|
||||
* - The exponent needs to be corrected by the difference in exponent bias between single-precision and half-precision
|
||||
* formats (0x7F - 0xF = 0x70)
|
||||
* - Inf and NaN values in the inputs should become Inf and NaN values after conversion to the single-precision number.
|
||||
* Therefore, if the biased exponent of the half-precision input was 0x1F (max possible value), the biased exponent
|
||||
* of the single-precision output must be 0xFF (max possible value). We do this correction in two steps:
|
||||
* - First, we adjust the exponent by (0xFF - 0x1F) = 0xE0 (see exp_offset below) rather than by 0x70 suggested
|
||||
* by the difference in the exponent bias (see above).
|
||||
* - Then we multiply the single-precision result of exponent adjustment by 2**(-112) to reverse the effect of
|
||||
* exponent adjustment by 0xE0 less the necessary exponent adjustment by 0x70 due to difference in exponent bias.
|
||||
* The floating-point multiplication hardware would ensure than Inf and NaN would retain their value on at least
|
||||
* partially IEEE754-compliant implementations.
|
||||
*
|
||||
* Note that the above operations do not handle denormal inputs (where biased exponent == 0). However, they also do not
|
||||
* operate on denormal inputs, and do not produce denormal results.
|
||||
*/
|
||||
const uint32_t exp_offset = UINT32_C(0xE0) << 23;
|
||||
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || defined(__GNUC__) && !defined(__STRICT_ANSI__)
|
||||
const float exp_scale = 0x1.0p-112f;
|
||||
#else
|
||||
const float exp_scale = fp32_from_bits(UINT32_C(0x7800000));
|
||||
#endif
|
||||
const float normalized_value = fp32_from_bits((two_w >> 4) + exp_offset) * exp_scale;
|
||||
|
||||
/*
|
||||
* Convert denormalized half-precision inputs into single-precision results (always normalized).
|
||||
* Zero inputs are also handled here.
|
||||
*
|
||||
* In a denormalized number the biased exponent is zero, and mantissa has on-zero bits.
|
||||
* First, we shift mantissa into bits 0-9 of the 32-bit word.
|
||||
*
|
||||
* zeros | mantissa
|
||||
* +---------------------------+------------+
|
||||
* |0000 0000 0000 0000 0000 00|MM MMMM MMMM|
|
||||
* +---------------------------+------------+
|
||||
* Bits 10-31 0-9
|
||||
*
|
||||
* Now, remember that denormalized half-precision numbers are represented as:
|
||||
* FP16 = mantissa * 2**(-24).
|
||||
* The trick is to construct a normalized single-precision number with the same mantissa and thehalf-precision input
|
||||
* and with an exponent which would scale the corresponding mantissa bits to 2**(-24).
|
||||
* A normalized single-precision floating-point number is represented as:
|
||||
* FP32 = (1 + mantissa * 2**(-23)) * 2**(exponent - 127)
|
||||
* Therefore, when the biased exponent is 126, a unit change in the mantissa of the input denormalized half-precision
|
||||
* number causes a change of the constructud single-precision number by 2**(-24), i.e. the same ammount.
|
||||
*
|
||||
* The last step is to adjust the bias of the constructed single-precision number. When the input half-precision number
|
||||
* is zero, the constructed single-precision number has the value of
|
||||
* FP32 = 1 * 2**(126 - 127) = 2**(-1) = 0.5
|
||||
* Therefore, we need to subtract 0.5 from the constructed single-precision number to get the numerical equivalent of
|
||||
* the input half-precision number.
|
||||
*/
|
||||
const uint32_t magic_mask = UINT32_C(126) << 23;
|
||||
const float magic_bias = 0.5f;
|
||||
const float denormalized_value = fp32_from_bits((two_w >> 17) | magic_mask) - magic_bias;
|
||||
|
||||
/*
|
||||
* - Choose either results of conversion of input as a normalized number, or as a denormalized number, depending on the
|
||||
* input exponent. The variable two_w contains input exponent in bits 27-31, therefore if its smaller than 2**27, the
|
||||
* input is either a denormal number, or zero.
|
||||
* - Combine the result of conversion of exponent and mantissa with the sign of the input number.
|
||||
*/
|
||||
const uint32_t denormalized_cutoff = UINT32_C(1) << 27;
|
||||
const uint32_t result = sign |
|
||||
(two_w < denormalized_cutoff ? fp32_to_bits(denormalized_value) : fp32_to_bits(normalized_value));
|
||||
return fp32_from_bits(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a 32-bit floating-point number in IEEE single-precision format to a 16-bit floating-point number in
|
||||
* IEEE half-precision format, in bit representation.
|
||||
*
|
||||
* @note The implementation relies on IEEE-like (no assumption about rounding mode and no operations on denormals)
|
||||
* floating-point operations and bitcasts between integer and floating-point variables.
|
||||
*/
|
||||
static inline uint16_t fp16_ieee_from_fp32_value(float f) {
|
||||
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) || defined(__GNUC__) && !defined(__STRICT_ANSI__)
|
||||
const float scale_to_inf = 0x1.0p+112f;
|
||||
const float scale_to_zero = 0x1.0p-110f;
|
||||
#else
|
||||
const float scale_to_inf = fp32_from_bits(UINT32_C(0x77800000));
|
||||
const float scale_to_zero = fp32_from_bits(UINT32_C(0x08800000));
|
||||
#endif
|
||||
float base = (fabsf(f) * scale_to_inf) * scale_to_zero;
|
||||
|
||||
const uint32_t w = fp32_to_bits(f);
|
||||
const uint32_t shl1_w = w + w;
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
uint32_t bias = shl1_w & UINT32_C(0xFF000000);
|
||||
if (bias < UINT32_C(0x71000000)) {
|
||||
bias = UINT32_C(0x71000000);
|
||||
}
|
||||
|
||||
base = fp32_from_bits((bias >> 1) + UINT32_C(0x07800000)) + base;
|
||||
const uint32_t bits = fp32_to_bits(base);
|
||||
const uint32_t exp_bits = (bits >> 13) & UINT32_C(0x00007C00);
|
||||
const uint32_t mantissa_bits = bits & UINT32_C(0x00000FFF);
|
||||
const uint32_t nonsign = exp_bits + mantissa_bits;
|
||||
return (sign >> 16) | (shl1_w > UINT32_C(0xFF000000) ? UINT16_C(0x7E00) : nonsign);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a 16-bit floating-point number in ARM alternative half-precision format, in bit representation, to
|
||||
* a 32-bit floating-point number in IEEE single-precision format, in bit representation.
|
||||
*
|
||||
* @note The implementation doesn't use any floating-point operations.
|
||||
*/
|
||||
static inline uint32_t fp16_alt_to_fp32_bits(uint16_t h) {
|
||||
/*
|
||||
* Extend the half-precision floating-point number to 32 bits and shift to the upper part of the 32-bit word:
|
||||
* +---+-----+------------+-------------------+
|
||||
* | S |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 31 26-30 16-25 0-15
|
||||
*
|
||||
* S - sign bit, E - bits of the biased exponent, M - bits of the mantissa, 0 - zero bits.
|
||||
*/
|
||||
const uint32_t w = (uint32_t) h << 16;
|
||||
/*
|
||||
* Extract the sign of the input number into the high bit of the 32-bit word:
|
||||
*
|
||||
* +---+----------------------------------+
|
||||
* | S |0000000 00000000 00000000 00000000|
|
||||
* +---+----------------------------------+
|
||||
* Bits 31 0-31
|
||||
*/
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
/*
|
||||
* Extract mantissa and biased exponent of the input number into the bits 0-30 of the 32-bit word:
|
||||
*
|
||||
* +---+-----+------------+-------------------+
|
||||
* | 0 |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 30 27-31 17-26 0-16
|
||||
*/
|
||||
const uint32_t nonsign = w & UINT32_C(0x7FFFFFFF);
|
||||
/*
|
||||
* Renorm shift is the number of bits to shift mantissa left to make the half-precision number normalized.
|
||||
* If the initial number is normalized, some of its high 6 bits (sign == 0 and 5-bit exponent) equals one.
|
||||
* In this case renorm_shift == 0. If the number is denormalize, renorm_shift > 0. Note that if we shift
|
||||
* denormalized nonsign by renorm_shift, the unit bit of mantissa will shift into exponent, turning the
|
||||
* biased exponent into 1, and making mantissa normalized (i.e. without leading 1).
|
||||
*/
|
||||
#ifdef _MSC_VER
|
||||
unsigned long nonsign_bsr;
|
||||
_BitScanReverse(&nonsign_bsr, (unsigned long) nonsign);
|
||||
uint32_t renorm_shift = (uint32_t) nonsign_bsr ^ 31;
|
||||
#else
|
||||
uint32_t renorm_shift = __builtin_clz(nonsign);
|
||||
#endif
|
||||
renorm_shift = renorm_shift > 5 ? renorm_shift - 5 : 0;
|
||||
/*
|
||||
* Iff nonsign is 0, it overflows into 0xFFFFFFFF, turning bit 31 into 1. Otherwise, bit 31 remains 0.
|
||||
* The signed shift right by 31 broadcasts bit 31 into all bits of the zero_mask. Thus
|
||||
* zero_mask ==
|
||||
* 0xFFFFFFFF if the half-precision number was zero (+0.0h or -0.0h)
|
||||
* 0x00000000 otherwise
|
||||
*/
|
||||
const int32_t zero_mask = (int32_t) (nonsign - 1) >> 31;
|
||||
/*
|
||||
* 1. Shift nonsign left by renorm_shift to normalize it (if the input was denormal)
|
||||
* 2. Shift nonsign right by 3 so the exponent (5 bits originally) becomes an 8-bit field and 10-bit mantissa
|
||||
* shifts into the 10 high bits of the 23-bit mantissa of IEEE single-precision number.
|
||||
* 3. Add 0x70 to the exponent (starting at bit 23) to compensate the different in exponent bias
|
||||
* (0x7F for single-precision number less 0xF for half-precision number).
|
||||
* 4. Subtract renorm_shift from the exponent (starting at bit 23) to account for renormalization. As renorm_shift
|
||||
* is less than 0x70, this can be combined with step 3.
|
||||
* 5. Binary ANDNOT with zero_mask to turn the mantissa and exponent into zero if the input was zero.
|
||||
* 6. Combine with the sign of the input number.
|
||||
*/
|
||||
return sign | (((nonsign << renorm_shift >> 3) + ((0x70 - renorm_shift) << 23)) & ~zero_mask);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a 16-bit floating-point number in ARM alternative half-precision format, in bit representation, to
|
||||
* a 32-bit floating-point number in IEEE single-precision format.
|
||||
*
|
||||
* @note The implementation relies on IEEE-like (no assumption about rounding mode and no operations on denormals)
|
||||
* floating-point operations and bitcasts between integer and floating-point variables.
|
||||
*/
|
||||
static inline float fp16_alt_to_fp32_value(uint16_t h) {
|
||||
/*
|
||||
* Extend the half-precision floating-point number to 32 bits and shift to the upper part of the 32-bit word:
|
||||
* +---+-----+------------+-------------------+
|
||||
* | S |EEEEE|MM MMMM MMMM|0000 0000 0000 0000|
|
||||
* +---+-----+------------+-------------------+
|
||||
* Bits 31 26-30 16-25 0-15
|
||||
*
|
||||
* S - sign bit, E - bits of the biased exponent, M - bits of the mantissa, 0 - zero bits.
|
||||
*/
|
||||
const uint32_t w = (uint32_t) h << 16;
|
||||
/*
|
||||
* Extract the sign of the input number into the high bit of the 32-bit word:
|
||||
*
|
||||
* +---+----------------------------------+
|
||||
* | S |0000000 00000000 00000000 00000000|
|
||||
* +---+----------------------------------+
|
||||
* Bits 31 0-31
|
||||
*/
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
/*
|
||||
* Extract mantissa and biased exponent of the input number into the high bits of the 32-bit word:
|
||||
*
|
||||
* +-----+------------+---------------------+
|
||||
* |EEEEE|MM MMMM MMMM|0 0000 0000 0000 0000|
|
||||
* +-----+------------+---------------------+
|
||||
* Bits 27-31 17-26 0-16
|
||||
*/
|
||||
const uint32_t two_w = w + w;
|
||||
|
||||
/*
|
||||
* Shift mantissa and exponent into bits 23-28 and bits 13-22 so they become mantissa and exponent
|
||||
* of a single-precision floating-point number:
|
||||
*
|
||||
* S|Exponent | Mantissa
|
||||
* +-+---+-----+------------+----------------+
|
||||
* |0|000|EEEEE|MM MMMM MMMM|0 0000 0000 0000|
|
||||
* +-+---+-----+------------+----------------+
|
||||
* Bits | 23-31 | 0-22
|
||||
*
|
||||
* Next, the exponent is adjusted for the difference in exponent bias between single-precision and half-precision
|
||||
* formats (0x7F - 0xF = 0x70). This operation never overflows or generates non-finite values, as the largest
|
||||
* half-precision exponent is 0x1F and after the adjustment is can not exceed 0x8F < 0xFE (largest single-precision
|
||||
* exponent for non-finite values).
|
||||
*
|
||||
* Note that this operation does not handle denormal inputs (where biased exponent == 0). However, they also do not
|
||||
* operate on denormal inputs, and do not produce denormal results.
|
||||
*/
|
||||
const float exp_offset = UINT32_C(0x70) << 23;
|
||||
const float normalized_value = fp32_from_bits((two_w >> 4) + exp_offset);
|
||||
|
||||
/*
|
||||
* Convert denormalized half-precision inputs into single-precision results (always normalized).
|
||||
* Zero inputs are also handled here.
|
||||
*
|
||||
* In a denormalized number the biased exponent is zero, and mantissa has on-zero bits.
|
||||
* First, we shift mantissa into bits 0-9 of the 32-bit word.
|
||||
*
|
||||
* zeros | mantissa
|
||||
* +---------------------------+------------+
|
||||
* |0000 0000 0000 0000 0000 00|MM MMMM MMMM|
|
||||
* +---------------------------+------------+
|
||||
* Bits 10-31 0-9
|
||||
*
|
||||
* Now, remember that denormalized half-precision numbers are represented as:
|
||||
* FP16 = mantissa * 2**(-24).
|
||||
* The trick is to construct a normalized single-precision number with the same mantissa and thehalf-precision input
|
||||
* and with an exponent which would scale the corresponding mantissa bits to 2**(-24).
|
||||
* A normalized single-precision floating-point number is represented as:
|
||||
* FP32 = (1 + mantissa * 2**(-23)) * 2**(exponent - 127)
|
||||
* Therefore, when the biased exponent is 126, a unit change in the mantissa of the input denormalized half-precision
|
||||
* number causes a change of the constructud single-precision number by 2**(-24), i.e. the same ammount.
|
||||
*
|
||||
* The last step is to adjust the bias of the constructed single-precision number. When the input half-precision number
|
||||
* is zero, the constructed single-precision number has the value of
|
||||
* FP32 = 1 * 2**(126 - 127) = 2**(-1) = 0.5
|
||||
* Therefore, we need to subtract 0.5 from the constructed single-precision number to get the numerical equivalent of
|
||||
* the input half-precision number.
|
||||
*/
|
||||
const uint32_t magic_mask = UINT32_C(126) << 23;
|
||||
const float magic_bias = 0.5f;
|
||||
const float denormalized_value = fp32_from_bits((two_w >> 17) | magic_mask) - magic_bias;
|
||||
|
||||
/*
|
||||
* - Choose either results of conversion of input as a normalized number, or as a denormalized number, depending on the
|
||||
* input exponent. The variable two_w contains input exponent in bits 27-31, therefore if its smaller than 2**27, the
|
||||
* input is either a denormal number, or zero.
|
||||
* - Combine the result of conversion of exponent and mantissa with the sign of the input number.
|
||||
*/
|
||||
const uint32_t denormalized_cutoff = UINT32_C(1) << 27;
|
||||
const uint32_t result = sign |
|
||||
(two_w < denormalized_cutoff ? fp32_to_bits(denormalized_value) : fp32_to_bits(normalized_value));
|
||||
return fp32_from_bits(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a 32-bit floating-point number in IEEE single-precision format to a 16-bit floating-point number in
|
||||
* ARM alternative half-precision format, in bit representation.
|
||||
*
|
||||
* @note The implementation relies on IEEE-like (no assumption about rounding mode and no operations on denormals)
|
||||
* floating-point operations and bitcasts between integer and floating-point variables.
|
||||
*/
|
||||
static inline uint16_t fp16_alt_from_fp32_value(float f) {
|
||||
const uint32_t w = fp32_to_bits(f);
|
||||
const uint32_t sign = w & UINT32_C(0x80000000);
|
||||
const uint32_t shl1_w = w + w;
|
||||
|
||||
const uint32_t shl1_max_fp16_fp32 = UINT32_C(0x8FFFC000);
|
||||
const uint32_t shl1_base = shl1_w > shl1_max_fp16_fp32 ? shl1_max_fp16_fp32 : shl1_w;
|
||||
uint32_t shl1_bias = shl1_base & UINT32_C(0xFF000000);
|
||||
const uint32_t exp_difference = 23 - 10;
|
||||
const uint32_t shl1_bias_min = (127 - 1 - exp_difference) << 24;
|
||||
if (shl1_bias < shl1_bias_min) {
|
||||
shl1_bias = shl1_bias_min;
|
||||
}
|
||||
|
||||
const float bias = fp32_from_bits((shl1_bias >> 1) + ((exp_difference + 2) << 23));
|
||||
const float base = fp32_from_bits((shl1_base >> 1) + (2 << 23)) + bias;
|
||||
|
||||
const uint32_t exp_f = fp32_to_bits(base) >> 13;
|
||||
return (sign >> 16) | ((exp_f & UINT32_C(0x00007C00)) + (fp32_to_bits(base) & UINT32_C(0x00000FFF)));
|
||||
}
|
||||
|
||||
#endif /* FP16_FP16_H */
|
||||
@@ -0,0 +1,419 @@
|
||||
#include <ruby.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "astc.h"
|
||||
#include "dxtc.h"
|
||||
#include "etc.h"
|
||||
#include "pvrtc.h"
|
||||
#include "rgb.h"
|
||||
|
||||
const char *error_msg = NULL;
|
||||
|
||||
#define DECODE_CHECK(call) \
|
||||
if (!call) { \
|
||||
rb_raise(rb_eRuntimeError, "%s", error_msg ? error_msg : "unknown internal error"); \
|
||||
error_msg = NULL; \
|
||||
return Qnil; \
|
||||
}
|
||||
|
||||
static int check_str_len(VALUE data, long len, long unit) {
|
||||
if (RSTRING_LEN(data) < len * unit) {
|
||||
rb_raise(rb_eStandardError, "Data size is not enough.");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int check_str_len_block(VALUE data, long w, long h, long bw, long bh, long unit) {
|
||||
long size = ((w + bw - 1) / bw) * ((h + bh - 1) / bh);
|
||||
return check_str_len(data, size, unit);
|
||||
}
|
||||
|
||||
static VALUE rb_alloc_rgb(long n) {
|
||||
VALUE ret = rb_str_buf_new(n * 3);
|
||||
rb_str_set_len(ret, n * 3);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static VALUE rb_alloc_rgba(long n) {
|
||||
VALUE ret = rb_str_buf_new(n * 4);
|
||||
rb_str_set_len(ret, n * 4);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from A8 binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_a8(VALUE self, VALUE rb_data, VALUE rb_size) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 1))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_a8((uint8_t *)RSTRING_PTR(rb_data), size, (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from R8 binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_r8(VALUE self, VALUE rb_data, VALUE rb_size) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 1))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_r8((uint8_t *)RSTRING_PTR(rb_data), size, (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from R16 binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @param [Boolean] rb_big whether input data are big endian
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_r16(VALUE self, VALUE rb_data, VALUE rb_size, VALUE rb_big) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 2))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_r16((uint8_t *)RSTRING_PTR(rb_data), size, RTEST(rb_big), (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from RGB565 binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @param [Boolean] rb_big whether input data are big endian
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_rgb565(VALUE self, VALUE rb_data, VALUE rb_size, VALUE rb_big) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 2))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_rgb565((uint16_t *)RSTRING_PTR(rb_data), size, RTEST(rb_big), (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from RHalf binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @param [Boolean] rb_big whether input data are big endian
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_rhalf(VALUE self, VALUE rb_data, VALUE rb_size, VALUE rb_big) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 2))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_rhalf((uint16_t *)RSTRING_PTR(rb_data), size, RTEST(rb_big), (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from RGHalf binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @param [Boolean] rb_big whether input data are big endian
|
||||
* @return [String] decoded rgb binary
|
||||
*/
|
||||
static VALUE rb_decode_rghalf(VALUE self, VALUE rb_data, VALUE rb_size, VALUE rb_big) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 4))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgb(size);
|
||||
if (!decode_rghalf((uint16_t *)RSTRING_PTR(rb_data), size, RTEST(rb_big), (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from RGBAHalf binary
|
||||
* Returned image is not flipped
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_size width * height
|
||||
* @param [Boolean] rb_big whether input data are big endian
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_rgbahalf(VALUE self, VALUE rb_data, VALUE rb_size, VALUE rb_big) {
|
||||
long size = FIX2LONG(rb_size);
|
||||
if (!check_str_len(rb_data, size, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(size);
|
||||
if (!decode_rgbahalf((uint16_t *)RSTRING_PTR(rb_data), size, RTEST(rb_big), (uint8_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc1(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_etc1((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_etc2((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 Alpha1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2a1(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_etc2a1((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ETC2 Alpha8 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_etc2a8(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 16))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_etc2a8((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from EAC R11 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_eacr(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_eacr((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from EAC Signed R11 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_eacsr(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_eacr_signed((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from EAC RG11 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_eacrg(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 16))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_eacrg((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from EAC RG11 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_eacsrg(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w), h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 16))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
if (!decode_eacrg_signed((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)))
|
||||
return Qnil;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from ASTC compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @param [Integer] rb_bw block width
|
||||
* @param [Integer] rb_bh block height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_astc(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h, VALUE rb_bw, VALUE rb_bh) {
|
||||
long w = FIX2LONG(rb_w);
|
||||
long h = FIX2LONG(rb_h);
|
||||
int bw = FIX2INT(rb_bw);
|
||||
int bh = FIX2INT(rb_bh);
|
||||
if (!check_str_len_block(rb_data, w, h, bw, bh, 16))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
DECODE_CHECK(decode_astc((uint8_t *)RSTRING_PTR(rb_data), w, h, bw, bh, (uint32_t *)RSTRING_PTR(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from DXT1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_dxt1(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w);
|
||||
long h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
DECODE_CHECK(decode_dxt1((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from DXT5 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_dxt5(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h) {
|
||||
long w = FIX2LONG(rb_w);
|
||||
long h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, 4, 4, 16))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
DECODE_CHECK(decode_dxt5((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret)));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode image from PVRTC1 compressed binary
|
||||
*
|
||||
* @param [String] rb_data binary to decode
|
||||
* @param [Integer] rb_w image width
|
||||
* @param [Integer] rb_h image height
|
||||
* @param [Boolean] rb_is2bpp whether 2bpp or not (4bpp)
|
||||
* @return [String] decoded rgba binary
|
||||
*/
|
||||
static VALUE rb_decode_pvrtc1(VALUE self, VALUE rb_data, VALUE rb_w, VALUE rb_h, VALUE rb_is2bpp) {
|
||||
int is2bpp = RTEST(rb_is2bpp);
|
||||
long w = FIX2LONG(rb_w);
|
||||
long h = FIX2LONG(rb_h);
|
||||
if (!check_str_len_block(rb_data, w, h, is2bpp ? 8 : 4, 4, 8))
|
||||
return Qnil;
|
||||
VALUE ret = rb_alloc_rgba(w * h);
|
||||
DECODE_CHECK(decode_pvrtc((uint8_t *)RSTRING_PTR(rb_data), w, h, (uint32_t *)RSTRING_PTR(ret), is2bpp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Init_native() {
|
||||
VALUE mMikunyan = rb_define_module("Mikunyan");
|
||||
VALUE mDecodeHelper = rb_define_module_under(mMikunyan, "DecodeHelper");
|
||||
rb_define_module_function(mDecodeHelper, "decode_a8", rb_decode_a8, 2);
|
||||
rb_define_module_function(mDecodeHelper, "decode_r8", rb_decode_r8, 2);
|
||||
rb_define_module_function(mDecodeHelper, "decode_r16", rb_decode_r16, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_rgb565", rb_decode_rgb565, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_rhalf", rb_decode_rhalf, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_rghalf", rb_decode_rghalf, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_rgbahalf", rb_decode_rgbahalf, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc1", rb_decode_etc1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2", rb_decode_etc2, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2a1", rb_decode_etc2a1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_etc2a8", rb_decode_etc2a8, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_eacr", rb_decode_eacr, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_eacsr", rb_decode_eacsr, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_eacrg", rb_decode_eacrg, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_eacsrg", rb_decode_eacsrg, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_astc", rb_decode_astc, 5);
|
||||
rb_define_module_function(mDecodeHelper, "decode_dxt1", rb_decode_dxt1, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_dxt5", rb_decode_dxt5, 3);
|
||||
rb_define_module_function(mDecodeHelper, "decode_pvrtc1", rb_decode_pvrtc1, 4);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
#include "pvrtc.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "color.h"
|
||||
#include "endianness.h"
|
||||
|
||||
static const int PVRTC1_STANDARD_WEIGHT[] = {0, 3, 5, 8};
|
||||
static const int PVRTC1_PUNCHTHROUGH_WEIGHT[] = {0, 4, 4, 8};
|
||||
|
||||
static inline long morton_index(const long x, const long y, const long min_dim) {
|
||||
long offset = 0, shift = 0;
|
||||
for (long mask = 1; mask < min_dim; mask <<= 1, shift++)
|
||||
offset |= (((y & mask) | ((x & mask) << 1))) << shift;
|
||||
offset |= ((x | y) >> shift) << (shift * 2);
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void get_texel_colors(const uint8_t *data, PVRTCTexelInfo *info) {
|
||||
uint16_t ca = lton16(*(uint16_t *)(data + 4));
|
||||
uint16_t cb = lton16(*(uint16_t *)(data + 6));
|
||||
if (ca & 0x8000) {
|
||||
info->a.r = ca >> 10 & 0x1f;
|
||||
info->a.g = ca >> 5 & 0x1f;
|
||||
info->a.b = (ca & 0x1e) | (ca >> 4 & 1);
|
||||
info->a.a = 0xf;
|
||||
} else {
|
||||
info->a.r = (ca >> 7 & 0x1e) | (ca >> 11 & 1);
|
||||
info->a.g = (ca >> 3 & 0x1e) | (ca >> 7 & 1);
|
||||
info->a.b = (ca << 1 & 0x1c) | (ca >> 2 & 3);
|
||||
info->a.a = ca >> 11 & 0xe;
|
||||
}
|
||||
if (cb & 0x8000) {
|
||||
info->b.r = cb >> 10 & 0x1f;
|
||||
info->b.g = cb >> 5 & 0x1f;
|
||||
info->b.b = cb & 0x1f;
|
||||
info->b.a = 0xf;
|
||||
} else {
|
||||
info->b.r = (cb >> 7 & 0x1e) | (cb >> 11 & 1);
|
||||
info->b.g = (cb >> 3 & 0x1e) | (cb >> 7 & 1);
|
||||
info->b.b = (cb << 1 & 0x1e) | (cb >> 3 & 1);
|
||||
info->b.a = cb >> 11 & 0xe;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_texel_weights_4bpp(const uint8_t *data, PVRTCTexelInfo *info) {
|
||||
info->punch_through_flag = 0;
|
||||
|
||||
int mod_mode = data[4] & 1;
|
||||
uint32_t mod_bits = lton32(*(uint32_t *)data);
|
||||
|
||||
if (mod_mode) {
|
||||
// punch-through
|
||||
for (int i = 0; i < 16; i++, mod_bits >>= 2) {
|
||||
info->weight[i] = PVRTC1_PUNCHTHROUGH_WEIGHT[mod_bits & 3];
|
||||
if ((mod_bits & 3) == 2)
|
||||
info->punch_through_flag |= 1 << i;
|
||||
}
|
||||
} else {
|
||||
// standard
|
||||
for (int i = 0; i < 16; i++, mod_bits >>= 2)
|
||||
info->weight[i] = PVRTC1_STANDARD_WEIGHT[mod_bits & 3];
|
||||
}
|
||||
}
|
||||
|
||||
static void get_texel_weights_2bpp(const uint8_t *data, PVRTCTexelInfo *info) {
|
||||
info->punch_through_flag = 0;
|
||||
|
||||
int mod_mode = data[4] & 1;
|
||||
uint32_t mod_bits = lton32(*(uint32_t *)data);
|
||||
|
||||
if (mod_mode) {
|
||||
// interporated modulation
|
||||
// ここは仕様書が間違ってる(4bpp の M=0 の standard bilinear のテーブルしか使わない・punch through は 2bpp
|
||||
// にはない)
|
||||
int fillflag = data[0] & 1 ? (data[2] & 0x10 ? -1 : -2) : -3;
|
||||
// 決定できない(後から補完しないといけない)ものは負の数で埋めておく
|
||||
// -3: 上下左右 / -2: 左右 / -1: 上下
|
||||
for (int y = 0, i = 1; y < 4; ++y & 1 ? --i : ++i)
|
||||
for (int x = 0; x < 4; x++, i += 2)
|
||||
info->weight[i] = fillflag;
|
||||
for (int y = 0, i = 0; y < 4; ++y & 1 ? ++i : --i)
|
||||
for (int x = 0; x < 4; x++, i += 2, mod_bits >>= 2)
|
||||
info->weight[i] = PVRTC1_STANDARD_WEIGHT[mod_bits & 3];
|
||||
// 0 は常に 1bpp
|
||||
info->weight[0] = (info->weight[0] + 3) & 8;
|
||||
if (data[0] & 1)
|
||||
// bit0 が 1 のときは (4, 2) が 1bpp
|
||||
info->weight[20] = (info->weight[20] + 3) & 8;
|
||||
} else {
|
||||
// 1bpp
|
||||
for (int i = 0; i < 32; i++, mod_bits >>= 1)
|
||||
info->weight[i] = mod_bits & 1 ? 8 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void applicate_color_4bpp(const uint8_t *data, PVRTCTexelInfo *const info[9], uint32_t buf[32]) {
|
||||
static const int INTERP_WEIGHT[4][3] = {{2, 2, 0}, {1, 3, 0}, {0, 4, 0}, {0, 3, 1}};
|
||||
PVRTCTexelColorInt clr_a[16] = {}, clr_b[16] = {};
|
||||
|
||||
for (int y = 0, i = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++, i++) {
|
||||
for (int acy = 0, ac = 0; acy < 3; acy++) {
|
||||
for (int acx = 0; acx < 3; acx++, ac++) {
|
||||
int interp_weight = INTERP_WEIGHT[x][acx] * INTERP_WEIGHT[y][acy];
|
||||
clr_a[i].r += info[ac]->a.r * interp_weight;
|
||||
clr_a[i].g += info[ac]->a.g * interp_weight;
|
||||
clr_a[i].b += info[ac]->a.b * interp_weight;
|
||||
clr_a[i].a += info[ac]->a.a * interp_weight;
|
||||
clr_b[i].r += info[ac]->b.r * interp_weight;
|
||||
clr_b[i].g += info[ac]->b.g * interp_weight;
|
||||
clr_b[i].b += info[ac]->b.b * interp_weight;
|
||||
clr_b[i].a += info[ac]->b.a * interp_weight;
|
||||
}
|
||||
}
|
||||
clr_a[i].r = (clr_a[i].r >> 1) + (clr_a[i].r >> 6);
|
||||
clr_a[i].g = (clr_a[i].g >> 1) + (clr_a[i].g >> 6);
|
||||
clr_a[i].b = (clr_a[i].b >> 1) + (clr_a[i].b >> 6);
|
||||
clr_a[i].a = (clr_a[i].a) + (clr_a[i].a >> 4);
|
||||
clr_b[i].r = (clr_b[i].r >> 1) + (clr_b[i].r >> 6);
|
||||
clr_b[i].g = (clr_b[i].g >> 1) + (clr_b[i].g >> 6);
|
||||
clr_b[i].b = (clr_b[i].b >> 1) + (clr_b[i].b >> 6);
|
||||
clr_b[i].a = (clr_b[i].a) + (clr_b[i].a >> 4);
|
||||
}
|
||||
}
|
||||
|
||||
const PVRTCTexelInfo *self_info = info[4];
|
||||
uint32_t punch_through_flag = self_info->punch_through_flag;
|
||||
for (int i = 0; i < 16; i++, punch_through_flag >>= 1) {
|
||||
buf[i] = color((clr_a[i].r * (8 - self_info->weight[i]) + clr_b[i].r * self_info->weight[i]) / 8,
|
||||
(clr_a[i].g * (8 - self_info->weight[i]) + clr_b[i].g * self_info->weight[i]) / 8,
|
||||
(clr_a[i].b * (8 - self_info->weight[i]) + clr_b[i].b * self_info->weight[i]) / 8,
|
||||
punch_through_flag & 1
|
||||
? 0
|
||||
: (clr_a[i].a * (8 - self_info->weight[i]) + clr_b[i].a * self_info->weight[i]) / 8);
|
||||
}
|
||||
}
|
||||
|
||||
static void applicate_color_2bpp(const uint8_t *data, PVRTCTexelInfo *const info[9], uint32_t buf[32]) {
|
||||
static const int INTERP_WEIGHT_X[8][3] = {{4, 4, 0}, {3, 5, 0}, {2, 6, 0}, {1, 7, 0},
|
||||
{0, 8, 0}, {0, 7, 1}, {0, 6, 2}, {0, 5, 3}};
|
||||
static const int INTERP_WEIGHT_Y[4][3] = {{2, 2, 0}, {1, 3, 0}, {0, 4, 0}, {0, 3, 1}};
|
||||
PVRTCTexelColorInt clr_a[32] = {}, clr_b[32] = {};
|
||||
|
||||
for (int y = 0, i = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 8; x++, i++) {
|
||||
for (int acy = 0, ac = 0; acy < 3; acy++) {
|
||||
for (int acx = 0; acx < 3; acx++, ac++) {
|
||||
int interp_weight = INTERP_WEIGHT_X[x][acx] * INTERP_WEIGHT_Y[y][acy];
|
||||
clr_a[i].r += info[ac]->a.r * interp_weight;
|
||||
clr_a[i].g += info[ac]->a.g * interp_weight;
|
||||
clr_a[i].b += info[ac]->a.b * interp_weight;
|
||||
clr_a[i].a += info[ac]->a.a * interp_weight;
|
||||
clr_b[i].r += info[ac]->b.r * interp_weight;
|
||||
clr_b[i].g += info[ac]->b.g * interp_weight;
|
||||
clr_b[i].b += info[ac]->b.b * interp_weight;
|
||||
clr_b[i].a += info[ac]->b.a * interp_weight;
|
||||
}
|
||||
}
|
||||
clr_a[i].r = (clr_a[i].r >> 2) + (clr_a[i].r >> 7);
|
||||
clr_a[i].g = (clr_a[i].g >> 2) + (clr_a[i].g >> 7);
|
||||
clr_a[i].b = (clr_a[i].b >> 2) + (clr_a[i].b >> 7);
|
||||
clr_a[i].a = (clr_a[i].a >> 1) + (clr_a[i].a >> 5);
|
||||
clr_b[i].r = (clr_b[i].r >> 2) + (clr_b[i].r >> 7);
|
||||
clr_b[i].g = (clr_b[i].g >> 2) + (clr_b[i].g >> 7);
|
||||
clr_b[i].b = (clr_b[i].b >> 2) + (clr_b[i].b >> 7);
|
||||
clr_b[i].a = (clr_b[i].a >> 1) + (clr_b[i].a >> 5);
|
||||
}
|
||||
}
|
||||
|
||||
static const int POSYA[4][2] = {{1, 24}, {4, -8}, {4, -8}, {4, -8}};
|
||||
static const int POSYB[4][2] = {{4, 8}, {4, 8}, {4, 8}, {7, -24}};
|
||||
static const int POSXL[8][2] = {{3, 7}, {4, -1}, {4, -1}, {4, -1}, {4, -1}, {4, -1}, {4, -1}, {4, -1}};
|
||||
static const int POSXR[8][2] = {{4, 1}, {4, 1}, {4, 1}, {4, 1}, {4, 1}, {4, 1}, {4, 1}, {5, -7}};
|
||||
|
||||
PVRTCTexelInfo *self_info = info[4];
|
||||
uint32_t punch_through_flag = self_info->punch_through_flag;
|
||||
for (int y = 0, i = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 8; x++, i++, punch_through_flag >>= 1) {
|
||||
switch (self_info->weight[i]) {
|
||||
case -1:
|
||||
self_info->weight[i] =
|
||||
(info[POSYA[y][0]]->weight[i + POSYA[y][1]] + info[POSYB[y][0]]->weight[i + POSYB[y][1]] + 1) / 2;
|
||||
break;
|
||||
case -2:
|
||||
self_info->weight[i] =
|
||||
(info[POSXL[x][0]]->weight[i + POSXL[x][1]] + info[POSXR[x][0]]->weight[i + POSXR[x][1]] + 1) / 2;
|
||||
break;
|
||||
case -3:
|
||||
self_info->weight[i] =
|
||||
(info[POSYA[y][0]]->weight[i + POSYA[y][1]] + info[POSYB[y][0]]->weight[i + POSYB[y][1]] +
|
||||
info[POSXL[x][0]]->weight[i + POSXL[x][1]] + info[POSXR[x][0]]->weight[i + POSXR[x][1]] + 2) /
|
||||
4;
|
||||
break;
|
||||
}
|
||||
buf[i] = color((clr_a[i].r * (8 - self_info->weight[i]) + clr_b[i].r * self_info->weight[i]) / 8,
|
||||
(clr_a[i].g * (8 - self_info->weight[i]) + clr_b[i].g * self_info->weight[i]) / 8,
|
||||
(clr_a[i].b * (8 - self_info->weight[i]) + clr_b[i].b * self_info->weight[i]) / 8,
|
||||
punch_through_flag & 1
|
||||
? 0
|
||||
: (clr_a[i].a * (8 - self_info->weight[i]) + clr_b[i].a * self_info->weight[i]) / 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int decode_pvrtc(const uint8_t *data, const long w, const long h, uint32_t *image, const int is2bpp) {
|
||||
long bw = is2bpp ? 8 : 4;
|
||||
long num_blocks_x = is2bpp ? (w + 7) / 8 : (w + 3) / 4;
|
||||
long num_blocks_y = (h + 3) / 4;
|
||||
long num_blocks = num_blocks_x * num_blocks_y;
|
||||
long min_num_blocks = num_blocks_x <= num_blocks_y ? num_blocks_x : num_blocks_y;
|
||||
|
||||
if ((num_blocks_x & (num_blocks_x - 1)) || (num_blocks_y & (num_blocks_y - 1))) {
|
||||
extern const char *error_msg;
|
||||
error_msg = "the number of blocks of each side must be a power of 2";
|
||||
return 0;
|
||||
}
|
||||
|
||||
PVRTCTexelInfo *texel_info = (PVRTCTexelInfo *)malloc(sizeof(PVRTCTexelInfo) * num_blocks);
|
||||
if (texel_info == NULL) {
|
||||
extern const char *error_msg;
|
||||
error_msg = "memory allocation failed";
|
||||
return 0;
|
||||
}
|
||||
|
||||
void (*get_texel_weights_func)(const uint8_t *, PVRTCTexelInfo *) =
|
||||
is2bpp ? get_texel_weights_2bpp : get_texel_weights_4bpp;
|
||||
void (*applicate_color_func)(const uint8_t *, PVRTCTexelInfo *const[9], uint32_t[32]) =
|
||||
is2bpp ? applicate_color_2bpp : applicate_color_4bpp;
|
||||
|
||||
const uint8_t *d = data;
|
||||
for (long i = 0; i < num_blocks; i++, d += 8) {
|
||||
get_texel_colors(d, &texel_info[i]);
|
||||
get_texel_weights_func(d, &texel_info[i]);
|
||||
}
|
||||
|
||||
uint32_t buffer[32];
|
||||
PVRTCTexelInfo *local_info[9];
|
||||
long pos_x[3], pos_y[3];
|
||||
|
||||
for (long by = 0; by < num_blocks_y; by++) {
|
||||
pos_y[0] = by == 0 ? num_blocks_y - 1 : by - 1;
|
||||
pos_y[1] = by;
|
||||
pos_y[2] = by == num_blocks_y - 1 ? 0 : by + 1;
|
||||
for (long bx = 0, x = 0; bx < num_blocks_x; bx++, x += 4) {
|
||||
pos_x[0] = bx == 0 ? num_blocks_x - 1 : bx - 1;
|
||||
pos_x[1] = bx;
|
||||
pos_x[2] = bx == num_blocks_x - 1 ? 0 : bx + 1;
|
||||
for (long cy = 0, c = 0; cy < 3; cy++)
|
||||
for (long cx = 0; cx < 3; cx++, c++)
|
||||
local_info[c] = &texel_info[morton_index(pos_x[cx], pos_y[cy], min_num_blocks)];
|
||||
applicate_color_func(data + morton_index(bx, by, min_num_blocks) * 8, local_info, buffer);
|
||||
copy_block_buffer(bx, by, w, h, bw, 4, buffer, image);
|
||||
}
|
||||
}
|
||||
|
||||
free(texel_info);
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef PVRTC_H
|
||||
#define PVRTC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
uint8_t a;
|
||||
} PVRTCTexelColor;
|
||||
|
||||
typedef struct {
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
int a;
|
||||
} PVRTCTexelColorInt;
|
||||
|
||||
typedef struct {
|
||||
PVRTCTexelColor a;
|
||||
PVRTCTexelColor b;
|
||||
int8_t weight[32];
|
||||
uint32_t punch_through_flag;
|
||||
} PVRTCTexelInfo;
|
||||
|
||||
int decode_pvrtc(const uint8_t *, const long, const long, uint32_t *, const int);
|
||||
|
||||
#endif /* end of include guard: PVRTC_H */
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "rgb.h"
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include "color.h"
|
||||
#include "fp16.h"
|
||||
|
||||
int decode_a8(const uint8_t *const data, const long size, uint8_t *image) {
|
||||
const uint8_t *d = data, *d_end = data + size;
|
||||
for (int i = 0; d < d_end; d++) {
|
||||
image[i++] = *d;
|
||||
image[i++] = *d;
|
||||
image[i++] = *d;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_r8(const uint8_t *const data, const long size, uint8_t *image) {
|
||||
const uint8_t *d = data, *d_end = data + size;
|
||||
for (int i = 0; d < d_end; d++) {
|
||||
image[i++] = *d;
|
||||
image[i++] = 0;
|
||||
image[i++] = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_r16(const uint8_t *const data, const long size, const int endian_big, uint8_t *image) {
|
||||
const uint8_t *d = endian_big ? data : data + 1;
|
||||
const uint8_t *d_end = data + size * 2;
|
||||
for (int i = 0; d < d_end; d += 2) {
|
||||
image[i++] = *d;
|
||||
image[i++] = 0;
|
||||
image[i++] = 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_rgb565(const uint16_t *const data, const long size, const int endian_big, uint8_t *image) {
|
||||
const uint16_t *d = data, *d_end = data + size;
|
||||
if (endian_big)
|
||||
for (; d < d_end; d++, image += 3)
|
||||
rgb565_bep(*d, image);
|
||||
else
|
||||
for (; d < d_end; d++, image += 3)
|
||||
rgb565_lep(*d, image);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline uint8_t u16_f16_u8(const uint16_t val) {
|
||||
float f = fp16_ieee_to_fp32_value(val);
|
||||
if (!isfinite(f) || f < 0)
|
||||
return 0;
|
||||
else if (f > 1)
|
||||
return 255;
|
||||
else
|
||||
return roundf(f * 255);
|
||||
}
|
||||
|
||||
int decode_rhalf(const uint16_t *data, const long size, const int endian_big, uint8_t *image) {
|
||||
if (endian_big) {
|
||||
for (long i = 0; i < size; i++, data++) {
|
||||
*image++ = u16_f16_u8(bton16(*data));
|
||||
*image++ = 0;
|
||||
*image++ = 0;
|
||||
}
|
||||
} else {
|
||||
for (long i = 0; i < size; i++, data++) {
|
||||
*image++ = u16_f16_u8(lton16(*data));
|
||||
*image++ = 0;
|
||||
*image++ = 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_rghalf(const uint16_t *data, const long size, const int endian_big, uint8_t *image) {
|
||||
if (endian_big) {
|
||||
for (long i = 0; i < size; i++, data++, image++) {
|
||||
*image++ = u16_f16_u8(bton16(*data++));
|
||||
*image++ = u16_f16_u8(bton16(*data++));
|
||||
*image++ = 0;
|
||||
}
|
||||
} else {
|
||||
for (long i = 0; i < size; i++, data++) {
|
||||
*image++ = u16_f16_u8(lton16(*data++));
|
||||
*image++ = u16_f16_u8(lton16(*data++));
|
||||
*image++ = 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_rgbahalf(const uint16_t *data, const long size, const int endian_big, uint8_t *image) {
|
||||
long lsize = size * 4;
|
||||
if (endian_big)
|
||||
for (long i = 0; i < lsize; i++, data++, image++)
|
||||
*image = u16_f16_u8(bton16(*data));
|
||||
else
|
||||
for (long i = 0; i < lsize; i++, data++, image++)
|
||||
*image = u16_f16_u8(lton16(*data));
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef RGB_H
|
||||
#define RGB_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
int decode_a8(const uint8_t *const, const long, uint8_t *);
|
||||
int decode_r8(const uint8_t *const, const long, uint8_t *);
|
||||
int decode_r16(const uint8_t *const, const long, const int, uint8_t *);
|
||||
int decode_rgb565(const uint16_t *const, const long, const int, uint8_t *);
|
||||
int decode_rhalf(const uint16_t *const, const long, const int, uint8_t *);
|
||||
int decode_rghalf(const uint16_t *const, const long, const int, uint8_t *);
|
||||
int decode_rgbahalf(const uint16_t *const, const long, const int, uint8_t *);
|
||||
|
||||
#endif /* end of include guard: RGB_H */
|
||||
+3
-7
@@ -1,11 +1,7 @@
|
||||
require "mikunyan/version"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "mikunyan/asset_bundle"
|
||||
require "mikunyan/asset"
|
||||
require "mikunyan/binary_reader"
|
||||
require "mikunyan/object_value"
|
||||
require "mikunyan/type_tree"
|
||||
require "mikunyan/constants"
|
||||
require 'mikunyan/version'
|
||||
require 'mikunyan/asset_bundle'
|
||||
|
||||
# Module for deserializing Unity Assets and AssetBundles
|
||||
module Mikunyan
|
||||
|
||||
+332
-322
@@ -1,329 +1,339 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan/type_tree'
|
||||
require 'mikunyan/constants'
|
||||
require 'mikunyan/object_value'
|
||||
require 'mikunyan/base_object'
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing Unity Asset
|
||||
# @attr_reader [String] name Asset name
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [Integer] target_platform target platform number
|
||||
# @attr_reader [Symbol] endian data endianness (:little or :big)
|
||||
# @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
|
||||
# @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects
|
||||
# @attr_reader [Array<Integer>] add_ids ?
|
||||
# @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
|
||||
class Asset
|
||||
attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
||||
# Class for representing Unity Asset
|
||||
# @attr_reader [String] name Asset name
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [Integer] target_platform target platform number
|
||||
# @attr_reader [Symbol] endian data endianness (:little or :big)
|
||||
# @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
|
||||
# @attr_reader [Array<Mikunyan::Asset::ObjectEntry>] objects contained objects
|
||||
# @attr_reader [Array<Mikunyan::Asset::LocalObjectEntry>] add_ids ?
|
||||
# @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
|
||||
class Asset
|
||||
attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
||||
|
||||
# Struct for representing Asset class definition
|
||||
# @attr [Integer] class_id class ID
|
||||
# @attr [Integer,nil] script_id script ID
|
||||
# @attr [String] hash hash value (16 or 32 bytes)
|
||||
# @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
|
||||
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
|
||||
# Struct for representing Asset class definition
|
||||
# @attr [Integer] class_id class ID
|
||||
# @attr [Boolean] stripped?
|
||||
# @attr [Integer,nil] script_id script ID
|
||||
# @attr [String] hash hash value (16 or 32 bytes)
|
||||
# @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
|
||||
Klass = Struct.new(:class_id, :stripped?, :script_id, :hash, :type_tree)
|
||||
|
||||
# Struct for representing Asset object information
|
||||
# @attr [Integer] path_id path ID
|
||||
# @attr [Integer] offset data offset
|
||||
# @attr [Integer] size data size
|
||||
# @attr [Integer,nil] type_id type ID
|
||||
# @attr [Integer,nil] class_id class ID
|
||||
# @attr [Integer,nil] class_idx class definition index
|
||||
# @attr [Boolean] destroyed? destroyed or not
|
||||
# @attr [String] data binary data of object
|
||||
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
|
||||
# Struct for representing Asset object information
|
||||
# @attr [Integer] path_id path ID
|
||||
# @attr [Integer] offset data offset
|
||||
# @attr [Integer] size data size
|
||||
# @attr [Integer,nil] type_id type ID
|
||||
# @attr [Integer,nil] class_id class ID
|
||||
# @attr [Integer,nil] class_idx class definition index
|
||||
# @attr [Boolean] destroyed? destroyed or not
|
||||
# @attr [String] data binary data of object
|
||||
# @attr [Mikunyan::Asset] parent_asset
|
||||
# @attr [Klass] klass
|
||||
ObjectEntry = Struct.new(
|
||||
:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :stripped?,
|
||||
:data, :parent_asset, :klass,
|
||||
keyword_init: true
|
||||
) do
|
||||
# Alias to {Asset#parse_object}
|
||||
def parse
|
||||
parent_asset.parse_object(self)
|
||||
end
|
||||
|
||||
# Struct for representing Asset reference information
|
||||
# @attr [String] path path
|
||||
# @attr [String] guid GUID (16 bytes)
|
||||
# @attr [Integer] type ?
|
||||
# @attr [String] file_path Asset name
|
||||
Reference = Struct.new(:path, :guid, :type, :file_path)
|
||||
# Alias to {Asset#parse_object_simple}
|
||||
def parse_simple
|
||||
parent_asset.parse_object_simple(self)
|
||||
end
|
||||
|
||||
# Load Asset from binary string
|
||||
# @param [String] bin binary data
|
||||
# @param [String] name Asset name
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.load(bin, name)
|
||||
r = Asset.new(name)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Load Asset from file
|
||||
# @param [String] file file name
|
||||
# @param [String] name Asset name (automatically generated if not specified)
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.file(file, name=nil)
|
||||
name = File.basename(name, '.*') unless name
|
||||
Asset.load(File.binread(file), name)
|
||||
end
|
||||
|
||||
# Returns list of all path IDs
|
||||
# @return [Array<Integer>] list of all path IDs
|
||||
def path_ids
|
||||
@objects.map{|e| e.path_id}
|
||||
end
|
||||
|
||||
# Returns list of containers
|
||||
# @return [Array<Hash>,nil] list of all containers
|
||||
def containers
|
||||
obj = parse_object(1)
|
||||
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
||||
obj.m_Container.value.map do |e|
|
||||
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
||||
end
|
||||
end
|
||||
|
||||
# Parse object of given path ID
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Mikunyan::ObjectValue,nil] parsed object
|
||||
def parse_object(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
type_tree = Asset.parse_type_tree(klass)
|
||||
return nil unless type_tree
|
||||
|
||||
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
||||
end
|
||||
|
||||
# Parse object of given path ID and simplify it
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Hash,nil] parsed object
|
||||
def parse_object_simple(path_id)
|
||||
Asset.object_simplify(parse_object(path_id))
|
||||
end
|
||||
|
||||
# Returns object type name string
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [String,nil] type name
|
||||
def object_type(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
||||
klass.type_tree.nodes[0].type
|
||||
elsif klass
|
||||
Mikunyan::CLASS_ID[klass.class_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@endian = :big
|
||||
end
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
metadata_size = br.i32u
|
||||
size = br.i32u
|
||||
@format = br.i32u
|
||||
data_offset = br.i32u
|
||||
|
||||
if @format >= 9
|
||||
@endian = :little if br.i32 == 0
|
||||
br.endian = @endian
|
||||
end
|
||||
|
||||
@generator_version = br.cstr
|
||||
@target_platform = br.i32
|
||||
@klasses = []
|
||||
|
||||
if @format >= 17
|
||||
has_type_trees = (br.i8 != 0)
|
||||
type_tree_count = br.i32u
|
||||
type_tree_count.times do
|
||||
class_id = br.i32
|
||||
br.adv(1)
|
||||
script_id = br.i16
|
||||
if class_id < 0 || class_id == 114
|
||||
hash = br.read(32)
|
||||
else
|
||||
hash = br.read(16)
|
||||
end
|
||||
@klasses << Klass.new(class_id, script_id, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
|
||||
end
|
||||
elsif @format >= 13
|
||||
has_type_trees = (br.i8 != 0)
|
||||
type_tree_count = br.i32u
|
||||
type_tree_count.times do
|
||||
class_id = br.i32
|
||||
if class_id < 0
|
||||
hash = br.read(32)
|
||||
else
|
||||
hash = br.read(16)
|
||||
end
|
||||
@klasses << Klass.new(class_id, nil, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
|
||||
end
|
||||
else
|
||||
@type_trees = {}
|
||||
type_tree_count = br.i32u
|
||||
type_tree_count.times do
|
||||
class_id = br.i32
|
||||
@klasses << Klass.new(class_id, nil, nil, @format == 10 || @format == 12 ? TypeTree.load(br) : TypeTree.load_legacy(br))
|
||||
end
|
||||
end
|
||||
|
||||
long_object_ids = (@format >= 14 || (7 <= @format && @format <= 13 && br.i32 != 0))
|
||||
|
||||
@objects = []
|
||||
object_count = br.i32u
|
||||
object_count.times do
|
||||
br.align(4) if @format >= 14
|
||||
path_id = long_object_ids ? br.i64 : br.i32
|
||||
offset = br.i32u
|
||||
size = br.i32u
|
||||
if @format >= 17
|
||||
@objects << ObjectData.new(path_id, offset, size, nil, nil, br.i32u, @format <= 10 && br.i16 != 0)
|
||||
else
|
||||
@objects << ObjectData.new(path_id, offset, size, br.i32, br.i16, nil, @format <= 10 && br.i16 != 0)
|
||||
end
|
||||
br.adv(2) if 11 <= @format && @format <= 16
|
||||
br.adv(1) if 15 <= @format && @format <= 16
|
||||
end
|
||||
|
||||
if @format >= 11
|
||||
@add_ids = []
|
||||
add_id_count = br.i32u
|
||||
add_id_count.times do
|
||||
br.align(4) if @format >= 14
|
||||
@add_ids << [(long_object_ids ? br.i64 : br.i32), br.i32]
|
||||
end
|
||||
end
|
||||
|
||||
if @format >= 6
|
||||
@references = []
|
||||
reference_count = br.i32u
|
||||
reference_count.times do
|
||||
@references << Reference.new(br.cstr, br.read(16), br.i32, br.cstr)
|
||||
end
|
||||
end
|
||||
|
||||
@objects.each do |e|
|
||||
br.jmp(data_offset + e.offset)
|
||||
e.data = br.read(e.size)
|
||||
end
|
||||
end
|
||||
|
||||
def parse_object_private(br, type_tree)
|
||||
r = nil
|
||||
node = type_tree[:node]
|
||||
children = type_tree[:children]
|
||||
|
||||
if node.array?
|
||||
data = []
|
||||
size = parse_object_private(br, children.find{|e| e[:name] == 'size'})
|
||||
data_type_tree = children.find{|e| e[:name] == 'data'}
|
||||
size.value.times do |i|
|
||||
data << parse_object_private(br, data_type_tree)
|
||||
end
|
||||
data = data.map{|e| e.value}.pack('C*') if node.type == 'TypelessData'
|
||||
r = ObjectValue.new(node.name, node.type, br.endian, data)
|
||||
elsif node.size == -1
|
||||
r = ObjectValue.new(node.name, node.type, br.endian)
|
||||
if children.size == 1 && children[0][:name] == 'Array' && children[0][:node].type == 'Array' && children[0][:node].array?
|
||||
r.value = parse_object_private(br, children[0]).value
|
||||
r.value = r.value.map{|e| e.value}.pack('C*').force_encoding("utf-8") if node.type == 'string'
|
||||
else
|
||||
children.each do |child|
|
||||
r[child[:name]] = parse_object_private(br, child)
|
||||
end
|
||||
end
|
||||
elsif children.size > 0
|
||||
pos = br.pos
|
||||
r = ObjectValue.new(node.name, node.type, br.endian)
|
||||
r.is_struct = true
|
||||
children.each do |child|
|
||||
r[child[:name]] = parse_object_private(br, child)
|
||||
end
|
||||
else
|
||||
pos = br.pos
|
||||
value = nil
|
||||
case node.type
|
||||
when 'bool'
|
||||
value = (br.i8 != 0)
|
||||
when 'SInt8'
|
||||
value = br.i8s
|
||||
when 'UInt8', 'char'
|
||||
value = br.i8u
|
||||
when 'SInt16', 'short'
|
||||
value = br.i16s
|
||||
when 'UInt16', 'unsigned short'
|
||||
value = br.i16u
|
||||
when 'SInt32', 'int'
|
||||
value = br.i32s
|
||||
when 'UInt32', 'unsigned int'
|
||||
value = br.i32u
|
||||
when 'SInt64', 'long long'
|
||||
value = br.i64s
|
||||
when 'UInt64', 'unsigned long long'
|
||||
value = br.i64u
|
||||
when 'float'
|
||||
value = br.float
|
||||
when 'double'
|
||||
value = br.double
|
||||
when 'ColorRGBA'
|
||||
value = [br.i8u, br.i8u, br.i8u, br.i8u]
|
||||
else
|
||||
value = br.read(node.size)
|
||||
end
|
||||
br.jmp(pos + node.size)
|
||||
r = ObjectValue.new(node.name, node.type, br.endian, value)
|
||||
end
|
||||
br.align(4) if node.flags & 0x4000 != 0
|
||||
r
|
||||
end
|
||||
|
||||
def self.object_simplify(obj)
|
||||
if obj.class != ObjectValue
|
||||
obj
|
||||
elsif obj.type == 'pair'
|
||||
[object_simplify(obj['first']), object_simplify(obj['second'])]
|
||||
elsif obj.type == 'map' && obj.array?
|
||||
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
|
||||
elsif obj.value?
|
||||
object_simplify(obj.value)
|
||||
elsif obj.array?
|
||||
obj.value.map{|e| object_simplify(e)}
|
||||
else
|
||||
hash = {}
|
||||
obj.keys.each do |key|
|
||||
hash[key] = object_simplify(obj[key])
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_type_tree(klass)
|
||||
return nil unless klass.type_tree
|
||||
nodes = klass.type_tree.nodes
|
||||
tree = {}
|
||||
stack = []
|
||||
nodes.each do |node|
|
||||
this = {:name => node.name, :node => node, :children => []}
|
||||
if node.depth == 0
|
||||
tree = this
|
||||
else
|
||||
stack[node.depth - 1][:children] << this
|
||||
end
|
||||
stack[node.depth] = this
|
||||
end
|
||||
tree
|
||||
end
|
||||
# Returns object type name string
|
||||
# @return [String,nil] type name
|
||||
def type
|
||||
klass&.type_tree&.tree&.type || Mikunyan::Constants::CLASS_ID2NAME[class_id || klass&.class_id]
|
||||
end
|
||||
end
|
||||
|
||||
LocalObjectEntry = Struct.new(:file_id, :local_id)
|
||||
|
||||
# Struct for representing Asset reference information
|
||||
# @attr [String] path path
|
||||
# @attr [String] guid GUID (16 bytes)
|
||||
# @attr [Integer] type ?
|
||||
# @attr [String] file_path Asset name
|
||||
Reference = Struct.new(:path, :guid, :type, :file_path)
|
||||
|
||||
# Strcut for container information
|
||||
# @attr [String] name
|
||||
# @attr [Integer] preload_index
|
||||
# @attr [Integer] path_id
|
||||
ContainerInfo = Struct.new(:name, :preload_index, :preload_size, :file_id, :path_id)
|
||||
|
||||
# Load Asset from binary string
|
||||
# @param [String,IO] bin binary data
|
||||
# @param [String] name Asset name
|
||||
# @param [Mikunyan::AssetBundle] parent_bundle Parent AssetBundle
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.load(bin, name, parent_bundle = nil)
|
||||
r = Asset.new(name, parent_bundle)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Load Asset from file
|
||||
# @param [String] file file name
|
||||
# @param [String] name Asset name (automatically generated if not specified)
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.file(file, name = nil)
|
||||
name ||= File.basename(name, '.*')
|
||||
File.open(file, 'rb') do |io|
|
||||
Asset.load(io, name)
|
||||
end
|
||||
end
|
||||
|
||||
# Same as objects.each
|
||||
# @return [Enumerator<Mikunyan::Asset::ObjectEntry>,Array<Mikunyan::Asset::ObjectEntry>]
|
||||
def each_object(&block)
|
||||
@objects.each(&block)
|
||||
end
|
||||
|
||||
# Returns object with specified path ID
|
||||
# @return [ObjectEntry,nil]
|
||||
def path_id(id)
|
||||
@path_id_table[id]
|
||||
end
|
||||
|
||||
# Returns list of all path IDs
|
||||
# @return [Array<Integer>] list of all path IDs
|
||||
def path_ids
|
||||
@objects.map(&:path_id)
|
||||
end
|
||||
|
||||
# Returns list of containers
|
||||
# @return [Array<Hash>,nil] list of all containers
|
||||
def containers
|
||||
obj = @path_id_table[1]
|
||||
return nil unless obj.klass&.type_tree&.tree&.type == 'AssetBundle'
|
||||
|
||||
parse_object(obj).m_Container.value.map do |e|
|
||||
ContainerInfo.new(e.first.value, e.second.preloadIndex.value, e.second.preloadSize.value,
|
||||
e.second.asset.m_FileID.value, e.second.asset.m_PathID.value)
|
||||
end
|
||||
end
|
||||
|
||||
# Parse object of given path ID
|
||||
# @param [Integer,ObjectEntry] obj path ID or object
|
||||
# @return [Mikunyan::BaseObject,nil] parsed object
|
||||
def parse_object(obj)
|
||||
obj = @path_id_table[obj] if obj.instance_of?(Integer)
|
||||
return nil unless obj.klass&.type_tree
|
||||
|
||||
value_klass = Mikunyan::CustomTypes.get_custom_type(obj.klass.type_tree.tree.type, obj.class_id)
|
||||
ret = parse_object_private(BinaryReader.new(obj.data, @endian), obj.klass.type_tree.tree, value_klass)
|
||||
ret.object_entry = obj
|
||||
ret
|
||||
end
|
||||
|
||||
# Parse object of given path ID and simplify it
|
||||
# @param [Integer,ObjectEntry] obj path ID or object
|
||||
# @return [Hash,nil] parsed object
|
||||
def parse_object_simple(obj)
|
||||
parse_object(obj)&.simplify
|
||||
end
|
||||
|
||||
# Returns object type name string
|
||||
# @param [Integer,ObjectEntry] obj path ID or object
|
||||
# @return [String,nil] type name
|
||||
def object_type(obj)
|
||||
obj = @path_id_table[obj] if obj.instance_of?(Integer)
|
||||
obj&.type
|
||||
end
|
||||
|
||||
# Alias to {ObjectValue#simplify} (for compatibility)
|
||||
def self.object_simplify(obj)
|
||||
obj.is_a?(ObjectValue) ? obj.simplify : obj
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @param [Mikunyan::AssetBundle] bundle
|
||||
def initialize(name, bundle = nil)
|
||||
@name = name
|
||||
@endian = :big
|
||||
@bundle = bundle
|
||||
end
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
|
||||
meta_size = br.i32u
|
||||
file_size = br.i32u
|
||||
@format = br.i32u
|
||||
data_offset = br.i32u
|
||||
|
||||
if @format >= 9
|
||||
@endian = br.bool ? :big : :little
|
||||
br.adv(3)
|
||||
else
|
||||
br.pos = file_size - meta_size
|
||||
@endian = br.bool ? :big : :little
|
||||
end
|
||||
|
||||
if @format >= 22
|
||||
_meta_size = br.i32u
|
||||
_file_size = br.i64u
|
||||
data_offset = br.i64u
|
||||
br.adv(8)
|
||||
end
|
||||
|
||||
br.endian = @endian
|
||||
|
||||
@generator_version = br.cstr if @format >= 7
|
||||
@target_platform = br.i32 if @format >= 8
|
||||
has_type_trees = @format >= 13 ? br.bool : true
|
||||
type_count = br.i32u
|
||||
|
||||
@klasses = Array.new(type_count) do
|
||||
class_id = br.i32s
|
||||
stripped = br.bool if @format >= 16
|
||||
script_id = br.i16 if @format >= 17
|
||||
hash = br.read(@format < 16 && class_id < 0 || @format >= 16 && class_id == 114 ? 32 : 16) if @format >= 13
|
||||
type_tree = has_type_trees ? TypeTree.load(br, @format) : TypeTree.load_default(class_id, hash)
|
||||
Klass.new(class_id, stripped, script_id, hash, type_tree)
|
||||
end
|
||||
|
||||
wide_path_id = @format >= 14 || @format >= 7 && br.i32 != 0
|
||||
|
||||
object_count = br.i32u
|
||||
@objects = Array.new(object_count) do
|
||||
br.align(4) if @format >= 14
|
||||
if @format >= 16
|
||||
ObjectEntry.new(
|
||||
path_id: wide_path_id ? br.i64s : br.i32s,
|
||||
offset: @format >= 22 ? br.i64u : br.i32u, size: br.i32u,
|
||||
class_idx: br.i32u, stripped?: @format == 16 ? br.bool : nil,
|
||||
parent_asset: self
|
||||
)
|
||||
else
|
||||
ObjectEntry.new(
|
||||
path_id: wide_path_id ? br.i64s : br.i32s, offset: br.i32u, size: br.i32u,
|
||||
type_id: br.i32, class_id: br.i16, destroyed?: br.i16 == 1, stripped?: @format == 15 ? br.bool : nil,
|
||||
parent_asset: self
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@path_id_table = @objects.map {|e| [e.path_id, e]}.to_h
|
||||
|
||||
if @format >= 11
|
||||
add_id_count = br.i32u
|
||||
@add_ids = Array.new(add_id_count) do
|
||||
br.align(4) if @format >= 14
|
||||
LocalObjectEntry.new(br.i32u, wide_path_id ? br.i64s : br.i32s)
|
||||
end
|
||||
end
|
||||
|
||||
reference_count = br.i32u
|
||||
@references = Array.new(reference_count) do
|
||||
Reference.new(@format >= 6 ? br.cstr : nil, @format >= 5 ? br.read(16) : nil, @format >= 5 ? br.i32s : nil,
|
||||
br.cstr)
|
||||
end
|
||||
|
||||
@comment = br.cstr if @format >= 5
|
||||
# _ = br.i32 if @format >= 21
|
||||
|
||||
@objects.each do |e|
|
||||
br.jmp(data_offset + e.offset)
|
||||
e.data = br.read(e.size)
|
||||
e.klass = if e.class_idx
|
||||
@klasses[e.class_idx]
|
||||
else
|
||||
@klasses.find {|e2| e2.class_id == e.class_id} || @klasses.find {|e2| e2.class_id == e.type_id}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @param [Mikunyan::TypeTree::Node] node
|
||||
def parse_object_private(br, node, klass = ObjectValue)
|
||||
ret = klass.new(node.name, node.type, br.endian)
|
||||
children = node.children
|
||||
|
||||
if children.empty?
|
||||
pos = br.pos
|
||||
ret.value =
|
||||
case node.type
|
||||
when 'bool'
|
||||
br.bool
|
||||
when 'SInt8'
|
||||
br.i8s
|
||||
when 'UInt8'
|
||||
br.i8u
|
||||
when 'SInt16', 'short'
|
||||
br.i16s
|
||||
when 'UInt16', 'unsigned short'
|
||||
br.i16u
|
||||
when 'SInt32', 'int'
|
||||
br.i32s
|
||||
when 'UInt32', 'unsigned int', 'Type*'
|
||||
br.i32u
|
||||
when 'SInt64', 'long long'
|
||||
br.i64s
|
||||
when 'UInt64', 'unsigned long long'
|
||||
br.i64u
|
||||
when 'float'
|
||||
br.float
|
||||
when 'double'
|
||||
br.double
|
||||
else
|
||||
br.read(node.size)
|
||||
end
|
||||
br.jmp(pos + node.size) if node.size >= 0
|
||||
elsif node.array?
|
||||
children.each do |child|
|
||||
next ret[child.name] = parse_object_private(br, child) unless child.name == 'data'
|
||||
|
||||
size = ret['size']&.value || raise('`size` node must appear before `data` node in array node')
|
||||
ret.value =
|
||||
if child.children.empty? && (!child.need_align? || br.pos % 4 == 0 && child.size % 4 == 0)
|
||||
if node.type == 'TypelessData'
|
||||
br.read(size * child.size)
|
||||
elsif child.type == 'char'
|
||||
# string
|
||||
br.read(size * child.size).force_encoding('utf-8')
|
||||
end
|
||||
end
|
||||
ret.value ||= Array.new(size) {parse_object_private(br, child)}
|
||||
ret['data'] = ret.value
|
||||
end
|
||||
elsif children.size == 1 && children[0].array? && children[0].type == 'Array' && children[0].name == 'Array'
|
||||
ret = parse_object_private(br, children[0])
|
||||
ret.name = node.name
|
||||
ret.type = node.type
|
||||
else
|
||||
ret.attr = children.map {|c| [c.name, parse_object_private(br, c)]}.to_h
|
||||
if node.type == 'StreamingInfo'
|
||||
ret.value = get_stream_blob(ret['path'].value, ret['offset'].value, ret['size'].value)
|
||||
else
|
||||
ret.is_struct = true
|
||||
end
|
||||
end
|
||||
br.align(4) if node.need_align?
|
||||
ret
|
||||
end
|
||||
|
||||
def get_stream_blob(path, offset, size)
|
||||
return nil unless path && @bundle
|
||||
return nil if path.empty?
|
||||
|
||||
path["archive:/#{@name}/"] = '' if path.start_with?("archive:/#{@name}/")
|
||||
@bundle.blobs[path]&.byteslice(offset, size)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+156
-101
@@ -1,108 +1,163 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'extlz4'
|
||||
require 'extlzma2'
|
||||
require 'mikunyan/asset'
|
||||
require 'mikunyan/binary_reader'
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing Unity AssetBundle
|
||||
# @attr_reader [String] signature file signature (UnityRaw or UnityFS)
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] unity_version version string of Unity to use this AssetBundle
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [Array<Mikunyan::Asset>] assets included Assets
|
||||
class AssetBundle
|
||||
attr_reader :signature, :format, :unity_version, :generator_version, :assets
|
||||
# Class for representing Unity AssetBundle
|
||||
# @attr_reader [String] signature file signature (UnityRaw or UnityFS)
|
||||
# @attr_reader [Integer] format file format number
|
||||
# @attr_reader [String] unity_version version string of Unity for this AssetBundle
|
||||
# @attr_reader [String] generator_version version string of generator
|
||||
# @attr_reader [String] guid unique identifier (can be zero)
|
||||
# @attr_reader [Array<Mikunyan::Asset>] assets contained Assets
|
||||
class AssetBundle
|
||||
attr_reader :signature, :format, :unity_version, :generator_version, :guid, :assets, :blobs
|
||||
|
||||
# Load AssetBundle from binary string
|
||||
# @param [String] bin binary data
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.load(bin)
|
||||
r = AssetBundle.new
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
AssetEntry = Struct.new(:name, :data, :blob?, :status, keyword_init: true)
|
||||
|
||||
# Load AssetBundle from file
|
||||
# @param [String] file file name
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.file(file)
|
||||
AssetBundle.load(File.binread(file))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
@signature = br.cstr
|
||||
@format = br.i32
|
||||
@unity_version = br.cstr
|
||||
@generator_version = br.cstr
|
||||
|
||||
case @signature
|
||||
when 'UnityRaw'
|
||||
load_unity_raw(br)
|
||||
when 'UnityFS'
|
||||
load_unity_fs(br)
|
||||
else
|
||||
warn("Unknown signature: #{@signature}")
|
||||
end
|
||||
end
|
||||
|
||||
def load_unity_raw(br)
|
||||
@assets = []
|
||||
|
||||
file_size = br.i32u
|
||||
header_size = br.i32u
|
||||
|
||||
br.jmp(header_size)
|
||||
asset_count = br.i32u
|
||||
asset_count.times do
|
||||
asset_pos = br.pos
|
||||
asset_name = br.cstr
|
||||
asset_header_size = br.i32u
|
||||
asset_size = br.i32u
|
||||
br.jmp(asset_pos + asset_header_size - 4)
|
||||
asset_data = br.read(asset_size)
|
||||
asset = Asset.load(asset_data, asset_name)
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
|
||||
def load_unity_fs(br)
|
||||
@assets = []
|
||||
|
||||
file_size = br.i64u
|
||||
ci_block_size = br.i32u
|
||||
ui_block_size = br.i32u
|
||||
flags = br.i32u
|
||||
|
||||
head = BinaryReader.new(uncompress(br.read(ci_block_size), ui_block_size, flags))
|
||||
guid = head.read(16)
|
||||
|
||||
blocks = []
|
||||
block_count = head.i32u
|
||||
block_count.times{ blocks << {:u => head.i32u, :c => head.i32u, :f => head.i16u} }
|
||||
|
||||
asset_blocks = []
|
||||
asset_count = head.i32u
|
||||
asset_count.times{ asset_blocks << {:offset => head.i64u, :size => head.i64u, :status => head.i32, :name => head.cstr} }
|
||||
|
||||
raw_data = ''
|
||||
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
|
||||
|
||||
asset_blocks.each do |b|
|
||||
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name])
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
|
||||
def uncompress(block, max_dest_size, flags)
|
||||
case flags & 0x3f
|
||||
when 0
|
||||
block
|
||||
when 2, 3
|
||||
LZ4::raw_decode(block, max_dest_size)
|
||||
else
|
||||
warn("Unknown compression flag: #{@flags}")
|
||||
block
|
||||
end
|
||||
end
|
||||
# @param [String,Integer] index
|
||||
# @return [Mikunyan::Asset,nil]
|
||||
def [](index)
|
||||
index.is_a?(String) ? @assets.find {|e| e.name == index} : @assets[index]
|
||||
end
|
||||
|
||||
# Same as assets.each
|
||||
# @return [Enumerator<Mikunyan::Asset>,Array<Mikunyan::Asset>]
|
||||
def each_asset(&block)
|
||||
@assets.each(&block)
|
||||
end
|
||||
|
||||
# Loads AssetBundle from binary string
|
||||
# @param [String,IO] bin binary data
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.load(bin)
|
||||
r = AssetBundle.new
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Loads AssetBundle from file
|
||||
# @param [String] file file name
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.file(file)
|
||||
File.open(file, 'rb') do |io|
|
||||
AssetBundle.load(io)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
@signature = br.cstr
|
||||
raise("Invalid signature: #{@signature}") unless @signature.start_with?('Unity')
|
||||
|
||||
@format = br.i32
|
||||
@unity_version = br.cstr
|
||||
@generator_version = br.cstr
|
||||
|
||||
@format == 6 || @signature == 'UnityFS' ? load_unity_fs(br, @signature) : load_unity_raw(br)
|
||||
end
|
||||
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
def load_unity_raw(br)
|
||||
@assets = []
|
||||
|
||||
_file_size = br.i32u
|
||||
header_size = br.i32u
|
||||
br.pos = header_size
|
||||
# この部分全然わからん(ファイルの最後まで読まないとダメらしい?)
|
||||
block = br.read(nil)
|
||||
data = @signature == 'UnityRaw' ? block : uncompress_lzma(block, true)
|
||||
br = BinaryReader.new(data)
|
||||
|
||||
asset_count = br.i32u
|
||||
asset_entries = Array.new(asset_count) do
|
||||
name = br.cstr
|
||||
offset = br.i32u
|
||||
size = br.i32u
|
||||
is_asset = ['', '.assets'].include?(split_name(name)[1]) && size > 16
|
||||
AssetEntry.new(name: name, data: br.read_abs(size, offset), blob?: !is_asset)
|
||||
end
|
||||
process_asset_entries(asset_entries)
|
||||
end
|
||||
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
def load_unity_fs(br, signature)
|
||||
file_size = br.i64u
|
||||
ci_block_size = br.i32u
|
||||
ui_block_size = br.i32u
|
||||
flags = br.i32u
|
||||
|
||||
br.adv(1) unless signature == 'UnityFS'
|
||||
|
||||
br.align(16) if @format >= 7
|
||||
|
||||
head_bin = flags & 0x80 == 0 ? br.read(ci_block_size) : br.read_abs(ci_block_size, file_size - ci_block_size)
|
||||
head = BinaryReader.new(uncompress(head_bin, ui_block_size, flags))
|
||||
@guid = head.read(16)
|
||||
|
||||
br.align(16) unless flags & 0x200 == 0
|
||||
|
||||
block_count = head.i32u
|
||||
raw_data = Array.new(block_count) do
|
||||
u_size = head.i32u
|
||||
c_size = head.i32u
|
||||
flags = head.i16u
|
||||
uncompress(br.read(c_size), u_size, flags)
|
||||
end.join
|
||||
|
||||
asset_count = head.i32u
|
||||
asset_entries = Array.new(asset_count) do
|
||||
offset = head.i64u
|
||||
size = head.i64u
|
||||
status = head.i32
|
||||
AssetEntry.new(name: head.cstr, data: raw_data.byteslice(offset, size), blob?: status != 4, status: status)
|
||||
end
|
||||
process_asset_entries(asset_entries)
|
||||
end
|
||||
|
||||
def process_asset_entries(asset_entries)
|
||||
@blobs = asset_entries.select(&:blob?).map {|e| [e.name, e.data]}.to_h
|
||||
@assets = asset_entries.reject(&:blob?).map do |e|
|
||||
Asset.load(e.data, e.name, self)
|
||||
end
|
||||
end
|
||||
|
||||
def uncompress(block, max_dest_size, flags)
|
||||
case flags & 0x3f
|
||||
when 0
|
||||
block
|
||||
when 1
|
||||
uncompress_lzma(block)
|
||||
when 2, 3
|
||||
LZ4.block_decode(block, max_dest_size)
|
||||
# when 4
|
||||
# LZHMA
|
||||
else
|
||||
warn("Unknown compression flag: #{flags}")
|
||||
block
|
||||
end
|
||||
end
|
||||
|
||||
def uncompress_lzma(block, with_max_len = false)
|
||||
prop = block.ord
|
||||
filter = LZMA.lzma1(dictsize: block.unpack1('@1V'), lc: prop % 9, lp: (prop / 9) % 5, pb: prop / 45)
|
||||
max_len = block.unpack1('@5V') | block.unpack1('@9V') << 32 if with_max_len
|
||||
StringIO.open(block) do |io|
|
||||
io.seek(with_max_len ? 13 : 5)
|
||||
LZMA.raw_decode(io, filter) do |lzma|
|
||||
lzma.read(max_len)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def split_name(str)
|
||||
m = str.match(/\A(.*?)(\.[^.]*)?\z/)
|
||||
[m[1], m[2] || '']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan/object_value'
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing decoded base object
|
||||
class BaseObject < Mikunyan::ObjectValue
|
||||
attr_accessor :object_entry
|
||||
|
||||
def path_id
|
||||
@object_entry&.path_id
|
||||
end
|
||||
|
||||
def object_name
|
||||
@attr['m_Name']&.value
|
||||
end
|
||||
end
|
||||
|
||||
module CustomTypes
|
||||
def self.get_custom_type(name, class_id = nil)
|
||||
class_id ||= Mikunyan::Constants::CLASS_NAME2ID[name]
|
||||
@custom_types&.[]([class_id, name]) || Mikunyan::BaseObject
|
||||
end
|
||||
|
||||
def self.set_custom_type(klass, name, class_id = nil)
|
||||
class_id ||= Mikunyan::Constants::CLASS_NAME2ID[name]
|
||||
@custom_types ||= {}
|
||||
@custom_types[[class_id, name].freeze] = klass
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'mikunyan/types/text_asset'
|
||||
require 'mikunyan/types/texture2d'
|
||||
+136
-146
@@ -1,152 +1,142 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'bin_utils'
|
||||
|
||||
module Mikunyan
|
||||
# Class for manipulating binary string
|
||||
# @attr [Symbol] endian endianness
|
||||
# @attr [Integer] pos position
|
||||
# @attr [Integer] length data size
|
||||
class BinaryReader
|
||||
attr_accessor :endian, :pos, :length
|
||||
# Class for manipulating binary string
|
||||
# @attr [Symbol] endian endianness
|
||||
class BinaryReader
|
||||
attr_accessor :endian
|
||||
|
||||
# Constructor
|
||||
# @param [String] data binary string
|
||||
# @param [Symbol] endian endianness
|
||||
def initialize(data, endian = :big)
|
||||
@data = data
|
||||
@pos = 0
|
||||
@length = data.bytesize
|
||||
@endian = endian
|
||||
end
|
||||
|
||||
# Returns whether little endian or not
|
||||
# @return [Boolean]
|
||||
def little?
|
||||
@endian == :little
|
||||
end
|
||||
|
||||
# Jump to given position
|
||||
# @param [Integer] pos position
|
||||
def jmp(pos=0)
|
||||
@pos = pos
|
||||
end
|
||||
|
||||
# Advance position given size
|
||||
# @param [Integer] size size
|
||||
def adv(size=0)
|
||||
@pos += size
|
||||
end
|
||||
|
||||
# Round up position to multiple of given size
|
||||
# @param [Integer] size size
|
||||
def align(size)
|
||||
@pos = (@pos + size - 1) / size * size
|
||||
end
|
||||
|
||||
# Read given size of binary string and seek
|
||||
# @param [Integer] size size
|
||||
# @return [String] data
|
||||
def read(size)
|
||||
data = @data.byteslice(@pos, size)
|
||||
@pos += size
|
||||
data
|
||||
end
|
||||
|
||||
# Read string until null character
|
||||
# @return [String] string
|
||||
def cstr
|
||||
r = @data.unpack("@#{pos}Z*")[0]
|
||||
@pos += r.bytesize + 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 8bit signed integer
|
||||
def i8
|
||||
i8s
|
||||
end
|
||||
|
||||
# Read 8bit signed integer
|
||||
def i8s
|
||||
r = BinUtils.get_sint8(@data, @pos)
|
||||
@pos += 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 8bit unsigned integer
|
||||
def i8u
|
||||
r = BinUtils.get_int8(@data, @pos)
|
||||
@pos += 1
|
||||
r
|
||||
end
|
||||
|
||||
# Read 16bit signed integer
|
||||
def i16
|
||||
i16s
|
||||
end
|
||||
|
||||
# Read 16bit signed integer
|
||||
def i16s
|
||||
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
|
||||
@pos += 2
|
||||
r
|
||||
end
|
||||
|
||||
# Read 16bit unsigned integer
|
||||
def i16u
|
||||
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
|
||||
@pos += 2
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit signed integer
|
||||
def i32
|
||||
i32s
|
||||
end
|
||||
|
||||
# Read 32bit signed integer
|
||||
def i32s
|
||||
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit unsigned integer
|
||||
def i32u
|
||||
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit signed integer
|
||||
def i64
|
||||
i64s
|
||||
end
|
||||
|
||||
# Read 64bit signed integer
|
||||
def i64s
|
||||
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
|
||||
@pos += 8
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit unsigned integer
|
||||
def i64u
|
||||
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
|
||||
@pos += 8
|
||||
r
|
||||
end
|
||||
|
||||
# Read 32bit floating point value
|
||||
def float
|
||||
r = little? ? @data.byteslice(@pos, 4).unpack('e')[0] : @data.byteslice(@pos, 4).unpack('g')[0]
|
||||
@pos += 4
|
||||
r
|
||||
end
|
||||
|
||||
# Read 64bit floating point value
|
||||
def double
|
||||
r = little? ? @data.byteslice(@pos, 8).unpack('E')[0] : @data.byteslice(@pos, 8).unpack('G')[0]
|
||||
@pos += 8
|
||||
r
|
||||
end
|
||||
# Constructor
|
||||
# @param [IO,String] io binary String or IO
|
||||
# @param [Symbol] endian endianness
|
||||
def initialize(io, endian = :big)
|
||||
@io = io.is_a?(String) ? StringIO.new(io, 'r') : io.dup
|
||||
@io.binmode
|
||||
@base_pos = @io.pos
|
||||
@endian = endian
|
||||
end
|
||||
|
||||
# Returns whether little endian or not
|
||||
# @return [Boolean]
|
||||
def little?
|
||||
@endian == :little
|
||||
end
|
||||
|
||||
# Tells current potision
|
||||
# @return [Integer]
|
||||
def pos
|
||||
@io.pos - @base_pos
|
||||
end
|
||||
|
||||
# Jumps to given position
|
||||
# @param [Integer] jmp_pos position
|
||||
def jmp(jmp_pos = 0)
|
||||
@io.pos = jmp_pos + @base_pos
|
||||
end
|
||||
alias pos= jmp
|
||||
|
||||
# Advances position given size
|
||||
# @param [Integer] size size
|
||||
def adv(size = 0)
|
||||
@io.seek(size, IO::SEEK_CUR)
|
||||
end
|
||||
|
||||
# Rounds up position to multiple of given size
|
||||
# @param [Integer] size size
|
||||
def align(size)
|
||||
rem = pos % size
|
||||
adv(size - rem) if rem > 0
|
||||
end
|
||||
|
||||
# Reads given size of binary string and seek
|
||||
# @param [Integer] size size
|
||||
# @return [String] data
|
||||
def read(size)
|
||||
ret = @io.read(size)
|
||||
raise EOFError if ret.nil? || size && ret.bytesize < size
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
# Reads given size of binary string from specified position. This method does not seek.
|
||||
# @param [Integer] size size
|
||||
# @param [Integer] jmp_pos position
|
||||
# @return [String] data
|
||||
def read_abs(size, jmp_pos)
|
||||
orig_pos = pos
|
||||
jmp(jmp_pos)
|
||||
ret = read(size)
|
||||
jmp(orig_pos)
|
||||
ret
|
||||
end
|
||||
|
||||
# Reads string until null character
|
||||
# @return [String] string
|
||||
def cstr
|
||||
raise EOFError if @io.eof?
|
||||
|
||||
@io.each_byte.take_while(&:nonzero?).pack('C*')
|
||||
end
|
||||
|
||||
# Reads an 8bit bool value
|
||||
def bool
|
||||
i8u != 0
|
||||
end
|
||||
|
||||
# Reads an 8bit signed integer value
|
||||
def i8s
|
||||
BinUtils.get_sint8(read(1))
|
||||
end
|
||||
alias i8 i8s
|
||||
|
||||
# Reads an 8bit unsigned integer value
|
||||
def i8u
|
||||
@io.getbyte
|
||||
end
|
||||
|
||||
# Reads a 16bit signed integer value
|
||||
def i16s
|
||||
little? ? BinUtils.get_sint16_le(read(2)) : BinUtils.get_sint16_be(read(2))
|
||||
end
|
||||
alias i16 i16s
|
||||
|
||||
# Reads a 16bit unsigned integer value
|
||||
def i16u
|
||||
little? ? BinUtils.get_int16_le(read(2)) : BinUtils.get_int16_be(read(2))
|
||||
end
|
||||
|
||||
# Reads a 32bit signed integer value
|
||||
def i32s
|
||||
little? ? BinUtils.get_sint32_le(read(4)) : BinUtils.get_sint32_be(read(4))
|
||||
end
|
||||
alias i32 i32s
|
||||
|
||||
# Reads a 32bit unsigned integer value
|
||||
def i32u
|
||||
little? ? BinUtils.get_int32_le(read(4)) : BinUtils.get_int32_be(read(4))
|
||||
end
|
||||
|
||||
# Reads a 64bit signed integer value
|
||||
def i64s
|
||||
little? ? BinUtils.get_sint64_le(read(8)) : BinUtils.get_sint64_be(read(8))
|
||||
end
|
||||
alias i64 i64s
|
||||
|
||||
# Reads a 64bit unsigned integer value
|
||||
def i64u
|
||||
little? ? BinUtils.get_int64_le(read(8)) : BinUtils.get_int64_be(read(8))
|
||||
end
|
||||
|
||||
# Reads a 32bit floating point value
|
||||
def float
|
||||
little? ? read(4).unpack1('e') : read(4).unpack1('g')
|
||||
end
|
||||
|
||||
# Reads a 64bit floating point value
|
||||
def double
|
||||
little? ? read(8).unpack1('E') : read(8).unpack1('G')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+508
-342
@@ -1,344 +1,510 @@
|
||||
module Mikunyan
|
||||
private
|
||||
STRING_TABLE = {
|
||||
0=>'AABB',
|
||||
5=>'AnimationClip',
|
||||
19=>'AnimationCurve',
|
||||
34=>'AnimationState',
|
||||
49=>'Array',
|
||||
55=>'Base',
|
||||
60=>'BitField',
|
||||
69=>'bitset',
|
||||
76=>'bool',
|
||||
81=>'char',
|
||||
86=>'ColorRGBA',
|
||||
96=>'Component',
|
||||
106=>'data',
|
||||
111=>'deque',
|
||||
117=>'double',
|
||||
124=>'dynamic_array',
|
||||
138=>'FastPropertyName',
|
||||
155=>'first',
|
||||
161=>'float',
|
||||
167=>'Font',
|
||||
172=>'GameObject',
|
||||
183=>'Generic Mono',
|
||||
196=>'GradientNEW',
|
||||
208=>'GUID',
|
||||
213=>'GUIStyle',
|
||||
222=>'int',
|
||||
226=>'list',
|
||||
231=>'long long',
|
||||
241=>'map',
|
||||
245=>'Matrix4x4f',
|
||||
256=>'MdFour',
|
||||
263=>'MonoBehaviour',
|
||||
277=>'MonoScript',
|
||||
288=>'m_ByteSize',
|
||||
299=>'m_Curve',
|
||||
307=>'m_EditorClassIdentifier',
|
||||
331=>'m_EditorHideFlags',
|
||||
349=>'m_Enabled',
|
||||
359=>'m_ExtensionPtr',
|
||||
374=>'m_GameObject',
|
||||
387=>'m_Index',
|
||||
395=>'m_IsArray',
|
||||
405=>'m_IsStatic',
|
||||
416=>'m_MetaFlag',
|
||||
427=>'m_Name',
|
||||
434=>'m_ObjectHideFlags',
|
||||
452=>'m_PrefabInternal',
|
||||
469=>'m_PrefabParentObject',
|
||||
490=>'m_Script',
|
||||
499=>'m_StaticEditorFlags',
|
||||
519=>'m_Type',
|
||||
526=>'m_Version',
|
||||
536=>'Object',
|
||||
543=>'pair',
|
||||
548=>'PPtr<Component>',
|
||||
564=>'PPtr<GameObject>',
|
||||
581=>'PPtr<Material>',
|
||||
596=>'PPtr<MonoBehaviour>',
|
||||
616=>'PPtr<MonoScript>',
|
||||
633=>'PPtr<Object>',
|
||||
646=>'PPtr<Prefab>',
|
||||
659=>'PPtr<Sprite>',
|
||||
672=>'PPtr<TextAsset>',
|
||||
688=>'PPtr<Texture>',
|
||||
702=>'PPtr<Texture2D>',
|
||||
718=>'PPtr<Transform>',
|
||||
734=>'Prefab',
|
||||
741=>'Quaternionf',
|
||||
753=>'Rectf',
|
||||
759=>'RectInt',
|
||||
767=>'RectOffset',
|
||||
778=>'second',
|
||||
785=>'set',
|
||||
789=>'short',
|
||||
795=>'size',
|
||||
800=>'SInt16',
|
||||
807=>'SInt32',
|
||||
814=>'SInt64',
|
||||
821=>'SInt8',
|
||||
827=>'staticvector',
|
||||
840=>'string',
|
||||
847=>'TextAsset',
|
||||
857=>'TextMesh',
|
||||
866=>'Texture',
|
||||
874=>'Texture2D',
|
||||
884=>'Transform',
|
||||
894=>'TypelessData',
|
||||
907=>'UInt16',
|
||||
914=>'UInt32',
|
||||
921=>'UInt64',
|
||||
928=>'UInt8',
|
||||
934=>'unsigned int',
|
||||
947=>'unsigned long long',
|
||||
966=>'unsigned short',
|
||||
981=>'vector',
|
||||
988=>'Vector2f',
|
||||
997=>'Vector3f',
|
||||
1006=>'Vector4f',
|
||||
1015=>'m_ScriptingClassIdentifier',
|
||||
1042=>'Gradient'
|
||||
}
|
||||
# frozen_string_literal: true
|
||||
|
||||
CLASS_ID = {
|
||||
1=>'GameObject',
|
||||
2=>'Component',
|
||||
3=>'LevelGameManager',
|
||||
4=>'Transform',
|
||||
5=>'TimeManager',
|
||||
6=>'GlobalGameManager',
|
||||
8=>'Behaviour',
|
||||
9=>'GameManager',
|
||||
11=>'AudioManager',
|
||||
12=>'ParticleAnimator',
|
||||
13=>'InputManager',
|
||||
15=>'EllipsoidParticleEmitter',
|
||||
17=>'Pipeline',
|
||||
18=>'EditorExtension',
|
||||
19=>'Physics2DSettings',
|
||||
20=>'Camera',
|
||||
21=>'Material',
|
||||
23=>'MeshRenderer',
|
||||
25=>'Renderer',
|
||||
26=>'ParticleRenderer',
|
||||
27=>'Texture',
|
||||
28=>'Texture2D',
|
||||
29=>'SceneSettings',
|
||||
30=>'GraphicsSettings',
|
||||
33=>'MeshFilter',
|
||||
41=>'OcclusionPortal',
|
||||
43=>'Mesh',
|
||||
45=>'Skybox',
|
||||
47=>'QualitySettings',
|
||||
48=>'Shader',
|
||||
49=>'TextAsset',
|
||||
50=>'Rigidbody2D',
|
||||
51=>'Physics2DManager',
|
||||
53=>'Collider2D',
|
||||
54=>'Rigidbody',
|
||||
55=>'PhysicsManager',
|
||||
56=>'Collider',
|
||||
57=>'Joint',
|
||||
58=>'CircleCollider2D',
|
||||
59=>'HingeJoint',
|
||||
60=>'PolygonCollider2D',
|
||||
61=>'BoxCollider2D',
|
||||
62=>'PhysicsMaterial2D',
|
||||
64=>'MeshCollider',
|
||||
65=>'BoxCollider',
|
||||
66=>'SpriteCollider2D',
|
||||
68=>'EdgeCollider2D',
|
||||
72=>'ComputeShader',
|
||||
74=>'AnimationClip',
|
||||
75=>'ConstantForce',
|
||||
76=>'WorldParticleCollider',
|
||||
78=>'TagManager',
|
||||
81=>'AudioListener',
|
||||
82=>'AudioSource',
|
||||
83=>'AudioClip',
|
||||
84=>'RenderTexture',
|
||||
87=>'MeshParticleEmitter',
|
||||
88=>'ParticleEmitter',
|
||||
89=>'Cubemap',
|
||||
90=>'Avatar',
|
||||
91=>'AnimatorController',
|
||||
92=>'GUILayer',
|
||||
93=>'RuntimeAnimatorController',
|
||||
94=>'ScriptMapper',
|
||||
95=>'Animator',
|
||||
96=>'TrailRenderer',
|
||||
98=>'DelayedCallManager',
|
||||
102=>'TextMesh',
|
||||
104=>'RenderSettings',
|
||||
108=>'Light',
|
||||
109=>'CGProgram',
|
||||
110=>'BaseAnimationTrack',
|
||||
111=>'Animation',
|
||||
114=>'MonoBehaviour',
|
||||
115=>'MonoScript',
|
||||
116=>'MonoManager',
|
||||
117=>'Texture3D',
|
||||
118=>'NewAnimationTrack',
|
||||
119=>'Projector',
|
||||
120=>'LineRenderer',
|
||||
121=>'Flare',
|
||||
122=>'Halo',
|
||||
123=>'LensFlare',
|
||||
124=>'FlareLayer',
|
||||
125=>'HaloLayer',
|
||||
126=>'NavMeshAreas',
|
||||
127=>'HaloManager',
|
||||
128=>'Font',
|
||||
129=>'PlayerSettings',
|
||||
130=>'NamedObject',
|
||||
131=>'GUITexture',
|
||||
132=>'GUIText',
|
||||
133=>'GUIElement',
|
||||
134=>'PhysicMaterial',
|
||||
135=>'SphereCollider',
|
||||
136=>'CapsuleCollider',
|
||||
137=>'SkinnedMeshRenderer',
|
||||
138=>'FixedJoint',
|
||||
140=>'RaycastCollider',
|
||||
141=>'BuildSettings',
|
||||
142=>'AssetBundle',
|
||||
143=>'CharacterController',
|
||||
144=>'CharacterJoint',
|
||||
145=>'SpringJoint',
|
||||
146=>'WheelCollider',
|
||||
147=>'ResourceManager',
|
||||
148=>'NetworkView',
|
||||
149=>'NetworkManager',
|
||||
150=>'PreloadData',
|
||||
152=>'MovieTexture',
|
||||
153=>'ConfigurableJoint',
|
||||
154=>'TerrainCollider',
|
||||
155=>'MasterServerInterface',
|
||||
156=>'TerrainData',
|
||||
157=>'LightmapSettings',
|
||||
158=>'WebCamTexture',
|
||||
159=>'EditorSettings',
|
||||
160=>'InteractiveCloth',
|
||||
161=>'ClothRenderer',
|
||||
162=>'EditorUserSettings',
|
||||
163=>'SkinnedCloth',
|
||||
164=>'AudioReverbFilter',
|
||||
165=>'AudioHighPassFilter',
|
||||
166=>'AudioChorusFilter',
|
||||
167=>'AudioReverbZone',
|
||||
168=>'AudioEchoFilter',
|
||||
169=>'AudioLowPassFilter',
|
||||
170=>'AudioDistortionFilter',
|
||||
171=>'SparseTexture',
|
||||
180=>'AudioBehaviour',
|
||||
181=>'AudioFilter',
|
||||
182=>'WindZone',
|
||||
183=>'Cloth',
|
||||
184=>'SubstanceArchive',
|
||||
185=>'ProceduralMaterial',
|
||||
186=>'ProceduralTexture',
|
||||
191=>'OffMeshLink',
|
||||
192=>'OcclusionArea',
|
||||
193=>'Tree',
|
||||
194=>'NavMeshObsolete',
|
||||
195=>'NavMeshAgent',
|
||||
196=>'NavMeshSettings',
|
||||
197=>'LightProbesLegacy',
|
||||
198=>'ParticleSystem',
|
||||
199=>'ParticleSystemRenderer',
|
||||
200=>'ShaderVariantCollection',
|
||||
205=>'LODGroup',
|
||||
206=>'BlendTree',
|
||||
207=>'Motion',
|
||||
208=>'NavMeshObstacle',
|
||||
210=>'TerrainInstance',
|
||||
212=>'SpriteRenderer',
|
||||
213=>'Sprite',
|
||||
214=>'CachedSpriteAtlas',
|
||||
215=>'ReflectionProbe',
|
||||
216=>'ReflectionProbes',
|
||||
218=>'Terrain',
|
||||
220=>'LightProbeGroup',
|
||||
221=>'AnimatorOverrideController',
|
||||
222=>'CanvasRenderer',
|
||||
223=>'Canvas',
|
||||
224=>'RectTransform',
|
||||
225=>'CanvasGroup',
|
||||
226=>'BillboardAsset',
|
||||
227=>'BillboardRenderer',
|
||||
228=>'SpeedTreeWindAsset',
|
||||
229=>'AnchoredJoint2D',
|
||||
230=>'Joint2D',
|
||||
231=>'SpringJoint2D',
|
||||
232=>'DistanceJoint2D',
|
||||
233=>'HingeJoint2D',
|
||||
234=>'SliderJoint2D',
|
||||
235=>'WheelJoint2D',
|
||||
238=>'NavMeshData',
|
||||
240=>'AudioMixer',
|
||||
241=>'AudioMixerController',
|
||||
243=>'AudioMixerGroupController',
|
||||
244=>'AudioMixerEffectController',
|
||||
245=>'AudioMixerSnapshotController',
|
||||
246=>'PhysicsUpdateBehaviour2D',
|
||||
247=>'ConstantForce2D',
|
||||
248=>'Effector2D',
|
||||
249=>'AreaEffector2D',
|
||||
250=>'PointEffector2D',
|
||||
251=>'PlatformEffector2D',
|
||||
252=>'SurfaceEffector2D',
|
||||
258=>'LightProbes',
|
||||
271=>'SampleClip',
|
||||
272=>'AudioMixerSnapshot',
|
||||
273=>'AudioMixerGroup',
|
||||
290=>'AssetBundleManifest',
|
||||
1001=>'Prefab',
|
||||
1002=>'EditorExtensionImpl',
|
||||
1003=>'AssetImporter',
|
||||
1004=>'AssetDatabase',
|
||||
1005=>'Mesh3DSImporter',
|
||||
1006=>'TextureImporter',
|
||||
1007=>'ShaderImporter',
|
||||
1008=>'ComputeShaderImporter',
|
||||
1011=>'AvatarMask',
|
||||
1020=>'AudioImporter',
|
||||
1026=>'HierarchyState',
|
||||
1027=>'GUIDSerializer',
|
||||
1028=>'AssetMetaData',
|
||||
1029=>'DefaultAsset',
|
||||
1030=>'DefaultImporter',
|
||||
1031=>'TextScriptImporter',
|
||||
1032=>'SceneAsset',
|
||||
1034=>'NativeFormatImporter',
|
||||
1035=>'MonoImporter',
|
||||
1037=>'AssetServerCache',
|
||||
1038=>'LibraryAssetImporter',
|
||||
1040=>'ModelImporter',
|
||||
1041=>'FBXImporter',
|
||||
1042=>'TrueTypeFontImporter',
|
||||
1044=>'MovieImporter',
|
||||
1045=>'EditorBuildSettings',
|
||||
1046=>'DDSImporter',
|
||||
1048=>'InspectorExpandedState',
|
||||
1049=>'AnnotationManager',
|
||||
1050=>'PluginImporter',
|
||||
1051=>'EditorUserBuildSettings',
|
||||
1052=>'PVRImporter',
|
||||
1053=>'ASTCImporter',
|
||||
1054=>'KTXImporter',
|
||||
1101=>'AnimatorStateTransition',
|
||||
1102=>'AnimatorState',
|
||||
1105=>'HumanTemplate',
|
||||
1107=>'AnimatorStateMachine',
|
||||
1108=>'PreviewAssetType',
|
||||
1109=>'AnimatorTransition',
|
||||
1110=>'SpeedTreeImporter',
|
||||
1111=>'AnimatorTransitionBase',
|
||||
1112=>'SubstanceImporter',
|
||||
1113=>'LightmapParameters',
|
||||
1120=>'LightmapSnapshot'
|
||||
}
|
||||
module Mikunyan
|
||||
# Module for defining and uzing constants
|
||||
module Constants
|
||||
# @param [Integer] pos position value
|
||||
# @param [String] buffer string buffer
|
||||
# @return [String,nil]
|
||||
def self.get_string_or_default(pos, buffer)
|
||||
pos & 0x80000000 == 0 ? buffer.unpack1("@#{pos}Z*") : STRING_TABLE[pos & 0x7fffffff]
|
||||
end
|
||||
|
||||
STRING_TABLE = {
|
||||
0 => 'AABB',
|
||||
5 => 'AnimationClip',
|
||||
19 => 'AnimationCurve',
|
||||
34 => 'AnimationState',
|
||||
49 => 'Array',
|
||||
55 => 'Base',
|
||||
60 => 'BitField',
|
||||
69 => 'bitset',
|
||||
76 => 'bool',
|
||||
81 => 'char',
|
||||
86 => 'ColorRGBA',
|
||||
96 => 'Component',
|
||||
106 => 'data',
|
||||
111 => 'deque',
|
||||
117 => 'double',
|
||||
124 => 'dynamic_array',
|
||||
138 => 'FastPropertyName',
|
||||
155 => 'first',
|
||||
161 => 'float',
|
||||
167 => 'Font',
|
||||
172 => 'GameObject',
|
||||
183 => 'Generic Mono',
|
||||
196 => 'GradientNEW',
|
||||
208 => 'GUID',
|
||||
213 => 'GUIStyle',
|
||||
222 => 'int',
|
||||
226 => 'list',
|
||||
231 => 'long long',
|
||||
241 => 'map',
|
||||
245 => 'Matrix4x4f',
|
||||
256 => 'MdFour',
|
||||
263 => 'MonoBehaviour',
|
||||
277 => 'MonoScript',
|
||||
288 => 'm_ByteSize',
|
||||
299 => 'm_Curve',
|
||||
307 => 'm_EditorClassIdentifier',
|
||||
331 => 'm_EditorHideFlags',
|
||||
349 => 'm_Enabled',
|
||||
359 => 'm_ExtensionPtr',
|
||||
374 => 'm_GameObject',
|
||||
387 => 'm_Index',
|
||||
395 => 'm_IsArray',
|
||||
405 => 'm_IsStatic',
|
||||
416 => 'm_MetaFlag',
|
||||
427 => 'm_Name',
|
||||
434 => 'm_ObjectHideFlags',
|
||||
452 => 'm_PrefabInternal',
|
||||
469 => 'm_PrefabParentObject',
|
||||
490 => 'm_Script',
|
||||
499 => 'm_StaticEditorFlags',
|
||||
519 => 'm_Type',
|
||||
526 => 'm_Version',
|
||||
536 => 'Object',
|
||||
543 => 'pair',
|
||||
548 => 'PPtr<Component>',
|
||||
564 => 'PPtr<GameObject>',
|
||||
581 => 'PPtr<Material>',
|
||||
596 => 'PPtr<MonoBehaviour>',
|
||||
616 => 'PPtr<MonoScript>',
|
||||
633 => 'PPtr<Object>',
|
||||
646 => 'PPtr<Prefab>',
|
||||
659 => 'PPtr<Sprite>',
|
||||
672 => 'PPtr<TextAsset>',
|
||||
688 => 'PPtr<Texture>',
|
||||
702 => 'PPtr<Texture2D>',
|
||||
718 => 'PPtr<Transform>',
|
||||
734 => 'Prefab',
|
||||
741 => 'Quaternionf',
|
||||
753 => 'Rectf',
|
||||
759 => 'RectInt',
|
||||
767 => 'RectOffset',
|
||||
778 => 'second',
|
||||
785 => 'set',
|
||||
789 => 'short',
|
||||
795 => 'size',
|
||||
800 => 'SInt16',
|
||||
807 => 'SInt32',
|
||||
814 => 'SInt64',
|
||||
821 => 'SInt8',
|
||||
827 => 'staticvector',
|
||||
840 => 'string',
|
||||
847 => 'TextAsset',
|
||||
857 => 'TextMesh',
|
||||
866 => 'Texture',
|
||||
874 => 'Texture2D',
|
||||
884 => 'Transform',
|
||||
894 => 'TypelessData',
|
||||
907 => 'UInt16',
|
||||
914 => 'UInt32',
|
||||
921 => 'UInt64',
|
||||
928 => 'UInt8',
|
||||
934 => 'unsigned int',
|
||||
947 => 'unsigned long long',
|
||||
966 => 'unsigned short',
|
||||
981 => 'vector',
|
||||
988 => 'Vector2f',
|
||||
997 => 'Vector3f',
|
||||
1006 => 'Vector4f',
|
||||
1015 => 'm_ScriptingClassIdentifier',
|
||||
1042 => 'Gradient',
|
||||
1051 => 'Type*',
|
||||
1057 => 'int2_storage',
|
||||
1070 => 'int3_storage',
|
||||
1083 => 'BoundsInt',
|
||||
1093 => 'm_CorrespondingSourceObject',
|
||||
1121 => 'm_PrefabInstance',
|
||||
1138 => 'm_PrefabAsset'
|
||||
}.freeze
|
||||
|
||||
CLASS_ID_TABLE = [
|
||||
[0, 'Object'],
|
||||
[1, 'GameObject'],
|
||||
[2, 'Component'],
|
||||
[3, 'LevelGameManager'],
|
||||
[4, 'Transform'],
|
||||
[5, 'TimeManager'],
|
||||
[6, 'GlobalGameManager'],
|
||||
[8, 'Behaviour'],
|
||||
[9, 'GameManager'],
|
||||
[11, 'AudioManager'],
|
||||
[12, 'ParticleAnimator'],
|
||||
[13, 'InputManager'],
|
||||
[15, 'EllipsoidParticleEmitter'],
|
||||
[17, 'Pipeline'],
|
||||
[18, 'EditorExtension'],
|
||||
[19, 'Physics2DSettings'],
|
||||
[20, 'Camera'],
|
||||
[21, 'Material'],
|
||||
[23, 'MeshRenderer'],
|
||||
[25, 'Renderer'],
|
||||
[26, 'ParticleRenderer'],
|
||||
[27, 'Texture'],
|
||||
[28, 'Texture2D'],
|
||||
[29, 'Scene'],
|
||||
[29, 'SceneSettings'],
|
||||
[29, 'OcclusionCullingSettings'],
|
||||
[30, 'RenderManager'],
|
||||
[30, 'GraphicsSettings'],
|
||||
[33, 'MeshFilter'],
|
||||
[41, 'OcclusionPortal'],
|
||||
[43, 'Mesh'],
|
||||
[45, 'Skybox'],
|
||||
[47, 'QualitySettings'],
|
||||
[48, 'Shader'],
|
||||
[49, 'TextAsset'],
|
||||
[50, 'Rigidbody2D'],
|
||||
[51, 'Physics2DManager'],
|
||||
[52, 'NotificationManager'],
|
||||
[53, 'Collider2D'],
|
||||
[54, 'Rigidbody'],
|
||||
[55, 'PhysicsManager'],
|
||||
[56, 'Collider'],
|
||||
[57, 'Joint'],
|
||||
[58, 'CircleCollider2D'],
|
||||
[59, 'HingeJoint'],
|
||||
[60, 'PolygonCollider2D'],
|
||||
[61, 'BoxCollider2D'],
|
||||
[62, 'PhysicsMaterial2D'],
|
||||
[64, 'MeshCollider'],
|
||||
[65, 'BoxCollider'],
|
||||
[66, 'SpriteCollider2D'],
|
||||
[66, 'CompositeCollider2D'],
|
||||
[68, 'EdgeCollider2D'],
|
||||
[70, 'CapsuleCollider2D'],
|
||||
[71, 'AnimationManager'],
|
||||
[72, 'ComputeShader'],
|
||||
[74, 'AnimationClip'],
|
||||
[75, 'ConstantForce'],
|
||||
[76, 'WorldParticleCollider'],
|
||||
[78, 'TagManager'],
|
||||
[81, 'AudioListener'],
|
||||
[82, 'AudioSource'],
|
||||
[83, 'AudioClip'],
|
||||
[84, 'RenderTexture'],
|
||||
[86, 'CustomRenderTexture'],
|
||||
[87, 'MeshParticleEmitter'],
|
||||
[88, 'ParticleEmitter'],
|
||||
[89, 'Cubemap'],
|
||||
[90, 'Avatar'],
|
||||
[91, 'AnimatorController'],
|
||||
[92, 'GUILayer'],
|
||||
[93, 'RuntimeAnimatorController'],
|
||||
[94, 'ScriptMapper'],
|
||||
[95, 'Animator'],
|
||||
[96, 'TrailRenderer'],
|
||||
[98, 'DelayedCallManager'],
|
||||
[102, 'TextMesh'],
|
||||
[104, 'RenderSettings'],
|
||||
[108, 'Light'],
|
||||
[109, 'CGProgram'],
|
||||
[110, 'BaseAnimationTrack'],
|
||||
[111, 'Animation'],
|
||||
[114, 'MonoBehaviour'],
|
||||
[115, 'MonoScript'],
|
||||
[116, 'MonoManager'],
|
||||
[117, 'Texture3D'],
|
||||
[118, 'NewAnimationTrack'],
|
||||
[119, 'Projector'],
|
||||
[120, 'LineRenderer'],
|
||||
[121, 'Flare'],
|
||||
[122, 'Halo'],
|
||||
[123, 'LensFlare'],
|
||||
[124, 'FlareLayer'],
|
||||
[125, 'HaloLayer'],
|
||||
[126, 'NavMeshLayers'],
|
||||
[126, 'NavMeshAreas'],
|
||||
[126, 'NavMeshProjectSettings'],
|
||||
[127, 'HaloManager'],
|
||||
[128, 'Font'],
|
||||
[129, 'PlayerSettings'],
|
||||
[130, 'NamedObject'],
|
||||
[131, 'GUITexture'],
|
||||
[132, 'GUIText'],
|
||||
[133, 'GUIElement'],
|
||||
[134, 'PhysicMaterial'],
|
||||
[135, 'SphereCollider'],
|
||||
[136, 'CapsuleCollider'],
|
||||
[137, 'SkinnedMeshRenderer'],
|
||||
[138, 'FixedJoint'],
|
||||
[140, 'RaycastCollider'],
|
||||
[141, 'BuildSettings'],
|
||||
[142, 'AssetBundle'],
|
||||
[143, 'CharacterController'],
|
||||
[144, 'CharacterJoint'],
|
||||
[145, 'SpringJoint'],
|
||||
[146, 'WheelCollider'],
|
||||
[147, 'ResourceManager'],
|
||||
[148, 'NetworkView'],
|
||||
[149, 'NetworkManager'],
|
||||
[150, 'PreloadData'],
|
||||
[152, 'MovieTexture'],
|
||||
[153, 'ConfigurableJoint'],
|
||||
[154, 'TerrainCollider'],
|
||||
[155, 'MasterServerInterface'],
|
||||
[156, 'TerrainData'],
|
||||
[157, 'LightmapSettings'],
|
||||
[158, 'WebCamTexture'],
|
||||
[159, 'EditorSettings'],
|
||||
[160, 'InteractiveCloth'],
|
||||
[161, 'ClothRenderer'],
|
||||
[162, 'EditorUserSettings'],
|
||||
[163, 'SkinnedCloth'],
|
||||
[164, 'AudioReverbFilter'],
|
||||
[165, 'AudioHighPassFilter'],
|
||||
[166, 'AudioChorusFilter'],
|
||||
[167, 'AudioReverbZone'],
|
||||
[168, 'AudioEchoFilter'],
|
||||
[169, 'AudioLowPassFilter'],
|
||||
[170, 'AudioDistortionFilter'],
|
||||
[171, 'SparseTexture'],
|
||||
[180, 'AudioBehaviour'],
|
||||
[181, 'AudioFilter'],
|
||||
[182, 'WindZone'],
|
||||
[183, 'Cloth'],
|
||||
[184, 'SubstanceArchive'],
|
||||
[185, 'ProceduralMaterial'],
|
||||
[186, 'ProceduralTexture'],
|
||||
[187, 'Texture2DArray'],
|
||||
[188, 'CubemapArray'],
|
||||
[191, 'OffMeshLink'],
|
||||
[192, 'OcclusionArea'],
|
||||
[193, 'Tree'],
|
||||
[194, 'NavMesh'],
|
||||
[194, 'NavMeshObsolete'],
|
||||
[195, 'NavMeshAgent'],
|
||||
[196, 'NavMeshSettings'],
|
||||
[197, 'LightProbeCloud'],
|
||||
[197, 'LightProbesLegacy'],
|
||||
[198, 'ParticleSystem'],
|
||||
[199, 'ParticleSystemRenderer'],
|
||||
[200, 'ShaderVariantCollection'],
|
||||
[205, 'LODGroup'],
|
||||
[206, 'BlendTree'],
|
||||
[207, 'Motion'],
|
||||
[208, 'NavMeshObstacle'],
|
||||
[210, 'TerrainInstance'],
|
||||
[210, 'SortingGroup'],
|
||||
[212, 'SpriteRenderer'],
|
||||
[213, 'Sprite'],
|
||||
[214, 'CachedSpriteAtlas'],
|
||||
[215, 'ReflectionProbe'],
|
||||
[216, 'ReflectionProbes'],
|
||||
[218, 'Terrain'],
|
||||
[220, 'LightProbeGroup'],
|
||||
[221, 'AnimatorOverrideController'],
|
||||
[222, 'CanvasRenderer'],
|
||||
[223, 'Canvas'],
|
||||
[224, 'RectTransform'],
|
||||
[225, 'CanvasGroup'],
|
||||
[226, 'BillboardAsset'],
|
||||
[227, 'BillboardRenderer'],
|
||||
[228, 'SpeedTreeWindAsset'],
|
||||
[229, 'AnchoredJoint2D'],
|
||||
[230, 'Joint2D'],
|
||||
[231, 'SpringJoint2D'],
|
||||
[232, 'DistanceJoint2D'],
|
||||
[233, 'HingeJoint2D'],
|
||||
[234, 'SliderJoint2D'],
|
||||
[235, 'WheelJoint2D'],
|
||||
[236, 'ClusterInputManager'],
|
||||
[237, 'BaseVideoTexture'],
|
||||
[238, 'NavMeshData'],
|
||||
[240, 'AudioMixer'],
|
||||
[241, 'AudioMixerController'],
|
||||
[243, 'AudioMixerGroupController'],
|
||||
[244, 'AudioMixerEffectController'],
|
||||
[245, 'AudioMixerSnapshotController'],
|
||||
[246, 'PhysicsUpdateBehaviour2D'],
|
||||
[247, 'ConstantForce2D'],
|
||||
[248, 'Effector2D'],
|
||||
[249, 'AreaEffector2D'],
|
||||
[250, 'PointEffector2D'],
|
||||
[251, 'PlatformEffector2D'],
|
||||
[252, 'SurfaceEffector2D'],
|
||||
[253, 'BuoyancyEffector2D'],
|
||||
[254, 'RelativeJoint2D'],
|
||||
[255, 'FixedJoint2D'],
|
||||
[256, 'FrictionJoint2D'],
|
||||
[257, 'TargetJoint2D'],
|
||||
[258, 'LightProbes'],
|
||||
[259, 'LightProbeProxyVolume'],
|
||||
[271, 'SampleClip'],
|
||||
[272, 'AudioMixerSnapshot'],
|
||||
[273, 'AudioMixerGroup'],
|
||||
[280, 'NScreenBridge'],
|
||||
[290, 'AssetBundleManifest'],
|
||||
[292, 'UnityAdsSettings'],
|
||||
[292, 'UnityAdsManager'],
|
||||
[300, 'RuntimeInitializeOnLoadManager'],
|
||||
[301, 'CloudWebServicesManager'],
|
||||
[303, 'UnityAnalyticsManager'],
|
||||
[304, 'CrashReportManager'],
|
||||
[305, 'PerformanceReportingManager'],
|
||||
[310, 'UnityConnectSettings'],
|
||||
[319, 'AvatarMask'],
|
||||
[320, 'PlayableDirector'],
|
||||
[328, 'VideoPlayer'],
|
||||
[329, 'VideoClip'],
|
||||
[330, 'ParticleSystemForceField'],
|
||||
[331, 'SpriteMask'],
|
||||
[362, 'WorldAnchor'],
|
||||
[363, 'OcclusionCullingData'],
|
||||
[1000, 'SmallestEditorClassID'],
|
||||
[1001, 'Prefab'], # 1001480554
|
||||
[1001, 'PrefabInstance'],
|
||||
[1002, 'EditorExtensionImpl'],
|
||||
[1003, 'AssetImporter'],
|
||||
[1004, 'AssetDatabase'],
|
||||
[1004, 'AssetDatabaseV1'],
|
||||
[1005, 'Mesh3DSImporter'],
|
||||
[1006, 'TextureImporter'],
|
||||
[1007, 'ShaderImporter'],
|
||||
[1008, 'ComputeShaderImporter'],
|
||||
[1011, 'AvatarMask'],
|
||||
[1020, 'AudioImporter'],
|
||||
[1026, 'HierarchyState'],
|
||||
[1027, 'GUIDSerializer'],
|
||||
[1028, 'AssetMetaData'],
|
||||
[1029, 'DefaultAsset'],
|
||||
[1030, 'DefaultImporter'],
|
||||
[1031, 'TextScriptImporter'],
|
||||
[1032, 'SceneAsset'],
|
||||
[1034, 'NativeFormatImporter'],
|
||||
[1035, 'MonoImporter'],
|
||||
[1037, 'AssetServerCache'],
|
||||
[1038, 'LibraryAssetImporter'],
|
||||
[1040, 'ModelImporter'],
|
||||
[1041, 'FBXImporter'],
|
||||
[1042, 'TrueTypeFontImporter'],
|
||||
[1044, 'MovieImporter'],
|
||||
[1045, 'EditorBuildSettings'],
|
||||
[1046, 'DDSImporter'],
|
||||
[1048, 'InspectorExpandedState'],
|
||||
[1049, 'AnnotationManager'],
|
||||
[1050, 'MonoAssemblyImporter'],
|
||||
[1050, 'PluginImporter'],
|
||||
[1051, 'EditorUserBuildSettings'],
|
||||
[1052, 'PVRImporter'],
|
||||
[1053, 'ASTCImporter'],
|
||||
[1054, 'KTXImporter'],
|
||||
[1055, 'IHVImageFormatImporter'],
|
||||
[1101, 'Transition'],
|
||||
[1101, 'AnimatorStateTransition'],
|
||||
[1102, 'State'],
|
||||
[1102, 'AnimatorState'],
|
||||
[1105, 'HumanTemplate'],
|
||||
[1107, 'StateMachine'],
|
||||
[1107, 'AnimatorStateMachine'],
|
||||
[1108, 'PreviewAssetType'],
|
||||
[1108, 'PreviewAnimationClip'],
|
||||
[1109, 'AnimatorTransition'],
|
||||
[1110, 'SpeedTreeImporter'],
|
||||
[1111, 'AnimatorTransitionBase'],
|
||||
[1112, 'SubstanceImporter'],
|
||||
[1113, 'LightmapParameters'],
|
||||
[1120, 'LightmapSnapshot'],
|
||||
[1120, 'LightingDataAsset'],
|
||||
[1121, 'GISRaster'],
|
||||
[1122, 'GISRasterImporter'],
|
||||
[1123, 'CadImporter'],
|
||||
[1124, 'SketchUpImporter'],
|
||||
[1125, 'BuildReport'],
|
||||
[1126, 'PackedAssets'],
|
||||
[1127, 'VideoClipImporter'],
|
||||
[2000, 'ActivationLogComponent'],
|
||||
[100_000, 'int'],
|
||||
[100_001, 'bool'],
|
||||
[100_002, 'float'],
|
||||
[100_003, 'MonoObject'],
|
||||
[100_004, 'Collision'],
|
||||
[100_005, 'Vector3f'],
|
||||
[100_006, 'RootMotionData'],
|
||||
[100_007, 'Collision2D'],
|
||||
[100_008, 'AudioMixerLiveUpdateFloat'],
|
||||
[100_009, 'AudioMixerLiveUpdateBool'],
|
||||
[100_010, 'Polygon2D'],
|
||||
[100_011, 'void'],
|
||||
[19_719_996, 'TilemapCollider2D'],
|
||||
[41_386_430, 'AssetImporterLog'],
|
||||
[73_398_921, 'VFXRenderer'],
|
||||
[76_251_197, 'SerializableManagedRefTestClass'],
|
||||
[156_049_354, 'Grid'],
|
||||
[181_963_792, 'Preset'],
|
||||
[277_625_683, 'EmptyObject'],
|
||||
[285_090_594, 'IConstraint'],
|
||||
[293_259_124, 'TestObjectWithSpecialLayoutOne'],
|
||||
[294_290_339, 'AssemblyDefinitionReferenceImporter'],
|
||||
[334_799_969, 'SiblingDerived'],
|
||||
[342_846_651, 'TestObjectWithSerializedMapStringNonAlignedStruct'],
|
||||
[367_388_927, 'SubDerived'],
|
||||
[369_655_926, 'AssetImportInProgressProxy'],
|
||||
[382_020_655, 'PluginBuildInfo'],
|
||||
[426_301_858, 'EditorProjectAccess'],
|
||||
[468_431_735, 'PrefabImporter'],
|
||||
[478_637_458, 'TestObjectWithSerializedArray'],
|
||||
[478_637_459, 'TestObjectWithSerializedAnimationCurve'],
|
||||
[483_693_784, 'TilemapRenderer'],
|
||||
[638_013_454, 'SpriteAtlasDatabase'],
|
||||
[641_289_076, 'AudioBuildInfo'],
|
||||
[644_342_135, 'CachedSpriteAtlasRuntimeData'],
|
||||
[646_504_946, 'RendererFake'],
|
||||
[662_584_278, 'AssemblyDefinitionReferenceAsset'],
|
||||
[668_709_126, 'BuiltAssetBundleInfoSet'],
|
||||
[687_078_895, 'SpriteAtlas'],
|
||||
[877_146_078, 'PlatformModuleSetup'],
|
||||
[895_512_359, 'AimConstraint'],
|
||||
[937_362_698, 'VFXManager'],
|
||||
[994_735_392, 'VisualEffectSubgraph'],
|
||||
[994_735_403, 'VisualEffectSubgraphOperator'],
|
||||
[994_735_404, 'VisualEffectSubgraphBlock'],
|
||||
[1_001_480_554, 'Prefab'],
|
||||
[1_027_052_791, 'LocalizationImporter'],
|
||||
[1_091_556_383, 'Derived'],
|
||||
[1_111_377_672, 'PropertyModificationsTargetTestObject'],
|
||||
[1_114_811_875, 'ReferencesArtifactGenerator'],
|
||||
[1_152_215_463, 'AssemblyDefinitionAsset'],
|
||||
[1_154_873_562, 'SceneVisibilityState'],
|
||||
[1_183_024_399, 'LookAtConstraint'],
|
||||
[1_223_240_404, 'MultiArtifactTestImporter'],
|
||||
[1_268_269_756, 'GameObjectRecorder'],
|
||||
[1_325_145_578, 'LightingDataAssetParent'],
|
||||
[1_386_491_679, 'PresetManager'],
|
||||
[1_392_443_030, 'TestObjectWithSpecialLayoutTwo'],
|
||||
[1_403_656_975, 'StreamingManager'],
|
||||
[1_480_428_607, 'LowerResBlitTexture'],
|
||||
[1_542_919_678, 'StreamingController'],
|
||||
[1_571_458_007, 'RenderPassAttachment'],
|
||||
[1_628_831_178, 'TestObjectVectorPairStringBool'],
|
||||
[1_742_807_556, 'GridLayout'],
|
||||
[1_766_753_193, 'AssemblyDefinitionImporter'],
|
||||
[1_773_428_102, 'ParentConstraint'],
|
||||
[1_803_986_026, 'FakeComponent'],
|
||||
[1_818_360_608, 'PositionConstraint'],
|
||||
[1_818_360_609, 'RotationConstraint'],
|
||||
[1_818_360_610, 'ScaleConstraint'],
|
||||
[1_839_735_485, 'Tilemap'],
|
||||
[1_896_753_125, 'PackageManifest'],
|
||||
[1_896_753_126, 'PackageManifestImporter'],
|
||||
[1_953_259_897, 'TerrainLayer'],
|
||||
[1_971_053_207, 'SpriteShapeRenderer'],
|
||||
[1_977_754_360, 'NativeObjectType'],
|
||||
[1_981_279_845, 'TestObjectWithSerializedMapStringBool'],
|
||||
[1_995_898_324, 'SerializableManagedHost'],
|
||||
[2_058_629_509, 'VisualEffectAsset'],
|
||||
[2_058_629_510, 'VisualEffectImporter'],
|
||||
[2_058_629_511, 'VisualEffectResource'],
|
||||
[2_059_678_085, 'VisualEffectObject'],
|
||||
[2_083_052_967, 'VisualEffect'],
|
||||
[2_083_778_819, 'LocalizationAsset'],
|
||||
[2_089_858_483, 'ScriptedImporter']
|
||||
]
|
||||
|
||||
CLASS_ID2NAME = CLASS_ID_TABLE.to_h.freeze
|
||||
CLASS_ID = CLASS_ID2NAME # compatibility
|
||||
CLASS_NAME2ID = CLASS_ID_TABLE.map(&:reverse).to_h.freeze
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
warn 'Warning: `mikunyan/decoders` is deprecated and will be removed at a future release.'
|
||||
|
||||
require 'mikunyan/decoders/image_decoder'
|
||||
|
||||
module Mikunyan
|
||||
# Module for helper classes for decoding object
|
||||
module DecodeHelper
|
||||
end
|
||||
# Module for helper classes for decoding object
|
||||
module DecodeHelper
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
require 'bin_utils'
|
||||
require 'fiddle'
|
||||
|
||||
module Mikunyan
|
||||
module DecodeHelper
|
||||
# Class for decode ASTC block
|
||||
# @attr_reader [String] data decoded data
|
||||
class AstcBlockDecoder
|
||||
attr_reader :data
|
||||
|
||||
# Decode block
|
||||
# @param [String] bin binary
|
||||
# @param [Integer] bw block width
|
||||
# @param [Integer] bh block height
|
||||
def initialize(bin, bw, bh)
|
||||
if bin[0].ord == 0xfc && bin[1].ord % 2 == 1
|
||||
@data = (bin[9] + bin[11] + bin[13] + bin[15]) * bw * bh
|
||||
else
|
||||
@d2 = BinUtils.get_int64_le(bin)
|
||||
@d1 = BinUtils.get_int64_le(bin, 8)
|
||||
@bw = bw
|
||||
@bh = bh
|
||||
|
||||
decode_block_params
|
||||
decode_endpoints
|
||||
decode_weights
|
||||
select_partition
|
||||
applicate_color
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
WeightPrecTableA = [nil, nil, 0, 3, 0, 5, 3, 0, nil, nil, 5, 3, 0, 5, 3, 0]
|
||||
WeightPrecTableB = [nil, nil, 1, 0, 2, 0, 1, 3, nil, nil, 1, 2, 4, 2, 3, 5]
|
||||
|
||||
CemTableA = [0, 0, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0]
|
||||
CemTableB = [1, 2, 1, 3, 1, 2, 4, 2, 3, 5, 3, 4, 6, 4, 5, 7, 5, 6, 8]
|
||||
|
||||
TritsTable = [
|
||||
[0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2],
|
||||
[0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1],
|
||||
[0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
||||
]
|
||||
|
||||
QuintsTable = [
|
||||
[0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1],
|
||||
[0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3],
|
||||
[0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4]
|
||||
]
|
||||
|
||||
def [](i, j = 1)
|
||||
if j < 1
|
||||
0
|
||||
elsif j == 1
|
||||
i < 64 ? @d2[i] : @d1[i - 64]
|
||||
else
|
||||
if i + j <= 64
|
||||
@d2 >> i & (1 << j) - 1
|
||||
elsif i >= 64
|
||||
@d1 >> (i - 64) & (1 << j) - 1
|
||||
else
|
||||
@d2 >> i | (@d1 & (1 << i + j - 64) - 1) << 64 - i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode_block_params
|
||||
# Block Mode
|
||||
@weight_range = @d2 >> 4 & 1 | @d2 >> 6 & 8
|
||||
@dual_plane = @d2 & 0x400 == 0x400
|
||||
if @d2 & 0x3 != 0
|
||||
@weight_range |= @d2 << 1 & 6
|
||||
case @d2 & 0xc
|
||||
when 0
|
||||
@width = (@d2 >> 7 & 3) + 4
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x4
|
||||
@width = (@d2 >> 7 & 3) + 8
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x8
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = (@d2 >> 7 & 3) + 8
|
||||
else # 0xc
|
||||
if @d2 & 0x100 == 0
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = @d2[7] + 6
|
||||
else
|
||||
@width = @d2[7] + 2
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
end
|
||||
end
|
||||
else
|
||||
@weight_range |= @d2 >> 1 & 6
|
||||
case @d2 & 0x180
|
||||
when 0
|
||||
@width = 12
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x80
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = 12
|
||||
when 0x180
|
||||
@width = (@d2 & 0x20 == 0) ? 6 : 10
|
||||
@height = 16 - @width
|
||||
else # 0x100
|
||||
@width = (@d2 >> 5 & 3) + 6
|
||||
@height = (@d2 >> 9 & 3) + 6
|
||||
@dual_plane = false
|
||||
@weight_range &= 7
|
||||
end
|
||||
end
|
||||
|
||||
# Count Partitions
|
||||
@part_num = (@d2 >> 11 & 3) + 1
|
||||
|
||||
# Count Weight Bits
|
||||
@weight_num = @width * @height
|
||||
@weight_num *= 2 if @dual_plane
|
||||
case WeightPrecTableA[@weight_range]
|
||||
when 3
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range] + (@weight_num * 8 + 4) / 5
|
||||
when 5
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range] + (@weight_num * 7 + 2) / 3
|
||||
else # 0
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range]
|
||||
end
|
||||
|
||||
# CEM
|
||||
if @part_num == 1
|
||||
@cem = [@d2 >> 13 & 0xf]
|
||||
config_bit = 17
|
||||
else
|
||||
cembase = @d2 >> 23 & 3
|
||||
if cembase == 0
|
||||
@cem = Array.new(@part_num, @d2 >> 25 & 0xf)
|
||||
config_bit = 29
|
||||
else
|
||||
@cem = (0...@part_num).map{|i| ((@d2 >> (25 + i) & 1) + cembase - 1) << 2}
|
||||
|
||||
case @part_num
|
||||
when 2
|
||||
@cem[0] |= @d2 >> 27 & 3
|
||||
@cem[1] |= self[126 - @weight_bit, 2]
|
||||
when 3
|
||||
@cem[0] |= @d2[28]
|
||||
@cem[0] |= self[123 - @weight_bit] << 1
|
||||
@cem[1] |= self[124 - @weight_bit, 2]
|
||||
@cem[2] |= self[126 - @weight_bit, 2]
|
||||
else # 4
|
||||
4.times do |i|
|
||||
@cem[i] |= self[120 + 2 * i - @weight_bit, 2]
|
||||
end
|
||||
end
|
||||
|
||||
config_bit = 25 + @part_num * 3
|
||||
end
|
||||
end
|
||||
|
||||
# Count Color Endpoint Bits
|
||||
config_bit += 2 if @dual_plane
|
||||
remain_bit = 128 - config_bit - @weight_bit
|
||||
@cem_num = @cem.map{|i| (i >> 1 & 6) + 2}.inject(:+)
|
||||
|
||||
CemTableA.count.times do |n|
|
||||
i = CemTableA.count - n - 1
|
||||
case CemTableA[i]
|
||||
when 3
|
||||
@cem_bit = @cem_num * CemTableB[i] + (@cem_num * 8 + 4) / 5
|
||||
when 5
|
||||
@cem_bit = @cem_num * CemTableB[i] + (@cem_num * 7 + 2) / 3
|
||||
else # 0
|
||||
@cem_bit = @cem_num * CemTableB[i]
|
||||
end
|
||||
|
||||
if @cem_bit <= remain_bit
|
||||
@cem_range = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if @dual_plane
|
||||
if @part_num == 1 || cembase == 0
|
||||
@plane_selector = self[126 - @weight_bit, 2]
|
||||
else
|
||||
@plane_selector = self[130 - @weight_bit - @part_num * 3, 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode_endpoints
|
||||
values = decode_intseq_raw(self[@part_num == 1 ? 17 : 29, @cem_bit], CemTableA[@cem_range], CemTableB[@cem_range], @cem_num).map do |e|
|
||||
unquantize_endpoint(CemTableA[@cem_range], CemTableB[@cem_range], e[0], e[1])
|
||||
end
|
||||
|
||||
@endpoint = @cem.map do |cem|
|
||||
v = values.slice!(0, (cem >> 1 & 6) + 2)
|
||||
case cem
|
||||
when 0
|
||||
[v[0], v[0], v[0], 255, v[1], v[1], v[1], 255]
|
||||
when 1
|
||||
l0 = (v[0] >> 2) | (v[1] & 0xc0)
|
||||
l1 = (l0 + (v[1] & 0x3f)).clamp(0, 255)
|
||||
[l0, l0, l0, 255, l1, l1, l1, 255]
|
||||
when 4
|
||||
[v[0], v[0], v[0], v[2], v[1], v[1], v[1], v[3]]
|
||||
when 5
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
[v[0], v[0], v[0], v[2], v[0] + v[1], v[0] + v[1], v[0] + v[1], v[2] + v[3]].map{|i| i.clamp(0, 255)}
|
||||
when 6
|
||||
[v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, 255, v[0], v[1], v[2], 255]
|
||||
when 8
|
||||
if v[0] + v[2] + v[4] <= v[1] + v[3] + v[5]
|
||||
[v[0], v[2], v[4], 255, v[1], v[3], v[5], 255]
|
||||
else
|
||||
blue_contract(v[1], v[3], v[5], 255, v[0], v[2], v[4], 255)
|
||||
end
|
||||
when 9
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
v[5], v[4] = bit_transfer_signed(v[5], v[4])
|
||||
if v[1] + v[3] + v[5] >= 0
|
||||
[v[0], v[2], v[4], 255, v[0] + v[1], v[2] + v[3], v[4] + v[5], 255].map{|i| i.clamp(0, 255)}
|
||||
else
|
||||
blue_contract(v[0] + v[1], v[2] + v[3], v[4] + v[5], 255, v[0], v[2], v[4], 255).map{|i| i.clamp(0, 255)}
|
||||
end
|
||||
when 10
|
||||
[v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, v[4], v[0], v[1], v[2], v[5]]
|
||||
when 12
|
||||
if v[0] + v[2] + v[4] <= v[1] + v[3] + v[5]
|
||||
[v[0], v[2], v[4], v[6], v[1], v[3], v[5], v[7]]
|
||||
else
|
||||
blue_contract(v[1], v[3], v[5], v[7], v[0], v[2], v[4], v[6])
|
||||
end
|
||||
when 13
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
v[5], v[4] = bit_transfer_signed(v[5], v[4])
|
||||
v[7], v[6] = bit_transfer_signed(v[7], v[6])
|
||||
if v[1] + v[3] + v[5] >= 0
|
||||
[v[0], v[2], v[4], v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7]].map{|i| i.clamp(0, 255)}
|
||||
else
|
||||
blue_contract(v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7], v[0], v[2], v[4], v[6]).map{|i| i.clamp(0, 255)}
|
||||
end
|
||||
else
|
||||
throw NotImplementedError.new("HDR image is not supported. (CEM: #{cem})")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bit_transfer_signed(a, b)
|
||||
b = (b >> 1) | (a & 0x80)
|
||||
a = (a >> 1) & 0x3f
|
||||
[a[5] == 1 ? a - 0x40 : a, b]
|
||||
end
|
||||
|
||||
def blue_contract(r1, g1, b1, a1, r2, g2, b2, a2)
|
||||
[(r1 + b1) >> 1, (g1 + b1) >> 1, b1, a1, (r2 + b2) >> 1, (g2 + b2) >> 1, b2, a2]
|
||||
end
|
||||
|
||||
def decode_weights
|
||||
data = (0...(@weight_bit + 7) / 8).map{|i| (((self[120 - i * 8, 8]) * 0x80200802) & 0x0884422110) * 0x0101010101 >> 32 & 0xff}
|
||||
.map.with_index{|e, i| e << i * 8}.inject(:|) & (1 << @weight_bit) - 1
|
||||
|
||||
weight_point = decode_intseq_raw(data, WeightPrecTableA[@weight_range], WeightPrecTableB[@weight_range], @weight_num).map do |e|
|
||||
unquantize_weight(WeightPrecTableA[@weight_range], WeightPrecTableB[@weight_range], e[0], e[1])
|
||||
end
|
||||
|
||||
ds = (1024 + @bw / 2) / (@bw - 1)
|
||||
dt = (1024 + @bh / 2) / (@bh - 1)
|
||||
|
||||
if @dual_plane
|
||||
@weight0 = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
@weight1 = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
|
||||
for t in 0...@bh
|
||||
for s in 0...@bw
|
||||
gs = (ds * s * (@width - 1) + 32) >> 6
|
||||
gt = (dt * t * (@height - 1) + 32) >> 6
|
||||
fs = gs & 0xf
|
||||
ft = gt & 0xf
|
||||
v = (gs >> 4) + (gt >> 4) * @width
|
||||
w11 = (fs * ft + 8) >> 4
|
||||
w10 = ft - w11
|
||||
w01 = fs - w11
|
||||
w00 = 16 - fs - ft + w11
|
||||
|
||||
p00 = weight_point[v * 2] || 0
|
||||
p01 = weight_point[(v + 1) * 2] || 0
|
||||
p10 = weight_point[(v + @width) * 2] || 0
|
||||
p11 = weight_point[(v + @width + 1) * 2] || 0
|
||||
@weight0[s + t * @bw] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4
|
||||
|
||||
p00 = weight_point[v * 2 + 1] || 0
|
||||
p01 = weight_point[(v + 1) * 2 + 1] || 0
|
||||
p10 = weight_point[(v + @width) * 2 + 1] || 0
|
||||
p11 = weight_point[(v + @width + 1) * 2 + 1] || 0
|
||||
@weight1[s + t * @bw] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4
|
||||
end
|
||||
end
|
||||
else
|
||||
@weight = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
|
||||
for t in 0...@bh
|
||||
for s in 0...@bw
|
||||
gs = (ds * s * (@width - 1) + 32) >> 6
|
||||
gt = (dt * t * (@height - 1) + 32) >> 6
|
||||
fs = gs & 0xf
|
||||
ft = gt & 0xf
|
||||
v = (gs >> 4) + (gt >> 4) * @width
|
||||
w11 = (fs * ft + 8) >> 4
|
||||
|
||||
p00 = weight_point[v] || 0
|
||||
p01 = weight_point[v + 1] || 0
|
||||
p10 = weight_point[v + @width] || 0
|
||||
p11 = weight_point[v + @width + 1] || 0
|
||||
@weight[s + t * @bw] = (p00 * (16 - fs - ft + w11) + p01 * (fs - w11) + p10 * (ft - w11) + p11 * w11 + 8) >> 4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def select_partition
|
||||
if @part_num > 1
|
||||
small_block = @bw * @bh < 31
|
||||
|
||||
seed = (@d2 >> 13 & 0x3ff) | ((@part_num - 1) << 10)
|
||||
|
||||
rnum = seed
|
||||
rnum ^= (rnum >> 15)
|
||||
rnum = (rnum - (rnum << 17)) & 0xffffffff
|
||||
rnum = (rnum + (rnum << 7)) & 0xffffffff
|
||||
rnum = (rnum + (rnum << 4)) & 0xffffffff
|
||||
rnum ^= rnum >> 5
|
||||
rnum = (rnum + (rnum << 16)) & 0xffffffff
|
||||
rnum ^= rnum >> 7
|
||||
rnum ^= rnum >> 3
|
||||
rnum = (rnum ^ (rnum << 6)) & 0xffffffff
|
||||
rnum = rnum ^ (rnum >> 17)
|
||||
|
||||
seeds = [0, 4, 8, 12, 16, 20, 24, 28].map{|i| (rnum >> i) & 0xf}.map!{|e| e * e}
|
||||
sh = [seed & 2 == 2 ? 4 : 5, @part_num == 3 ? 6 : 5]
|
||||
sh.reverse! if seed & 1 == 0
|
||||
seeds.map!.with_index{|e, i| e >> sh[i % 2]}
|
||||
|
||||
@partition = Array.new(@bw * @bh)
|
||||
|
||||
for y in 0...@bh
|
||||
for x in 0...@bw
|
||||
idx = x + y * @bw
|
||||
|
||||
if small_block
|
||||
x <<= 1
|
||||
y <<= 1
|
||||
end
|
||||
|
||||
a = (seeds[0] * x + seeds[1] * y + (rnum >> 14)) & 0x3f
|
||||
b = (seeds[2] * x + seeds[3] * y + (rnum >> 10)) & 0x3f
|
||||
c = @part_num < 3 ? 0 : (seeds[4] * x + seeds[5] * y + (rnum >> 6)) & 0x3f
|
||||
d = @part_num < 4 ? 0 : (seeds[6] * x + seeds[7] * y + (rnum >> 2)) & 0x3f
|
||||
|
||||
@partition[idx] = 3 - [d, c, b, a].each_with_index.max[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def applicate_color
|
||||
mem = Fiddle::Pointer.malloc(@bw * @bh * 4)
|
||||
|
||||
if @dual_plane
|
||||
plane_arr = [0, 1, 2, 3]
|
||||
plane_arr.delete_at(@plane_selector)
|
||||
|
||||
if @partition
|
||||
(@bw * @bh).times do |i|
|
||||
part = @partition[i]
|
||||
plane_arr.each{|c| mem[i * 4 + c] = select_color(@endpoint[part][c], @endpoint[part][4 + c], @weight0[i])}
|
||||
mem[i * 4 + @plane_selector] = select_color(@endpoint[part][@plane_selector], @endpoint[part][4 + @plane_selector], @weight1[i])
|
||||
end
|
||||
else
|
||||
(@bw * @bh).times do |i|
|
||||
plane_arr.each{|c| mem[i * 4 + c] = select_color(@endpoint[0][c], @endpoint[0][4 + c], @weight0[i])}
|
||||
mem[i * 4 + @plane_selector] = select_color(@endpoint[0][@plane_selector], @endpoint[0][4 + @plane_selector], @weight1[i])
|
||||
end
|
||||
end
|
||||
elsif @partition
|
||||
(@bw * @bh).times do |i|
|
||||
part = @partition[i]
|
||||
4.times{|c| mem[i * 4 + c] = select_color(@endpoint[part][c], @endpoint[part][4 + c], @weight[i])}
|
||||
end
|
||||
else
|
||||
(@bw * @bh).times do |i|
|
||||
4.times{|c| mem[i * 4 + c] = select_color(@endpoint[0][c], @endpoint[0][4 + c], @weight[i])}
|
||||
end
|
||||
end
|
||||
|
||||
@data = mem.to_str
|
||||
end
|
||||
|
||||
def select_color(v0, v1, weight)
|
||||
v0 |= v0 << 8
|
||||
v1 |= v1 << 8
|
||||
v = (v0 * (64 - weight) + v1 * weight + 32) >> 6
|
||||
(v * 255 + 32768) / 65536
|
||||
end
|
||||
|
||||
def decode_intseq_raw(data, a, b, count)
|
||||
mask = (1 << b) - 1
|
||||
case a
|
||||
when 3
|
||||
rc = (count + 4) / 5
|
||||
ret = Array.new(rc * 5)
|
||||
m = [0, 2 + b, 4 + b * 2, 5 + b * 3, 7 + b * 4]
|
||||
rc.times do |i|
|
||||
t = (data >> b & 3) | (data >> b * 2 & 0xc) | (data >> b * 3 & 0x10) | (data >> b * 4 & 0x60) | (data >> b * 5 & 0x80)
|
||||
5.times do |j|
|
||||
ret[i * 5 + j] = [data >> m[j] & mask, TritsTable[j][t]]
|
||||
end
|
||||
data >>= b * 5 + 8
|
||||
end
|
||||
ret[0, count]
|
||||
when 5
|
||||
rc = (count + 2) / 3
|
||||
ret = Array.new(rc * 3)
|
||||
m = [0, 3 + b, 5 + b * 2]
|
||||
rc.times do |i|
|
||||
q = (data >> b & 7) | (data >> b * 2 & 0x18) | (data >> b * 3 & 0x60)
|
||||
3.times do |j|
|
||||
ret[i * 3 + j] = [data >> m[j] & mask, QuintsTable[j][q]]
|
||||
end
|
||||
data >>= b * 3 + 7
|
||||
end
|
||||
ret[0, count]
|
||||
else # 0
|
||||
(0...count).map do |i|
|
||||
[data >> b * i & mask, 0]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unquantize_endpoint(a, b, bit, val_d)
|
||||
if a == 0
|
||||
case b
|
||||
when 1
|
||||
bit * 0xff
|
||||
when 2
|
||||
bit * 0x55
|
||||
when 3
|
||||
bit << 5 | bit << 2 | bit >> 1
|
||||
when 4
|
||||
bit << 4 | bit
|
||||
when 5
|
||||
bit << 3 | bit >> 2
|
||||
when 6
|
||||
bit << 2 | bit >> 4
|
||||
when 7
|
||||
bit << 1 | bit >> 6
|
||||
else # 8
|
||||
bit
|
||||
end
|
||||
else
|
||||
val_a = (bit & 1) * 0x1ff
|
||||
tmp_b = bit >> 1
|
||||
case b
|
||||
when 1
|
||||
val_b = 0
|
||||
val_c = a == 3 ? 204 : 113
|
||||
when 2
|
||||
val_b = a == 3 ? (0b100010110) * tmp_b : (0b100001100) * tmp_b
|
||||
val_c = a == 3 ? 93 : 54
|
||||
when 3
|
||||
val_b = a == 3 ? tmp_b << 7 | tmp_b << 2 | tmp_b : tmp_b << 7 | tmp_b << 1 | tmp_b >> 1
|
||||
val_c = a == 3 ? 44 : 26
|
||||
when 4
|
||||
val_b = tmp_b << 6 | tmp_b >> (a == 3 ? 0 : 1)
|
||||
val_c = a == 3 ? 22 : 13
|
||||
when 5
|
||||
val_b = tmp_b << 5 | tmp_b >> (a == 3 ? 2 : 3)
|
||||
val_c = a == 3 ? 11 : 6
|
||||
else # 6
|
||||
val_b = tmp_b << 4 | tmp_b >> 4
|
||||
val_c = 5
|
||||
end
|
||||
t = val_d * val_c + val_b
|
||||
t ^= val_a
|
||||
(val_a & 0x80) | (t >> 2)
|
||||
end
|
||||
end
|
||||
|
||||
def unquantize_weight(a, b, bit, val_d)
|
||||
if a == 0
|
||||
case b
|
||||
when 1
|
||||
t = bit == 1 ? 63 : 0
|
||||
when 2
|
||||
t = bit << 4 | bit << 2 | bit
|
||||
when 3
|
||||
t = bit << 3 | bit
|
||||
when 4
|
||||
t = bit << 2 | bit >> 2
|
||||
else # 5
|
||||
t = bit << 1 | bit >> 4
|
||||
end
|
||||
elsif b == 0
|
||||
t = (a == 3 ? [0, 32, 63] : [0, 16, 32, 47, 63])[val_d]
|
||||
else
|
||||
val_a = (bit & 1) * 0x7f
|
||||
case b
|
||||
when 1
|
||||
val_b = 0
|
||||
val_c = a == 3 ? 50 : 28
|
||||
when 2
|
||||
val_b = (a == 3 ? 0b1000101 : 0b1000010) * bit[1]
|
||||
val_c = a == 3 ? 23 : 13
|
||||
else # 3
|
||||
val_b = (bit << 4 | bit >> 1) & 0b1100011
|
||||
val_c = 11
|
||||
end
|
||||
t = val_d * val_c + val_b
|
||||
t ^= val_a
|
||||
t = (val_a & 0x20) | (t >> 2)
|
||||
end
|
||||
t > 32 ? t + 1 : t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
+106
-89
@@ -1,93 +1,110 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing decoded object
|
||||
# @attr [String] name object name
|
||||
# @attr [String] type object type name
|
||||
# @attr [Object] value object
|
||||
# @attr [Symbol] endian endianness
|
||||
# @attr [Boolean] is_struct
|
||||
class ObjectValue
|
||||
attr_accessor :name, :type, :value, :endian, :is_struct
|
||||
# Class for representing decoded object
|
||||
# @attr [String] name object name
|
||||
# @attr [String] type object type name
|
||||
# @attr [Hash<String,Mikunyan::ObjectValue>] attr
|
||||
# @attr [Object] value object
|
||||
# @attr [Symbol] endian endianness
|
||||
# @attr [Boolean] is_struct
|
||||
class ObjectValue
|
||||
attr_accessor :name, :type, :attr, :value, :endian, :is_struct
|
||||
|
||||
# Constructor
|
||||
# @param [String] name object name
|
||||
# @param [String] type object type name
|
||||
# @param [Symbol] endian endianness
|
||||
# @param [Object] value object
|
||||
def initialize(name, type, endian, value = nil)
|
||||
@name = name
|
||||
@type = type
|
||||
@endian = endian
|
||||
@value = value
|
||||
@is_struct = false
|
||||
@attr = {}
|
||||
end
|
||||
|
||||
# Return whether object is array or not
|
||||
# @return [Boolean]
|
||||
def array?
|
||||
value && value.class == Array
|
||||
end
|
||||
|
||||
# Return whether object is value or not
|
||||
# @return [Boolean]
|
||||
def value?
|
||||
value && value.class != Array
|
||||
end
|
||||
|
||||
# Return whether object is struct or not
|
||||
# @return [Boolean]
|
||||
def struct?
|
||||
is_struct
|
||||
end
|
||||
|
||||
# Return all keys
|
||||
# @return [Array] list of keys
|
||||
def keys
|
||||
@attr.keys
|
||||
end
|
||||
|
||||
# Return whether object contains key
|
||||
# @param [String] key
|
||||
# @return [Boolean]
|
||||
def key?(key)
|
||||
@attr.key?(key)
|
||||
end
|
||||
|
||||
# Return value
|
||||
# @return [Object] value
|
||||
def []
|
||||
@value
|
||||
end
|
||||
|
||||
# Return value of selected index or key
|
||||
# @param [Integer,String] i index or key
|
||||
# @return [Object] value
|
||||
def [](i)
|
||||
if array? && i.class == Integer
|
||||
@value[i]
|
||||
else
|
||||
@attr[i]
|
||||
end
|
||||
end
|
||||
|
||||
# Set value of selected key
|
||||
# @param [String] name key
|
||||
# @param [Object] value value
|
||||
# @return [Object] value
|
||||
def []=(name, value)
|
||||
@attr[name] = value
|
||||
end
|
||||
|
||||
# Return value of called key
|
||||
# @param [String] name key
|
||||
# @return [Object] value
|
||||
def method_missing(name, *args)
|
||||
@attr[name.to_s]
|
||||
end
|
||||
|
||||
# Implementation of respond_to_missing?
|
||||
def respond_to_missing?(symbol, include_private)
|
||||
@attr.key?(symbol.to_s)
|
||||
end
|
||||
# Constructor
|
||||
# @param [String] name object name
|
||||
# @param [String] type object type name
|
||||
# @param [Symbol] endian endianness
|
||||
# @param [Object] value object
|
||||
def initialize(name, type, endian, value = nil)
|
||||
@name = name
|
||||
@type = type
|
||||
@endian = endian
|
||||
@value = value
|
||||
@is_struct = false
|
||||
@attr = {}
|
||||
end
|
||||
|
||||
# Return whether object is array or not
|
||||
# @return [Boolean]
|
||||
def array?
|
||||
value&.is_a?(Array)
|
||||
end
|
||||
|
||||
# Return whether object is value or not
|
||||
# @return [Boolean]
|
||||
def value?
|
||||
value && !value.is_a?(Array)
|
||||
end
|
||||
|
||||
# Return whether object is struct or not
|
||||
# @return [Boolean]
|
||||
def struct?
|
||||
is_struct
|
||||
end
|
||||
|
||||
# Return all keys
|
||||
# @return [Array] list of keys
|
||||
def keys
|
||||
@attr.keys
|
||||
end
|
||||
|
||||
# Return whether object contains key
|
||||
# @param [String] key
|
||||
# @return [Boolean]
|
||||
def key?(key)
|
||||
@attr.key?(key)
|
||||
end
|
||||
|
||||
# Return value of selected index or key
|
||||
# @param [Integer,String] key index or key
|
||||
# @return [Object] value
|
||||
def [](key = nil)
|
||||
if key.nil?
|
||||
@value
|
||||
elsif array? && key.is_a?(Integer)
|
||||
@value[key]
|
||||
else
|
||||
@attr[key]
|
||||
end
|
||||
end
|
||||
|
||||
# Set value of selected key
|
||||
# @param [String] name key
|
||||
# @param [Object] value value
|
||||
# @return [Object] value
|
||||
def []=(name, value)
|
||||
@attr[name] = value
|
||||
end
|
||||
|
||||
# Return value of called key
|
||||
# @param [String] name key
|
||||
# @return [Object] value
|
||||
def method_missing(name, *args)
|
||||
n = name.to_s
|
||||
@attr.key?(n) ? @attr[n] : super
|
||||
end
|
||||
|
||||
# Implementation of respond_to_missing?
|
||||
def respond_to_missing?(symbol, _include_private)
|
||||
@attr.key?(symbol.to_s)
|
||||
end
|
||||
|
||||
# Simplifies self, or serializes self with ruby primitive types
|
||||
def simplify
|
||||
if @type == 'pair'
|
||||
[@attr['first'].simplify, @attr['second'].simplify]
|
||||
elsif @type == 'map' && @value.is_a?(Array)
|
||||
@value.map {|e| [e['first'].simplify, e['second'].simplify]}.to_h
|
||||
elsif is_struct
|
||||
@attr.transform_values(&:simplify)
|
||||
elsif @value.is_a?(Array)
|
||||
@value.map {|e| e.is_a?(ObjectValue) ? e.simplify : e}
|
||||
elsif @value.is_a?(ObjectValue)
|
||||
@value.simplify
|
||||
else
|
||||
@value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+150
-77
@@ -1,81 +1,154 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'mikunyan/binary_reader'
|
||||
|
||||
module Mikunyan
|
||||
# Class for representing TypeTree
|
||||
# @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes
|
||||
class TypeTree
|
||||
attr_accessor :nodes
|
||||
# Class for representing TypeTree
|
||||
# @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes
|
||||
class TypeTree
|
||||
attr_accessor :nodes
|
||||
|
||||
# Struct for representing Node in TypeTree
|
||||
# @attr [String] version version string
|
||||
# @attr [Integer] depth depth of node (>= 0)
|
||||
# @attr [Boolean] array? array node or not
|
||||
# @attr [String] type type name
|
||||
# @attr [String] name node (attribute) name
|
||||
# @attr [Integer] index index in node list
|
||||
# @attr [Integer] flags flags of node
|
||||
Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags)
|
||||
|
||||
# Create TypeTree from binary string (new version)
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @return [Mikunyan::TypeTree] created TypeTree
|
||||
def self.load(br)
|
||||
nodes = []
|
||||
node_count = br.i32u
|
||||
buffer_size = br.i32u
|
||||
node_count.times do
|
||||
nodes << Node.new(br.i16u, br.i8u, br.i8u != 0, br.i32, br.i32, br.i32, br.i32u, br.i32u)
|
||||
end
|
||||
buffer = br.read(buffer_size)
|
||||
nodes.each do |n|
|
||||
if n.type >= 0
|
||||
n.type = buffer.unpack("@#{n.type}Z*")[0]
|
||||
else
|
||||
n.type = Mikunyan::STRING_TABLE[n.type + 2**31]
|
||||
end
|
||||
if n.name >= 0
|
||||
n.name = buffer.unpack("@#{n.name}Z*")[0]
|
||||
else
|
||||
n.name = Mikunyan::STRING_TABLE[n.name + 2**31]
|
||||
end
|
||||
end
|
||||
r = TypeTree.new
|
||||
r.nodes = nodes
|
||||
r
|
||||
end
|
||||
|
||||
# Create TypeTree from binary string (legacy version)
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @return [Mikunyan::TypeTree] created TypeTree
|
||||
def self.load_legacy(br)
|
||||
nodes = []
|
||||
stack = [0]
|
||||
while stack.size > 0
|
||||
depth = stack.pop
|
||||
type = br.cstr
|
||||
name = br.cstr
|
||||
size = br.i32
|
||||
index = br.i32u
|
||||
is_array = (br.i32 != 0)
|
||||
version = br.i32u
|
||||
flags = br.i32u
|
||||
child_count = br.i32u
|
||||
child_count.times{ stack << depth + 1 }
|
||||
nodes << Node.new(version, depth, is_array, type, name, size, index, flags)
|
||||
end
|
||||
r = TypeTree.new
|
||||
r.nodes = nodes
|
||||
r
|
||||
end
|
||||
|
||||
# Create default TypeTree from hash string (if exists)
|
||||
# @param [String] hash
|
||||
# @return [Mikunyan::TypeTree,nil] created TypeTree
|
||||
def self.load_default(hash)
|
||||
hash_str = hash.unpack('H*')[0]
|
||||
file = File.expand_path("../typetrees/#{hash_str}.dat", __FILE__)
|
||||
return nil unless File.file?(file)
|
||||
r = TypeTree.new
|
||||
r.nodes = Marshal.load(File.binread(file))
|
||||
r
|
||||
end
|
||||
# Struct for representing Node in TypeTree
|
||||
# @attr [String] version version string
|
||||
# @attr [Integer] level level of node (>= 0)
|
||||
# @attr [Boolean] array? array node or not
|
||||
# @attr [String] type type name
|
||||
# @attr [String] name node (attribute) name
|
||||
# @attr [Integer] index index in node list
|
||||
# @attr [Integer] flags flags of node
|
||||
# @attr [Integer,nil] v18meta
|
||||
# @attr [Mikunyan::TypeTree::Node,nil] parent ̑
|
||||
# @attr [Array<Mikunyan::TypeTree::Node>] children
|
||||
Node = Struct.new(:version, :level, :array?, :type, :name, :size, :index, :flags, :v18meta, :parent, :children,
|
||||
keyword_init: true) do
|
||||
def need_align?
|
||||
flags & 0x4000 != 0
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the root node of the typetree
|
||||
# @return [Mikunyan::TypeTree::Node,nil]
|
||||
def tree
|
||||
nodes&.[](0)
|
||||
end
|
||||
|
||||
# Generates JSON-compatible serialized representation of typetree information
|
||||
def serialize
|
||||
{
|
||||
'nodes' =>
|
||||
nodes.map do |e|
|
||||
{
|
||||
'version' => e.version,
|
||||
'level' => e.level,
|
||||
'is_array' => e.array?,
|
||||
'type' => e.type,
|
||||
'name' => e.name,
|
||||
'size' => e.size,
|
||||
'flags' => e.flags,
|
||||
'v18meta' => e.v18meta
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Creates TypeTree from binary string
|
||||
# @param [Mikunyan::BinaryReader] br
|
||||
# @param [Integer] version asset format version
|
||||
# @return [Mikunyan::TypeTree] created TypeTree
|
||||
def self.load(br, version)
|
||||
if version == 10 || version >= 12
|
||||
node_count = br.i32u
|
||||
buffer_size = br.i32u
|
||||
nodes = Array.new(node_count) do
|
||||
Node.new(
|
||||
version: br.i16u,
|
||||
level: br.i8u,
|
||||
array?: br.bool,
|
||||
type: br.i32u,
|
||||
name: br.i32u,
|
||||
size: br.i32s,
|
||||
index: br.i32u,
|
||||
flags: br.i32u,
|
||||
v18meta: version >= 18 ? br.i64u : nil,
|
||||
children: []
|
||||
)
|
||||
end
|
||||
buffer = br.read(buffer_size)
|
||||
stack = []
|
||||
nodes.each do |n|
|
||||
n.type = Mikunyan::Constants.get_string_or_default(n.type, buffer)
|
||||
n.name = Mikunyan::Constants.get_string_or_default(n.name, buffer)
|
||||
if n.level > 0
|
||||
n.parent = stack[n.level - 1]
|
||||
n.parent.children << n
|
||||
end
|
||||
stack[n.level] = n
|
||||
end
|
||||
br.adv(4) if version >= 21
|
||||
else
|
||||
nodes = []
|
||||
stack = []
|
||||
until stack.empty? && !nodes.empty?
|
||||
parent = stack.pop
|
||||
node = Node.new(
|
||||
type: br.cstr,
|
||||
name: br.cstr,
|
||||
size: br.i32s,
|
||||
index: br.i32u,
|
||||
array?: br.i32 != 0,
|
||||
version: br.i32u,
|
||||
flags: br.i32u,
|
||||
level: parent ? parent.level + 1 : 0,
|
||||
parent: parent,
|
||||
children: []
|
||||
)
|
||||
nodes << node
|
||||
parent.children << node if parent
|
||||
stack += Array.new(br.i32u, node)
|
||||
end
|
||||
end
|
||||
ret = TypeTree.new
|
||||
ret.nodes = nodes
|
||||
ret
|
||||
end
|
||||
|
||||
# Create default TypeTree from hash string (if exists)
|
||||
# @param [Integer] class_id
|
||||
# @param [String] hash
|
||||
# @return [Mikunyan::TypeTree,nil] created TypeTree
|
||||
def self.load_default(class_id, hash)
|
||||
file = File.expand_path("../typetrees/#{class_id}/#{hash.unpack1('H*')}.json", __FILE__)
|
||||
return nil unless File.file?(file)
|
||||
|
||||
TypeTree.deserialize(JSON.parse(File.read(file)))
|
||||
end
|
||||
|
||||
# Creates TypeTree from serialized object
|
||||
# @param [Hash] obj
|
||||
def self.deserialize(obj)
|
||||
stack = []
|
||||
ret = TypeTree.new
|
||||
ret.nodes = obj['nodes'].map.with_index do |e, index|
|
||||
level = e['level'] || e['depth']
|
||||
parent = level > 0 ? stack[level - 1] : nil
|
||||
n = Node.new(
|
||||
version: e['version'],
|
||||
level: level,
|
||||
array?: e['is_array'],
|
||||
type: e['type'],
|
||||
name: e['name'],
|
||||
size: e['size'],
|
||||
index: index,
|
||||
flags: e['flags'],
|
||||
v18meta: e['v18meta'],
|
||||
parent: parent,
|
||||
children: []
|
||||
)
|
||||
parent.children << n if parent
|
||||
stack[level] = n
|
||||
end
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan/base_object'
|
||||
|
||||
module Mikunyan
|
||||
module CustomTypes
|
||||
class TextAsset < Mikunyan::BaseObject
|
||||
Mikunyan::CustomTypes.set_custom_type(self, 'TextAsset')
|
||||
|
||||
def text
|
||||
@attr['m_Script']&.value
|
||||
end
|
||||
|
||||
def bytes
|
||||
@attr['m_Script']&.value.dup.force_encoding('ASCII-8BIT')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'mikunyan/base_object'
|
||||
require 'mikunyan/decoders/image_decoder'
|
||||
|
||||
module Mikunyan
|
||||
module CustomTypes
|
||||
class Texture2D < Mikunyan::BaseObject
|
||||
Mikunyan::CustomTypes.set_custom_type(self, 'Texture2D')
|
||||
|
||||
# Generates an png image (an instance of {ChunkyPNG::Image}) from the texture data
|
||||
def generate_png
|
||||
Mikunyan::Decoder::ImageDecoder.decode_object(self)
|
||||
end
|
||||
|
||||
def width
|
||||
@attr['m_Width']&.value
|
||||
end
|
||||
|
||||
def height
|
||||
@attr['m_Height']&.value
|
||||
end
|
||||
|
||||
def texture_format
|
||||
@attr['m_TextureFormat']&.value
|
||||
end
|
||||
|
||||
def mipmap_count
|
||||
@attr['m_MipCount']&.value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"nodes":[{"version":5,"level":0,"is_array":false,"type":"GameObject","name":"Base","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"vector","name":"m_Component","size":-1,"flags":65,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":65,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":65,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"ComponentPair","name":"data","size":12,"flags":65,"v18meta":null},{"version":1,"level":4,"is_array":false,"type":"PPtr<Component>","name":"component","size":12,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"int","name":"m_FileID","size":4,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"SInt64","name":"m_PathID","size":8,"flags":65,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"unsigned int","name":"m_Layer","size":4,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"string","name":"m_Name","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":16385,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":1,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"char","name":"data","size":1,"flags":1,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"UInt16","name":"m_Tag","size":2,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"bool","name":"m_IsActive","size":1,"flags":0,"v18meta":null}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes":[{"version":6,"level":0,"is_array":false,"type":"GameObject","name":"Base","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"vector","name":"m_Component","size":-1,"flags":32833,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":16449,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":65,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"ComponentPair","name":"data","size":12,"flags":65,"v18meta":null},{"version":1,"level":4,"is_array":false,"type":"PPtr<Component>","name":"component","size":12,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"int","name":"m_FileID","size":4,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"SInt64","name":"m_PathID","size":8,"flags":65,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"unsigned int","name":"m_Layer","size":4,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"string","name":"m_Name","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":16385,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":1,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"char","name":"data","size":1,"flags":1,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"UInt16","name":"m_Tag","size":2,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"bool","name":"m_IsActive","size":1,"flags":0,"v18meta":null}]}
|
||||
@@ -0,0 +1 @@
|
||||
{"nodes":[{"version":5,"level":0,"is_array":false,"type":"GameObject","name":"Base","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"vector","name":"m_Component","size":-1,"flags":32833,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":16449,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":65,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"ComponentPair","name":"data","size":12,"flags":65,"v18meta":null},{"version":1,"level":4,"is_array":false,"type":"PPtr<Component>","name":"component","size":12,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"int","name":"m_FileID","size":4,"flags":65,"v18meta":null},{"version":1,"level":5,"is_array":false,"type":"SInt64","name":"m_PathID","size":8,"flags":65,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"unsigned int","name":"m_Layer","size":4,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"string","name":"m_Name","size":-1,"flags":32768,"v18meta":null},{"version":1,"level":2,"is_array":true,"type":"Array","name":"Array","size":-1,"flags":16385,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"int","name":"size","size":4,"flags":1,"v18meta":null},{"version":1,"level":3,"is_array":false,"type":"char","name":"data","size":1,"flags":1,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"UInt16","name":"m_Tag","size":2,"flags":0,"v18meta":null},{"version":1,"level":1,"is_array":false,"type":"bool","name":"m_IsActive","size":1,"flags":0,"v18meta":null}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user