mikunyan 3.9.0 → 3.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -5
- data/exe/mikunyan-json +19 -8
- data/lib/mikunyan.rb +4 -0
- data/lib/mikunyan/asset.rb +156 -99
- data/lib/mikunyan/asset_bundle.rb +18 -8
- data/lib/mikunyan/binary_reader.rb +36 -2
- data/lib/mikunyan/constants.rb +1 -0
- data/lib/mikunyan/decoders/image_decoder.rb +352 -58
- data/lib/mikunyan/object_value.rb +39 -4
- data/lib/mikunyan/type_tree.rb +20 -0
- data/lib/mikunyan/version.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c39b71c73cdf957d6227efcd70aca3813759e15
|
4
|
+
data.tar.gz: d755c5b99e3dd798f52b8149872e359717470533
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 788e10112ff00680cc19adbd2327c4a7bf80b8a097accdf58c976e2bbcb9f7c4f917abf7bebab54e0454e540b1f65f86dfd277cd13ee4b994cb3279e0d3c8f1a
|
7
|
+
data.tar.gz: 03ad199057859b6924aeafd8b52c31fe959a6aaeb989b475013972dfd88e79900284b2b4191abe92f1f4f1a0f02ad060d681116012a255fe6753ef6d0ec03f3a
|
data/README.md
CHANGED
@@ -18,6 +18,14 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install mikunyan
|
20
20
|
|
21
|
+
If you want to install development build:
|
22
|
+
|
23
|
+
$ git clone https://github.com/Ishotihadus/mikunyan
|
24
|
+
$ cd mikunyan
|
25
|
+
$ bundle install
|
26
|
+
$ rake build
|
27
|
+
$ gem install -l pkg/mikunyan-3.9.x.gem
|
28
|
+
|
21
29
|
## Usage
|
22
30
|
|
23
31
|
### Basic Usage
|
@@ -96,7 +104,7 @@ obj.key
|
|
96
104
|
|
97
105
|
You can get png file directly from Texture2D asset. Output object's class is `ChunkyPNG::Image`.
|
98
106
|
|
99
|
-
|
107
|
+
Only some basic texture formats (1--5, 7, 9, 13--20, 22, 62, and 63) and ETC_RGB4 (34) are available.
|
100
108
|
|
101
109
|
```ruby
|
102
110
|
require 'mikunyan/decoders'
|
@@ -111,16 +119,19 @@ img = Mikunyan::ImageDecoder.decode_object(obj)
|
|
111
119
|
img.save('mikunyan.png')
|
112
120
|
```
|
113
121
|
|
114
|
-
|
122
|
+
Mikunyan cannot decode ASTC files. Use `Mikunyan::ImageDecoder.create_astc_file` instead.
|
123
|
+
|
124
|
+
### Json / YAML Outputer
|
115
125
|
|
116
|
-
`mikunyan-json` is
|
126
|
+
`mikunyan-json` is an executable command for converting unity3d to json.
|
117
127
|
|
118
128
|
$ mikunyan-json bundle.unity3d > bundle.json
|
119
129
|
|
120
130
|
Available options:
|
121
131
|
|
122
132
|
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
|
123
|
-
- `--pretty` (`-p`): prettify output json
|
133
|
+
- `--pretty` (`-p`): prettify output json (`mikunyan-json` only)
|
134
|
+
- `--yaml` (`-y`): YAML mode
|
124
135
|
|
125
136
|
## Dependencies
|
126
137
|
|
@@ -129,7 +140,7 @@ Available options:
|
|
129
140
|
- [bin_utils](https://rubygems.org/gems/bin_utils)
|
130
141
|
- [chunky_png](https://rubygems.org/gems/chunky_png)
|
131
142
|
|
132
|
-
Mikunyan
|
143
|
+
Mikunyan uses [oily_png](https://rubygems.org/gems/oily_png) instead of chunky_png if available.
|
133
144
|
|
134
145
|
## FAQ
|
135
146
|
|
data/exe/mikunyan-json
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'mikunyan'
|
3
|
-
require 'json'
|
4
3
|
require 'base64'
|
5
4
|
|
6
5
|
def obj64(obj)
|
@@ -19,7 +18,7 @@ def obj64(obj)
|
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
opts = {:as_asset => false, :pretty => false}
|
21
|
+
opts = {:as_asset => false, :pretty => false, :yaml => false}
|
23
22
|
arg = nil
|
24
23
|
i = 0
|
25
24
|
while i < ARGV.count
|
@@ -29,9 +28,10 @@ while i < ARGV.count
|
|
29
28
|
opts[:as_asset] = true
|
30
29
|
when '--pretty', '-p'
|
31
30
|
opts[:pretty] = true
|
31
|
+
when '--yaml', '-y'
|
32
|
+
opts[:yaml] = true
|
32
33
|
else
|
33
34
|
warn("Unknown option: #{ARGV[i]}")
|
34
|
-
exit(1)
|
35
35
|
end
|
36
36
|
else
|
37
37
|
arg = ARGV[i] unless arg
|
@@ -39,6 +39,10 @@ while i < ARGV.count
|
|
39
39
|
i += 1
|
40
40
|
end
|
41
41
|
|
42
|
+
if option[:pretty] && option[:yaml]
|
43
|
+
warn("Option --pretty is ignored if --yaml is specified.")
|
44
|
+
end
|
45
|
+
|
42
46
|
unless File.file?(arg)
|
43
47
|
warn("File not found: #{arg}")
|
44
48
|
exit(1)
|
@@ -51,7 +55,7 @@ if opts[:as_asset]
|
|
51
55
|
objs = []
|
52
56
|
asset.path_ids.each do |e|
|
53
57
|
obj = asset.parse_object_simple(e)
|
54
|
-
objs <<
|
58
|
+
objs << obj
|
55
59
|
end
|
56
60
|
assets[asset.name] = objs
|
57
61
|
else
|
@@ -60,14 +64,21 @@ else
|
|
60
64
|
objs = []
|
61
65
|
asset.path_ids.each do |e|
|
62
66
|
obj = asset.parse_object_simple(e)
|
63
|
-
objs <<
|
67
|
+
objs << obj
|
64
68
|
end
|
65
69
|
assets[asset.name] = objs
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
|
-
if opts[:
|
70
|
-
|
73
|
+
if opts[:yaml]
|
74
|
+
require 'yaml'
|
75
|
+
puts YAML.dump(assets)
|
71
76
|
else
|
72
|
-
|
77
|
+
require 'json'
|
78
|
+
assets = assets.map{|k, v| [k, obj64(v)]}.to_h
|
79
|
+
if opts[:pretty]
|
80
|
+
puts JSON.pretty_generate(assets)
|
81
|
+
else
|
82
|
+
puts JSON.generate(assets)
|
83
|
+
end
|
73
84
|
end
|
data/lib/mikunyan.rb
CHANGED
data/lib/mikunyan/asset.rb
CHANGED
@@ -1,21 +1,133 @@
|
|
1
1
|
module Mikunyan
|
2
|
+
# Class for representing Unity Asset
|
3
|
+
# @attr_reader [String] name Asset name
|
4
|
+
# @attr_reader [Integer] format file format number
|
5
|
+
# @attr_reader [String] generator_version version string of generator
|
6
|
+
# @attr_reader [Integer] target_platform target platform number
|
7
|
+
# @attr_reader [Symbol] endian data endianness (:little or :big)
|
8
|
+
# @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
|
9
|
+
# @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects
|
10
|
+
# @attr_reader [Array<Integer>] add_ids ?
|
11
|
+
# @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
|
2
12
|
class Asset
|
3
|
-
|
13
|
+
attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
|
14
|
+
|
15
|
+
# Struct for representing Asset class definition
|
16
|
+
# @attr [Integer] class_id class ID
|
17
|
+
# @attr [Integer,nil] script_id script ID
|
18
|
+
# @attr [String] hash hash value (16 or 32 bytes)
|
19
|
+
# @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
|
4
20
|
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
|
21
|
+
|
22
|
+
# Struct for representing Asset object information
|
23
|
+
# @attr [Integer] path_id path ID
|
24
|
+
# @attr [Integer] offset data offset
|
25
|
+
# @attr [Integer] size data size
|
26
|
+
# @attr [Integer,nil] type_id type ID
|
27
|
+
# @attr [Integer,nil] class_id class ID
|
28
|
+
# @attr [Integer,nil] class_idx class definition index
|
29
|
+
# @attr [Boolean] destroyed? destroyed or not
|
30
|
+
# @attr [String] data binary data of object
|
5
31
|
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
|
32
|
+
|
33
|
+
# Struct for representing Asset reference information
|
34
|
+
# @attr [String] path path
|
35
|
+
# @attr [String] guid GUID (16 bytes)
|
36
|
+
# @attr [Integer] type ?
|
37
|
+
# @attr [String] file_path Asset name
|
6
38
|
Reference = Struct.new(:path, :guid, :type, :file_path)
|
7
39
|
|
40
|
+
# Load Asset from binary string
|
41
|
+
# @param [String] bin binary data
|
42
|
+
# @param [String] name Asset name
|
43
|
+
# @return [Mikunyan::Asset] deserialized Asset object
|
44
|
+
def self.load(bin, name)
|
45
|
+
r = Asset.new(name)
|
46
|
+
r.send(:load, bin)
|
47
|
+
r
|
48
|
+
end
|
49
|
+
|
50
|
+
# Load Asset from file
|
51
|
+
# @param [String] file file name
|
52
|
+
# @param [String] name Asset name (automatically generated if not specified)
|
53
|
+
# @return [Mikunyan::Asset] deserialized Asset object
|
54
|
+
def self.file(file, name=nil)
|
55
|
+
name = File.basename(name, '.*') unless name
|
56
|
+
Asset.load(File.binread(file), name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns list of all path IDs
|
60
|
+
# @return [Array<Integer>] list of all path IDs
|
61
|
+
def path_ids
|
62
|
+
@objects.map{|e| e.path_id}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns list of containers
|
66
|
+
# @return [Array<Hash>,nil] list of all containers
|
67
|
+
def containers
|
68
|
+
obj = parse_object(1)
|
69
|
+
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
70
|
+
obj.m_Container.value.map do |e|
|
71
|
+
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parse object of given path ID
|
76
|
+
# @param [Integer,ObjectData] path_id path ID or object
|
77
|
+
# @return [Mikunyan::ObjectValue,nil] parsed object
|
78
|
+
def parse_object(path_id)
|
79
|
+
if path_id.class == Integer
|
80
|
+
obj = @objects.find{|e| e.path_id == path_id}
|
81
|
+
return nil unless obj
|
82
|
+
elsif path_id.class == ObjectData
|
83
|
+
obj = path_id
|
84
|
+
else
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
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})
|
89
|
+
type_tree = Asset.parse_type_tree(klass)
|
90
|
+
return nil unless type_tree
|
91
|
+
|
92
|
+
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Parse object of given path ID and simplify it
|
96
|
+
# @param [Integer,ObjectData] path_id path ID or object
|
97
|
+
# @return [Hash,nil] parsed object
|
98
|
+
def parse_object_simple(path_id)
|
99
|
+
Asset.object_simplify(parse_object(path_id))
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns object type name string
|
103
|
+
# @param [Integer,ObjectData] path_id path ID or object
|
104
|
+
# @return [String,nil] type name
|
105
|
+
def object_type(path_id)
|
106
|
+
if path_id.class == Integer
|
107
|
+
obj = @objects.find{|e| e.path_id == path_id}
|
108
|
+
return nil unless obj
|
109
|
+
elsif path_id.class == ObjectData
|
110
|
+
obj = path_id
|
111
|
+
else
|
112
|
+
return nil
|
113
|
+
end
|
114
|
+
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})
|
115
|
+
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
116
|
+
klass.type_tree.nodes[0].type
|
117
|
+
elsif klass
|
118
|
+
Mikunyan::CLASS_ID[klass.class_id]
|
119
|
+
else
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
8
126
|
def initialize(name)
|
9
127
|
@name = name
|
10
128
|
@endian = :big
|
11
129
|
end
|
12
130
|
|
13
|
-
def self.file(file, name)
|
14
|
-
r = Asset.new(name)
|
15
|
-
r.load(File.binread(file))
|
16
|
-
r
|
17
|
-
end
|
18
|
-
|
19
131
|
def load(bin)
|
20
132
|
br = BinaryReader.new(bin)
|
21
133
|
metadata_size = br.i32u
|
@@ -108,97 +220,6 @@ module Mikunyan
|
|
108
220
|
end
|
109
221
|
end
|
110
222
|
|
111
|
-
def path_ids
|
112
|
-
@objects.map{|e| e.path_id}
|
113
|
-
end
|
114
|
-
|
115
|
-
def containers
|
116
|
-
obj = parse_object(1)
|
117
|
-
return nil unless obj && obj.m_Container && obj.m_Container.array?
|
118
|
-
obj.m_Container.value.map do |e|
|
119
|
-
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def parse_object(path_id)
|
124
|
-
if path_id.class == Integer
|
125
|
-
obj = @objects.find{|e| e.path_id == path_id}
|
126
|
-
return nil unless obj
|
127
|
-
elsif path_id.class == ObjectData
|
128
|
-
obj = path_id
|
129
|
-
else
|
130
|
-
return nil
|
131
|
-
end
|
132
|
-
|
133
|
-
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})
|
134
|
-
type_tree = Asset.parse_type_tree(klass)
|
135
|
-
return nil unless type_tree
|
136
|
-
|
137
|
-
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
|
138
|
-
end
|
139
|
-
|
140
|
-
def parse_object_simple(path_id)
|
141
|
-
Asset.object_simplify(parse_object(path_id))
|
142
|
-
end
|
143
|
-
|
144
|
-
def object_type(path_id)
|
145
|
-
if path_id.class == Integer
|
146
|
-
obj = @objects.find{|e| e.path_id == path_id}
|
147
|
-
return nil unless obj
|
148
|
-
elsif path_id.class == ObjectData
|
149
|
-
obj = path_id
|
150
|
-
else
|
151
|
-
return nil
|
152
|
-
end
|
153
|
-
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})
|
154
|
-
if klass && klass.type_tree && klass.type_tree.nodes[0]
|
155
|
-
klass.type_tree.nodes[0].type
|
156
|
-
elsif klass
|
157
|
-
Mikunyan::CLASS_ID[klass.class_id]
|
158
|
-
else
|
159
|
-
nil
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
def self.parse_type_tree(klass)
|
164
|
-
return nil unless klass.type_tree
|
165
|
-
nodes = klass.type_tree.nodes
|
166
|
-
tree = {}
|
167
|
-
stack = []
|
168
|
-
nodes.each do |node|
|
169
|
-
this = {:name => node.name, :node => node, :children => []}
|
170
|
-
if node.depth == 0
|
171
|
-
tree = this
|
172
|
-
else
|
173
|
-
stack[node.depth - 1][:children] << this
|
174
|
-
end
|
175
|
-
stack[node.depth] = this
|
176
|
-
end
|
177
|
-
tree
|
178
|
-
end
|
179
|
-
|
180
|
-
def self.object_simplify(obj)
|
181
|
-
if obj.class != ObjectValue
|
182
|
-
obj
|
183
|
-
elsif obj.type == 'pair'
|
184
|
-
[object_simplify(obj['first']), object_simplify(obj['second'])]
|
185
|
-
elsif obj.type == 'map' && obj.array?
|
186
|
-
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
|
187
|
-
elsif obj.value?
|
188
|
-
object_simplify(obj.value)
|
189
|
-
elsif obj.array?
|
190
|
-
obj.value.map{|e| object_simplify(e)}
|
191
|
-
else
|
192
|
-
hash = {}
|
193
|
-
obj.keys.each do |key|
|
194
|
-
hash[key] = object_simplify(obj[key])
|
195
|
-
end
|
196
|
-
hash
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
private
|
201
|
-
|
202
223
|
def parse_object_private(br, type_tree)
|
203
224
|
r = nil
|
204
225
|
node = type_tree[:node]
|
@@ -230,7 +251,6 @@ module Mikunyan
|
|
230
251
|
children.each do |child|
|
231
252
|
r[child[:name]] = parse_object_private(br, child)
|
232
253
|
end
|
233
|
-
br.jmp(pos + node.size)
|
234
254
|
else
|
235
255
|
pos = br.pos
|
236
256
|
value = nil
|
@@ -268,5 +288,42 @@ module Mikunyan
|
|
268
288
|
br.align(4) if node.flags & 0x4000 != 0
|
269
289
|
r
|
270
290
|
end
|
291
|
+
|
292
|
+
def self.object_simplify(obj)
|
293
|
+
if obj.class != ObjectValue
|
294
|
+
obj
|
295
|
+
elsif obj.type == 'pair'
|
296
|
+
[object_simplify(obj['first']), object_simplify(obj['second'])]
|
297
|
+
elsif obj.type == 'map' && obj.array?
|
298
|
+
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
|
299
|
+
elsif obj.value?
|
300
|
+
object_simplify(obj.value)
|
301
|
+
elsif obj.array?
|
302
|
+
obj.value.map{|e| object_simplify(e)}
|
303
|
+
else
|
304
|
+
hash = {}
|
305
|
+
obj.keys.each do |key|
|
306
|
+
hash[key] = object_simplify(obj[key])
|
307
|
+
end
|
308
|
+
hash
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.parse_type_tree(klass)
|
313
|
+
return nil unless klass.type_tree
|
314
|
+
nodes = klass.type_tree.nodes
|
315
|
+
tree = {}
|
316
|
+
stack = []
|
317
|
+
nodes.each do |node|
|
318
|
+
this = {:name => node.name, :node => node, :children => []}
|
319
|
+
if node.depth == 0
|
320
|
+
tree = this
|
321
|
+
else
|
322
|
+
stack[node.depth - 1][:children] << this
|
323
|
+
end
|
324
|
+
stack[node.depth] = this
|
325
|
+
end
|
326
|
+
tree
|
327
|
+
end
|
271
328
|
end
|
272
329
|
end
|
@@ -1,19 +1,33 @@
|
|
1
1
|
require 'extlz4'
|
2
2
|
|
3
3
|
module Mikunyan
|
4
|
+
# Class for representing Unity AssetBundle
|
5
|
+
# @attr_reader [String] signature file signature (UnityRaw or UnityFS)
|
6
|
+
# @attr_reader [Integer] format file format number
|
7
|
+
# @attr_reader [String] unity_version version string of Unity to use this AssetBundle
|
8
|
+
# @attr_reader [String] generator_version version string of generator
|
9
|
+
# @attr_reader [Array<Mikunyan::Asset>] assets included Assets
|
4
10
|
class AssetBundle
|
5
|
-
|
11
|
+
attr_reader :signature, :format, :unity_version, :generator_version, :assets
|
6
12
|
|
13
|
+
# Load AssetBundle from binary string
|
14
|
+
# @param [String] bin binary data
|
15
|
+
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
7
16
|
def self.load(bin)
|
8
17
|
r = AssetBundle.new
|
9
|
-
r.load
|
18
|
+
r.send(:load, bin)
|
10
19
|
r
|
11
20
|
end
|
12
21
|
|
22
|
+
# Load AssetBundle from file
|
23
|
+
# @param [String] file file name
|
24
|
+
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
|
13
25
|
def self.file(file)
|
14
26
|
AssetBundle.load(File.binread(file))
|
15
27
|
end
|
16
28
|
|
29
|
+
private
|
30
|
+
|
17
31
|
def load(bin)
|
18
32
|
br = BinaryReader.new(bin)
|
19
33
|
@signature = br.cstr
|
@@ -31,8 +45,6 @@ module Mikunyan
|
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
34
|
-
private
|
35
|
-
|
36
48
|
def load_unity_raw(br)
|
37
49
|
@assets = []
|
38
50
|
|
@@ -48,8 +60,7 @@ module Mikunyan
|
|
48
60
|
asset_size = br.i32u
|
49
61
|
br.jmp(asset_pos + asset_header_size - 4)
|
50
62
|
asset_data = br.read(asset_size)
|
51
|
-
asset = Asset.
|
52
|
-
asset.load(asset_data)
|
63
|
+
asset = Asset.load(asset_data, asset_name)
|
53
64
|
@assets << asset
|
54
65
|
end
|
55
66
|
end
|
@@ -77,8 +88,7 @@ module Mikunyan
|
|
77
88
|
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
|
78
89
|
|
79
90
|
asset_blocks.each do |b|
|
80
|
-
asset = Asset.
|
81
|
-
asset.load(raw_data.byteslice(b[:offset], b[:size]))
|
91
|
+
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name])
|
82
92
|
@assets << asset
|
83
93
|
end
|
84
94
|
end
|
@@ -1,9 +1,16 @@
|
|
1
1
|
require 'bin_utils'
|
2
2
|
|
3
3
|
module Mikunyan
|
4
|
+
# Class for manipulating binary string
|
5
|
+
# @attr [Symbol] endian endianness
|
6
|
+
# @attr [Integer] pos position
|
7
|
+
# @attr [Integer] length data size
|
4
8
|
class BinaryReader
|
5
9
|
attr_accessor :endian, :pos, :length
|
6
10
|
|
11
|
+
# Constructor
|
12
|
+
# @param [String] data binary string
|
13
|
+
# @param [Symbol] endian endianness
|
7
14
|
def initialize(data, endian = :big)
|
8
15
|
@data = data
|
9
16
|
@pos = 0
|
@@ -11,104 +18,131 @@ module Mikunyan
|
|
11
18
|
@endian = endian
|
12
19
|
end
|
13
20
|
|
21
|
+
# Returns whether little endian or not
|
22
|
+
# @return [Boolean]
|
14
23
|
def little?
|
15
24
|
@endian == :little
|
16
25
|
end
|
17
26
|
|
18
|
-
|
27
|
+
# Jump to given position
|
28
|
+
# @param [Integer] pos position
|
29
|
+
def jmp(pos=0)
|
19
30
|
@pos = pos
|
20
31
|
end
|
21
32
|
|
22
|
-
|
33
|
+
# Advance position given size
|
34
|
+
# @param [Integer] size size
|
35
|
+
def adv(size=0)
|
23
36
|
@pos += size
|
24
37
|
end
|
25
38
|
|
39
|
+
# Round up position to multiple of given size
|
40
|
+
# @param [Integer] size size
|
26
41
|
def align(size)
|
27
42
|
@pos = (@pos + size - 1) / size * size
|
28
43
|
end
|
29
44
|
|
45
|
+
# Read given size of binary string and seek
|
46
|
+
# @param [Integer] size size
|
47
|
+
# @return [String] data
|
30
48
|
def read(size)
|
31
49
|
data = @data.byteslice(@pos, size)
|
32
50
|
@pos += size
|
33
51
|
data
|
34
52
|
end
|
35
53
|
|
54
|
+
# Read string until null character
|
55
|
+
# @return [String] string
|
36
56
|
def cstr
|
37
57
|
r = @data.unpack("@#{pos}Z*")[0]
|
38
58
|
@pos += r.bytesize + 1
|
39
59
|
r
|
40
60
|
end
|
41
61
|
|
62
|
+
# Read 8bit signed integer
|
42
63
|
def i8
|
43
64
|
i8s
|
44
65
|
end
|
45
66
|
|
67
|
+
# Read 8bit signed integer
|
46
68
|
def i8s
|
47
69
|
r = BinUtils.get_sint8(@data, @pos)
|
48
70
|
@pos += 1
|
49
71
|
r
|
50
72
|
end
|
51
73
|
|
74
|
+
# Read 8bit unsigned integer
|
52
75
|
def i8u
|
53
76
|
r = BinUtils.get_int8(@data, @pos)
|
54
77
|
@pos += 1
|
55
78
|
r
|
56
79
|
end
|
57
80
|
|
81
|
+
# Read 16bit signed integer
|
58
82
|
def i16
|
59
83
|
i16s
|
60
84
|
end
|
61
85
|
|
86
|
+
# Read 16bit signed integer
|
62
87
|
def i16s
|
63
88
|
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
|
64
89
|
@pos += 2
|
65
90
|
r
|
66
91
|
end
|
67
92
|
|
93
|
+
# Read 16bit unsigned integer
|
68
94
|
def i16u
|
69
95
|
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
|
70
96
|
@pos += 2
|
71
97
|
r
|
72
98
|
end
|
73
99
|
|
100
|
+
# Read 32bit signed integer
|
74
101
|
def i32
|
75
102
|
i32s
|
76
103
|
end
|
77
104
|
|
105
|
+
# Read 32bit signed integer
|
78
106
|
def i32s
|
79
107
|
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
|
80
108
|
@pos += 4
|
81
109
|
r
|
82
110
|
end
|
83
111
|
|
112
|
+
# Read 32bit unsigned integer
|
84
113
|
def i32u
|
85
114
|
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
|
86
115
|
@pos += 4
|
87
116
|
r
|
88
117
|
end
|
89
118
|
|
119
|
+
# Read 64bit signed integer
|
90
120
|
def i64
|
91
121
|
i64s
|
92
122
|
end
|
93
123
|
|
124
|
+
# Read 64bit signed integer
|
94
125
|
def i64s
|
95
126
|
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
|
96
127
|
@pos += 8
|
97
128
|
r
|
98
129
|
end
|
99
130
|
|
131
|
+
# Read 64bit unsigned integer
|
100
132
|
def i64u
|
101
133
|
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
|
102
134
|
@pos += 8
|
103
135
|
r
|
104
136
|
end
|
105
137
|
|
138
|
+
# Read 32bit floating point value
|
106
139
|
def float
|
107
140
|
r = little? ? @data.byteslice(@pos, 4).unpack('e')[0] : @data.byteslice(@pos, 4).unpack('g')[0]
|
108
141
|
@pos += 4
|
109
142
|
r
|
110
143
|
end
|
111
144
|
|
145
|
+
# Read 64bit floating point value
|
112
146
|
def double
|
113
147
|
r = little? ? @data.byteslice(@pos, 8).unpack('E')[0] : @data.byteslice(@pos, 8).unpack('G')[0]
|
114
148
|
@pos += 8
|
data/lib/mikunyan/constants.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end
|
2
2
|
require 'bin_utils'
|
3
|
+
require 'fiddle'
|
3
4
|
|
4
5
|
module Mikunyan
|
6
|
+
# Class for image decoding tools
|
5
7
|
class ImageDecoder
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
# Decode image from Mikunyan::ObjectValue
|
9
|
+
# @param [Mikunyan::ObjectValue] object object to decode
|
10
|
+
# @return [ChunkyPNG::Image,nil] decoded image
|
9
11
|
def self.decode_object(object)
|
10
12
|
return nil unless object.class == ObjectValue
|
11
13
|
|
@@ -27,100 +29,364 @@ module Mikunyan
|
|
27
29
|
when 2
|
28
30
|
decode_argb4444(width, height, bin, endian)
|
29
31
|
when 3
|
30
|
-
decode_rgb888(width, height, bin
|
32
|
+
decode_rgb888(width, height, bin)
|
31
33
|
when 4
|
32
|
-
decode_rgba8888(width, height, bin
|
34
|
+
decode_rgba8888(width, height, bin)
|
33
35
|
when 5
|
34
|
-
decode_argb8888(width, height, bin
|
36
|
+
decode_argb8888(width, height, bin)
|
35
37
|
when 7
|
36
38
|
decode_rgb565(width, height, bin, endian)
|
39
|
+
when 9
|
40
|
+
decode_r16(width, height, bin)
|
37
41
|
when 13
|
38
42
|
decode_rgba4444(width, height, bin, endian)
|
43
|
+
when 14
|
44
|
+
decode_bgra8888(width, height, bin)
|
45
|
+
when 15
|
46
|
+
decode_rhalf(width, height, bin, endian)
|
47
|
+
when 16
|
48
|
+
decode_rghalf(width, height, bin, endian)
|
49
|
+
when 17
|
50
|
+
decode_rgbahalf(width, height, bin, endian)
|
51
|
+
when 18
|
52
|
+
decode_rfloat(width, height, bin, endian)
|
53
|
+
when 19
|
54
|
+
decode_rgfloat(width, height, bin, endian)
|
55
|
+
when 20
|
56
|
+
decode_rgbafloat(width, height, bin, endian)
|
57
|
+
when 22
|
58
|
+
decode_rgb9e5float(width, height, bin, endian)
|
39
59
|
when 34
|
40
60
|
decode_etc1(width, height, bin)
|
61
|
+
when 62
|
62
|
+
decode_rg16(width, height, bin)
|
63
|
+
when 63
|
64
|
+
decode_r8(width, height, bin)
|
41
65
|
else
|
42
66
|
nil
|
43
67
|
end
|
44
68
|
end
|
45
69
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
70
|
+
# Decode image from RGBA4444 binary
|
71
|
+
# @param [Integer] width image width
|
72
|
+
# @param [Integer] height image height
|
73
|
+
# @param [String] bin binary to decode
|
74
|
+
# @param [Symbol] endian endianness of binary
|
75
|
+
# @return [ChunkyPNG::Image] decoded image
|
76
|
+
def self.decode_rgba4444(width, height, bin, endian = :big)
|
77
|
+
mem = String.new(capacity: width * height * 4)
|
78
|
+
(width * height).times do |i|
|
79
|
+
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
80
|
+
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
|
81
|
+
BinUtils.append_int32_be!(mem, c << 4 | c)
|
59
82
|
end
|
60
|
-
ChunkyPNG::Image.from_rgba_stream(
|
83
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
61
84
|
end
|
62
85
|
|
63
|
-
|
64
|
-
|
65
|
-
|
86
|
+
# Decode image from ARGB4444 binary
|
87
|
+
# @param [Integer] width image width
|
88
|
+
# @param [Integer] height image height
|
89
|
+
# @param [String] bin binary to decode
|
90
|
+
# @param [Symbol] endian endianness of binary
|
91
|
+
# @return [ChunkyPNG::Image] decoded image
|
92
|
+
def self.decode_argb4444(width, height, bin, endian = :big)
|
93
|
+
mem = String.new(capacity: width * height * 4)
|
94
|
+
(width * height).times do |i|
|
95
|
+
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
96
|
+
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
|
97
|
+
BinUtils.append_int32_be!(mem, c << 4 | c)
|
66
98
|
end
|
67
|
-
ChunkyPNG::Image.
|
99
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
68
100
|
end
|
69
101
|
|
102
|
+
# Decode image from RGB565 binary
|
103
|
+
# @param [Integer] width image width
|
104
|
+
# @param [Integer] height image height
|
105
|
+
# @param [String] bin binary to decode
|
106
|
+
# @param [Symbol] endian endianness of binary
|
107
|
+
# @return [ChunkyPNG::Image] decoded image
|
70
108
|
def self.decode_rgb565(width, height, bin, endian = :big)
|
71
|
-
|
109
|
+
mem = String.new(capacity: width * height * 3)
|
110
|
+
(width * height).times do |i|
|
111
|
+
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
72
112
|
r = (c & 0xf800) >> 8
|
73
113
|
g = (c & 0x07e0) >> 3
|
74
114
|
b = (c & 0x001f) << 3
|
75
|
-
r
|
76
|
-
g = g | g >> 6
|
77
|
-
b = b | b >> 5
|
78
|
-
r << 24 | g << 16 | b << 8 | 0xff
|
115
|
+
BinUtils.append_int8!(mem, r | r >> 5, g | g >> 6, b | b >> 5)
|
79
116
|
end
|
80
|
-
ChunkyPNG::Image.
|
117
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
81
118
|
end
|
82
119
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
120
|
+
# Decode image from A8 binary
|
121
|
+
# @param [Integer] width image width
|
122
|
+
# @param [Integer] height image height
|
123
|
+
# @param [String] bin binary to decode
|
124
|
+
# @return [ChunkyPNG::Image] decoded image
|
125
|
+
def self.decode_a8(width, height, bin)
|
126
|
+
mem = String.new(capacity: width * height * 3)
|
127
|
+
(width * height).times do |i|
|
128
|
+
c = BinUtils.get_int8(bin, i)
|
129
|
+
BinUtils.append_int8!(mem, c, c, c)
|
88
130
|
end
|
131
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
89
132
|
end
|
90
133
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
134
|
+
# Decode image from R8 binary
|
135
|
+
# @param [Integer] width image width
|
136
|
+
# @param [Integer] height image height
|
137
|
+
# @param [String] bin binary to decode
|
138
|
+
# @return [ChunkyPNG::Image] decoded image
|
139
|
+
def self.decode_r8(width, height, bin)
|
140
|
+
decode_a8(width, height, bin)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Decode image from RG16 binary
|
144
|
+
# @param [Integer] width image width
|
145
|
+
# @param [Integer] height image height
|
146
|
+
# @param [String] bin binary to decode
|
147
|
+
# @return [ChunkyPNG::Image] decoded image
|
148
|
+
def self.decode_rg16(width, height, bin)
|
149
|
+
mem = String.new(capacity: width * height * 3)
|
150
|
+
(width * height).times do |i|
|
151
|
+
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i*2), 0)
|
95
152
|
end
|
96
|
-
ChunkyPNG::Image.
|
153
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
97
154
|
end
|
98
155
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
156
|
+
# Decode image from RGB24 binary
|
157
|
+
# @param [Integer] width image width
|
158
|
+
# @param [Integer] height image height
|
159
|
+
# @param [String] bin binary to decode
|
160
|
+
# @return [ChunkyPNG::Image] decoded image
|
161
|
+
def self.decode_rgb24(width, height, bin)
|
162
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, bin)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Decode image from RGBA32 binary
|
166
|
+
# @param [Integer] width image width
|
167
|
+
# @param [Integer] height image height
|
168
|
+
# @param [String] bin binary to decode
|
169
|
+
# @return [ChunkyPNG::Image] decoded image
|
170
|
+
def self.decode_rgba32(width, height, bin)
|
171
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, bin)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Decode image from ARGB32 binary
|
175
|
+
# @param [Integer] width image width
|
176
|
+
# @param [Integer] height image height
|
177
|
+
# @param [String] bin binary to decode
|
178
|
+
# @return [ChunkyPNG::Image] decoded image
|
179
|
+
def self.decode_argb32(width, height, bin)
|
180
|
+
mem = String.new(capacity: width * height * 4)
|
181
|
+
(width * height).times do |i|
|
182
|
+
c = BinUtils.get_int32_be(bin, i*4)
|
183
|
+
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
|
103
184
|
end
|
104
|
-
ChunkyPNG::Image.
|
185
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
105
186
|
end
|
106
187
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
188
|
+
# Decode image from BGRA32 binary
|
189
|
+
# @param [Integer] width image width
|
190
|
+
# @param [Integer] height image height
|
191
|
+
# @param [String] bin binary to decode
|
192
|
+
# @return [ChunkyPNG::Image] decoded image
|
193
|
+
def self.decode_bgra32(width, height, bin)
|
194
|
+
mem = String.new(capacity: width * height * 4)
|
195
|
+
(width * height).times do |i|
|
196
|
+
c = BinUtils.get_int32_le(bin, i*4)
|
197
|
+
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
|
198
|
+
end
|
199
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Decode image from R16 binary
|
203
|
+
# @param [Integer] width image width
|
204
|
+
# @param [Integer] height image height
|
205
|
+
# @param [String] bin binary to decode
|
206
|
+
# @param [Symbol] endian endianness of binary
|
207
|
+
# @return [ChunkyPNG::Image] decoded image
|
208
|
+
def self.decode_r16(width, height, bin, endian = :big)
|
209
|
+
mem = String.new(capacity: width * height * 3)
|
210
|
+
(width * height).times do |i|
|
211
|
+
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
|
212
|
+
c = f2i(r / 65535.0)
|
213
|
+
BinUtils.append_int8!(mem, c, c, c)
|
214
|
+
end
|
215
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Decode image from RGB9e5 binary
|
219
|
+
# @param [Integer] width image width
|
220
|
+
# @param [Integer] height image height
|
221
|
+
# @param [String] bin binary to decode
|
222
|
+
# @param [Symbol] endian endianness of binary
|
223
|
+
# @return [ChunkyPNG::Image] decoded image
|
224
|
+
def self.decode_rgb9e5float(width, height, bin, endian = :big)
|
225
|
+
mem = String.new(capacity: width * height * 3)
|
226
|
+
(width * height).times do |i|
|
227
|
+
n = endian == :little ? BinUtils.get_int32_le(bin, i*4) : BinUtils.get_int32_be(bin, i*4)
|
228
|
+
e = (n & 0xf8000000) >> 27
|
229
|
+
r = (n & 0x7fc0000) >> 9
|
230
|
+
g = (n & 0x3fe00) >> 9
|
231
|
+
b = n & 0x1ff
|
232
|
+
r = (r / 512r + 1) * (2**(e-15))
|
233
|
+
g = (g / 512r + 1) * (2**(e-15))
|
234
|
+
b = (b / 512r + 1) * (2**(e-15))
|
235
|
+
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b))
|
236
|
+
end
|
237
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Decode image from R Half-float binary
|
241
|
+
# @param [Integer] width image width
|
242
|
+
# @param [Integer] height image height
|
243
|
+
# @param [String] bin binary to decode
|
244
|
+
# @param [Symbol] endian endianness of binary
|
245
|
+
# @return [ChunkyPNG::Image] decoded image
|
246
|
+
def self.decode_rhalf(width, height, bin, endian = :big)
|
247
|
+
mem = String.new(capacity: width * height * 3)
|
248
|
+
(width * height).times do |i|
|
249
|
+
c = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)))
|
250
|
+
BinUtils.append_int8!(mem, c, c, c)
|
251
|
+
end
|
252
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Decode image from RG Half-float binary
|
256
|
+
# @param [Integer] width image width
|
257
|
+
# @param [Integer] height image height
|
258
|
+
# @param [String] bin binary to decode
|
259
|
+
# @param [Symbol] endian endianness of binary
|
260
|
+
# @return [ChunkyPNG::Image] decoded image
|
261
|
+
def self.decode_rghalf(width, height, bin, endian = :big)
|
262
|
+
mem = String.new(capacity: width * height * 3)
|
263
|
+
(width * height).times do |i|
|
264
|
+
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4) : BinUtils.get_int16_be(bin, i*4)))
|
265
|
+
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4+2) : BinUtils.get_int16_be(bin, i*4+2)))
|
266
|
+
BinUtils.append_int8!(mem, r, g, 0)
|
267
|
+
end
|
268
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Decode image from RGBA Half-float binary
|
272
|
+
# @param [Integer] width image width
|
273
|
+
# @param [Integer] height image height
|
274
|
+
# @param [String] bin binary to decode
|
275
|
+
# @param [Symbol] endian endianness of binary
|
276
|
+
# @return [ChunkyPNG::Image] decoded image
|
277
|
+
def self.decode_rgbahalf(width, height, bin, endian = :big)
|
278
|
+
mem = String.new(capacity: width * height * 4)
|
279
|
+
(width * height).times do |i|
|
280
|
+
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8) : BinUtils.get_int16_be(bin, i*8)))
|
281
|
+
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+2) : BinUtils.get_int16_be(bin, i*8+2)))
|
282
|
+
b = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+4) : BinUtils.get_int16_be(bin, i*8+4)))
|
283
|
+
a = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+6) : BinUtils.get_int16_be(bin, i*8+6)))
|
284
|
+
BinUtils.append_int8!(mem, r, g, b, a)
|
285
|
+
end
|
286
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Decode image from R float binary
|
290
|
+
# @param [Integer] width image width
|
291
|
+
# @param [Integer] height image height
|
292
|
+
# @param [String] bin binary to decode
|
293
|
+
# @param [Symbol] endian endianness of binary
|
294
|
+
# @return [ChunkyPNG::Image] decoded image
|
295
|
+
def self.decode_rfloat(width, height, bin, endian = :big)
|
296
|
+
mem = String.new(capacity: width * height * 3)
|
297
|
+
unpackstr = endian == :little ? 'e' : 'g'
|
298
|
+
(width * height).times do |i|
|
299
|
+
c = f2i(bin.byteslice(i*4, 4).unpack(unpackstr)[0])
|
300
|
+
BinUtils.append_int8!(mem, c, c, c)
|
301
|
+
end
|
302
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Decode image from RG float binary
|
306
|
+
# @param [Integer] width image width
|
307
|
+
# @param [Integer] height image height
|
308
|
+
# @param [String] bin binary to decode
|
309
|
+
# @param [Symbol] endian endianness of binary
|
310
|
+
# @return [ChunkyPNG::Image] decoded image
|
311
|
+
def self.decode_rgfloat(width, height, bin, endian = :big)
|
312
|
+
mem = String.new(capacity: width * height * 3)
|
313
|
+
unpackstr = endian == :little ? 'e2' : 'g2'
|
314
|
+
(width * height).times do |i|
|
315
|
+
r, g = bin.byteslice(i*8, 8).unpack(unpackstr)
|
316
|
+
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
|
317
|
+
end
|
318
|
+
ChunkyPNG::Image.from_rgb_stream(width, height, mem)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Decode image from RGBA float binary
|
322
|
+
# @param [Integer] width image width
|
323
|
+
# @param [Integer] height image height
|
324
|
+
# @param [String] bin binary to decode
|
325
|
+
# @param [Symbol] endian endianness of binary
|
326
|
+
# @return [ChunkyPNG::Image] decoded image
|
327
|
+
def self.decode_rgbafloat(width, height, bin, endian = :big)
|
328
|
+
mem = String.new(capacity: width * height * 4)
|
329
|
+
unpackstr = endian == :little ? 'e4' : 'g4'
|
330
|
+
(width * height).times do |i|
|
331
|
+
r, g, b, a = bin.byteslice(i*16, 16).unpack(unpackstr)
|
332
|
+
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
|
333
|
+
end
|
334
|
+
ChunkyPNG::Image.from_rgba_stream(width, height, mem)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Decode image from ETC1 compressed binary
|
338
|
+
# @param [Integer] width image width
|
339
|
+
# @param [Integer] height image height
|
340
|
+
# @param [String] bin binary to decode
|
341
|
+
# @return [ChunkyPNG::Image] decoded image
|
342
|
+
def self.decode_etc1(width, height, bin)
|
343
|
+
bw = (width + 3) / 4
|
344
|
+
bh = (height + 3) / 4
|
345
|
+
mem = Fiddle::Pointer.malloc(bw * bh * 48)
|
346
|
+
bh.times do |by|
|
347
|
+
bw.times do |bx|
|
348
|
+
block = decode_etc1_block(BinUtils.get_sint64_be(bin, (bx + by * bw) * 8))
|
349
|
+
16.times do |i|
|
350
|
+
mem[((i / 4 + bx * 4) + (i % 4 + by * 4) * bw * 4) * 3, 3] = block[i]
|
351
|
+
end
|
352
|
+
end
|
112
353
|
end
|
354
|
+
ChunkyPNG::Image.from_rgb_stream(bw * 4, bh * 4, mem.to_str).crop!(0, 0, width, height)
|
113
355
|
end
|
114
356
|
|
115
|
-
|
116
|
-
|
117
|
-
|
357
|
+
# Create ASTC file data from ObjectValue
|
358
|
+
# @param [Mikunyan::ObjectValue,Hash] object target object
|
359
|
+
# @return [String,nil] created file
|
360
|
+
def self.create_astc_file(object)
|
361
|
+
astc_list = {
|
362
|
+
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
|
363
|
+
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12
|
364
|
+
}
|
365
|
+
width = object['m_Width']
|
366
|
+
height = object['m_Height']
|
367
|
+
fmt = object['m_TextureFormat']
|
368
|
+
bin = object['image data']
|
369
|
+
width = width.value if width.class == ObjectValue
|
370
|
+
height = height.value if height.class == ObjectValue
|
371
|
+
fmt = fmt.value if fmt.class == ObjectValue
|
372
|
+
bin = bin.value if bin.class == ObjectValue
|
373
|
+
if width && height && fmt && astc_list[fmt]
|
374
|
+
header = "\x13\xAB\xA1\x5C".force_encoding('ascii-8bit')
|
375
|
+
header << [astc_list[fmt], astc_list[fmt], 1].pack("C*")
|
376
|
+
header << [width].pack("V").byteslice(0, 3)
|
377
|
+
header << [height].pack("V").byteslice(0, 3)
|
378
|
+
header << "\x01\x00\x00"
|
379
|
+
header + bin
|
380
|
+
else
|
381
|
+
nil
|
118
382
|
end
|
119
|
-
ChunkyPNG::Image.new(width, height, pixels)
|
120
383
|
end
|
121
384
|
|
122
385
|
private
|
123
386
|
|
387
|
+
Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]]
|
388
|
+
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]]
|
389
|
+
|
124
390
|
def self.decode_etc1_block(bin)
|
125
391
|
colors = []
|
126
392
|
codes = [bin >> 37 & 7, bin >> 34 & 7]
|
@@ -152,10 +418,38 @@ module Mikunyan
|
|
152
418
|
r = (color >> 16 & 0xff) + modifier
|
153
419
|
g = (color >> 8 & 0xff) + modifier
|
154
420
|
b = (color & 0xff) + modifier
|
155
|
-
r
|
156
|
-
|
157
|
-
|
158
|
-
|
421
|
+
r.clamp(0, 255).chr + g.clamp(0, 255).chr + b.clamp(0, 255).chr
|
422
|
+
end
|
423
|
+
|
424
|
+
# convert 16bit float
|
425
|
+
def self.n2f(n)
|
426
|
+
case n
|
427
|
+
when 0x0000
|
428
|
+
0.0
|
429
|
+
when 0x8000
|
430
|
+
-0.0
|
431
|
+
when 0x7c00
|
432
|
+
Float::INFINITY
|
433
|
+
when 0xfc00
|
434
|
+
-Float::INFINITY
|
435
|
+
else
|
436
|
+
s = n & 0x8000 != 0
|
437
|
+
e = n & 0x7c00
|
438
|
+
f = n & 0x03ff
|
439
|
+
case e
|
440
|
+
when 0x7c00
|
441
|
+
Float::NAN
|
442
|
+
when 0
|
443
|
+
(s ? -f : f) * 2.0**-24
|
444
|
+
else
|
445
|
+
(s ? -1 : 1) * (f / 1024.0 + 1) * (2.0 ** ((e >> 10)-15))
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
# [0.0,1.0] -> [0,255]
|
451
|
+
def self.f2i(d)
|
452
|
+
(d * 255).round.clamp(0, 255)
|
159
453
|
end
|
160
454
|
end
|
161
455
|
end
|
@@ -1,7 +1,18 @@
|
|
1
1
|
module Mikunyan
|
2
|
+
# Class for representing decoded object
|
3
|
+
# @attr [String] name object name
|
4
|
+
# @attr [String] type object type name
|
5
|
+
# @attr [Object] value object
|
6
|
+
# @attr [Symbol] endian endianness
|
7
|
+
# @attr [Boolean] is_struct
|
2
8
|
class ObjectValue
|
3
9
|
attr_accessor :name, :type, :value, :endian, :is_struct
|
4
10
|
|
11
|
+
# Constructor
|
12
|
+
# @param [String] name object name
|
13
|
+
# @param [String] type object type name
|
14
|
+
# @param [Symbol] endian endianness
|
15
|
+
# @param [Object] value object
|
5
16
|
def initialize(name, type, endian, value = nil)
|
6
17
|
@name = name
|
7
18
|
@type = type
|
@@ -11,46 +22,70 @@ module Mikunyan
|
|
11
22
|
@attr = {}
|
12
23
|
end
|
13
24
|
|
25
|
+
# Return whether object is array or not
|
26
|
+
# @return [Boolean]
|
14
27
|
def array?
|
15
28
|
value && value.class == Array
|
16
29
|
end
|
17
30
|
|
31
|
+
# Return whether object is value or not
|
32
|
+
# @return [Boolean]
|
18
33
|
def value?
|
19
34
|
value && value.class != Array
|
20
35
|
end
|
21
36
|
|
37
|
+
# Return whether object is struct or not
|
38
|
+
# @return [Boolean]
|
22
39
|
def struct?
|
23
40
|
is_struct
|
24
41
|
end
|
25
42
|
|
43
|
+
# Return all keys
|
44
|
+
# @return [Array] list of keys
|
26
45
|
def keys
|
27
46
|
@attr.keys
|
28
47
|
end
|
29
48
|
|
49
|
+
# Return whether object contains key
|
50
|
+
# @param [String] key
|
51
|
+
# @return [Boolean]
|
30
52
|
def key?(key)
|
31
53
|
@attr.key?(key)
|
32
54
|
end
|
33
55
|
|
56
|
+
# Return value
|
57
|
+
# @return [Object] value
|
34
58
|
def []
|
35
59
|
@value
|
36
60
|
end
|
37
61
|
|
38
|
-
|
39
|
-
|
40
|
-
|
62
|
+
# Return value of selected index or key
|
63
|
+
# @param [Integer,String] i index or key
|
64
|
+
# @return [Object] value
|
65
|
+
def [](i)
|
66
|
+
if array? && i.class == Integer
|
67
|
+
@value[i]
|
41
68
|
else
|
42
|
-
@attr[
|
69
|
+
@attr[i]
|
43
70
|
end
|
44
71
|
end
|
45
72
|
|
73
|
+
# Set value of selected key
|
74
|
+
# @param [String] name key
|
75
|
+
# @param [Object] value value
|
76
|
+
# @return [Object] value
|
46
77
|
def []=(name, value)
|
47
78
|
@attr[name] = value
|
48
79
|
end
|
49
80
|
|
81
|
+
# Return value of called key
|
82
|
+
# @param [String] name key
|
83
|
+
# @return [Object] value
|
50
84
|
def method_missing(name, *args)
|
51
85
|
@attr[name.to_s]
|
52
86
|
end
|
53
87
|
|
88
|
+
# Implementation of respond_to_missing?
|
54
89
|
def respond_to_missing?(symbol, include_private)
|
55
90
|
@attr.key?(symbol.to_s)
|
56
91
|
end
|
data/lib/mikunyan/type_tree.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
module Mikunyan
|
2
|
+
# Class for representing TypeTree
|
3
|
+
# @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes
|
2
4
|
class TypeTree
|
3
5
|
attr_accessor :nodes
|
6
|
+
|
7
|
+
# Struct for representing Node in TypeTree
|
8
|
+
# @attr [String] version version string
|
9
|
+
# @attr [Integer] depth depth of node (>= 0)
|
10
|
+
# @attr [Boolean] array? array node or not
|
11
|
+
# @attr [String] type type name
|
12
|
+
# @attr [String] name node (attribute) name
|
13
|
+
# @attr [Integer] index index in node list
|
14
|
+
# @attr [Integer] flags flags of node
|
4
15
|
Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags)
|
5
16
|
|
17
|
+
# Create TypeTree from binary string (new version)
|
18
|
+
# @param [Mikunyan::BinaryReader] br
|
19
|
+
# @return [Mikunyan::TypeTree] created TypeTree
|
6
20
|
def self.load(br)
|
7
21
|
nodes = []
|
8
22
|
node_count = br.i32u
|
@@ -28,6 +42,9 @@ module Mikunyan
|
|
28
42
|
r
|
29
43
|
end
|
30
44
|
|
45
|
+
# Create TypeTree from binary string (legacy version)
|
46
|
+
# @param [Mikunyan::BinaryReader] br
|
47
|
+
# @return [Mikunyan::TypeTree] created TypeTree
|
31
48
|
def self.load_legacy(br)
|
32
49
|
nodes = []
|
33
50
|
stack = [0]
|
@@ -49,6 +66,9 @@ module Mikunyan
|
|
49
66
|
r
|
50
67
|
end
|
51
68
|
|
69
|
+
# Create default TypeTree from hash string (if exists)
|
70
|
+
# @param [String] hash
|
71
|
+
# @return [Mikunyan::TypeTree,nil] created TypeTree
|
52
72
|
def self.load_default(hash)
|
53
73
|
hash_str = hash.unpack('H*')[0]
|
54
74
|
file = File.expand_path("../typetrees/#{hash_str}.dat", __FILE__)
|
data/lib/mikunyan/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mikunyan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.9.
|
4
|
+
version: 3.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ishotihadus
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-07
|
11
|
+
date: 2017-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: extlz4
|