mikunyan 3.9.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|