Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6cb66b42a | |||
| 8cf35dd34d | |||
| 691f3ccc97 | |||
| 2d660efb78 | |||
| a8a5303f7b | |||
| 3b3db58a20 | |||
| bcffc1b1e3 | |||
| 9ba90f1bd5 | |||
| 9686bdca95 | |||
| ad9ee60a5b | |||
| 68252ce102 | |||
| afd776e836 | |||
| cb0ca77c9b | |||
| 4204f05c0b | |||
| c8c34d6c80 | |||
| c37dd49732 | |||
| 0e5dafa424 | |||
| 7bb8f0a0a4 | |||
| 2c8f95b6fd | |||
| d40f307802 | |||
| 28df8f2271 | |||
| 9731fcde26 | |||
| 93b3b5789c | |||
| d2ef991e76 | |||
| ed1870920d | |||
| 0007810984 | |||
| 1c6a2099ab | |||
| df03ce3d44 | |||
| d864b9f734 | |||
| cbedf9a749 | |||
| f4127166ac | |||
| 3cd7efdf5c |
@@ -18,6 +18,14 @@ Or install it yourself as:
|
||||
|
||||
$ gem install mikunyan
|
||||
|
||||
If you want to install development build:
|
||||
|
||||
$ git clone https://github.com/Ishotihadus/mikunyan
|
||||
$ cd mikunyan
|
||||
$ bundle install
|
||||
$ rake build
|
||||
$ gem install -l pkg/mikunyan-3.9.x.gem
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
@@ -96,7 +104,7 @@ obj.key
|
||||
|
||||
You can get png file directly from Texture2D asset. Output object's class is `ChunkyPNG::Image`.
|
||||
|
||||
Acceptable format is basic texture formats (1, 2, 3, 4, 5, 7 and 13) and ETC_RGB4 (34).
|
||||
Some basic texture formats (1–5, 7, 9, 13–20, 22, 62, and 63), ETC_RGB4 (34), ETC2 (45, 47), and ASTC (48–59) are available.
|
||||
|
||||
```ruby
|
||||
require 'mikunyan/decoders'
|
||||
@@ -111,9 +119,11 @@ img = Mikunyan::ImageDecoder.decode_object(obj)
|
||||
img.save('mikunyan.png')
|
||||
```
|
||||
|
||||
### Json Outputer
|
||||
Mikunyan cannot decode ASTC with HDR data. Use `Mikunyan::ImageDecoder.create_astc_file` instead.
|
||||
|
||||
`mikunyan-json` is the executable command for converting unity3d to json.
|
||||
### Json / YAML Outputter
|
||||
|
||||
`mikunyan-json` is an executable command for converting unity3d to json.
|
||||
|
||||
$ mikunyan-json bundle.unity3d > bundle.json
|
||||
|
||||
@@ -121,6 +131,94 @@ Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
|
||||
- `--pretty` (`-p`): prettify output json
|
||||
- `--yaml` (`-y`): YAML mode
|
||||
|
||||
### Image Outputter
|
||||
|
||||
`mikunyan-image` is an executable command for unpacking images from unity3d.
|
||||
|
||||
$ mikunyan-image bundle.unity3d
|
||||
|
||||
The console log is json data of output textures as below.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "bg_b",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": -744818715421265689
|
||||
},
|
||||
{
|
||||
"name": "bg_a",
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"path_id": 5562124901460497987
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
If the option `--sprite` specified, `mikunyan-image` will output sprites. The log json also contains sprite information.
|
||||
|
||||
```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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Available options:
|
||||
|
||||
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
|
||||
- `--outputdir` (`-o`): output directory (default is a basename of input file without an extention)
|
||||
- `--sprite` (`-s`): output sprites instead of textures
|
||||
- `--pretty` (`-p`): prettify output json
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -129,7 +227,7 @@ Available options:
|
||||
- [bin_utils](https://rubygems.org/gems/bin_utils)
|
||||
- [chunky_png](https://rubygems.org/gems/chunky_png)
|
||||
|
||||
Mikunyan use [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
||||
Mikunyan uses [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
||||
|
||||
## FAQ
|
||||
|
||||
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'mikunyan'
|
||||
require 'mikunyan/decoders'
|
||||
require 'fileutils'
|
||||
require 'json'
|
||||
|
||||
opts = {:as_asset => false, :outputdir => nil, :sprite => false, :pretty => false}
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
if ARGV[i].start_with?('-')
|
||||
case ARGV[i]
|
||||
when '--as-asset', '-a'
|
||||
opts[:as_asset] = true
|
||||
when '--outputdir', '-o'
|
||||
i += 1
|
||||
opts[:outputdir] = ARGV[i]
|
||||
when '--sprite', '-s'
|
||||
opts[:sprite] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
end
|
||||
else
|
||||
arg = ARGV[i] unless arg
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
|
||||
unless arg
|
||||
warn("Input file is not specified")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
assets = []
|
||||
|
||||
if opts[:as_asset]
|
||||
assets = [Mikunyan::Asset.file(arg, File.basename(arg, '.*'))]
|
||||
else
|
||||
assets = Mikunyan::AssetBundle.file(arg).assets
|
||||
end
|
||||
|
||||
outdir = opts[:outputdir] || File.basename(arg, '.*')
|
||||
FileUtils.mkpath(outdir)
|
||||
|
||||
assets.each do |asset|
|
||||
if opts[:sprite]
|
||||
json = {}
|
||||
textures = {}
|
||||
|
||||
asset.path_ids.select{|path_id| asset.object_type(path_id) == 'Sprite'}.each do |path_id|
|
||||
obj = asset.parse_object(path_id)
|
||||
name = obj.m_Name.value
|
||||
tex_id = obj.m_RD.texture.m_PathID.value
|
||||
|
||||
unless textures[tex_id]
|
||||
tex_obj = asset.parse_object(tex_id)
|
||||
textures[tex_id] = Mikunyan::ImageDecoder.decode_object(tex_obj) if 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 => []}
|
||||
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 => path_id}
|
||||
textures[tex_id].crop(x.round, (textures[tex_id].height - height - y).round, width.round, height.round).save("#{outdir}/#{name}.png")
|
||||
end
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json.values) : JSON.generate(json.values)
|
||||
else
|
||||
json = []
|
||||
asset.path_ids.select{|path_id| asset.object_type(path_id) == 'Texture2D'}.each do |path_id|
|
||||
obj = asset.parse_object(path_id)
|
||||
name = obj.m_Name.value
|
||||
image = Mikunyan::ImageDecoder.decode_object(obj)
|
||||
if image
|
||||
json << {:name => name, :width => image.width, :height => image.height, :path_id => path_id}
|
||||
image.save("#{outdir}/#{name}.png")
|
||||
end
|
||||
end
|
||||
puts opts[:pretty] ? JSON.pretty_generate(json) : JSON.generate(json)
|
||||
end
|
||||
end
|
||||
+19
-8
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'mikunyan'
|
||||
require 'json'
|
||||
require 'base64'
|
||||
|
||||
def obj64(obj)
|
||||
@@ -19,7 +18,7 @@ def obj64(obj)
|
||||
end
|
||||
end
|
||||
|
||||
opts = {:as_asset => false, :pretty => false}
|
||||
opts = {:as_asset => false, :pretty => false, :yaml => false}
|
||||
arg = nil
|
||||
i = 0
|
||||
while i < ARGV.count
|
||||
@@ -29,9 +28,10 @@ while i < ARGV.count
|
||||
opts[:as_asset] = true
|
||||
when '--pretty', '-p'
|
||||
opts[:pretty] = true
|
||||
when '--yaml', '-y'
|
||||
opts[:yaml] = true
|
||||
else
|
||||
warn("Unknown option: #{ARGV[i]}")
|
||||
exit(1)
|
||||
end
|
||||
else
|
||||
arg = ARGV[i] unless arg
|
||||
@@ -39,6 +39,10 @@ while i < ARGV.count
|
||||
i += 1
|
||||
end
|
||||
|
||||
if opts[:pretty] && opts[:yaml]
|
||||
warn("Option --pretty is ignored if --yaml is specified.")
|
||||
end
|
||||
|
||||
unless File.file?(arg)
|
||||
warn("File not found: #{arg}")
|
||||
exit(1)
|
||||
@@ -51,7 +55,7 @@ if opts[:as_asset]
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj64(obj) if obj
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
else
|
||||
@@ -60,14 +64,21 @@ else
|
||||
objs = []
|
||||
asset.path_ids.each do |e|
|
||||
obj = asset.parse_object_simple(e)
|
||||
objs << obj64(obj) if obj
|
||||
objs << obj
|
||||
end
|
||||
assets[asset.name] = objs
|
||||
end
|
||||
end
|
||||
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
if opts[:yaml]
|
||||
require 'yaml'
|
||||
puts YAML.dump(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
require 'json'
|
||||
assets = assets.map{|k, v| [k, obj64(v)]}.to_h
|
||||
if opts[:pretty]
|
||||
puts JSON.pretty_generate(assets)
|
||||
else
|
||||
puts JSON.generate(assets)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,3 +6,7 @@ require "mikunyan/binary_reader"
|
||||
require "mikunyan/object_value"
|
||||
require "mikunyan/type_tree"
|
||||
require "mikunyan/constants"
|
||||
|
||||
# Module for deserializing Unity Assets and AssetBundles
|
||||
module Mikunyan
|
||||
end
|
||||
|
||||
+156
-99
@@ -1,21 +1,133 @@
|
||||
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_accessor :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
||||
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 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 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)
|
||||
|
||||
# Load Asset from binary string
|
||||
# @param [String] bin binary data
|
||||
# @param [String] name Asset name
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.load(bin, name)
|
||||
r = Asset.new(name)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# Load Asset from file
|
||||
# @param [String] file file name
|
||||
# @param [String] name Asset name (automatically generated if not specified)
|
||||
# @return [Mikunyan::Asset] deserialized Asset object
|
||||
def self.file(file, name=nil)
|
||||
name = File.basename(name, '.*') unless name
|
||||
Asset.load(File.binread(file), name)
|
||||
end
|
||||
|
||||
# Returns list of all path IDs
|
||||
# @return [Array<Integer>] list of all path IDs
|
||||
def path_ids
|
||||
@objects.map{|e| e.path_id}
|
||||
end
|
||||
|
||||
# Returns list of containers
|
||||
# @return [Array<Hash>,nil] list of all containers
|
||||
def containers
|
||||
obj = parse_object(1)
|
||||
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
||||
obj.m_Container.value.map do |e|
|
||||
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
||||
end
|
||||
end
|
||||
|
||||
# Parse object of given path ID
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Mikunyan::ObjectValue,nil] parsed object
|
||||
def parse_object(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
type_tree = Asset.parse_type_tree(klass)
|
||||
return nil unless type_tree
|
||||
|
||||
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
||||
end
|
||||
|
||||
# Parse object of given path ID and simplify it
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [Hash,nil] parsed object
|
||||
def parse_object_simple(path_id)
|
||||
Asset.object_simplify(parse_object(path_id))
|
||||
end
|
||||
|
||||
# Returns object type name string
|
||||
# @param [Integer,ObjectData] path_id path ID or object
|
||||
# @return [String,nil] type name
|
||||
def object_type(path_id)
|
||||
if path_id.class == Integer
|
||||
obj = @objects.find{|e| e.path_id == path_id}
|
||||
return nil unless obj
|
||||
elsif path_id.class == ObjectData
|
||||
obj = path_id
|
||||
else
|
||||
return nil
|
||||
end
|
||||
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
|
||||
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
||||
klass.type_tree.nodes[0].type
|
||||
elsif klass
|
||||
Mikunyan::CLASS_ID[klass.class_id]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@endian = :big
|
||||
end
|
||||
|
||||
def self.file(file, name)
|
||||
r = Asset.new(name)
|
||||
r.load(File.binread(file))
|
||||
r
|
||||
end
|
||||
|
||||
def load(bin)
|
||||
br = BinaryReader.new(bin)
|
||||
metadata_size = br.i32u
|
||||
@@ -108,97 +220,6 @@ module Mikunyan
|
||||
end
|
||||
end
|
||||
|
||||
def path_ids
|
||||
@objects.map{|e| e.path_id}
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def parse_object_simple(path_id)
|
||||
Asset.object_simplify(parse_object(path_id))
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
private
|
||||
|
||||
def parse_object_private(br, type_tree)
|
||||
r = nil
|
||||
node = type_tree[:node]
|
||||
@@ -230,7 +251,6 @@ module Mikunyan
|
||||
children.each do |child|
|
||||
r[child[:name]] = parse_object_private(br, child)
|
||||
end
|
||||
br.jmp(pos + node.size)
|
||||
else
|
||||
pos = br.pos
|
||||
value = nil
|
||||
@@ -268,5 +288,42 @@ module Mikunyan
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
require 'extlz4'
|
||||
|
||||
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_accessor :signature, :format, :unity_version, :generator_version, :assets
|
||||
attr_reader :signature, :format, :unity_version, :generator_version, :assets
|
||||
|
||||
# Load AssetBundle from binary string
|
||||
# @param [String] bin binary data
|
||||
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
||||
def self.load(bin)
|
||||
r = AssetBundle.new
|
||||
r.load(bin)
|
||||
r.send(:load, bin)
|
||||
r
|
||||
end
|
||||
|
||||
# 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
|
||||
@@ -31,8 +45,6 @@ module Mikunyan
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_unity_raw(br)
|
||||
@assets = []
|
||||
|
||||
@@ -48,8 +60,7 @@ module Mikunyan
|
||||
asset_size = br.i32u
|
||||
br.jmp(asset_pos + asset_header_size - 4)
|
||||
asset_data = br.read(asset_size)
|
||||
asset = Asset.new(asset_name)
|
||||
asset.load(asset_data)
|
||||
asset = Asset.load(asset_data, asset_name)
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
@@ -77,8 +88,7 @@ module Mikunyan
|
||||
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
|
||||
|
||||
asset_blocks.each do |b|
|
||||
asset = Asset.new(b[:name])
|
||||
asset.load(raw_data.byteslice(b[:offset], b[:size]))
|
||||
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name])
|
||||
@assets << asset
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
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
|
||||
|
||||
# Constructor
|
||||
# @param [String] data binary string
|
||||
# @param [Symbol] endian endianness
|
||||
def initialize(data, endian = :big)
|
||||
@data = data
|
||||
@pos = 0
|
||||
@@ -11,104 +18,131 @@ module Mikunyan
|
||||
@endian = endian
|
||||
end
|
||||
|
||||
# Returns whether little endian or not
|
||||
# @return [Boolean]
|
||||
def little?
|
||||
@endian == :little
|
||||
end
|
||||
|
||||
def jmp(pos = 0)
|
||||
# Jump to given position
|
||||
# @param [Integer] pos position
|
||||
def jmp(pos=0)
|
||||
@pos = pos
|
||||
end
|
||||
|
||||
def adv(size = 0)
|
||||
# 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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
module Mikunyan
|
||||
private
|
||||
STRING_TABLE = {
|
||||
0=>'AABB',
|
||||
5=>'AnimationClip',
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
require 'mikunyan/decoders/image_decoder'
|
||||
|
||||
module Mikunyan
|
||||
# Module for helper classes for decoding object
|
||||
module DecodeHelper
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,526 @@
|
||||
require 'bin_utils'
|
||||
require 'fiddle'
|
||||
|
||||
module Mikunyan
|
||||
module DecodeHelper
|
||||
# Class for decode ASTC block
|
||||
# @attr_reader [String] data decoded data
|
||||
class AstcBlockDecoder
|
||||
attr_reader :data
|
||||
|
||||
# Decode block
|
||||
# @param [String] bin binary
|
||||
# @param [Integer] bw block width
|
||||
# @param [Integer] bh block height
|
||||
def initialize(bin, bw, bh)
|
||||
if bin[0].ord == 0xfc && bin[1].ord % 2 == 1
|
||||
@data = (bin[9] + bin[11] + bin[13] + bin[15]) * bw * bh
|
||||
else
|
||||
@d2 = BinUtils.get_int64_le(bin)
|
||||
@d1 = BinUtils.get_int64_le(bin, 8)
|
||||
@bw = bw
|
||||
@bh = bh
|
||||
|
||||
decode_block_params
|
||||
decode_endpoints
|
||||
decode_weights
|
||||
select_partition
|
||||
applicate_color
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
WeightPrecTableA = [nil, nil, 0, 3, 0, 5, 3, 0, nil, nil, 5, 3, 0, 5, 3, 0]
|
||||
WeightPrecTableB = [nil, nil, 1, 0, 2, 0, 1, 3, nil, nil, 1, 2, 4, 2, 3, 5]
|
||||
|
||||
CemTableA = [0, 0, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0, 5, 3, 0]
|
||||
CemTableB = [1, 2, 1, 3, 1, 2, 4, 2, 3, 5, 3, 4, 6, 4, 5, 7, 5, 6, 8]
|
||||
|
||||
TritsTable = [
|
||||
[0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2],
|
||||
[0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1],
|
||||
[0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
|
||||
]
|
||||
|
||||
QuintsTable = [
|
||||
[0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1],
|
||||
[0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3],
|
||||
[0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4]
|
||||
]
|
||||
|
||||
def [](i, j = 1)
|
||||
if j < 1
|
||||
0
|
||||
elsif j == 1
|
||||
i < 64 ? @d2[i] : @d1[i - 64]
|
||||
else
|
||||
if i + j <= 64
|
||||
@d2 >> i & (1 << j) - 1
|
||||
elsif i >= 64
|
||||
@d1 >> (i - 64) & (1 << j) - 1
|
||||
else
|
||||
@d2 >> i | (@d1 & (1 << i + j - 64) - 1) << 64 - i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode_block_params
|
||||
# Block Mode
|
||||
@weight_range = @d2 >> 4 & 1 | @d2 >> 6 & 8
|
||||
@dual_plane = @d2 & 0x400 == 0x400
|
||||
if @d2 & 0x3 != 0
|
||||
@weight_range |= @d2 << 1 & 6
|
||||
case @d2 & 0xc
|
||||
when 0
|
||||
@width = (@d2 >> 7 & 3) + 4
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x4
|
||||
@width = (@d2 >> 7 & 3) + 8
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x8
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = (@d2 >> 7 & 3) + 8
|
||||
else # 0xc
|
||||
if @d2 & 0x100 == 0
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = @d2[7] + 6
|
||||
else
|
||||
@width = @d2[7] + 2
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
end
|
||||
end
|
||||
else
|
||||
@weight_range |= @d2 >> 1 & 6
|
||||
case @d2 & 0x180
|
||||
when 0
|
||||
@width = 12
|
||||
@height = (@d2 >> 5 & 3) + 2
|
||||
when 0x80
|
||||
@width = (@d2 >> 5 & 3) + 2
|
||||
@height = 12
|
||||
when 0x180
|
||||
@width = (@d2 & 0x20 == 0) ? 6 : 10
|
||||
@height = 16 - @width
|
||||
else # 0x100
|
||||
@width = (@d2 >> 5 & 3) + 6
|
||||
@height = (@d2 >> 9 & 3) + 6
|
||||
@dual_plane = false
|
||||
@weight_range &= 7
|
||||
end
|
||||
end
|
||||
|
||||
# Count Partitions
|
||||
@part_num = (@d2 >> 11 & 3) + 1
|
||||
|
||||
# Count Weight Bits
|
||||
@weight_num = @width * @height
|
||||
@weight_num *= 2 if @dual_plane
|
||||
case WeightPrecTableA[@weight_range]
|
||||
when 3
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range] + (@weight_num * 8 + 4) / 5
|
||||
when 5
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range] + (@weight_num * 7 + 2) / 3
|
||||
else # 0
|
||||
@weight_bit = @weight_num * WeightPrecTableB[@weight_range]
|
||||
end
|
||||
|
||||
# CEM
|
||||
if @part_num == 1
|
||||
@cem = [@d2 >> 13 & 0xf]
|
||||
config_bit = 17
|
||||
else
|
||||
cembase = @d2 >> 23 & 3
|
||||
if cembase == 0
|
||||
@cem = Array.new(@part_num, @d2 >> 25 & 0xf)
|
||||
config_bit = 29
|
||||
else
|
||||
@cem = (0...@part_num).map{|i| ((@d2 >> (25 + i) & 1) + cembase - 1) << 2}
|
||||
|
||||
case @part_num
|
||||
when 2
|
||||
@cem[0] |= @d2 >> 27 & 3
|
||||
@cem[1] |= self[126 - @weight_bit, 2]
|
||||
when 3
|
||||
@cem[0] |= @d2[28]
|
||||
@cem[0] |= self[123 - @weight_bit] << 1
|
||||
@cem[1] |= self[124 - @weight_bit, 2]
|
||||
@cem[2] |= self[126 - @weight_bit, 2]
|
||||
else # 4
|
||||
4.times do |i|
|
||||
@cem[i] |= self[120 + 2 * i - @weight_bit, 2]
|
||||
end
|
||||
end
|
||||
|
||||
config_bit = 25 + @part_num * 3
|
||||
end
|
||||
end
|
||||
|
||||
# Count Color Endpoint Bits
|
||||
config_bit += 2 if @dual_plane
|
||||
remain_bit = 128 - config_bit - @weight_bit
|
||||
@cem_num = @cem.map{|i| (i >> 1 & 6) + 2}.inject(:+)
|
||||
|
||||
CemTableA.count.times do |n|
|
||||
i = CemTableA.count - n - 1
|
||||
case CemTableA[i]
|
||||
when 3
|
||||
@cem_bit = @cem_num * CemTableB[i] + (@cem_num * 8 + 4) / 5
|
||||
when 5
|
||||
@cem_bit = @cem_num * CemTableB[i] + (@cem_num * 7 + 2) / 3
|
||||
else # 0
|
||||
@cem_bit = @cem_num * CemTableB[i]
|
||||
end
|
||||
|
||||
if @cem_bit <= remain_bit
|
||||
@cem_range = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if @dual_plane
|
||||
if @part_num == 1 || cembase == 0
|
||||
@plane_selector = self[126 - @weight_bit, 2]
|
||||
else
|
||||
@plane_selector = self[130 - @weight_bit - @part_num * 3, 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def decode_endpoints
|
||||
values = decode_intseq_raw(self[@part_num == 1 ? 17 : 29, @cem_bit], CemTableA[@cem_range], CemTableB[@cem_range], @cem_num).map do |e|
|
||||
unquantize_endpoint(CemTableA[@cem_range], CemTableB[@cem_range], e[0], e[1])
|
||||
end
|
||||
|
||||
@endpoint = @cem.map do |cem|
|
||||
v = values.slice!(0, (cem >> 1 & 6) + 2)
|
||||
case cem
|
||||
when 0
|
||||
[v[0], v[0], v[0], 255, v[1], v[1], v[1], 255]
|
||||
when 1
|
||||
l0 = (v[0] >> 2) | (v[1] & 0xc0)
|
||||
l1 = (l0 + (v[1] & 0x3f)).clamp(0, 255)
|
||||
[l0, l0, l0, 255, l1, l1, l1, 255]
|
||||
when 4
|
||||
[v[0], v[0], v[0], v[2], v[1], v[1], v[1], v[3]]
|
||||
when 5
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
[v[0], v[0], v[0], v[2], v[0] + v[1], v[0] + v[1], v[0] + v[1], v[2] + v[3]].map{|i| i.clamp(0, 255)}
|
||||
when 6
|
||||
[v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, 255, v[0], v[1], v[2], 255]
|
||||
when 8
|
||||
if v[0] + v[2] + v[4] <= v[1] + v[3] + v[5]
|
||||
[v[0], v[2], v[4], 255, v[1], v[3], v[5], 255]
|
||||
else
|
||||
blue_contract(v[1], v[3], v[5], 255, v[0], v[2], v[4], 255)
|
||||
end
|
||||
when 9
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
v[5], v[4] = bit_transfer_signed(v[5], v[4])
|
||||
if v[1] + v[3] + v[5] >= 0
|
||||
[v[0], v[2], v[4], 255, v[0] + v[1], v[2] + v[3], v[4] + v[5], 255].map{|i| i.clamp(0, 255)}
|
||||
else
|
||||
blue_contract(v[0] + v[1], v[2] + v[3], v[4] + v[5], 255, v[0], v[2], v[4], 255).map{|i| i.clamp(0, 255)}
|
||||
end
|
||||
when 10
|
||||
[v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8, v[4], v[0], v[1], v[2], v[5]]
|
||||
when 12
|
||||
if v[0] + v[2] + v[4] <= v[1] + v[3] + v[5]
|
||||
[v[0], v[2], v[4], v[6], v[1], v[3], v[5], v[7]]
|
||||
else
|
||||
blue_contract(v[1], v[3], v[5], v[7], v[0], v[2], v[4], v[6])
|
||||
end
|
||||
when 13
|
||||
v[1], v[0] = bit_transfer_signed(v[1], v[0])
|
||||
v[3], v[2] = bit_transfer_signed(v[3], v[2])
|
||||
v[5], v[4] = bit_transfer_signed(v[5], v[4])
|
||||
v[7], v[6] = bit_transfer_signed(v[7], v[6])
|
||||
if v[1] + v[3] + v[5] >= 0
|
||||
[v[0], v[2], v[4], v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7]].map{|i| i.clamp(0, 255)}
|
||||
else
|
||||
blue_contract(v[0] + v[1], v[2] + v[3], v[4] + v[5], v[6] + v[7], v[0], v[2], v[4], v[6]).map{|i| i.clamp(0, 255)}
|
||||
end
|
||||
else
|
||||
throw NotImplementedError.new("HDR image is not supported. (CEM: #{cem})")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bit_transfer_signed(a, b)
|
||||
b = (b >> 1) | (a & 0x80)
|
||||
a = (a >> 1) & 0x3f
|
||||
[a[5] == 1 ? a - 0x40 : a, b]
|
||||
end
|
||||
|
||||
def blue_contract(r1, g1, b1, a1, r2, g2, b2, a2)
|
||||
[(r1 + b1) >> 1, (g1 + b1) >> 1, b1, a1, (r2 + b2) >> 1, (g2 + b2) >> 1, b2, a2]
|
||||
end
|
||||
|
||||
def decode_weights
|
||||
data = (0...(@weight_bit + 7) / 8).map{|i| (((self[120 - i * 8, 8]) * 0x80200802) & 0x0884422110) * 0x0101010101 >> 32 & 0xff}
|
||||
.map.with_index{|e, i| e << i * 8}.inject(:|) & (1 << @weight_bit) - 1
|
||||
|
||||
weight_point = decode_intseq_raw(data, WeightPrecTableA[@weight_range], WeightPrecTableB[@weight_range], @weight_num).map do |e|
|
||||
unquantize_weight(WeightPrecTableA[@weight_range], WeightPrecTableB[@weight_range], e[0], e[1])
|
||||
end
|
||||
|
||||
ds = (1024 + @bw / 2) / (@bw - 1)
|
||||
dt = (1024 + @bh / 2) / (@bh - 1)
|
||||
|
||||
if @dual_plane
|
||||
@weight0 = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
@weight1 = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
|
||||
for t in 0...@bh
|
||||
for s in 0...@bw
|
||||
gs = (ds * s * (@width - 1) + 32) >> 6
|
||||
gt = (dt * t * (@height - 1) + 32) >> 6
|
||||
fs = gs & 0xf
|
||||
ft = gt & 0xf
|
||||
v = (gs >> 4) + (gt >> 4) * @width
|
||||
w11 = (fs * ft + 8) >> 4
|
||||
w10 = ft - w11
|
||||
w01 = fs - w11
|
||||
w00 = 16 - fs - ft + w11
|
||||
|
||||
p00 = weight_point[v * 2] || 0
|
||||
p01 = weight_point[(v + 1) * 2] || 0
|
||||
p10 = weight_point[(v + @width) * 2] || 0
|
||||
p11 = weight_point[(v + @width + 1) * 2] || 0
|
||||
@weight0[s + t * @bw] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4
|
||||
|
||||
p00 = weight_point[v * 2 + 1] || 0
|
||||
p01 = weight_point[(v + 1) * 2 + 1] || 0
|
||||
p10 = weight_point[(v + @width) * 2 + 1] || 0
|
||||
p11 = weight_point[(v + @width + 1) * 2 + 1] || 0
|
||||
@weight1[s + t * @bw] = (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4
|
||||
end
|
||||
end
|
||||
else
|
||||
@weight = Fiddle::Pointer.malloc(@bw * @bh)
|
||||
|
||||
for t in 0...@bh
|
||||
for s in 0...@bw
|
||||
gs = (ds * s * (@width - 1) + 32) >> 6
|
||||
gt = (dt * t * (@height - 1) + 32) >> 6
|
||||
fs = gs & 0xf
|
||||
ft = gt & 0xf
|
||||
v = (gs >> 4) + (gt >> 4) * @width
|
||||
w11 = (fs * ft + 8) >> 4
|
||||
|
||||
p00 = weight_point[v] || 0
|
||||
p01 = weight_point[v + 1] || 0
|
||||
p10 = weight_point[v + @width] || 0
|
||||
p11 = weight_point[v + @width + 1] || 0
|
||||
@weight[s + t * @bw] = (p00 * (16 - fs - ft + w11) + p01 * (fs - w11) + p10 * (ft - w11) + p11 * w11 + 8) >> 4
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def select_partition
|
||||
if @part_num > 1
|
||||
small_block = @bw * @bh < 31
|
||||
|
||||
seed = (@d2 >> 13 & 0x3ff) | ((@part_num - 1) << 10)
|
||||
|
||||
rnum = seed
|
||||
rnum ^= (rnum >> 15)
|
||||
rnum = (rnum - (rnum << 17)) & 0xffffffff
|
||||
rnum = (rnum + (rnum << 7)) & 0xffffffff
|
||||
rnum = (rnum + (rnum << 4)) & 0xffffffff
|
||||
rnum ^= rnum >> 5
|
||||
rnum = (rnum + (rnum << 16)) & 0xffffffff
|
||||
rnum ^= rnum >> 7
|
||||
rnum ^= rnum >> 3
|
||||
rnum = (rnum ^ (rnum << 6)) & 0xffffffff
|
||||
rnum = rnum ^ (rnum >> 17)
|
||||
|
||||
seeds = [0, 4, 8, 12, 16, 20, 24, 28].map{|i| (rnum >> i) & 0xf}.map!{|e| e * e}
|
||||
sh = [seed & 2 == 2 ? 4 : 5, @part_num == 3 ? 6 : 5]
|
||||
sh.reverse! if seed & 1 == 0
|
||||
seeds.map!.with_index{|e, i| e >> sh[i % 2]}
|
||||
|
||||
@partition = (0...@bw * @bh).map do |i|
|
||||
x = i % @bw
|
||||
y = i / @bw
|
||||
if small_block
|
||||
x <<= 1
|
||||
y <<= 1
|
||||
end
|
||||
|
||||
a = (seeds[0] * x + seeds[1] * y + (rnum >> 14)) & 0x3f
|
||||
b = (seeds[2] * x + seeds[3] * y + (rnum >> 10)) & 0x3f
|
||||
c = @part_num < 3 ? 0 : (seeds[4] * x + seeds[5] * y + (rnum >> 6)) & 0x3f
|
||||
d = @part_num < 4 ? 0 : (seeds[6] * x + seeds[7] * y + (rnum >> 2)) & 0x3f
|
||||
|
||||
3 - [d, c, b, a].each_with_index.max[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def applicate_color
|
||||
mem = Fiddle::Pointer.malloc(@bw * @bh * 4)
|
||||
|
||||
if @dual_plane
|
||||
plane_arr = [0, 1, 2, 3]
|
||||
plane_arr.delete_at(@plane_selector)
|
||||
|
||||
if @partition
|
||||
(@bw * @bh).times do |i|
|
||||
part = @partition[i]
|
||||
plane_arr.each{|c| mem[i * 4 + c] = select_color(@endpoint[part][c], @endpoint[part][4 + c], @weight0[i])}
|
||||
mem[i * 4 + @plane_selector] = select_color(@endpoint[part][@plane_selector], @endpoint[part][4 + @plane_selector], @weight1[i])
|
||||
end
|
||||
else
|
||||
(@bw * @bh).times do |i|
|
||||
plane_arr.each{|c| mem[i * 4 + c] = select_color(@endpoint[0][c], @endpoint[0][4 + c], @weight0[i])}
|
||||
mem[i * 4 + @plane_selector] = select_color(@endpoint[0][@plane_selector], @endpoint[0][4 + @plane_selector], @weight1[i])
|
||||
end
|
||||
end
|
||||
elsif @partition
|
||||
(@bw * @bh).times do |i|
|
||||
part = @partition[i]
|
||||
4.times{|c| mem[i * 4 + c] = select_color(@endpoint[part][c], @endpoint[part][4 + c], @weight[i])}
|
||||
end
|
||||
else
|
||||
(@bw * @bh).times do |i|
|
||||
4.times{|c| mem[i * 4 + c] = select_color(@endpoint[0][c], @endpoint[0][4 + c], @weight[i])}
|
||||
end
|
||||
end
|
||||
|
||||
@data = mem.to_str
|
||||
end
|
||||
|
||||
def select_color(v0, v1, weight)
|
||||
v0 |= v0 << 8
|
||||
v1 |= v1 << 8
|
||||
v = (v0 * (64 - weight) + v1 * weight + 32) >> 6
|
||||
(v * 255 + 32768) / 65536
|
||||
end
|
||||
|
||||
def decode_intseq_raw(data, a, b, count)
|
||||
mask = (1 << b) - 1
|
||||
case a
|
||||
when 3
|
||||
rc = (count + 4) / 5
|
||||
ret = Array.new(rc * 5)
|
||||
m = [0, 2 + b, 4 + b * 2, 5 + b * 3, 7 + b * 4]
|
||||
rc.times do |i|
|
||||
t = (data >> b & 3) | (data >> b * 2 & 0xc) | (data >> b * 3 & 0x10) | (data >> b * 4 & 0x60) | (data >> b * 5 & 0x80)
|
||||
5.times do |j|
|
||||
ret[i * 5 + j] = [data >> m[j] & mask, TritsTable[j][t]]
|
||||
end
|
||||
data >>= b * 5 + 8
|
||||
end
|
||||
ret[0, count]
|
||||
when 5
|
||||
rc = (count + 2) / 3
|
||||
ret = Array.new(rc * 3)
|
||||
m = [0, 3 + b, 5 + b * 2]
|
||||
rc.times do |i|
|
||||
q = (data >> b & 7) | (data >> b * 2 & 0x18) | (data >> b * 3 & 0x60)
|
||||
3.times do |j|
|
||||
ret[i * 3 + j] = [data >> m[j] & mask, QuintsTable[j][q]]
|
||||
end
|
||||
data >>= b * 3 + 7
|
||||
end
|
||||
ret[0, count]
|
||||
else # 0
|
||||
(0...count).map do |i|
|
||||
[data >> b * i & mask, 0]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unquantize_endpoint(a, b, bit, val_d)
|
||||
if a == 0
|
||||
case b
|
||||
when 1
|
||||
bit * 0xff
|
||||
when 2
|
||||
bit * 0x55
|
||||
when 3
|
||||
bit << 5 | bit << 2 | bit >> 1
|
||||
when 4
|
||||
bit << 4 | bit
|
||||
when 5
|
||||
bit << 3 | bit >> 2
|
||||
when 6
|
||||
bit << 2 | bit >> 4
|
||||
when 7
|
||||
bit << 1 | bit >> 6
|
||||
else # 8
|
||||
bit
|
||||
end
|
||||
else
|
||||
val_a = (bit & 1) * 0x1ff
|
||||
tmp_b = bit >> 1
|
||||
case b
|
||||
when 1
|
||||
val_b = 0
|
||||
val_c = a == 3 ? 204 : 113
|
||||
when 2
|
||||
val_b = a == 3 ? (0b100010110) * tmp_b : (0b100001100) * tmp_b
|
||||
val_c = a == 3 ? 93 : 54
|
||||
when 3
|
||||
val_b = a == 3 ? tmp_b << 7 | tmp_b << 2 | tmp_b : tmp_b << 7 | tmp_b << 1 | tmp_b >> 1
|
||||
val_c = a == 3 ? 44 : 26
|
||||
when 4
|
||||
val_b = tmp_b << 6 | tmp_b >> (a == 3 ? 0 : 1)
|
||||
val_c = a == 3 ? 22 : 13
|
||||
when 5
|
||||
val_b = tmp_b << 5 | tmp_b >> (a == 3 ? 2 : 3)
|
||||
val_c = a == 3 ? 11 : 6
|
||||
else # 6
|
||||
val_b = tmp_b << 4 | tmp_b >> 4
|
||||
val_c = 5
|
||||
end
|
||||
t = val_d * val_c + val_b
|
||||
t ^= val_a
|
||||
(val_a & 0x80) | (t >> 2)
|
||||
end
|
||||
end
|
||||
|
||||
def unquantize_weight(a, b, bit, val_d)
|
||||
if a == 0
|
||||
case b
|
||||
when 1
|
||||
t = bit == 1 ? 63 : 0
|
||||
when 2
|
||||
t = bit << 4 | bit << 2 | bit
|
||||
when 3
|
||||
t = bit << 3 | bit
|
||||
when 4
|
||||
t = bit << 2 | bit >> 2
|
||||
else # 5
|
||||
t = bit << 1 | bit >> 4
|
||||
end
|
||||
elsif b == 0
|
||||
t = (a == 3 ? [0, 32, 63] : [0, 16, 32, 47, 63])[val_d]
|
||||
else
|
||||
val_a = (bit & 1) * 0x7f
|
||||
case b
|
||||
when 1
|
||||
val_b = 0
|
||||
val_c = a == 3 ? 50 : 28
|
||||
when 2
|
||||
val_b = (a == 3 ? 0b1000101 : 0b1000010) * bit[1]
|
||||
val_c = a == 3 ? 23 : 13
|
||||
else # 3
|
||||
val_b = (bit << 4 | bit >> 1) & 0b1100011
|
||||
val_c = 11
|
||||
end
|
||||
t = val_d * val_c + val_b
|
||||
t ^= val_a
|
||||
t = (val_a & 0x20) | (t >> 2)
|
||||
end
|
||||
t > 32 ? t + 1 : t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,14 @@
|
||||
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end
|
||||
require 'bin_utils'
|
||||
require 'fiddle'
|
||||
require 'mikunyan/decoders/astc_block_decoder'
|
||||
|
||||
module Mikunyan
|
||||
# Class for image decoding tools
|
||||
class ImageDecoder
|
||||
Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]]
|
||||
Etc1SubblockTable = [[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]]
|
||||
|
||||
# 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
|
||||
|
||||
@@ -27,100 +30,455 @@ module Mikunyan
|
||||
when 2
|
||||
decode_argb4444(width, height, bin, endian)
|
||||
when 3
|
||||
decode_rgb888(width, height, bin, endian)
|
||||
decode_rgb24(width, height, bin)
|
||||
when 4
|
||||
decode_rgba8888(width, height, bin, endian)
|
||||
decode_rgba32(width, height, bin)
|
||||
when 5
|
||||
decode_argb8888(width, height, bin, endian)
|
||||
decode_argb32(width, height, bin)
|
||||
when 7
|
||||
decode_rgb565(width, height, bin, endian)
|
||||
when 9
|
||||
decode_r16(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 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
|
||||
end
|
||||
|
||||
def self.decode_etc1(width, height, bin)
|
||||
bw = (width + 3) / 4
|
||||
bh = (height + 3) / 4
|
||||
pixels = "\0" * (64 * bw * bh)
|
||||
pixels.force_encoding('ascii-8bit')
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
block = decode_etc1_block(BinUtils.get_sint64_be(bin, (bx + by * bw) * 8)).pack('N16')
|
||||
pixels[( bx * 4 * bh + by) * 16, 16] = block.byteslice( 0, 16)
|
||||
pixels[((bx * 4 + 1) * bh + by) * 16, 16] = block.byteslice(16, 16)
|
||||
pixels[((bx * 4 + 2) * bh + by) * 16, 16] = block.byteslice(32, 16)
|
||||
pixels[((bx * 4 + 3) * bh + by) * 16, 16] = block.byteslice(48, 16)
|
||||
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(bh * 4, bw * 4, pixels).rotate_right!.crop!(0, 0, width, height)
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
def self.decode_a8(width, height, bin)
|
||||
pixels = bin.unpack('C*').map do |c|
|
||||
c << 24 | c << 16 | c << 8 | 0xff
|
||||
# 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.new(width, height, pixels)
|
||||
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)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
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)
|
||||
r = (c & 0xf800) >> 8
|
||||
g = (c & 0x07e0) >> 3
|
||||
b = (c & 0x001f) << 3
|
||||
r = r | r >> 5
|
||||
g = g | g >> 6
|
||||
b = b | b >> 5
|
||||
r << 24 | g << 16 | b << 8 | 0xff
|
||||
BinUtils.append_int8!(mem, r | r >> 5, g | g >> 6, b | b >> 5)
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
|
||||
end
|
||||
|
||||
def self.decode_rgb888(width, height, bin, endian = :big)
|
||||
if endian == :little
|
||||
ChunkyPNG::Image.from_bgr_stream(width, height, bin)
|
||||
# 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
|
||||
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).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
|
||||
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
|
||||
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 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 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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 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)
|
||||
bw = (width + 3) / 4
|
||||
bh = (height + 3) / 4
|
||||
ret = ChunkyPNG::Image.new(bh * 4, bw * 4)
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
block = decode_etc1_block(BinUtils.get_sint64_be(bin, (bx + by * bw) * 8))
|
||||
ret.replace!(ChunkyPNG::Image.from_rgb_stream(4, 4, block), by * 4, bx * 4)
|
||||
end
|
||||
end
|
||||
ret.crop(0, 0, height, width).rotate_left
|
||||
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)
|
||||
bw = (width + 3) / 4
|
||||
bh = (height + 3) / 4
|
||||
ret = ChunkyPNG::Image.new(bh * 4, bw * 4)
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
block = decode_etc2_block(BinUtils.get_sint64_be(bin, (bx + by * bw) * 8))
|
||||
ret.replace!(ChunkyPNG::Image.from_rgb_stream(4, 4, block.join), by * 4, bx * 4)
|
||||
end
|
||||
end
|
||||
ret.crop(0, 0, height, width).rotate_left
|
||||
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)
|
||||
bw = (width + 3) / 4
|
||||
bh = (height + 3) / 4
|
||||
ret = ChunkyPNG::Image.new(bh * 4, bw * 4)
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
alpha = decode_etc2alpha_block(BinUtils.get_int64_be(bin, (bx + by * bw) * 16))
|
||||
block = decode_etc2_block(BinUtils.get_int64_be(bin, (bx + by * bw) * 16 + 8))
|
||||
mem = String.new(capacity: 64)
|
||||
16.times{|i| mem << block[i] + alpha[i]}
|
||||
ret.replace!(ChunkyPNG::Image.from_rgba_stream(4, 4, mem), by * 4, bx * 4)
|
||||
end
|
||||
end
|
||||
ret.crop(0, 0, height, width).rotate_left
|
||||
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)
|
||||
bw = (width + blocksize - 1) / blocksize
|
||||
bh = (height + blocksize - 1) / blocksize
|
||||
ret = ChunkyPNG::Image.new(bw * blocksize, bh * blocksize)
|
||||
bh.times do |by|
|
||||
bw.times do |bx|
|
||||
block = DecodeHelper::AstcBlockDecoder.new(bin.byteslice((by * bw + bx) * 16, 16), blocksize, blocksize).data
|
||||
ret.replace!(ChunkyPNG::Image.from_rgba_stream(blocksize, blocksize, block), bx * blocksize, by * blocksize)
|
||||
end
|
||||
end
|
||||
ret.crop(0, 0, width, height).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
|
||||
}
|
||||
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
|
||||
ChunkyPNG::Image.from_rgb_stream(width, height, bin)
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode_rgba4444(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
|
||||
c << 4 | c
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
def self.decode_argb4444(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
|
||||
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
|
||||
c << 4 | c
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
def self.decode_rgba8888(width, height, bin, endian = :big)
|
||||
if endian == :little
|
||||
ChunkyPNG::Image.from_abgr_stream(width, height, bin)
|
||||
else
|
||||
ChunkyPNG::Image.from_rgba_stream(width, height, bin)
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode_argb8888(width, height, bin, endian = :big)
|
||||
pixels = bin.unpack(endian == :little ? 'V*' : 'N*').map do |c|
|
||||
c = ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24)
|
||||
end
|
||||
ChunkyPNG::Image.new(width, height, pixels)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]]
|
||||
Etc1SubblockTable = [[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]]
|
||||
Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64]
|
||||
Etc2AlphaModTable = [
|
||||
[-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]
|
||||
]
|
||||
|
||||
def self.decode_etc1_block(bin)
|
||||
colors = []
|
||||
codes = [bin >> 37 & 7, bin >> 34 & 7]
|
||||
@@ -140,22 +498,140 @@ module Mikunyan
|
||||
colors[1] = colors[1] | (colors[1] >> 5 & 0x70707)
|
||||
end
|
||||
|
||||
ret = Array.new(16, 0)
|
||||
mem = Fiddle::Pointer.malloc(48)
|
||||
16.times do |i|
|
||||
modifier = Etc1ModifierTable[codes[subblocks[i]]][bin[i]]
|
||||
ret[i] = etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
|
||||
mem[i * 3, 3] = etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
|
||||
end
|
||||
ret
|
||||
mem.to_str
|
||||
end
|
||||
|
||||
def self.etc1colormod(color, modifier)
|
||||
r = (color >> 16 & 0xff) + modifier
|
||||
g = (color >> 8 & 0xff) + modifier
|
||||
b = (color & 0xff) + modifier
|
||||
r = (r > 255 ? 255 : r < 0 ? 0 : r)
|
||||
g = (g > 255 ? 255 : g < 0 ? 0 : g)
|
||||
b = (b > 255 ? 255 : b < 0 ? 0 : b)
|
||||
r << 24 | g << 16 | b << 8 | 0xff
|
||||
r.clamp(0, 255).chr + g.clamp(0, 255).chr + b.clamp(0, 255).chr
|
||||
end
|
||||
|
||||
def self.decode_etc2_block(bin)
|
||||
if bin[33] == 0
|
||||
# individual
|
||||
colors = [0, 0]
|
||||
colors[0] = bin >> 40 & 0xf0f0f0
|
||||
colors[0] = colors[0] | colors[0] >> 4
|
||||
colors[1] = bin >> 36 & 0xf0f0f0
|
||||
colors[1] = colors[1] | colors[1] >> 4
|
||||
codes = [bin >> 37 & 7, bin >> 34 & 7]
|
||||
subblocks = Etc1SubblockTable[bin[32]]
|
||||
(0...16).map do |i|
|
||||
modifier = Etc1ModifierTable[codes[subblocks[i]]][bin[i]]
|
||||
etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
|
||||
end
|
||||
else
|
||||
r = bin >> 59
|
||||
dr = (bin >> 56 & 3) - (bin >> 56 & 4)
|
||||
g = bin >> 51 & 0x1f
|
||||
dg = (bin >> 48 & 3) - (bin >> 48 & 4)
|
||||
b = bin >> 43 & 0x1f
|
||||
db = (bin >> 40 & 3) - (bin >> 40 & 4)
|
||||
if r + dr < 0 || r + dr > 31
|
||||
# T mode
|
||||
base1 = (bin >> 49 & 0xc00) | (bin >> 48 & 0x3ff)
|
||||
base1 = (base1 & 0xf00) << 8 | (base1 & 0xf0) << 4 | (base1 & 0xf)
|
||||
base1 = (base1 << 4) | base1
|
||||
base2 = bin >> 36 & 0xfff
|
||||
base2 = (base2 & 0xf00) << 8 | (base2 & 0xf0) << 4 | (base2 & 0xf)
|
||||
base2 = (base2 << 4) | base2
|
||||
d = Etc2DistanceTable[(bin >> 33 & 6) + bin[32]]
|
||||
colors = [[base1].pack('N')[1,3], etc1colormod(base2, d), [base2].pack('N')[1,3], etc1colormod(base2, -d)]
|
||||
(0...16).map{|i| colors[bin[i] + bin[i + 16] * 2]}
|
||||
elsif g + dg < 0 || g + dg > 31
|
||||
# H mode
|
||||
base1 = (bin >> 51 & 0xfe0) | (bin >> 48 & 0x18) | (bin >> 47 & 7)
|
||||
base1 = (base1 & 0xf00) << 8 | (base1 & 0xf0) << 4 | (base1 & 0xf)
|
||||
base1 = (base1 << 4) | base1
|
||||
base2 = bin >> 35 & 0xfff
|
||||
base2 = (base2 & 0xf00) << 8 | (base2 & 0xf0) << 4 | (base2 & 0xf)
|
||||
base2 = (base2 << 4) | base2
|
||||
d = Etc2DistanceTable[bin[34] * 2 + bin[32]]
|
||||
colors = [etc1colormod(base1, d), etc1colormod(base1, -d), etc1colormod(base2, d), etc1colormod(base2, -d)]
|
||||
(0...16).map{|i| colors[bin[i] + bin[i + 16] * 2]}
|
||||
elsif b + db < 0 || b + db > 31
|
||||
# planar mode
|
||||
color_or = (bin >> 55 & 0xfc) | (bin >> 61 & 0x03)
|
||||
color_og = (bin >> 49 & 0x80) | (bin >> 48 & 0x7e) | bin[56]
|
||||
color_ob = (bin >> 41 & 0x80) | (bin >> 38 & 0x60) | (bin >> 37 & 0x1c) | (bin >> 47 & 2) | bin[44]
|
||||
color_hr = (bin >> 31 & 0xf8) | (bin >> 30 & 0x04) | (bin >> 37 & 0x03)
|
||||
color_hg = (bin >> 24 & 0xfe) | bin[31]
|
||||
color_hb = (bin >> 17 & 0xfc) | (bin >> 23 & 0x03)
|
||||
color_vr = (bin >> 11 & 0xfc) | (bin >> 17 & 0x03)
|
||||
color_vg = (bin >> 5 & 0xfe) | bin[12]
|
||||
color_vb = (bin << 2 & 0xfc) | (bin >> 4 & 0x03)
|
||||
(0...16).map do |i|
|
||||
x = i / 4
|
||||
y = i % 4
|
||||
r = (x * (color_hr - color_or) + y * (color_vr - color_or) + 4 * color_or + 2) >> 2
|
||||
g = (x * (color_hg - color_og) + y * (color_vg - color_og) + 4 * color_og + 2) >> 2
|
||||
b = (x * (color_hb - color_ob) + y * (color_vb - color_ob) + 4 * color_ob + 2) >> 2
|
||||
r.clamp(0, 255).chr + g.clamp(0, 255).chr + b.clamp(0, 255).chr
|
||||
end
|
||||
else
|
||||
# differential mode
|
||||
colors = [0, 0]
|
||||
colors[0] = bin >> 40 & 0xf8f8f8
|
||||
colors[1] = colors[0] + (dr << 19) + (dg << 11) + (db << 3)
|
||||
colors[0] = colors[0] | (colors[0] >> 5 & 0x70707)
|
||||
colors[1] = colors[1] | (colors[1] >> 5 & 0x70707)
|
||||
codes = [bin >> 37 & 7, bin >> 34 & 7]
|
||||
subblocks = Etc1SubblockTable[bin[32]]
|
||||
(0...16).map do |i|
|
||||
modifier = Etc1ModifierTable[codes[subblocks[i]]][bin[i]]
|
||||
etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.decode_etc2alpha_block(bin)
|
||||
if bin & 0xf0000000000000 == 0
|
||||
Array.new(16, (bin >> 56).chr)
|
||||
else
|
||||
base = bin >> 56
|
||||
mult = bin >> 52 & 0xf
|
||||
table = Etc2AlphaModTable[bin >> 48 & 0xf]
|
||||
(0...16).map{|i| (base + table[bin >> i*3 & 7] * mult).clamp(0, 255).chr}
|
||||
end
|
||||
end
|
||||
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
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
|
||||
|
||||
# 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
|
||||
@@ -11,46 +22,70 @@ module Mikunyan
|
||||
@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
|
||||
|
||||
def [](name)
|
||||
if array? && name.class == Integer
|
||||
@value[name]
|
||||
# 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[name]
|
||||
@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
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
module Mikunyan
|
||||
# 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
|
||||
@@ -28,6 +42,9 @@ module Mikunyan
|
||||
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]
|
||||
@@ -49,6 +66,9 @@ module Mikunyan
|
||||
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__)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
module Mikunyan
|
||||
VERSION = "3.9.0"
|
||||
# version string
|
||||
VERSION = "3.9.4"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user