46 Commits

Author SHA1 Message Date
Ishotihadus c32481cc13 Bump version to v3.9.13 2026-01-11 01:41:47 +09:00
Ishotihadus a462047004 Fix format 2026-01-11 01:41:41 +09:00
Ishotihadus 13711a710a Add several typetrees 2026-01-11 01:40:31 +09:00
Ishotihadus 01701b4d86 Bump version to 3.9.12 2023-06-28 14:13:12 +09:00
Ishotihadus be0d1fc84d Add TypeTrees 2023-06-28 14:13:00 +09:00
Ishotihadus 2b07a718f7 Bump version to 3.9.11 2023-05-16 11:37:00 +09:00
Ishotihadus 59345b5d83 Fix Unity 2021 2023-05-16 11:09:15 +09:00
Ishotihadus 4db2517018 Bump version to 3.9.10 2022-11-23 21:43:48 +09:00
Ishotihadus 95573408d5 Add default TypeTrees 2022-11-23 21:43:22 +09:00
Ishotihadus d46e96b844 Fix extconf 2022-11-23 21:37:10 +09:00
Ishotihadus 4d204a9c45 Add comment to README by the suggestion #4 2022-11-23 21:20:10 +09:00
Ishotihadus 5c5ec70784 Fix Rakefile 2022-11-23 21:12:42 +09:00
Ishotihadus 5564af7ba9 Improve code format 2022-11-23 21:07:33 +09:00
Ishotihadus b0991a8bf6 Add Ruby 3.2 support 2022-11-23 21:03:21 +09:00
Ishotihadus de47d41874 bump version to 3.9.9 2021-08-10 17:40:37 +09:00
Ishotihadus 3f7a4b3c38 add default typetrees 2021-08-10 17:40:01 +09:00
Ishotihadus d0312af3d9 fix unity 2020 2021-08-10 17:39:55 +09:00
Ishotihadus 6972cefb1f improve robustness of mikunyan-image 2021-06-22 13:38:04 +09:00
Ishotihadus 0485c187ec add default typetrees 2021-06-15 14:16:41 +09:00
Ishotihadus a1c268e469 refactoring 2020-06-20 18:50:22 +09:00
Ishotihadus 387a683ff0 imporove mikunyan-image 2020-06-20 18:50:02 +09:00
Ishotihadus 9a520637e9 add Asset#path_id 2020-06-20 17:56:42 +09:00
Ishotihadus d37c72de80 improve Asset::ObjectEntry#type 2020-03-05 01:39:53 +09:00
Ishotihadus 0ab1fd27a6 bump version to 3.9.8 2020-03-01 02:32:26 +09:00
Ishotihadus 652a437d14 add support for version 21 & 22 2020-03-01 02:29:33 +09:00
Ishotihadus b1ea2cae30 bump version to 3.9.7 2019-12-21 00:44:01 +09:00
Ishotihadus ba5e300ff6 improve documents 2019-12-21 00:34:38 +09:00
Ishotihadus af25ef731b add EAC support 2019-12-21 00:08:01 +09:00
Ishotihadus d3111e1cad specify rake-compiler version 2019-12-20 18:14:08 +09:00
Ishotihadus b61b10856d Merge pull request #2 from sikachu/sikachu-add-missing-dev-dependency
Add rake-compiler to development dependency
2019-12-20 18:12:10 +09:00
Ishotihadus dab6548230 improve readme 2019-12-19 02:47:24 +09:00
Ishotihadus 3f9256ce81 fix handling of streaming assets 2019-12-19 02:01:42 +09:00
Ishotihadus 39ae15d9aa refactor image decoders 2019-12-19 01:46:47 +09:00
Ishotihadus e683b3c91a add PVRTC support 2019-12-13 03:15:35 +09:00
Ishotihadus b92b00fc4a add new typetrees 2019-12-12 03:23:55 +09:00
Ishotihadus 89347bbdbe add pvrtc1 4bpp support 2019-12-12 03:13:20 +09:00
Prem Sichanugrist 244c4977c6 Add rake-compiler to development dependency
This gem is required for `rake build` task.
2019-12-11 11:29:08 +09:00
Ishotihadus aa29a229ea improve image decoders 2019-12-10 23:46:30 +09:00
Ishotihadus 2271b33cdd fix bugs on building extensions 2019-12-10 19:22:00 +09:00
Ishotihadus 857a0c3fae add crunched texture support 2019-12-10 19:05:33 +09:00
Ishotihadus bd4b10f871 add ASTC/HDR support and improve ImageDecoder 2019-12-10 04:25:58 +09:00
Ishotihadus e1ac05e540 more and more update 2019-12-09 00:44:21 +09:00
Ishotihadus 629e82353a update dependencies 2019-12-02 01:54:37 +09:00
Ishotihadus 9d189b2550 format codes 2019-11-21 22:20:36 +09:00
Ishotihadus 5c32fc9b8c support UnityWeb assets 2018-09-30 22:11:58 +09:00
Ishotihadus fd470c97f7 add support for a header of AssetBundle on the last in the file 2018-08-05 18:15:33 +09:00
1176 changed files with 10431 additions and 2487 deletions
+2
View File
@@ -15,3 +15,5 @@
*.bundle
.DS_Store
.idea/
*.iml
+42
View File
@@ -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 -1
View File
@@ -1,2 +1,2 @@
source "https://rubygems.org"
source 'https://rubygems.org'
gemspec
+104 -76
View File
@@ -1,8 +1,8 @@
# mikunyan
A library to deserialize AssetBundle files (\*.unity3d) and asset files of Unity.
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).
The name Mikunyan is derived from [Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
Ruby-Doc: http://www.rubydoc.info/gems/mikunyan/
@@ -50,7 +50,7 @@ asset = bundle.assets[0]
# asset = Mikunyan::Asset.file(filename)
# get a list of objects
list = asset.objects
objects = asset.objects
# get PathIds of objects
path_ids = asset.path_ids
@@ -59,10 +59,10 @@ path_ids = asset.path_ids
containers = asset.containers
# load an object (Mikunyan::ObjectValue)
obj = asset.parse_object(path_ids[0])
obj = asset.parse_object(objects[0])
# load an object to Ruby data structures
obj_hash = asset.parse_object_simple(path_ids[0])
obj_hash = asset.parse_object_simple(objects[0])
# a Hash can be serialized to JSON
require 'json'
@@ -71,7 +71,7 @@ obj_hash.to_json
### Mikunyan::ObjectValue
`Mikunyan::ObjectValue` can be 3 types: value, array and key-value table.
`Mikunyan::ObjectValue` is formed in 3 types: value, array, and key-value table.
```ruby
# get whether obj is value or not
@@ -106,26 +106,22 @@ obj.key
### Unpack Texture2D
You can get image data directly from Texture2D object. Output object's class is `ChunkyPNG::Image`.
Mikunyan generates `ChunkyPNG::Image` images directly from Texture2D objects.
Some basic texture formats (15, 7, 9, 1320, 22, 62, and 63), DXT1 (10), DXT5 (12), ETC_RGB4 (34), ETC2 (4547), and ASTC (4859) are available.
Mikunyan can decode images in basic texture formats (15, 7, 9, 1320, 22, 62, 63), DXT1 (10), DXT5 (12), PVRTC1 (3033), ETC (34), EAC (4144), ETC2 (4547), ASTC (4859), HDR ASTC (6671), 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 data
img = Mikunyan::ImageDecoder.decode_object(obj)
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 Outputter
`mikunyan-json` is an executable command for converting unity3d to JSON.
@@ -143,77 +139,97 @@ Available options:
$ mikunyan-image bundle.unity3d
The console log is JSON data of output textures as follows.
The command generates JSON text, which contains information about unpacked images.
```json
[
{
"name": "bg_b",
"width": 1024,
"height": 1024,
"path_id": -744818715421265689
},
{
"name": "bg_a",
"width": 1024,
"height": 1024,
"path_id": 5562124901460497987
}
{
"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` specified, `mikunyan-image` will output sprites. The logged JSON also contains sprite information.
If the option `--sprite` is specified, `mikunyan-image` will output sprites. The logged JSON also contains information about sprites.
```json
[
{
"name": "bg_a",
"width": 1024,
"height": 1024,
"path_id": 5562124901460497987,
"sprites": [
{
"name": "bg_a_0",
"x": 1.0,
"y": 303.0,
"width": 1022.0,
"height": 720.0,
"path_id": -7546240288260780845
},
{
"name": "bg_a_1",
"x": 1.0,
"y": 1.0,
"width": 720.0,
"height": 258.0,
"path_id": -5293490190204738553
}
]
},
{
"name": "bg_b",
"width": 1024,
"height": 1024,
"path_id": -744818715421265689,
"sprites": [
{
"name": "bg_b_1",
"x": 1.0,
"y": 1.0,
"width": 720.0,
"height": 258.0,
"path_id": 4884595733995530103
},
{
"name": "bg_b_0",
"x": 1.0,
"y": 303.0,
"width": 1022.0,
"height": 720.0,
"path_id": 7736251300187116441
}
]
}
{
"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
}
]
}
]
```
@@ -228,11 +244,15 @@ Available options:
- [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.
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.
## Implementation in other languages
- TypeScript: [shibunyan](https://github.com/AnemoneStar/shibunyan)
@@ -244,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)
+15 -7
View File
@@ -1,14 +1,22 @@
require "bundler/gem_tasks"
require "rake/extensiontask"
# frozen_string_literal: true
require 'bundler/gem_tasks'
require 'rake/extensiontask'
task :scream do
puts "みくは自分を曲げないよ!"
puts 'みくは自分を曲げないよ!'
end
task :build => :compile
task build: :compile
Rake::ExtensionTask.new('decoders/native') do |ext|
ext.lib_dir = 'lib/mikunyan'
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 :default => [:clobber, :compile, :spec]
task compile: ext_dirs.map {|e| "compile:#{e}".to_sym}
task default: %i[clobber compile spec]
+90 -69
View File
@@ -1,101 +1,122 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'mikunyan'
require 'mikunyan/decoders'
require 'fileutils'
begin
require 'usamin'
require 'usamin/overwrite'
require 'usamin'
require 'usamin/overwrite'
rescue LoadError
require 'json'
require 'json'
end
opts = {:as_asset => false, :outputdir => nil, :sprite => false, :pretty => false}
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
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
arg = ARGV[i] unless arg
warn("Unknown option: #{ARGV[i]}")
end
i += 1
else
arg ||= ARGV[i]
end
i += 1
end
unless arg
warn("Input file is not specified")
exit(1)
warn('Input file is not specified')
exit(1)
end
unless File.file?(arg)
warn("File not found: #{arg}")
exit(1)
warn("File not found: #{arg}")
exit(1)
end
assets = []
if opts[:as_asset]
assets = [Mikunyan::Asset.file(arg, File.basename(arg, '.*'))]
assets = [Mikunyan::Asset.file(arg)]
else
assets = Mikunyan::AssetBundle.file(arg).assets
bundle = Mikunyan::AssetBundle.file(arg)
assets = bundle.assets
end
outdir = opts[:outputdir] || File.basename(arg, '.*')
FileUtils.mkpath(outdir)
assets.each do |asset|
if opts[:sprite]
json = {}
textures = {}
if opts[:sprite]
textures = {}
textures_meta = {}
assets.each do |asset|
json = {}
asset.objects.select{|o| asset.object_type(o) == 'Sprite'}.each do |o|
obj = asset.parse_object(o)
next unless obj
name = obj.m_Name.value
tex_id = obj.m_RD.texture.m_PathID.value
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[tex_id]
tex_obj = asset.parse_object(tex_id)
if tex_obj
textures[tex_id] = Mikunyan::ImageDecoder.decode_object(tex_obj)
json[tex_id] = {:name => tex_obj.m_Name.value, :width => textures[tex_id].width, :height => textures[tex_id].height, :path_id => tex_id, :sprites => []} if textures[tex_id]
end
end
if textures[tex_id]
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[tex_id][:sprites] << {:name => name, :x => x, :y => y, :width => width, :height => height, :path_id => o.path_id}
textures[tex_id].crop(x.round, (textures[tex_id].height - height - y).round, width.round, height.round).save("#{outdir}/#{name}.png")
end
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
puts opts[:pretty] ? JSON.pretty_generate(json.values) : JSON.generate(json.values)
else
json = []
asset.objects.select{|o| asset.object_type(o) == 'Texture2D'}.each do |o|
obj = asset.parse_object(o)
next unless obj
name = obj.m_Name.value
image = Mikunyan::ImageDecoder.decode_object(obj)
if image
json << {:name => name, :width => image.width, :height => image.height, :path_id => o.path_id}
image.save("#{outdir}/#{name}.png")
end
end
puts opts[:pretty] ? JSON.pretty_generate(json) : JSON.generate(json)
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
+46 -65
View File
@@ -1,89 +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'
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
begin
require 'usamin'
require 'usamin/overwrite'
rescue LoadError
require 'json'
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
+286
View File
@@ -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
+642
View File
@@ -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.
//
//------------------------------------------------------------------------------
+149
View File
@@ -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
+12
View File
@@ -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')
+174
View File
@@ -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);
}
}
+755 -362
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,6 +3,6 @@
#include <stdint.h>
void decode_astc(const uint8_t*, const int, const int, const int, const int, uint32_t*);
int decode_astc(const uint8_t *, const long, const long, const int, const int, uint32_t *);
#endif /* end of include guard: ASTC_H */
+87
View File
@@ -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 */
+44 -71
View File
@@ -1,104 +1,77 @@
#include "dxtc.h"
#include <stdint.h>
#include <string.h>
#include "dxtc.h"
#include "color.h"
#include "endianness.h"
static inline uint_fast32_t color(uint_fast32_t r, uint_fast32_t g, uint_fast32_t b, uint_fast32_t a) {
return r | g << 8 | b << 16 | a << 24;
}
static inline void rgb565(const uint_fast16_t c, int *r, int *g, int *b) {
*r = (c & 0xf800) >> 8;
*g = (c & 0x07e0) >> 3;
*b = (c & 0x001f) << 3;
*r |= *r >> 5;
*g |= *g >> 6;
*b |= *b >> 5;
}
static inline void decode_dxt1_block(const uint64_t *data, uint32_t *outbuf) {
int r0, g0, b0, r1, g1, b1;
int q0 = ((uint16_t*)data)[0];
int q1 = ((uint16_t*)data)[1];
rgb565(q0, &r0, &g0, &b0);
rgb565(q1, &r1, &g1, &b1);
uint_fast32_t c[4] = { color(r0, g0, b0, 255), color(r1, g1, b1, 255) };
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 = *data >> 32;
uint_fast32_t d = lton32(*(uint32_t *)(data + 4));
for (int i = 0; i < 16; i++, d >>= 2)
outbuf[i] = c[d & 3];
}
void decode_dxt1(const uint64_t *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint64_t *d = data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d++) {
decode_dxt1_block(d, buf);
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
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 uint64_t *data, uint32_t *outbuf) {
uint_fast32_t a[8] = { ((uint8_t*)data)[0], ((uint8_t*)data)[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[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;
a[7] = (a[0] + a[1] * 6) / 7;
} else {
a[2] = (a[0] * 4 + a[1] ) / 5;
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[5] = (a[0] + a[1] * 4) / 5;
a[6] = 0;
a[7] = 255;
}
for (int i = 0; i < 8; i++)
a[i] <<= 24;
int r0, g0, b0, r1, g1, b1;
int q0 = ((uint16_t*)(data + 1))[0];
int q1 = ((uint16_t*)(data + 1))[1];
rgb565(q0, &r0, &g0, &b0);
rgb565(q1, &r1, &g1, &b1);
uint_fast32_t c[4] = { color(r0, g0, b0, 0), color(r1, g1, b1, 0) };
if (q0 > q1) {
c[2] = color((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 0);
c[3] = color((r0 + r1 * 2) / 3, (g0 + g1 * 2) / 3, (b0 + b1 * 2) / 3, 0);
} else {
c[2] = color((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 0);
}
uint_fast64_t da = *data >> 16;
uint_fast32_t dc = *(data + 1) >> 32;
for (int i = 0; i < 16; i++, da >>= 3, dc >>= 2)
outbuf[i] = a[da & 7] | c[dc & 3];
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];
}
void decode_dxt5(const uint64_t *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint64_t *d = data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d += 2) {
decode_dxt5_block(d, buf);
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
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;
}
+2 -2
View File
@@ -3,7 +3,7 @@
#include <stdint.h>
void decode_dxt1(const uint64_t*, const int, const int, uint32_t*);
void decode_dxt5(const uint64_t*, const int, const int, uint32_t*);
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 */
+180
View File
@@ -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
+335 -163
View File
@@ -1,34 +1,25 @@
#include "etc.h"
#include <stdint.h>
#include <string.h>
#include "etc.h"
#include "color.h"
uint_fast8_t WriteOrderTable[16] = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 };
uint_fast8_t WriteOrderTableRev[16] = { 15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0 };
uint_fast8_t Etc1ModifierTable[8][2] = {{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}};
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}};
uint_fast8_t Etc2DistanceTable[8] = {3, 6, 11, 16, 23, 32, 41, 64};
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_fast32_t color(uint_fast32_t r, uint_fast32_t g, uint_fast32_t b, uint_fast32_t a) {
return r | g << 8 | b << 16 | a << 24;
}
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;
@@ -38,15 +29,20 @@ 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 inline void decode_etc1_block(const uint8_t *data, uint32_t *outbuf) {
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
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;
@@ -60,16 +56,17 @@ static inline void decode_etc1_block(const uint8_t *data, uint32_t *outbuf) {
c[1][1] |= c[1][1] >> 5;
c[1][2] |= c[1][2] >> 5;
} else {
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;
// 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];
uint_fast16_t k = data[4] << 8 | data[5];
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];
@@ -77,28 +74,13 @@ static inline void decode_etc1_block(const uint8_t *data, uint32_t *outbuf) {
}
}
void decode_etc1(const void *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint8_t *d = (uint8_t*)data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d += 8) {
decode_etc1_block(d, buf);
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
}
}
}
static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
uint_fast16_t j = data[6] << 8 | data[7];
uint_fast16_t k = data[4] << 8 | data[5];
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;
@@ -107,57 +89,52 @@ static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
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;
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)
};
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 << 1 & 2 | j & 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][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] = (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][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]))))
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)
};
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 << 1 & 2 | j & 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][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][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;
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);
@@ -168,8 +145,8 @@ static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
}
} else {
// differential
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
uint_fast8_t *table = Etc1SubblockTable[data[3] & 1];
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;
@@ -186,15 +163,15 @@ static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
}
}
} else {
// individual
uint_fast8_t code[2] = { data[3] >> 5, data[3] >> 2 & 7 };
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;
// 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];
@@ -203,69 +180,264 @@ static inline void decode_etc2_block(const uint8_t *data, uint32_t *outbuf) {
}
}
static inline void decode_etc2a8_block(const uint8_t *data, uint32_t *outbuf) {
if (data[1] & 0xf0) {
uint_fast8_t mult = data[1] >> 4;
int_fast8_t *table = Etc2AlphaModTable[data[1] & 0xf];
uint_fast64_t l =
data[7] | (uint_fast16_t)data[6] << 8 |
(uint_fast32_t)data[5] << 16 | (uint_fast32_t)data[4] << 24 |
(uint_fast64_t)data[3] << 32 | (uint_fast64_t)data[2] << 40;
for (int i = 0; i < 16; i++, l >>= 3)
((uint8_t*)(outbuf + WriteOrderTableRev[i]))[3] = clamp(data[0] + mult * table[l & 7]);
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 {
for (int i = 0; i < 16; i++)
((uint8_t*)(outbuf + i))[3] = data[0];
}
}
void decode_etc2(const void *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint8_t *d = (uint8_t*)data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d += 8) {
decode_etc2_block(d, buf);
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
// 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));
}
}
}
void decode_etc2a1(const void *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint8_t *d = (uint8_t*)data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d += 9) {
decode_etc2_block(d + 1, buf);
for (int i = 0; i < 16; i++)
((uint8_t*)(buf + i))[3] = d[0];
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
}
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];
}
}
void decode_etc2a8(const void *data, const int w, const int h, uint32_t *image) {
int bcw = (w + 3) / 4;
int bch = (h + 3) / 4;
int clen_last = (w + 3) % 4 + 1;
uint32_t buf[16];
const uint8_t *d = (uint8_t*)data;
for (int t = 0; t < bch; t++) {
for (int s = 0; s < bcw; s++, d += 16) {
decode_etc2_block(d + 8, buf);
decode_etc2a8_block(d, buf);
int clen = (s < bcw - 1 ? 4 : clen_last) * 4;
for (int i = 0, y = h - t * 4 - 1; i < 4 && y >= 0; i++, y--)
memcpy(image + y * w + s * 4, buf + i * 4, clen);
}
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;
}
+8 -4
View File
@@ -3,9 +3,13 @@
#include <stdint.h>
void decode_etc1(const void*, const int, const int, uint32_t*);
void decode_etc2(const void*, const int, const int, uint32_t*);
void decode_etc2a1(const void*, const int, const int, uint32_t*);
void decode_etc2a8(const void*, const int, const int, uint32_t*);
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 */
+4 -1
View File
@@ -1,8 +1,11 @@
# frozen_string_literal: true
require 'mkmf'
append_cppflags('-std=c11')
append_cppflags('-O3')
append_cppflags('-O2')
append_cppflags('-Wall')
append_cppflags('-Wextra')
append_cppflags('-Wvla')
create_makefile('mikunyan/decoders/native')
+36
View File
@@ -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.
*
*/
+76
View File
@@ -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 */
+451
View File
@@ -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 */
+332 -80
View File
@@ -1,26 +1,174 @@
#include <stdlib.h>
#include <stdint.h>
#include <ruby.h>
#include "rgb.h"
#include "etc.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] size width * height
* @param [Boolean] big whether input data are big endian
* @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_rgb565(VALUE self, VALUE rb_data, VALUE size, VALUE big) {
if (RSTRING_LEN(rb_data) < FIX2LONG(size) * 2)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint8_t *image = (uint8_t*)malloc(FIX2LONG(size) * 4);
decode_rgb565((uint16_t*)RSTRING_PTR(rb_data), FIX2INT(size), RTEST(big), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(size) * 4);
free(image);
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;
}
@@ -28,17 +176,17 @@ static VALUE rb_decode_rgb565(VALUE self, VALUE rb_data, VALUE size, VALUE big)
* Decode image from ETC1 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_etc1((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -46,17 +194,17 @@ static VALUE rb_decode_etc1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
* Decode image from ETC2 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_etc2((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -64,17 +212,17 @@ static VALUE rb_decode_etc2(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
* Decode image from ETC2 Alpha1 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 9)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_etc2a8((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -82,17 +230,89 @@ static VALUE rb_decode_etc2a1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
* Decode image from ETC2 Alpha8 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 16)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_etc2a8((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -100,20 +320,21 @@ static VALUE rb_decode_etc2a8(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
* Decode image from ASTC compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @param [Integer] bw block width
* @param [Integer] bh block height
* @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 w, VALUE h, VALUE bw, VALUE bh) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + FIX2LONG(bw) - 1) / FIX2LONG(bw)) * ((FIX2LONG(h) + FIX2LONG(bh) - 1) / FIX2LONG(bh)) * 16)
rb_raise(rb_eStandardError, "Data size is not enough.");
const uint8_t *data = (uint8_t*)RSTRING_PTR(rb_data);
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_astc(data, FIX2INT(w), FIX2INT(h), FIX2INT(bw), FIX2INT(bh), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -121,17 +342,17 @@ static VALUE rb_decode_astc(VALUE self, VALUE rb_data, VALUE w, VALUE h, VALUE b
* Decode image from DXT1 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 8)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_dxt1((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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;
}
@@ -139,29 +360,60 @@ static VALUE rb_decode_dxt1(VALUE self, VALUE rb_data, VALUE w, VALUE h) {
* Decode image from DXT5 compressed binary
*
* @param [String] rb_data binary to decode
* @param [Integer] w image width
* @param [Integer] h image height
* @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 w, VALUE h) {
if (RSTRING_LEN(rb_data) < ((FIX2LONG(w) + 3) / 4) * ((FIX2LONG(h) + 3) / 4) * 16)
rb_raise(rb_eStandardError, "Data size is not enough.");
uint32_t *image = (uint32_t*)calloc(FIX2LONG(w) * FIX2LONG(h), sizeof(uint32_t));
decode_dxt5((uint64_t*)RSTRING_PTR(rb_data), FIX2INT(w), FIX2INT(h), image);
VALUE ret = rb_str_new((char*)image, FIX2LONG(w) * FIX2LONG(h) * sizeof(uint32_t));
free(image);
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_astc", rb_decode_astc, 5);
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);
}
+258
View File
@@ -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;
}
+29
View File
@@ -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 */
+94 -18
View File
@@ -1,26 +1,102 @@
#include "rgb.h"
#include <math.h>
#include <stdint.h>
#include "color.h"
#include "fp16.h"
static inline int is_system_little() {
int x = 1;
return *(char*)&x == 1;
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;
}
void decode_rgb565(const uint16_t* data, const int size, const int is_big_endian, uint8_t* image) {
const uint16_t *d = data;
if (is_big_endian == is_system_little()) {
uint8_t *p = image;
for (int i = 0; i < size; i++, d++, p += 4) {
uint_fast8_t r = *d & 0x00f8;
uint_fast8_t g = (*d & 0x0007) << 5 | (*d & 0xe000) >> 11;
uint_fast8_t b = (*d & 0x1f00) >> 5;
p[0] = r | r >> 5;
p[1] = g | g >> 6;
p[2] = b | b >> 5;
p[3] = 255;
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 {
uint32_t *p = (uint32_t*)image;
for (int i = 0; i < size; i++, d++, p++)
*p = (*d & 0xf800) >> 8 | *d >> 13 | (*d & 0x7e0) << 5 | (*d & 0x60) << 3 | *d << 19 | (*d & 0x1c) << 14 | 0xff000000;
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;
}
+7 -1
View File
@@ -3,6 +3,12 @@
#include <stdint.h>
void decode_rgb565(const uint16_t*, const int, const int, uint8_t*);
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
View File
@@ -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 -333
View File
@@ -1,340 +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, :res_s
# 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
# @param [String] res_s resS data
# @return [Mikunyan::Asset] deserialized Asset object
def self.load(bin, name, res_s = nil)
r = Asset.new(name, res_s)
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, res_s = nil)
@name = name
@endian = :big
@res_s = res_s
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 = nil
size = parse_object_private(br, children.find{|e| e[:name] == 'size'}).value
data_type_tree = children.find{|e| e[:name] == 'data'}
if node.type == 'TypelessData'
data = br.read(size * data_type_tree[:node].size)
else
data = size.times.map{ parse_object_private(br, data_type_tree) }
end
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?
if node.type == 'string'
size = parse_object_private(br, children[0][:children].find{|e| e[:name] == 'size'}).value
r.value = br.read(size * children[0][:children].find{|e| e[:name] == 'data'}[:node].size).force_encoding("utf-8")
br.align(4) if children[0][:node].flags & 0x4000 != 0
else
r.value = parse_object_private(br, children[0]).value
end
elsif node.type == 'StreamingInfo'
children.each{|child| r[child[:name]] = parse_object_private(br, child)}
r.value = @res_s.byteslice(r['offset'].value, r['size'].value) if r['path'].value == "archive:/#{name}/#{name}.resS"
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 -103
View File
@@ -1,110 +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 = String.new
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
asset_blocks.each do |b|
next if b[:name].end_with?('.resS')
res_s = asset_blocks.find{|e| e[:name] == "#{b[:name]}.resS"}
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name], res_s && raw_data.byteslice(res_s[:offset], res_s[:size]))
@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
+34
View File
@@ -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
View File
@@ -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
View File
@@ -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
+7 -3
View File
@@ -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
+491 -444
View File
@@ -1,481 +1,528 @@
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end
# frozen_string_literal: true
begin
require 'oily_png'
rescue LoadError
require 'chunky_png'
end
require 'bin_utils'
require 'mikunyan/decoders/native'
require 'mikunyan/decoders/crunch'
module Mikunyan
module Decoder
# Class for image decoding tools
class ImageDecoder
# Decode image from Mikunyan::ObjectValue
# @param [Mikunyan::ObjectValue] object object to decode
# @return [ChunkyPNG::Image,nil] decoded image
def self.decode_object(object)
return nil unless object.class == ObjectValue
# Decode image from Mikunyan::ObjectValue
# @param [Mikunyan::ObjectValue] object object to decode
# @return [ChunkyPNG::Image,nil] decoded image
def self.decode_object(object)
return nil unless object.is_a?(ObjectValue)
endian = object.endian
width = object['m_Width']
height = object['m_Height']
bin = object['image data']
fmt = object['m_TextureFormat']
return nil unless width && height && bin && fmt
endian = object.endian
width = object['m_Width']&.value
height = object['m_Height']&.value
bin = object['image data']&.value
fmt = object['m_TextureFormat']&.value
return nil unless width && height && bin && fmt
width = width.value
height = height.value
bin = bin.value
fmt = fmt.value
bin = object['m_StreamData']&.value if bin.empty?
return nil unless bin
if bin.size == 0 && object['m_StreamData']
bin = object['m_StreamData'].value
return nil unless bin
end
case fmt
when 1
decode_a8(width, height, bin)
when 2
decode_argb4444(width, height, bin, endian)
when 3
decode_rgb24(width, height, bin)
when 4
decode_rgba32(width, height, bin)
when 5
decode_argb32(width, height, bin)
when 7
decode_rgb565(width, height, bin, endian)
when 9
decode_r16(width, height, bin)
when 10
decode_dxt1(width, height, bin)
when 12
decode_dxt5(width, height, bin)
when 13
decode_rgba4444(width, height, bin, endian)
when 14
decode_bgra32(width, height, bin)
when 15
decode_rhalf(width, height, bin, endian)
when 16
decode_rghalf(width, height, bin, endian)
when 17
decode_rgbahalf(width, height, bin, endian)
when 18
decode_rfloat(width, height, bin, endian)
when 19
decode_rgfloat(width, height, bin, endian)
when 20
decode_rgbafloat(width, height, bin, endian)
when 22
decode_rgb9e5float(width, height, bin, endian)
when 34
decode_etc1(width, height, bin)
when 45
decode_etc2rgb(width, height, bin)
when 46
decode_etc2rgba1(width, height, bin)
when 47
decode_etc2rgba8(width, height, bin)
when 48, 54
decode_astc(width, height, 4, bin)
when 49, 55
decode_astc(width, height, 5, bin)
when 50, 56
decode_astc(width, height, 6, bin)
when 51, 57
decode_astc(width, height, 8, bin)
when 52, 58
decode_astc(width, height, 10, bin)
when 53, 59
decode_astc(width, height, 12, bin)
when 62
decode_rg16(width, height, bin)
when 63
decode_r8(width, height, bin)
else
nil
end
case fmt
when 1 # Alpha8
decode_a8(width, height, bin)
when 2 # ARGB4444
decode_argb4444(width, height, bin, endian)
when 3 # RGB24
decode_rgb24(width, height, bin)
when 4 # RGBA32
decode_rgba32(width, height, bin)
when 5 # ARGB32
decode_argb32(width, height, bin)
when 7 # RGB565
decode_rgb565(width, height, bin, endian)
when 9 # R16
decode_r16(width, height, bin)
when 10 # DXT1
decode_dxt1(width, height, bin)
when 12 # DXT5
decode_dxt5(width, height, bin)
when 13 # RGBA4444
decode_rgba4444(width, height, bin, endian)
when 14 # BGRA32
decode_bgra32(width, height, bin)
when 15 # RHalf
decode_rhalf(width, height, bin, endian)
when 16 # RGHalf
decode_rghalf(width, height, bin, endian)
when 17 # RGBAHalf
decode_rgbahalf(width, height, bin, endian)
when 18 # RFloat
decode_rfloat(width, height, bin, endian)
when 19 # RGFloat
decode_rgfloat(width, height, bin, endian)
when 20 # RGBAFloat
decode_rgbafloat(width, height, bin, endian)
# when 21 # YUY2
when 22 # RGB9e5Float
decode_rgb9e5float(width, height, bin, endian)
# when 24 # BC6H
# when 25 # BC7
# when 26 # BC4
# when 27 # BC5
when 28, 29, 64, 65 # DXT1Crunched, DXT5Crunched, ETC_RGB4Crunched, ETC2_RGBA8Crunched
decode_crunched(width, height, bin)
when 30, 31, -127 # PVRTC_RGB2, PVRTC_RGBA2, PVRTC_2BPP_RGBA
decode_pvrtc1(width, height, bin, 2)
when 32, 33 # PVRTC_RGB4, PVRTC_RGBA4
decode_pvrtc1(width, height, bin, 4)
when 34 # ETC_RGB4
decode_etc1(width, height, bin)
when 41 # EAC_R
decode_eacr(width, height, bin)
when 42 # EAC_R_SIGNED
decode_eacsr(width, height, bin)
when 43 # EAC_RG
decode_eacrg(width, height, bin)
when 44 # EAC_RG_SIGNED
decode_eacsrg(width, height, bin)
when 45 # ETC2_RGB
decode_etc2rgb(width, height, bin)
when 46 # ETC2_RGBA1
decode_etc2rgba1(width, height, bin)
when 47 # ETC2_RGBA8
decode_etc2rgba8(width, height, bin)
when 48, 54, 66 # ASTC_RGB_4x4, ASTC_RGBA_4x4, ASTC_HDR_4x4
decode_astc(width, height, 4, bin)
when 49, 55, 67 # ASTC_RGB_5x5, ASTC_RGBA_5x5, ASTC_HDR_5x5
decode_astc(width, height, 5, bin)
when 50, 56, 68 # ASTC_RGB_6x6, ASTC_RGBA_6x6, ASTC_HDR_6x6
decode_astc(width, height, 6, bin)
when 51, 57, 69 # ASTC_RGB_8x8, ASTC_RGBA_8x8, ASTC_HDR_8x8
decode_astc(width, height, 8, bin)
when 52, 58, 70 # ASTC_10x10, ASTC_RGBA_10x10, ASTC_HDR_10x10
decode_astc(width, height, 10, bin)
when 53, 59, 71 # ASTC_RGB_12x12, ASTC_RGBA_12x12, ASTC_HDR_12x12
decode_astc(width, height, 12, bin)
# when 60 # ETC_RGB4_3DS
# when 61 # ETC_RGBA8_3DS
when 62 # RG16
decode_rg16(width, height, bin)
when 63 # R8
decode_r8(width, height, bin)
end
end
# Decode image from RGBA4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
# Decode image from A8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_a8(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, DecodeHelper.decode_a8(bin, width * height)).flip
end
# Decode image from R8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_r8(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, DecodeHelper.decode_r8(bin, width * height)).flip
end
# Decode image from ARGB4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from ARGB4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
# Decode image from RGB24 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb24(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, bin).flip
end
# Decode image from RGBA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba32(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, bin).flip
end
# Decode image from ARGB32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_be(bin, i * 4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from RGB565 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb565(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_rgb565(bin, width * height, endian == :big)).flip
# Decode image from RGB565 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb565(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgb_stream(width, height,
DecodeHelper.decode_rgb565(bin, width * height, endian == :big)).flip
end
# Decode image from R16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_r16(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgb_stream(width, height,
DecodeHelper.decode_r16(bin, width * height, endian == :big)).flip
end
# Decode image from RGBA4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from A8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_a8(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = BinUtils.get_int8(bin, i)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
# Decode image from RG16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rg16(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i * 2), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from R8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_r8(width, height, bin)
decode_a8(width, height, bin)
# Decode image from BGRA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_bgra32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_le(bin, i * 4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from RG16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rg16(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i*2), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
# Decode image from RGB9e5 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb9e5float(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
n = endian == :little ? BinUtils.get_int32_le(bin, i * 4) : BinUtils.get_int32_be(bin, i * 4)
b = n >> 18 & 0x1ff
g = n >> 9 & 0x1ff
r = n & 0x1ff
scale = n >> 27 & 0x1f
scale = 2**(scale - 24)
BinUtils.append_int8!(mem, f2i(r * scale), f2i(g * scale), f2i(b * scale))
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGB24 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb24(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, bin).flip
# Decode image from R Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rhalf(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgb_stream(width, height,
DecodeHelper.decode_rhalf(bin, width * height, endian == :big)).flip
end
# Decode image from RG Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rghalf(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgb_stream(width, height,
DecodeHelper.decode_rghalf(bin, width * height, endian == :big)).flip
end
# Decode image from RGBA Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbahalf(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgba_stream(width, height,
DecodeHelper.decode_rgbahalf(bin, width * height, endian == :big)).flip
end
# Decode image from R float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e' : 'g'
(width * height).times do |i|
c = f2i(bin.byteslice(i * 4, 4).unpack1(unpackstr))
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba32(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, bin).flip
# Decode image from RG float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e2' : 'g2'
(width * height).times do |i|
r, g = bin.byteslice(i * 8, 8).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from ARGB32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_be(bin, i*4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
# Decode image from RGBA float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbafloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
unpackstr = endian == :little ? 'e4' : 'g4'
(width * height).times do |i|
r, g, b, a = bin.byteslice(i * 16, 16).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from BGRA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_bgra32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_le(bin, i*4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
# Decode image from DXT1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt1(bin, width, height))
end
# Decode image from DXT5 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt5(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt5(bin, width, height))
end
# Decode image from PVRTC1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Integer] bpp bit per pixel (2 or 4)
# @return [ChunkyPNG::Image] decoded image
def self.decode_pvrtc1(width, height, bin, bpp)
raise 'bpp of PVRTC1 must be 2 or 4' unless [2, 4].include?(bpp)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_pvrtc1(bin, width, height, bpp == 2))
end
# Decode image from ETC1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc1(bin, width, height))
end
# Decode image from EAC R11 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_eacr(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_eacr(bin, width, height))
end
# Decode image from EAC Signed R11 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_eacsr(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_eacsr(bin, width, height))
end
# Decode image from EAC RG11 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_eacrg(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_eacrg(bin, width, height))
end
# Decode image from EAC Signed RG11 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_eacsrg(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_eacsrg(bin, width, height))
end
# Decode image from ETC2 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgb(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2(bin, width, height))
end
# Decode image from ETC2 Alpha1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a1(bin, width, height))
end
# Decode image from ETC2 Alpha8 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba8(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a8(bin, width, height))
end
# Decode image from ASTC compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [Integer] blocksize block size
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_astc(width, height, blocksize, bin)
ChunkyPNG::Image.from_rgba_stream(width, height,
DecodeHelper.decode_astc(bin, width, height, blocksize, blocksize))
end
# Decode image from crunched texture binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image,nil] decoded image
def self.decode_crunched(width, height, bin)
file = Mikunyan::DecodeHelper::CrunchStream.new(bin)
level_info = file.level_info(0)
case level_info.format
when Mikunyan::DecodeHelper::CrunchStream::Format::DXT1
decode_dxt1(width, height, file.unpack_level(0))
when Mikunyan::DecodeHelper::CrunchStream::Format::DXT5
decode_dxt5(width, height, file.unpack_level(0))
when Mikunyan::DecodeHelper::CrunchStream::Format::ETC1
decode_etc1(width, height, file.unpack_level(0))
when Mikunyan::DecodeHelper::CrunchStream::Format::ETC2
decode_etc2rgb(width, height, file.unpack_level(0))
when Mikunyan::DecodeHelper::CrunchStream::Format::ETC2A
decode_etc2rgba8(width, height, file.unpack_level(0))
end
end
# Decode image from R16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_r16(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = f2i(r / 65535.0)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Create ASTC file data from ObjectValue
# @param [Mikunyan::ObjectValue,Hash] object target object
# @return [String,nil] created file
def self.create_astc_file(object)
astc_list = {
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12,
66 => 4, 67 => 5, 68 => 6, 69 => 8, 70 => 10, 71 => 12
}
width = object['m_Width']&.value
height = object['m_Height']&.value
fmt = object['m_TextureFormat']&.value
bin = object['image data']&.value
return unless width && height && fmt && bin && astc_list[fmt]
# Decode image from RGB9e5 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb9e5float(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
n = endian == :little ? BinUtils.get_int32_le(bin, i*4) : BinUtils.get_int32_be(bin, i*4)
e = (n & 0xf8000000) >> 27
r = (n & 0x7fc0000) >> 9
g = (n & 0x3fe00) >> 9
b = n & 0x1ff
r = (r / 512r + 1) * (2**(e-15))
g = (g / 512r + 1) * (2**(e-15))
b = (b / 512r + 1) * (2**(e-15))
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b))
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
bin = object['m_StreamData']&.value if bin.empty?
return unless bin
# Decode image from R Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rhalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)))
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
header = [0x13, 0xab, 0xa1, 0x5c, astc_list[fmt], astc_list[fmt], 1].pack('C*')
header << [width].pack('V').byteslice(0, 3)
header << [height].pack('V').byteslice(0, 3)
header << [1, 0, 0].pack('C*')
header + bin
end
# Decode image from RG Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rghalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4) : BinUtils.get_int16_be(bin, i*4)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4+2) : BinUtils.get_int16_be(bin, i*4+2)))
BinUtils.append_int8!(mem, r, g, 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# [0.0,1.0] -> [0,255]
def self.f2i(val)
return 0 unless val.finite?
# Decode image from RGBA Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbahalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8) : BinUtils.get_int16_be(bin, i*8)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+2) : BinUtils.get_int16_be(bin, i*8+2)))
b = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+4) : BinUtils.get_int16_be(bin, i*8+4)))
a = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+6) : BinUtils.get_int16_be(bin, i*8+6)))
BinUtils.append_int8!(mem, r, g, b, a)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from R float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e' : 'g'
(width * height).times do |i|
c = f2i(bin.byteslice(i*4, 4).unpack(unpackstr)[0])
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RG float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e2' : 'g2'
(width * height).times do |i|
r, g = bin.byteslice(i*8, 8).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbafloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
unpackstr = endian == :little ? 'e4' : 'g4'
(width * height).times do |i|
r, g, b, a = bin.byteslice(i*16, 16).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from DXT1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt1(bin, width, height))
end
# Decode image from DXT5 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt5(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt5(bin, width, height))
end
# Decode image from ETC1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc1(bin, width, height))
end
# Decode image from ETC2 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgb(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2(bin, width, height))
end
# Decode image from ETC2 Alpha1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a1(bin, width, height))
end
# Decode image from ETC2 Alpha8 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba8(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a8(bin, width, height))
end
# Decode image from ASTC compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [Integer] blocksize block size
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_astc(width, height, blocksize, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_astc(bin, width, height, blocksize, blocksize))
end
# Create ASTC file data from ObjectValue
# @param [Mikunyan::ObjectValue,Hash] object target object
# @return [String,nil] created file
def self.create_astc_file(object)
astc_list = {
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12
}
width = object['m_Width']
height = object['m_Height']
fmt = object['m_TextureFormat']
bin = object['image data']
width = width.value if width.class == ObjectValue
height = height.value if height.class == ObjectValue
fmt = fmt.value if fmt.class == ObjectValue
bin = bin.value if bin.class == ObjectValue
if width && height && fmt && astc_list[fmt]
header = "\x13\xAB\xA1\x5C".force_encoding('ascii-8bit')
header << [astc_list[fmt], astc_list[fmt], 1].pack("C*")
header << [width].pack("V").byteslice(0, 3)
header << [height].pack("V").byteslice(0, 3)
header << "\x01\x00\x00"
header + bin
else
nil
end
end
private
# convert 16bit float
def self.n2f(n)
case n
when 0x0000
0.0
when 0x8000
-0.0
when 0x7c00
Float::INFINITY
when 0xfc00
-Float::INFINITY
else
s = n & 0x8000 != 0
e = n & 0x7c00
f = n & 0x03ff
case e
when 0x7c00
Float::NAN
when 0
(s ? -f : f) * 2.0**-24
else
(s ? -1 : 1) * (f / 1024.0 + 1) * (2.0 ** ((e >> 10)-15))
end
end
end
# [0.0,1.0] -> [0,255]
def self.f2i(d)
(d * 255).round.clamp(0, 255)
end
(val * 255).round.clamp(0, 255)
end
end
end
# @deprecated
class ImageDecoder
# @deprecated Use {Decoder::ImageDecoder#decode_object} or {CustomTypes::Texture2D#generate_png} instead.
def self.decode_object(object)
warn 'Warning: Mikunyan::ImageDecoder#decode_object is deprecated and will be removed at a future release. ' \
'Use Mikunyan::Decoder::ImageDecoder#decode_object or' \
'Mikunyan::CustomTypes::Texture2D#generate_png instead.'
Mikunyan::Decoder::ImageDecoder.decode_object(object)
end
end
end
+106 -89
View File
@@ -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 -78
View File
@@ -1,82 +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
node = Node.new(br.i16u, br.i8u, br.i8u != 0, br.i32, br.i32, br.i32, br.i32u, br.i32u)
nodes << node
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
+19
View File
@@ -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
+33
View File
@@ -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
@@ -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}]}

Some files were not shown because too many files have changed in this diff Show More