version 3.9.0

This commit is contained in:
Ishotihadus
2017-07-05 01:37:28 +09:00
commit 1a1bed5302
910 changed files with 1434 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
/testdata/
# rspec failure tracking
.rspec_status
.DS_Store
+5
View File
@@ -0,0 +1,5 @@
# Change log of mikunyan
## 3.9.0 - 2017-07-05
- First release
+2
View File
@@ -0,0 +1,2 @@
source "https://rubygems.org"
gemspec
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Ishotihadus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+154
View File
@@ -0,0 +1,154 @@
# mikunyan
Library to deserialize Unity AssetBundle files (\*.unity3d) and asset files.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'mikunyan'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install mikunyan
## Usage
### Basic Usage
```ruby
require 'mikunyan'
# load AssetBundle
bundle = Mikunyan::AssetBundle.file(filename)
# you can load AssetBundle from blob
# bundle = Mikunyan::AssetBundle.load(blob)
# select asset (normaly only one asset)
asset = bundle.assets[0]
# or you can directly load asset
# asset = Mikunyan::Asset.file(filename)
# object list
list = asset.objects
# object PathIds
path_ids = asset.path_ids
# object container table (if available)
containers = asset.containers
# load object (Mikunyan::ObjectValue)
obj = asset.parse_object(path_ids[0])
# simplified structure (based on Hash)
obj_hash = asset.parse_object_simple(path_ids[0])
# hash can be easily serialized to json
require 'json'
obj_hash.to_json
```
### Mikunyan::ObjectValue
`Mikunyan::ObjectValue` can be 3 types. Value, array and map.
```ruby
# You can get whether obj is value or not
obj.value?
# get value
obj.value
# same as obj.value
obj[]
# You can get whether obj is array or not
obj.array?
# get array
obj.value
# you can directly access by index
obj[0]
# If obj is map, you can get keys
obj.keys
# get child object
obj[key]
# same as obj[key]
obj.key
```
### Unpack Texture2D
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).
```ruby
require 'mikunyan/decoders'
# get some Texture2D asset
obj = asset.parse_object(path_ids[1])
# you can get Image object
img = Mikunyan::ImageDecoder.decode_object(obj)
# save it!
img.save('mikunyan.png')
```
### Json Outputer
`mikunyan-json` is the executable command for converting unity3d to json.
$ mikunyan-json bundle.unity3d > bundle.json
Available options:
- `--as-asset` (`-a`): interpret input file as not AssetBudnle but Asset
- `--pretty` (`-p`): prettify output json
## Dependencies
- [json](https://rubygems.org/gems/json)
- [extlz4](https://rubygems.org/gems/extlz4)
- [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.
## FAQ
### Sometimes unpacking fails
I'm sorry...
### Can I unpack Mesh files?
It's hard work for me...
### What mikunyan comes from?
[Miku Maekawa](http://www.project-imas.com/wiki/Miku_Maekawa).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/Ishotihadus/mikunyan.
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
+5
View File
@@ -0,0 +1,5 @@
require "bundler/gem_tasks"
task :scream do
puts "みくは自分を曲げないよ!"
end
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require "pry"
Pry.start
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
bundle install
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env ruby
require 'mikunyan'
require 'json'
require 'base64'
def obj64(obj)
if obj.class == Hash
obj.map{|k, v| [k, obj64(v)]}.to_h
elsif obj.class == Array
obj.map{|e| obj64(e)}
elsif obj.class == String
if obj.encoding == Encoding::UTF_8
obj
else
Base64::strict_encode64(obj)
end
else
obj
end
end
opts = {:as_asset => 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 '--pretty', '-p'
opts[:pretty] = true
else
warn("Unknown option: #{ARGV[i]}")
exit(1)
end
else
arg = ARGV[i] unless arg
end
i += 1
end
unless File.file?(arg)
warn("File not found: #{arg}")
exit(1)
end
assets = {}
if opts[:as_asset]
asset = Mikunyan::Asset.file(arg, arg.match(/([^\/]*?)(\.[^.]*)?\z/)[1])
objs = []
asset.path_ids.each do |e|
obj = asset.parse_object_simple(e)
objs << obj64(obj) if obj
end
assets[asset.name] = objs
else
bundle = Mikunyan::AssetBundle.file(arg)
bundle.assets.each do |asset|
objs = []
asset.path_ids.each do |e|
obj = asset.parse_object_simple(e)
objs << obj64(obj) if obj
end
assets[asset.name] = objs
end
end
if opts[:pretty]
puts JSON.pretty_generate(assets)
else
puts JSON.generate(assets)
end
+8
View File
@@ -0,0 +1,8 @@
require "mikunyan/version"
require "mikunyan/asset_bundle"
require "mikunyan/asset"
require "mikunyan/binary_reader"
require "mikunyan/object_value"
require "mikunyan/type_tree"
require "mikunyan/constants"
+272
View File
@@ -0,0 +1,272 @@
module Mikunyan
class Asset
attr_accessor :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
Reference = Struct.new(:path, :guid, :type, :file_path)
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
size = br.i32u
@format = br.i32u
data_offset = br.i32u
if @format >= 9
@endian = :little if br.i32 == 0
br.endian = @endian
end
@generator_version = br.cstr
@target_platform = br.i32
@klasses = []
if @format >= 17
has_type_trees = (br.i8 != 0)
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
br.adv(1)
script_id = br.i16
if class_id < 0 || class_id == 114
hash = br.read(32)
else
hash = br.read(16)
end
@klasses << Klass.new(class_id, script_id, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
end
elsif @format >= 13
has_type_trees = (br.i8 != 0)
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
if class_id < 0
hash = br.read(32)
else
hash = br.read(16)
end
@klasses << Klass.new(class_id, nil, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
end
else
@type_trees = {}
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
@klasses << Klass.new(class_id, nil, nil, @format == 10 || @format == 12 ? TypeTree.load(br) : TypeTree.load_legacy(br))
end
end
long_object_ids = (@format >= 14 || (7 <= @format && @format <= 13 && br.i32 != 0))
@objects = []
object_count = br.i32u
object_count.times do
br.align(4) if @format >= 14
path_id = long_object_ids ? br.i64 : br.i32
offset = br.i32u
size = br.i32u
if @format >= 17
@objects << ObjectData.new(path_id, offset, size, nil, nil, br.i32u, @format <= 10 && br.i16 != 0)
else
@objects << ObjectData.new(path_id, offset, size, br.i32, br.i16, nil, @format <= 10 && br.i16 != 0)
end
br.adv(2) if 11 <= @format && @format <= 16
br.adv(1) if 15 <= @format && @format <= 16
end
if @format >= 11
@add_ids = []
add_id_count = br.i32u
add_id_count.times do
br.align(4) if @format >= 14
@add_ids << [(long_object_ids ? br.i64 : br.i32), br.i32]
end
end
if @format >= 6
@references = []
reference_count = br.i32u
reference_count.times do
@references << Reference.new(br.cstr, br.read(16), br.i32, br.cstr)
end
end
@objects.each do |e|
br.jmp(data_offset + e.offset)
e.data = br.read(e.size)
end
end
def 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]
children = type_tree[:children]
if node.array?
data = []
size = parse_object_private(br, children.find{|e| e[:name] == 'size'})
data_type_tree = children.find{|e| e[:name] == 'data'}
size.value.times do |i|
data << parse_object_private(br, data_type_tree)
end
data = data.map{|e| e.value}.pack('C*') if node.type == 'TypelessData'
r = ObjectValue.new(node.name, node.type, br.endian, data)
elsif node.size == -1
r = ObjectValue.new(node.name, node.type, br.endian)
if children.size == 1 && children[0][:name] == 'Array' && children[0][:node].type == 'Array' && children[0][:node].array?
r.value = parse_object_private(br, children[0]).value
r.value = r.value.map{|e| e.value}.pack('C*').force_encoding("utf-8") if node.type == 'string'
else
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
end
elsif children.size > 0
pos = br.pos
r = ObjectValue.new(node.name, node.type, br.endian)
r.is_struct = true
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
br.jmp(pos + node.size)
else
pos = br.pos
value = nil
case node.type
when 'bool'
value = (br.i8 != 0)
when 'SInt8'
value = br.i8s
when 'UInt8', 'char'
value = br.i8u
when 'SInt16', 'short'
value = br.i16s
when 'UInt16', 'unsigned short'
value = br.i16u
when 'SInt32', 'int'
value = br.i32s
when 'UInt32', 'unsigned int'
value = br.i32u
when 'SInt64', 'long long'
value = br.i64s
when 'UInt64', 'unsigned long long'
value = br.i64u
when 'float'
value = br.float
when 'double'
value = br.double
when 'ColorRGBA'
value = [br.i8u, br.i8u, br.i8u, br.i8u]
else
value = br.read(node.size)
end
br.jmp(pos + node.size)
r = ObjectValue.new(node.name, node.type, br.endian, value)
end
br.align(4) if node.flags & 0x4000 != 0
r
end
end
end
+98
View File
@@ -0,0 +1,98 @@
require 'extlz4'
module Mikunyan
class AssetBundle
attr_accessor :signature, :format, :unity_version, :generator_version, :assets
def self.load(bin)
r = AssetBundle.new
r.load(bin)
r
end
def self.file(file)
AssetBundle.load(File.binread(file))
end
def load(bin)
br = BinaryReader.new(bin)
@signature = br.cstr
@format = br.i32
@unity_version = br.cstr
@generator_version = br.cstr
case @signature
when 'UnityRaw'
load_unity_raw(br)
when 'UnityFS'
load_unity_fs(br)
else
warn("Unknown signature: #{@signature}")
end
end
private
def load_unity_raw(br)
@assets = []
file_size = br.i32u
header_size = br.i32u
br.jmp(header_size)
asset_count = br.i32u
asset_count.times do
asset_pos = br.pos
asset_name = br.cstr
asset_header_size = br.i32u
asset_size = br.i32u
br.jmp(asset_pos + asset_header_size - 4)
asset_data = br.read(asset_size)
asset = Asset.new(asset_name)
asset.load(asset_data)
@assets << asset
end
end
def load_unity_fs(br)
@assets = []
file_size = br.i64u
ci_block_size = br.i32u
ui_block_size = br.i32u
flags = br.i32u
head = BinaryReader.new(uncompress(br.read(ci_block_size), ui_block_size, flags))
guid = head.read(16)
blocks = []
block_count = head.i32u
block_count.times{ blocks << {:u => head.i32u, :c => head.i32u, :f => head.i16u} }
asset_blocks = []
asset_count = head.i32u
asset_count.times{ asset_blocks << {:offset => head.i64u, :size => head.i64u, :status => head.i32, :name => head.cstr} }
raw_data = ''
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
asset_blocks.each do |b|
asset = Asset.new(b[:name])
asset.load(raw_data.byteslice(b[:offset], b[:size]))
@assets << asset
end
end
def uncompress(block, max_dest_size, flags)
case flags & 0x3f
when 0
block
when 2, 3
LZ4::raw_decode(block, max_dest_size)
else
warn("Unknown compression flag: #{@flags}")
block
end
end
end
end
+118
View File
@@ -0,0 +1,118 @@
require 'bin_utils'
module Mikunyan
class BinaryReader
attr_accessor :endian, :pos, :length
def initialize(data, endian = :big)
@data = data
@pos = 0
@length = data.bytesize
@endian = endian
end
def little?
@endian == :little
end
def jmp(pos = 0)
@pos = pos
end
def adv(size = 0)
@pos += size
end
def align(size)
@pos = (@pos + size - 1) / size * size
end
def read(size)
data = @data.byteslice(@pos, size)
@pos += size
data
end
def cstr
r = @data.unpack("@#{pos}Z*")[0]
@pos += r.bytesize + 1
r
end
def i8
i8s
end
def i8s
r = BinUtils.get_sint8(@data, @pos)
@pos += 1
r
end
def i8u
r = BinUtils.get_int8(@data, @pos)
@pos += 1
r
end
def i16
i16s
end
def i16s
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
@pos += 2
r
end
def i16u
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
@pos += 2
r
end
def i32
i32s
end
def i32s
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
@pos += 4
r
end
def i32u
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
@pos += 4
r
end
def i64
i64s
end
def i64s
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
@pos += 8
r
end
def i64u
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
@pos += 8
r
end
def float
r = little? ? @data.byteslice(@pos, 4).unpack('e')[0] : @data.byteslice(@pos, 4).unpack('g')[0]
@pos += 4
r
end
def double
r = little? ? @data.byteslice(@pos, 8).unpack('E')[0] : @data.byteslice(@pos, 8).unpack('G')[0]
@pos += 8
r
end
end
end
+343
View File
@@ -0,0 +1,343 @@
module Mikunyan
STRING_TABLE = {
0=>'AABB',
5=>'AnimationClip',
19=>'AnimationCurve',
34=>'AnimationState',
49=>'Array',
55=>'Base',
60=>'BitField',
69=>'bitset',
76=>'bool',
81=>'char',
86=>'ColorRGBA',
96=>'Component',
106=>'data',
111=>'deque',
117=>'double',
124=>'dynamic_array',
138=>'FastPropertyName',
155=>'first',
161=>'float',
167=>'Font',
172=>'GameObject',
183=>'Generic Mono',
196=>'GradientNEW',
208=>'GUID',
213=>'GUIStyle',
222=>'int',
226=>'list',
231=>'long long',
241=>'map',
245=>'Matrix4x4f',
256=>'MdFour',
263=>'MonoBehaviour',
277=>'MonoScript',
288=>'m_ByteSize',
299=>'m_Curve',
307=>'m_EditorClassIdentifier',
331=>'m_EditorHideFlags',
349=>'m_Enabled',
359=>'m_ExtensionPtr',
374=>'m_GameObject',
387=>'m_Index',
395=>'m_IsArray',
405=>'m_IsStatic',
416=>'m_MetaFlag',
427=>'m_Name',
434=>'m_ObjectHideFlags',
452=>'m_PrefabInternal',
469=>'m_PrefabParentObject',
490=>'m_Script',
499=>'m_StaticEditorFlags',
519=>'m_Type',
526=>'m_Version',
536=>'Object',
543=>'pair',
548=>'PPtr<Component>',
564=>'PPtr<GameObject>',
581=>'PPtr<Material>',
596=>'PPtr<MonoBehaviour>',
616=>'PPtr<MonoScript>',
633=>'PPtr<Object>',
646=>'PPtr<Prefab>',
659=>'PPtr<Sprite>',
672=>'PPtr<TextAsset>',
688=>'PPtr<Texture>',
702=>'PPtr<Texture2D>',
718=>'PPtr<Transform>',
734=>'Prefab',
741=>'Quaternionf',
753=>'Rectf',
759=>'RectInt',
767=>'RectOffset',
778=>'second',
785=>'set',
789=>'short',
795=>'size',
800=>'SInt16',
807=>'SInt32',
814=>'SInt64',
821=>'SInt8',
827=>'staticvector',
840=>'string',
847=>'TextAsset',
857=>'TextMesh',
866=>'Texture',
874=>'Texture2D',
884=>'Transform',
894=>'TypelessData',
907=>'UInt16',
914=>'UInt32',
921=>'UInt64',
928=>'UInt8',
934=>'unsigned int',
947=>'unsigned long long',
966=>'unsigned short',
981=>'vector',
988=>'Vector2f',
997=>'Vector3f',
1006=>'Vector4f',
1015=>'m_ScriptingClassIdentifier',
1042=>'Gradient'
}
CLASS_ID = {
1=>'GameObject',
2=>'Component',
3=>'LevelGameManager',
4=>'Transform',
5=>'TimeManager',
6=>'GlobalGameManager',
8=>'Behaviour',
9=>'GameManager',
11=>'AudioManager',
12=>'ParticleAnimator',
13=>'InputManager',
15=>'EllipsoidParticleEmitter',
17=>'Pipeline',
18=>'EditorExtension',
19=>'Physics2DSettings',
20=>'Camera',
21=>'Material',
23=>'MeshRenderer',
25=>'Renderer',
26=>'ParticleRenderer',
27=>'Texture',
28=>'Texture2D',
29=>'SceneSettings',
30=>'GraphicsSettings',
33=>'MeshFilter',
41=>'OcclusionPortal',
43=>'Mesh',
45=>'Skybox',
47=>'QualitySettings',
48=>'Shader',
49=>'TextAsset',
50=>'Rigidbody2D',
51=>'Physics2DManager',
53=>'Collider2D',
54=>'Rigidbody',
55=>'PhysicsManager',
56=>'Collider',
57=>'Joint',
58=>'CircleCollider2D',
59=>'HingeJoint',
60=>'PolygonCollider2D',
61=>'BoxCollider2D',
62=>'PhysicsMaterial2D',
64=>'MeshCollider',
65=>'BoxCollider',
66=>'SpriteCollider2D',
68=>'EdgeCollider2D',
72=>'ComputeShader',
74=>'AnimationClip',
75=>'ConstantForce',
76=>'WorldParticleCollider',
78=>'TagManager',
81=>'AudioListener',
82=>'AudioSource',
83=>'AudioClip',
84=>'RenderTexture',
87=>'MeshParticleEmitter',
88=>'ParticleEmitter',
89=>'Cubemap',
90=>'Avatar',
91=>'AnimatorController',
92=>'GUILayer',
93=>'RuntimeAnimatorController',
94=>'ScriptMapper',
95=>'Animator',
96=>'TrailRenderer',
98=>'DelayedCallManager',
102=>'TextMesh',
104=>'RenderSettings',
108=>'Light',
109=>'CGProgram',
110=>'BaseAnimationTrack',
111=>'Animation',
114=>'MonoBehaviour',
115=>'MonoScript',
116=>'MonoManager',
117=>'Texture3D',
118=>'NewAnimationTrack',
119=>'Projector',
120=>'LineRenderer',
121=>'Flare',
122=>'Halo',
123=>'LensFlare',
124=>'FlareLayer',
125=>'HaloLayer',
126=>'NavMeshAreas',
127=>'HaloManager',
128=>'Font',
129=>'PlayerSettings',
130=>'NamedObject',
131=>'GUITexture',
132=>'GUIText',
133=>'GUIElement',
134=>'PhysicMaterial',
135=>'SphereCollider',
136=>'CapsuleCollider',
137=>'SkinnedMeshRenderer',
138=>'FixedJoint',
140=>'RaycastCollider',
141=>'BuildSettings',
142=>'AssetBundle',
143=>'CharacterController',
144=>'CharacterJoint',
145=>'SpringJoint',
146=>'WheelCollider',
147=>'ResourceManager',
148=>'NetworkView',
149=>'NetworkManager',
150=>'PreloadData',
152=>'MovieTexture',
153=>'ConfigurableJoint',
154=>'TerrainCollider',
155=>'MasterServerInterface',
156=>'TerrainData',
157=>'LightmapSettings',
158=>'WebCamTexture',
159=>'EditorSettings',
160=>'InteractiveCloth',
161=>'ClothRenderer',
162=>'EditorUserSettings',
163=>'SkinnedCloth',
164=>'AudioReverbFilter',
165=>'AudioHighPassFilter',
166=>'AudioChorusFilter',
167=>'AudioReverbZone',
168=>'AudioEchoFilter',
169=>'AudioLowPassFilter',
170=>'AudioDistortionFilter',
171=>'SparseTexture',
180=>'AudioBehaviour',
181=>'AudioFilter',
182=>'WindZone',
183=>'Cloth',
184=>'SubstanceArchive',
185=>'ProceduralMaterial',
186=>'ProceduralTexture',
191=>'OffMeshLink',
192=>'OcclusionArea',
193=>'Tree',
194=>'NavMeshObsolete',
195=>'NavMeshAgent',
196=>'NavMeshSettings',
197=>'LightProbesLegacy',
198=>'ParticleSystem',
199=>'ParticleSystemRenderer',
200=>'ShaderVariantCollection',
205=>'LODGroup',
206=>'BlendTree',
207=>'Motion',
208=>'NavMeshObstacle',
210=>'TerrainInstance',
212=>'SpriteRenderer',
213=>'Sprite',
214=>'CachedSpriteAtlas',
215=>'ReflectionProbe',
216=>'ReflectionProbes',
218=>'Terrain',
220=>'LightProbeGroup',
221=>'AnimatorOverrideController',
222=>'CanvasRenderer',
223=>'Canvas',
224=>'RectTransform',
225=>'CanvasGroup',
226=>'BillboardAsset',
227=>'BillboardRenderer',
228=>'SpeedTreeWindAsset',
229=>'AnchoredJoint2D',
230=>'Joint2D',
231=>'SpringJoint2D',
232=>'DistanceJoint2D',
233=>'HingeJoint2D',
234=>'SliderJoint2D',
235=>'WheelJoint2D',
238=>'NavMeshData',
240=>'AudioMixer',
241=>'AudioMixerController',
243=>'AudioMixerGroupController',
244=>'AudioMixerEffectController',
245=>'AudioMixerSnapshotController',
246=>'PhysicsUpdateBehaviour2D',
247=>'ConstantForce2D',
248=>'Effector2D',
249=>'AreaEffector2D',
250=>'PointEffector2D',
251=>'PlatformEffector2D',
252=>'SurfaceEffector2D',
258=>'LightProbes',
271=>'SampleClip',
272=>'AudioMixerSnapshot',
273=>'AudioMixerGroup',
290=>'AssetBundleManifest',
1001=>'Prefab',
1002=>'EditorExtensionImpl',
1003=>'AssetImporter',
1004=>'AssetDatabase',
1005=>'Mesh3DSImporter',
1006=>'TextureImporter',
1007=>'ShaderImporter',
1008=>'ComputeShaderImporter',
1011=>'AvatarMask',
1020=>'AudioImporter',
1026=>'HierarchyState',
1027=>'GUIDSerializer',
1028=>'AssetMetaData',
1029=>'DefaultAsset',
1030=>'DefaultImporter',
1031=>'TextScriptImporter',
1032=>'SceneAsset',
1034=>'NativeFormatImporter',
1035=>'MonoImporter',
1037=>'AssetServerCache',
1038=>'LibraryAssetImporter',
1040=>'ModelImporter',
1041=>'FBXImporter',
1042=>'TrueTypeFontImporter',
1044=>'MovieImporter',
1045=>'EditorBuildSettings',
1046=>'DDSImporter',
1048=>'InspectorExpandedState',
1049=>'AnnotationManager',
1050=>'PluginImporter',
1051=>'EditorUserBuildSettings',
1052=>'PVRImporter',
1053=>'ASTCImporter',
1054=>'KTXImporter',
1101=>'AnimatorStateTransition',
1102=>'AnimatorState',
1105=>'HumanTemplate',
1107=>'AnimatorStateMachine',
1108=>'PreviewAssetType',
1109=>'AnimatorTransition',
1110=>'SpeedTreeImporter',
1111=>'AnimatorTransitionBase',
1112=>'SubstanceImporter',
1113=>'LightmapParameters',
1120=>'LightmapSnapshot'
}
end
+1
View File
@@ -0,0 +1 @@
require 'mikunyan/decoders/image_decoder'
+161
View File
@@ -0,0 +1,161 @@
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end
require 'bin_utils'
module Mikunyan
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]]
def self.decode_object(object)
return nil unless object.class == ObjectValue
endian = object.endian
width = object['m_Width']
height = object['m_Height']
bin = object['image data']
fmt = object['m_TextureFormat']
return nil unless width && height && bin && fmt
width = width.value
height = height.value
bin = bin.value
fmt = fmt.value
case fmt
when 1
decode_a8(width, height, bin)
when 2
decode_argb4444(width, height, bin, endian)
when 3
decode_rgb888(width, height, bin, endian)
when 4
decode_rgba8888(width, height, bin, endian)
when 5
decode_argb8888(width, height, bin, endian)
when 7
decode_rgb565(width, height, bin, endian)
when 13
decode_rgba4444(width, height, bin, endian)
when 34
decode_etc1(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
end
ChunkyPNG::Image.from_rgba_stream(bh * 4, bw * 4, pixels).rotate_right!.crop!(0, 0, width, height)
end
def self.decode_a8(width, height, bin)
pixels = bin.unpack('C*').map do |c|
c << 24 | c << 16 | c << 8 | 0xff
end
ChunkyPNG::Image.new(width, height, pixels)
end
def self.decode_rgb565(width, height, bin, endian = :big)
pixels = bin.unpack(endian == :little ? 'v*' : 'n*').map do |c|
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
end
ChunkyPNG::Image.new(width, height, pixels)
end
def self.decode_rgb888(width, height, bin, endian = :big)
if endian == :little
ChunkyPNG::Image.from_bgr_stream(width, height, bin)
else
ChunkyPNG::Image.from_rgb_stream(width, height, bin)
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
def self.decode_etc1_block(bin)
colors = []
codes = [bin >> 37 & 7, bin >> 34 & 7]
subblocks = Etc1SubblockTable[bin[32]]
if bin[33] == 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
else
colors[0] = bin >> 40 & 0xf8f8f8
dr = (bin >> 56 & 3) - (bin >> 56 & 4)
dg = (bin >> 48 & 3) - (bin >> 48 & 4)
db = (bin >> 40 & 3) - (bin >> 40 & 4)
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)
end
ret = Array.new(16, 0)
16.times do |i|
modifier = Etc1ModifierTable[codes[subblocks[i]]][bin[i]]
ret[i] = etc1colormod(colors[subblocks[i]], bin[i + 16] == 0 ? modifier : -modifier)
end
ret
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
end
end
end
+58
View File
@@ -0,0 +1,58 @@
module Mikunyan
class ObjectValue
attr_accessor :name, :type, :value, :endian, :is_struct
def initialize(name, type, endian, value = nil)
@name = name
@type = type
@endian = endian
@value = value
@is_struct = false
@attr = {}
end
def array?
value && value.class == Array
end
def value?
value && value.class != Array
end
def struct?
is_struct
end
def keys
@attr.keys
end
def key?(key)
@attr.key?(key)
end
def []
@value
end
def [](name)
if array? && name.class == Integer
@value[name]
else
@attr[name]
end
end
def []=(name, value)
@attr[name] = value
end
def method_missing(name, *args)
@attr[name.to_s]
end
def respond_to_missing?(symbol, include_private)
@attr.key?(symbol.to_s)
end
end
end
+61
View File
@@ -0,0 +1,61 @@
module Mikunyan
class TypeTree
attr_accessor :nodes
Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags)
def self.load(br)
nodes = []
node_count = br.i32u
buffer_size = br.i32u
node_count.times do
nodes << Node.new(br.i16u, br.i8u, br.i8u != 0, br.i32, br.i32, br.i32, br.i32u, br.i32u)
end
buffer = br.read(buffer_size)
nodes.each do |n|
if n.type >= 0
n.type = buffer.unpack("@#{n.type}Z*")[0]
else
n.type = Mikunyan::STRING_TABLE[n.type + 2**31]
end
if n.name >= 0
n.name = buffer.unpack("@#{n.name}Z*")[0]
else
n.name = Mikunyan::STRING_TABLE[n.name + 2**31]
end
end
r = TypeTree.new
r.nodes = nodes
r
end
def self.load_legacy(br)
nodes = []
stack = [0]
while stack.size > 0
depth = stack.pop
type = br.cstr
name = br.cstr
size = br.i32
index = br.i32u
is_array = (br.i32 != 0)
version = br.i32u
flags = br.i32u
child_count = br.i32u
child_count.times{ stack << depth + 1 }
nodes << Node.new(version, depth, is_array, type, name, size, index, flags)
end
r = TypeTree.new
r.nodes = nodes
r
end
def self.load_default(hash)
hash_str = hash.unpack('H*')[0]
file = File.expand_path("../typetrees/#{hash_str}.dat", __FILE__)
return nil unless File.file?(file)
r = TypeTree.new
r.nodes = Marshal.load(File.binread(file))
r
end
end
end

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