format codes

This commit is contained in:
Ishotihadus
2019-11-21 22:20:36 +09:00
parent 5c32fc9b8c
commit 9d189b2550
12 changed files with 1648 additions and 1626 deletions
+8 -6
View File
@@ -1,14 +1,16 @@
require "bundler/gem_tasks" # frozen_string_literal: true
require "rake/extensiontask"
require 'bundler/gem_tasks'
require 'rake/extensiontask'
task :scream do task :scream do
puts "みくは自分を曲げないよ!" puts 'みくは自分を曲げないよ!'
end end
task :build => :compile task build: :compile
Rake::ExtensionTask.new('decoders/native') do |ext| Rake::ExtensionTask.new('decoders/native') do |ext|
ext.lib_dir = 'lib/mikunyan' ext.lib_dir = 'lib/mikunyan'
end end
task :default => [:clobber, :compile, :spec] task default: %i[clobber compile spec]
+9 -7
View File
@@ -1,11 +1,13 @@
require "mikunyan/version" # frozen_string_literal: true
require "mikunyan/asset_bundle" require 'mikunyan/version'
require "mikunyan/asset"
require "mikunyan/binary_reader" require 'mikunyan/asset_bundle'
require "mikunyan/object_value" require 'mikunyan/asset'
require "mikunyan/type_tree" require 'mikunyan/binary_reader'
require "mikunyan/constants" require 'mikunyan/object_value'
require 'mikunyan/type_tree'
require 'mikunyan/constants'
# Module for deserializing Unity Assets and AssetBundles # Module for deserializing Unity Assets and AssetBundles
module Mikunyan module Mikunyan
+333 -333
View File
@@ -1,340 +1,340 @@
# frozen_string_literal: true
module Mikunyan module Mikunyan
# Class for representing Unity Asset # Class for representing Unity Asset
# @attr_reader [String] name Asset name # @attr_reader [String] name Asset name
# @attr_reader [Integer] format file format number # @attr_reader [Integer] format file format number
# @attr_reader [String] generator_version version string of generator # @attr_reader [String] generator_version version string of generator
# @attr_reader [Integer] target_platform target platform number # @attr_reader [Integer] target_platform target platform number
# @attr_reader [Symbol] endian data endianness (:little or :big) # @attr_reader [Symbol] endian data endianness (:little or :big)
# @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes # @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
# @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects # @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects
# @attr_reader [Array<Integer>] add_ids ? # @attr_reader [Array<Integer>] add_ids ?
# @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data # @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
class Asset class Asset
attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references, :res_s attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references, :res_s
# Struct for representing Asset class definition # Struct for representing Asset class definition
# @attr [Integer] class_id class ID # @attr [Integer] class_id class ID
# @attr [Integer,nil] script_id script ID # @attr [Integer,nil] script_id script ID
# @attr [String] hash hash value (16 or 32 bytes) # @attr [String] hash hash value (16 or 32 bytes)
# @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree # @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree) Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
# Struct for representing Asset object information # Struct for representing Asset object information
# @attr [Integer] path_id path ID # @attr [Integer] path_id path ID
# @attr [Integer] offset data offset # @attr [Integer] offset data offset
# @attr [Integer] size data size # @attr [Integer] size data size
# @attr [Integer,nil] type_id type ID # @attr [Integer,nil] type_id type ID
# @attr [Integer,nil] class_id class ID # @attr [Integer,nil] class_id class ID
# @attr [Integer,nil] class_idx class definition index # @attr [Integer,nil] class_idx class definition index
# @attr [Boolean] destroyed? destroyed or not # @attr [Boolean] destroyed? destroyed or not
# @attr [String] data binary data of object # @attr [String] data binary data of object
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data) ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
# Struct for representing Asset reference information # Struct for representing Asset reference information
# @attr [String] path path # @attr [String] path path
# @attr [String] guid GUID (16 bytes) # @attr [String] guid GUID (16 bytes)
# @attr [Integer] type ? # @attr [Integer] type ?
# @attr [String] file_path Asset name # @attr [String] file_path Asset name
Reference = Struct.new(:path, :guid, :type, :file_path) Reference = Struct.new(:path, :guid, :type, :file_path)
# Load Asset from binary string # Load Asset from binary string
# @param [String] bin binary data # @param [String] bin binary data
# @param [String] name Asset name # @param [String] name Asset name
# @param [String] res_s resS data # @param [String] res_s resS data
# @return [Mikunyan::Asset] deserialized Asset object # @return [Mikunyan::Asset] deserialized Asset object
def self.load(bin, name, res_s = nil) def self.load(bin, name, res_s = nil)
r = Asset.new(name, res_s) r = Asset.new(name, res_s)
r.send(:load, bin) r.send(:load, bin)
r r
end
# Load Asset from file
# @param [String] file file name
# @param [String] name Asset name (automatically generated if not specified)
# @return [Mikunyan::Asset] deserialized Asset object
def self.file(file, name=nil)
name = File.basename(name, '.*') unless name
Asset.load(File.binread(file), name)
end
# Returns list of all path IDs
# @return [Array<Integer>] list of all path IDs
def path_ids
@objects.map{|e| e.path_id}
end
# Returns list of containers
# @return [Array<Hash>,nil] list of all containers
def containers
obj = parse_object(1)
return nil unless obj && obj.m_Container && obj.m_Container.array?
obj.m_Container.value.map do |e|
{:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
end
end
# Parse object of given path ID
# @param [Integer,ObjectData] path_id path ID or object
# @return [Mikunyan::ObjectValue,nil] parsed object
def parse_object(path_id)
if path_id.class == Integer
obj = @objects.find{|e| e.path_id == path_id}
return nil unless obj
elsif path_id.class == ObjectData
obj = path_id
else
return nil
end
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
type_tree = Asset.parse_type_tree(klass)
return nil unless type_tree
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
end
# Parse object of given path ID and simplify it
# @param [Integer,ObjectData] path_id path ID or object
# @return [Hash,nil] parsed object
def parse_object_simple(path_id)
Asset.object_simplify(parse_object(path_id))
end
# Returns object type name string
# @param [Integer,ObjectData] path_id path ID or object
# @return [String,nil] type name
def object_type(path_id)
if path_id.class == Integer
obj = @objects.find{|e| e.path_id == path_id}
return nil unless obj
elsif path_id.class == ObjectData
obj = path_id
else
return nil
end
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
if klass && klass.type_tree && klass.type_tree.nodes[0]
klass.type_tree.nodes[0].type
elsif klass
Mikunyan::CLASS_ID[klass.class_id]
else
nil
end
end
private
def initialize(name, res_s = nil)
@name = name
@endian = :big
@res_s = res_s
end
def load(bin)
br = BinaryReader.new(bin)
metadata_size = br.i32u
size = br.i32u
@format = br.i32u
data_offset = br.i32u
if @format >= 9
@endian = :little if br.i32 == 0
br.endian = @endian
end
@generator_version = br.cstr
@target_platform = br.i32
@klasses = []
if @format >= 17
has_type_trees = (br.i8 != 0)
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
br.adv(1)
script_id = br.i16
if class_id < 0 || class_id == 114
hash = br.read(32)
else
hash = br.read(16)
end
@klasses << Klass.new(class_id, script_id, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
end
elsif @format >= 13
has_type_trees = (br.i8 != 0)
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
if class_id < 0
hash = br.read(32)
else
hash = br.read(16)
end
@klasses << Klass.new(class_id, nil, hash, has_type_trees ? TypeTree.load(br) : TypeTree.load_default(hash))
end
else
@type_trees = {}
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
@klasses << Klass.new(class_id, nil, nil, @format == 10 || @format == 12 ? TypeTree.load(br) : TypeTree.load_legacy(br))
end
end
long_object_ids = (@format >= 14 || (7 <= @format && @format <= 13 && br.i32 != 0))
@objects = []
object_count = br.i32u
object_count.times do
br.align(4) if @format >= 14
path_id = long_object_ids ? br.i64 : br.i32
offset = br.i32u
size = br.i32u
if @format >= 17
@objects << ObjectData.new(path_id, offset, size, nil, nil, br.i32u, @format <= 10 && br.i16 != 0)
else
@objects << ObjectData.new(path_id, offset, size, br.i32, br.i16, nil, @format <= 10 && br.i16 != 0)
end
br.adv(2) if 11 <= @format && @format <= 16
br.adv(1) if 15 <= @format && @format <= 16
end
if @format >= 11
@add_ids = []
add_id_count = br.i32u
add_id_count.times do
br.align(4) if @format >= 14
@add_ids << [(long_object_ids ? br.i64 : br.i32), br.i32]
end
end
if @format >= 6
@references = []
reference_count = br.i32u
reference_count.times do
@references << Reference.new(br.cstr, br.read(16), br.i32, br.cstr)
end
end
@objects.each do |e|
br.jmp(data_offset + e.offset)
e.data = br.read(e.size)
end
end
def parse_object_private(br, type_tree)
r = nil
node = type_tree[:node]
children = type_tree[:children]
if node.array?
data = nil
size = parse_object_private(br, children.find{|e| e[:name] == 'size'}).value
data_type_tree = children.find{|e| e[:name] == 'data'}
if node.type == 'TypelessData'
data = br.read(size * data_type_tree[:node].size)
else
data = size.times.map{ parse_object_private(br, data_type_tree) }
end
r = ObjectValue.new(node.name, node.type, br.endian, data)
elsif node.size == -1
r = ObjectValue.new(node.name, node.type, br.endian)
if children.size == 1 && children[0][:name] == 'Array' && children[0][:node].type == 'Array' && children[0][:node].array?
if node.type == 'string'
size = parse_object_private(br, children[0][:children].find{|e| e[:name] == 'size'}).value
r.value = br.read(size * children[0][:children].find{|e| e[:name] == 'data'}[:node].size).force_encoding("utf-8")
br.align(4) if children[0][:node].flags & 0x4000 != 0
else
r.value = parse_object_private(br, children[0]).value
end
elsif node.type == 'StreamingInfo'
children.each{|child| r[child[:name]] = parse_object_private(br, child)}
r.value = @res_s.byteslice(r['offset'].value, r['size'].value) if r['path'].value == "archive:/#{name}/#{name}.resS"
else
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
end
elsif children.size > 0
pos = br.pos
r = ObjectValue.new(node.name, node.type, br.endian)
r.is_struct = true
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
else
pos = br.pos
value = nil
case node.type
when 'bool'
value = (br.i8 != 0)
when 'SInt8'
value = br.i8s
when 'UInt8', 'char'
value = br.i8u
when 'SInt16', 'short'
value = br.i16s
when 'UInt16', 'unsigned short'
value = br.i16u
when 'SInt32', 'int'
value = br.i32s
when 'UInt32', 'unsigned int'
value = br.i32u
when 'SInt64', 'long long'
value = br.i64s
when 'UInt64', 'unsigned long long'
value = br.i64u
when 'float'
value = br.float
when 'double'
value = br.double
when 'ColorRGBA'
value = [br.i8u, br.i8u, br.i8u, br.i8u]
else
value = br.read(node.size)
end
br.jmp(pos + node.size)
r = ObjectValue.new(node.name, node.type, br.endian, value)
end
br.align(4) if node.flags & 0x4000 != 0
r
end
def self.object_simplify(obj)
if obj.class != ObjectValue
obj
elsif obj.type == 'pair'
[object_simplify(obj['first']), object_simplify(obj['second'])]
elsif obj.type == 'map' && obj.array?
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])] }.to_h
elsif obj.value?
object_simplify(obj.value)
elsif obj.array?
obj.value.map{|e| object_simplify(e)}
else
hash = {}
obj.keys.each do |key|
hash[key] = object_simplify(obj[key])
end
hash
end
end
def self.parse_type_tree(klass)
return nil unless klass.type_tree
nodes = klass.type_tree.nodes
tree = {}
stack = []
nodes.each do |node|
this = {:name => node.name, :node => node, :children => []}
if node.depth == 0
tree = this
else
stack[node.depth - 1][:children] << this
end
stack[node.depth] = this
end
tree
end
end end
# Load Asset from file
# @param [String] file file name
# @param [String] name Asset name (automatically generated if not specified)
# @return [Mikunyan::Asset] deserialized Asset object
def self.file(file, name = nil)
name ||= File.basename(name, '.*')
Asset.load(File.binread(file), name)
end
# Returns list of all path IDs
# @return [Array<Integer>] list of all path IDs
def path_ids
@objects.map(&:path_id)
end
# Returns list of containers
# @return [Array<Hash>,nil] list of all containers
def containers
obj = parse_object(1)
return nil unless obj&.m_Container && obj.m_Container.array?
obj.m_Container.value.map do |e|
{ name: e.first.value, preload_index: e.second.preloadIndex.value, path_id: e.second.asset.m_PathID.value }
end
end
# Parse object of given path ID
# @param [Integer,ObjectData] path_id path ID or object
# @return [Mikunyan::ObjectValue,nil] parsed object
def parse_object(path_id)
if path_id.class == Integer
obj = @objects.find{|e| e.path_id == path_id}
return nil unless obj
elsif path_id.class == ObjectData
obj = path_id
else
return nil
end
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
type_tree = Asset.parse_type_tree(klass)
return nil unless type_tree
parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
end
# Parse object of given path ID and simplify it
# @param [Integer,ObjectData] path_id path ID or object
# @return [Hash,nil] parsed object
def parse_object_simple(path_id)
Asset.object_simplify(parse_object(path_id))
end
# Returns object type name string
# @param [Integer,ObjectData] path_id path ID or object
# @return [String,nil] type name
def object_type(path_id)
if path_id.class == Integer
obj = @objects.find{|e| e.path_id == path_id}
return nil unless obj
elsif path_id.class == ObjectData
obj = path_id
else
return nil
end
klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
if klass&.type_tree && klass.type_tree.nodes[0]
klass.type_tree.nodes[0].type
elsif klass
Mikunyan::CLASS_ID[klass.class_id]
end
end
private
def initialize(name, res_s = nil)
@name = name
@endian = :big
@res_s = res_s
end
def load(bin)
br = BinaryReader.new(bin)
metadata_size = br.i32u
size = br.i32u
@format = br.i32u
data_offset = br.i32u
if @format >= 9
@endian = :little if br.i32 == 0
br.endian = @endian
end
@generator_version = br.cstr
@target_platform = br.i32
@klasses = []
if @format >= 17
has_type_trees = (br.i8 != 0)
type_tree_count = br.i32u
type_tree_count.times do
class_id = br.i32
br.adv(1)
script_id = br.i16
hash = if class_id < 0 || class_id == 114
br.read(32)
else
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
hash = if class_id < 0
br.read(32)
else
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
@objects << if @format >= 17
ObjectData.new(path_id, offset, size, nil, nil, br.i32u, @format <= 10 && br.i16 != 0)
else
ObjectData.new(path_id, offset, size, br.i32, br.i16, nil, @format <= 10 && br.i16 != 0)
end
br.adv(2) if 11 <= @format && @format <= 16
br.adv(1) if 15 <= @format && @format <= 16
end
if @format >= 11
@add_ids = []
add_id_count = br.i32u
add_id_count.times do
br.align(4) if @format >= 14
@add_ids << [(long_object_ids ? br.i64 : br.i32), br.i32]
end
end
if @format >= 6
@references = []
reference_count = br.i32u
reference_count.times do
@references << Reference.new(br.cstr, br.read(16), br.i32, br.cstr)
end
end
@objects.each do |e|
br.jmp(data_offset + e.offset)
e.data = br.read(e.size)
end
end
def parse_object_private(br, type_tree)
r = nil
node = type_tree[:node]
children = type_tree[:children]
if node.array?
data = nil
size = parse_object_private(br, children.find{|e| e[:name] == 'size'}).value
data_type_tree = children.find{|e| e[:name] == 'data'}
data = if node.type == 'TypelessData'
br.read(size * data_type_tree[:node].size)
else
size.times.map{parse_object_private(br, data_type_tree)}
end
r = ObjectValue.new(node.name, node.type, br.endian, data)
elsif node.size == -1
r = ObjectValue.new(node.name, node.type, br.endian)
if children.size == 1 && children[0][:name] == 'Array' && children[0][:node].type == 'Array' && children[0][:node].array?
if node.type == 'string'
size = parse_object_private(br, children[0][:children].find{|e| e[:name] == 'size'}).value
r.value = br.read(size * children[0][:children].find{|e| e[:name] == 'data'}[:node].size).force_encoding('utf-8')
br.align(4) if children[0][:node].flags & 0x4000 != 0
else
r.value = parse_object_private(br, children[0]).value
end
elsif node.type == 'StreamingInfo'
children.each{|child| r[child[:name]] = parse_object_private(br, child)}
r.value = @res_s.byteslice(r['offset'].value, r['size'].value) if r['path'].value == "archive:/#{name}/#{name}.resS"
else
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
end
elsif !children.empty?
pos = br.pos
r = ObjectValue.new(node.name, node.type, br.endian)
r.is_struct = true
children.each do |child|
r[child[:name]] = parse_object_private(br, child)
end
else
pos = br.pos
value = nil
case node.type
when 'bool'
value = (br.i8 != 0)
when 'SInt8'
value = br.i8s
when 'UInt8', 'char'
value = br.i8u
when 'SInt16', 'short'
value = br.i16s
when 'UInt16', 'unsigned short'
value = br.i16u
when 'SInt32', 'int'
value = br.i32s
when 'UInt32', 'unsigned int'
value = br.i32u
when 'SInt64', 'long long'
value = br.i64s
when 'UInt64', 'unsigned long long'
value = br.i64u
when 'float'
value = br.float
when 'double'
value = br.double
when 'ColorRGBA'
value = [br.i8u, br.i8u, br.i8u, br.i8u]
else
value = br.read(node.size)
end
br.jmp(pos + node.size)
r = ObjectValue.new(node.name, node.type, br.endian, value)
end
br.align(4) if node.flags & 0x4000 != 0
r
end
def self.object_simplify(obj)
if obj.class != ObjectValue
obj
elsif obj.type == 'pair'
[object_simplify(obj['first']), object_simplify(obj['second'])]
elsif obj.type == 'map' && obj.array?
obj.value.map{|e| [object_simplify(e['first']), object_simplify(e['second'])]}.to_h
elsif obj.value?
object_simplify(obj.value)
elsif obj.array?
obj.value.map{|e| object_simplify(e)}
else
hash = {}
obj.keys.each do |key|
hash[key] = object_simplify(obj[key])
end
hash
end
end
def self.parse_type_tree(klass)
return nil unless klass.type_tree
nodes = klass.type_tree.nodes
tree = {}
stack = []
nodes.each do |node|
this = { name: node.name, node: node, children: [] }
if node.depth == 0
tree = this
else
stack[node.depth - 1][:children] << this
end
stack[node.depth] = this
end
tree
end
end
end end
+127 -125
View File
@@ -1,132 +1,134 @@
# frozen_string_literal: true
require 'extlz4' require 'extlz4'
require 'extlzma' require 'extlzma'
module Mikunyan module Mikunyan
# Class for representing Unity AssetBundle # Class for representing Unity AssetBundle
# @attr_reader [String] signature file signature (UnityRaw or UnityFS) # @attr_reader [String] signature file signature (UnityRaw or UnityFS)
# @attr_reader [Integer] format file format number # @attr_reader [Integer] format file format number
# @attr_reader [String] unity_version version string of Unity to use this AssetBundle # @attr_reader [String] unity_version version string of Unity to use this AssetBundle
# @attr_reader [String] generator_version version string of generator # @attr_reader [String] generator_version version string of generator
# @attr_reader [Array<Mikunyan::Asset>] assets included Assets # @attr_reader [Array<Mikunyan::Asset>] assets included Assets
class AssetBundle class AssetBundle
attr_reader :signature, :format, :unity_version, :generator_version, :assets attr_reader :signature, :format, :unity_version, :generator_version, :assets
# Load AssetBundle from binary string # Load AssetBundle from binary string
# @param [String] bin binary data # @param [String] bin binary data
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object # @return [Mikunyan::AssetBundle] deserialized AssetBundle object
def self.load(bin) def self.load(bin)
r = AssetBundle.new r = AssetBundle.new
r.send(:load, bin) r.send(:load, bin)
r r
end
# Load AssetBundle from file
# @param [String] file file name
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
def self.file(file)
AssetBundle.load(File.binread(file))
end
private
def load(bin)
br = BinaryReader.new(bin)
@signature = br.cstr
@format = br.i32
@unity_version = br.cstr
@generator_version = br.cstr
case @signature
when 'UnityRaw'
load_unity_raw(br, false)
when 'UnityFS'
load_unity_fs(br)
when 'UnityWeb'
if @format <= 3
load_unity_raw(br, true)
else
load_unity_fs(br)
end
else
raise("Unknown signature: #{@signature}")
end
end
def load_unity_raw(br, compressed)
@assets = []
file_size = br.i32u
header_size = br.i32u
if compressed
br.adv(4)
block_count = br.i32u
blocks = block_count.times.map{{ :c => br.i32u, :u => br.i32u }}
br.jmp(header_size)
data = String.new
blocks.each{|b| data << LZMA.decode(br.read(b[:c]))}
br = BinaryReader.new(data)
else
br.jmp(header_size)
end
asset_count = br.i32u
asset_count.times do
asset_pos = br.pos
asset_name = br.cstr
asset_header_size = br.i32u
asset_size = br.i32u
br.jmp(asset_pos + asset_header_size - 4)
asset_data = br.read(asset_size)
asset = Asset.load(asset_data, asset_name)
@assets << asset
end
end
def load_unity_fs(br)
@assets = []
file_size = br.i64u
ci_block_size = br.i32u
ui_block_size = br.i32u
flags = br.i32u
head = BinaryReader.new(uncompress(flags & 0x80 == 0 ? br.read(ci_block_size) : br.read_abs(ci_block_size, file_size - ci_block_size), ui_block_size, flags))
guid = head.read(16)
blocks = []
block_count = head.i32u
block_count.times{ blocks << {:u => head.i32u, :c => head.i32u, :f => head.i16u} }
asset_blocks = []
asset_count = head.i32u
asset_count.times{ asset_blocks << {:offset => head.i64u, :size => head.i64u, :status => head.i32, :name => head.cstr} }
raw_data = String.new
blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])}
asset_blocks.each do |b|
next if b[:name].end_with?('.resS')
res_s = asset_blocks.find{|e| e[:name] == "#{b[:name]}.resS"}
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name], res_s && raw_data.byteslice(res_s[:offset], res_s[:size]))
@assets << asset
end
end
def uncompress(block, max_dest_size, flags)
case flags & 0x3f
when 0
block
# when 1
# LZMA
when 2, 3
LZ4.raw_decode(block, max_dest_size)
# when 4
# LZHMA
else
warn("Unknown compression flag: #{@flags}")
block
end
end
end end
# Load AssetBundle from file
# @param [String] file file name
# @return [Mikunyan::AssetBundle] deserialized AssetBundle object
def self.file(file)
AssetBundle.load(File.binread(file))
end
private
def load(bin)
br = BinaryReader.new(bin)
@signature = br.cstr
@format = br.i32
@unity_version = br.cstr
@generator_version = br.cstr
case @signature
when 'UnityRaw'
load_unity_raw(br, false)
when 'UnityFS'
load_unity_fs(br)
when 'UnityWeb'
if @format <= 3
load_unity_raw(br, true)
else
load_unity_fs(br)
end
else
raise("Unknown signature: #{@signature}")
end
end
def load_unity_raw(br, compressed)
@assets = []
file_size = br.i32u
header_size = br.i32u
if compressed
br.adv(4)
block_count = br.i32u
blocks = block_count.times.map{{ c: br.i32u, u: br.i32u }}
br.jmp(header_size)
data = ''
blocks.each{|b| data << LZMA.decode(br.read(b[:c]))}
br = BinaryReader.new(data)
else
br.jmp(header_size)
end
asset_count = br.i32u
asset_count.times do
asset_pos = br.pos
asset_name = br.cstr
asset_header_size = br.i32u
asset_size = br.i32u
br.jmp(asset_pos + asset_header_size - 4)
asset_data = br.read(asset_size)
asset = Asset.load(asset_data, asset_name)
@assets << asset
end
end
def load_unity_fs(br)
@assets = []
file_size = br.i64u
ci_block_size = br.i32u
ui_block_size = br.i32u
flags = br.i32u
head = BinaryReader.new(uncompress(flags & 0x80 == 0 ? br.read(ci_block_size) : br.read_abs(ci_block_size, file_size - 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|
next if b[:name].end_with?('.resS')
res_s = asset_blocks.find{|e| e[:name] == "#{b[:name]}.resS"}
asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name], res_s && raw_data.byteslice(res_s[:offset], res_s[:size]))
@assets << asset
end
end
def uncompress(block, max_dest_size, flags)
case flags & 0x3f
when 0
block
# when 1
# LZMA
when 2, 3
LZ4.raw_decode(block, max_dest_size)
# when 4
# LZHMA
else
warn("Unknown compression flag: #{@flags}")
block
end
end
end
end end
+156 -154
View File
@@ -1,160 +1,162 @@
# frozen_string_literal: true
require 'bin_utils' require 'bin_utils'
module Mikunyan module Mikunyan
# Class for manipulating binary string # Class for manipulating binary string
# @attr [Symbol] endian endianness # @attr [Symbol] endian endianness
# @attr [Integer] pos position # @attr [Integer] pos position
# @attr [Integer] length data size # @attr [Integer] length data size
class BinaryReader class BinaryReader
attr_accessor :endian, :pos, :length attr_accessor :endian, :pos, :length
# Constructor # Constructor
# @param [String] data binary string # @param [String] data binary string
# @param [Symbol] endian endianness # @param [Symbol] endian endianness
def initialize(data, endian = :big) def initialize(data, endian = :big)
@data = data @data = data
@pos = 0 @pos = 0
@length = data.bytesize @length = data.bytesize
@endian = endian @endian = endian
end
# Returns whether little endian or not
# @return [Boolean]
def little?
@endian == :little
end
# Jump to given position
# @param [Integer] pos position
def jmp(pos=0)
@pos = pos
end
# Advance position given size
# @param [Integer] size size
def adv(size=0)
@pos += size
end
# Round up position to multiple of given size
# @param [Integer] size size
def align(size)
@pos = (@pos + size - 1) / size * size
end
# Read given size of binary string and seek
# @param [Integer] size size
# @return [String] data
def read(size)
data = @data.byteslice(@pos, size)
@pos += size
data
end
# Read given size of binary string from specified position. This method does not seek.
# @param [Integer] size size
# @param [Integer] pos position
# @return [String] data
def read_abs(size, pos)
@data.byteslice(pos, size)
end
# Read string until null character
# @return [String] string
def cstr
r = @data.unpack("@#{pos}Z*")[0]
@pos += r.bytesize + 1
r
end
# Read 8bit signed integer
def i8
i8s
end
# Read 8bit signed integer
def i8s
r = BinUtils.get_sint8(@data, @pos)
@pos += 1
r
end
# Read 8bit unsigned integer
def i8u
r = BinUtils.get_int8(@data, @pos)
@pos += 1
r
end
# Read 16bit signed integer
def i16
i16s
end
# Read 16bit signed integer
def i16s
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
@pos += 2
r
end
# Read 16bit unsigned integer
def i16u
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
@pos += 2
r
end
# Read 32bit signed integer
def i32
i32s
end
# Read 32bit signed integer
def i32s
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
@pos += 4
r
end
# Read 32bit unsigned integer
def i32u
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
@pos += 4
r
end
# Read 64bit signed integer
def i64
i64s
end
# Read 64bit signed integer
def i64s
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
@pos += 8
r
end
# Read 64bit unsigned integer
def i64u
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
@pos += 8
r
end
# Read 32bit floating point value
def float
r = little? ? @data.byteslice(@pos, 4).unpack('e')[0] : @data.byteslice(@pos, 4).unpack('g')[0]
@pos += 4
r
end
# Read 64bit floating point value
def double
r = little? ? @data.byteslice(@pos, 8).unpack('E')[0] : @data.byteslice(@pos, 8).unpack('G')[0]
@pos += 8
r
end
end end
# Returns whether little endian or not
# @return [Boolean]
def little?
@endian == :little
end
# Jump to given position
# @param [Integer] pos position
def jmp(pos = 0)
@pos = pos
end
# Advance position given size
# @param [Integer] size size
def adv(size = 0)
@pos += size
end
# Round up position to multiple of given size
# @param [Integer] size size
def align(size)
@pos = (@pos + size - 1) / size * size
end
# Read given size of binary string and seek
# @param [Integer] size size
# @return [String] data
def read(size)
data = @data.byteslice(@pos, size)
@pos += size
data
end
# Read given size of binary string from specified position. This method does not seek.
# @param [Integer] size size
# @param [Integer] pos position
# @return [String] data
def read_abs(size, pos)
@data.byteslice(pos, size)
end
# Read string until null character
# @return [String] string
def cstr
r = @data.unpack1("@#{pos}Z*")
@pos += r.bytesize + 1
r
end
# Read 8bit signed integer
def i8
i8s
end
# Read 8bit signed integer
def i8s
r = BinUtils.get_sint8(@data, @pos)
@pos += 1
r
end
# Read 8bit unsigned integer
def i8u
r = BinUtils.get_int8(@data, @pos)
@pos += 1
r
end
# Read 16bit signed integer
def i16
i16s
end
# Read 16bit signed integer
def i16s
r = little? ? BinUtils.get_sint16_le(@data, @pos) : BinUtils.get_sint16_be(@data, @pos)
@pos += 2
r
end
# Read 16bit unsigned integer
def i16u
r = little? ? BinUtils.get_int16_le(@data, @pos) : BinUtils.get_int16_be(@data, @pos)
@pos += 2
r
end
# Read 32bit signed integer
def i32
i32s
end
# Read 32bit signed integer
def i32s
r = little? ? BinUtils.get_sint32_le(@data, @pos) : BinUtils.get_sint32_be(@data, @pos)
@pos += 4
r
end
# Read 32bit unsigned integer
def i32u
r = little? ? BinUtils.get_int32_le(@data, @pos) : BinUtils.get_int32_be(@data, @pos)
@pos += 4
r
end
# Read 64bit signed integer
def i64
i64s
end
# Read 64bit signed integer
def i64s
r = little? ? BinUtils.get_sint64_le(@data, @pos) : BinUtils.get_sint64_be(@data, @pos)
@pos += 8
r
end
# Read 64bit unsigned integer
def i64u
r = little? ? BinUtils.get_int64_le(@data, @pos) : BinUtils.get_int64_be(@data, @pos)
@pos += 8
r
end
# Read 32bit floating point value
def float
r = little? ? @data.byteslice(@pos, 4).unpack1('e') : @data.byteslice(@pos, 4).unpack1('g')
@pos += 4
r
end
# Read 64bit floating point value
def double
r = little? ? @data.byteslice(@pos, 8).unpack1('E') : @data.byteslice(@pos, 8).unpack1('G')
@pos += 8
r
end
end
end end
+345 -342
View File
@@ -1,344 +1,347 @@
module Mikunyan # frozen_string_literal: true
private
STRING_TABLE = {
0=>'AABB',
5=>'AnimationClip',
19=>'AnimationCurve',
34=>'AnimationState',
49=>'Array',
55=>'Base',
60=>'BitField',
69=>'bitset',
76=>'bool',
81=>'char',
86=>'ColorRGBA',
96=>'Component',
106=>'data',
111=>'deque',
117=>'double',
124=>'dynamic_array',
138=>'FastPropertyName',
155=>'first',
161=>'float',
167=>'Font',
172=>'GameObject',
183=>'Generic Mono',
196=>'GradientNEW',
208=>'GUID',
213=>'GUIStyle',
222=>'int',
226=>'list',
231=>'long long',
241=>'map',
245=>'Matrix4x4f',
256=>'MdFour',
263=>'MonoBehaviour',
277=>'MonoScript',
288=>'m_ByteSize',
299=>'m_Curve',
307=>'m_EditorClassIdentifier',
331=>'m_EditorHideFlags',
349=>'m_Enabled',
359=>'m_ExtensionPtr',
374=>'m_GameObject',
387=>'m_Index',
395=>'m_IsArray',
405=>'m_IsStatic',
416=>'m_MetaFlag',
427=>'m_Name',
434=>'m_ObjectHideFlags',
452=>'m_PrefabInternal',
469=>'m_PrefabParentObject',
490=>'m_Script',
499=>'m_StaticEditorFlags',
519=>'m_Type',
526=>'m_Version',
536=>'Object',
543=>'pair',
548=>'PPtr<Component>',
564=>'PPtr<GameObject>',
581=>'PPtr<Material>',
596=>'PPtr<MonoBehaviour>',
616=>'PPtr<MonoScript>',
633=>'PPtr<Object>',
646=>'PPtr<Prefab>',
659=>'PPtr<Sprite>',
672=>'PPtr<TextAsset>',
688=>'PPtr<Texture>',
702=>'PPtr<Texture2D>',
718=>'PPtr<Transform>',
734=>'Prefab',
741=>'Quaternionf',
753=>'Rectf',
759=>'RectInt',
767=>'RectOffset',
778=>'second',
785=>'set',
789=>'short',
795=>'size',
800=>'SInt16',
807=>'SInt32',
814=>'SInt64',
821=>'SInt8',
827=>'staticvector',
840=>'string',
847=>'TextAsset',
857=>'TextMesh',
866=>'Texture',
874=>'Texture2D',
884=>'Transform',
894=>'TypelessData',
907=>'UInt16',
914=>'UInt32',
921=>'UInt64',
928=>'UInt8',
934=>'unsigned int',
947=>'unsigned long long',
966=>'unsigned short',
981=>'vector',
988=>'Vector2f',
997=>'Vector3f',
1006=>'Vector4f',
1015=>'m_ScriptingClassIdentifier',
1042=>'Gradient'
}
CLASS_ID = { module Mikunyan
1=>'GameObject', private
2=>'Component',
3=>'LevelGameManager', STRING_TABLE = {
4=>'Transform', 0 => 'AABB',
5=>'TimeManager', 5 => 'AnimationClip',
6=>'GlobalGameManager', 19 => 'AnimationCurve',
8=>'Behaviour', 34 => 'AnimationState',
9=>'GameManager', 49 => 'Array',
11=>'AudioManager', 55 => 'Base',
12=>'ParticleAnimator', 60 => 'BitField',
13=>'InputManager', 69 => 'bitset',
15=>'EllipsoidParticleEmitter', 76 => 'bool',
17=>'Pipeline', 81 => 'char',
18=>'EditorExtension', 86 => 'ColorRGBA',
19=>'Physics2DSettings', 96 => 'Component',
20=>'Camera', 106 => 'data',
21=>'Material', 111 => 'deque',
23=>'MeshRenderer', 117 => 'double',
25=>'Renderer', 124 => 'dynamic_array',
26=>'ParticleRenderer', 138 => 'FastPropertyName',
27=>'Texture', 155 => 'first',
28=>'Texture2D', 161 => 'float',
29=>'SceneSettings', 167 => 'Font',
30=>'GraphicsSettings', 172 => 'GameObject',
33=>'MeshFilter', 183 => 'Generic Mono',
41=>'OcclusionPortal', 196 => 'GradientNEW',
43=>'Mesh', 208 => 'GUID',
45=>'Skybox', 213 => 'GUIStyle',
47=>'QualitySettings', 222 => 'int',
48=>'Shader', 226 => 'list',
49=>'TextAsset', 231 => 'long long',
50=>'Rigidbody2D', 241 => 'map',
51=>'Physics2DManager', 245 => 'Matrix4x4f',
53=>'Collider2D', 256 => 'MdFour',
54=>'Rigidbody', 263 => 'MonoBehaviour',
55=>'PhysicsManager', 277 => 'MonoScript',
56=>'Collider', 288 => 'm_ByteSize',
57=>'Joint', 299 => 'm_Curve',
58=>'CircleCollider2D', 307 => 'm_EditorClassIdentifier',
59=>'HingeJoint', 331 => 'm_EditorHideFlags',
60=>'PolygonCollider2D', 349 => 'm_Enabled',
61=>'BoxCollider2D', 359 => 'm_ExtensionPtr',
62=>'PhysicsMaterial2D', 374 => 'm_GameObject',
64=>'MeshCollider', 387 => 'm_Index',
65=>'BoxCollider', 395 => 'm_IsArray',
66=>'SpriteCollider2D', 405 => 'm_IsStatic',
68=>'EdgeCollider2D', 416 => 'm_MetaFlag',
72=>'ComputeShader', 427 => 'm_Name',
74=>'AnimationClip', 434 => 'm_ObjectHideFlags',
75=>'ConstantForce', 452 => 'm_PrefabInternal',
76=>'WorldParticleCollider', 469 => 'm_PrefabParentObject',
78=>'TagManager', 490 => 'm_Script',
81=>'AudioListener', 499 => 'm_StaticEditorFlags',
82=>'AudioSource', 519 => 'm_Type',
83=>'AudioClip', 526 => 'm_Version',
84=>'RenderTexture', 536 => 'Object',
87=>'MeshParticleEmitter', 543 => 'pair',
88=>'ParticleEmitter', 548 => 'PPtr<Component>',
89=>'Cubemap', 564 => 'PPtr<GameObject>',
90=>'Avatar', 581 => 'PPtr<Material>',
91=>'AnimatorController', 596 => 'PPtr<MonoBehaviour>',
92=>'GUILayer', 616 => 'PPtr<MonoScript>',
93=>'RuntimeAnimatorController', 633 => 'PPtr<Object>',
94=>'ScriptMapper', 646 => 'PPtr<Prefab>',
95=>'Animator', 659 => 'PPtr<Sprite>',
96=>'TrailRenderer', 672 => 'PPtr<TextAsset>',
98=>'DelayedCallManager', 688 => 'PPtr<Texture>',
102=>'TextMesh', 702 => 'PPtr<Texture2D>',
104=>'RenderSettings', 718 => 'PPtr<Transform>',
108=>'Light', 734 => 'Prefab',
109=>'CGProgram', 741 => 'Quaternionf',
110=>'BaseAnimationTrack', 753 => 'Rectf',
111=>'Animation', 759 => 'RectInt',
114=>'MonoBehaviour', 767 => 'RectOffset',
115=>'MonoScript', 778 => 'second',
116=>'MonoManager', 785 => 'set',
117=>'Texture3D', 789 => 'short',
118=>'NewAnimationTrack', 795 => 'size',
119=>'Projector', 800 => 'SInt16',
120=>'LineRenderer', 807 => 'SInt32',
121=>'Flare', 814 => 'SInt64',
122=>'Halo', 821 => 'SInt8',
123=>'LensFlare', 827 => 'staticvector',
124=>'FlareLayer', 840 => 'string',
125=>'HaloLayer', 847 => 'TextAsset',
126=>'NavMeshAreas', 857 => 'TextMesh',
127=>'HaloManager', 866 => 'Texture',
128=>'Font', 874 => 'Texture2D',
129=>'PlayerSettings', 884 => 'Transform',
130=>'NamedObject', 894 => 'TypelessData',
131=>'GUITexture', 907 => 'UInt16',
132=>'GUIText', 914 => 'UInt32',
133=>'GUIElement', 921 => 'UInt64',
134=>'PhysicMaterial', 928 => 'UInt8',
135=>'SphereCollider', 934 => 'unsigned int',
136=>'CapsuleCollider', 947 => 'unsigned long long',
137=>'SkinnedMeshRenderer', 966 => 'unsigned short',
138=>'FixedJoint', 981 => 'vector',
140=>'RaycastCollider', 988 => 'Vector2f',
141=>'BuildSettings', 997 => 'Vector3f',
142=>'AssetBundle', 1006 => 'Vector4f',
143=>'CharacterController', 1015 => 'm_ScriptingClassIdentifier',
144=>'CharacterJoint', 1042 => 'Gradient'
145=>'SpringJoint', }.freeze
146=>'WheelCollider',
147=>'ResourceManager', CLASS_ID = {
148=>'NetworkView', 1 => 'GameObject',
149=>'NetworkManager', 2 => 'Component',
150=>'PreloadData', 3 => 'LevelGameManager',
152=>'MovieTexture', 4 => 'Transform',
153=>'ConfigurableJoint', 5 => 'TimeManager',
154=>'TerrainCollider', 6 => 'GlobalGameManager',
155=>'MasterServerInterface', 8 => 'Behaviour',
156=>'TerrainData', 9 => 'GameManager',
157=>'LightmapSettings', 11 => 'AudioManager',
158=>'WebCamTexture', 12 => 'ParticleAnimator',
159=>'EditorSettings', 13 => 'InputManager',
160=>'InteractiveCloth', 15 => 'EllipsoidParticleEmitter',
161=>'ClothRenderer', 17 => 'Pipeline',
162=>'EditorUserSettings', 18 => 'EditorExtension',
163=>'SkinnedCloth', 19 => 'Physics2DSettings',
164=>'AudioReverbFilter', 20 => 'Camera',
165=>'AudioHighPassFilter', 21 => 'Material',
166=>'AudioChorusFilter', 23 => 'MeshRenderer',
167=>'AudioReverbZone', 25 => 'Renderer',
168=>'AudioEchoFilter', 26 => 'ParticleRenderer',
169=>'AudioLowPassFilter', 27 => 'Texture',
170=>'AudioDistortionFilter', 28 => 'Texture2D',
171=>'SparseTexture', 29 => 'SceneSettings',
180=>'AudioBehaviour', 30 => 'GraphicsSettings',
181=>'AudioFilter', 33 => 'MeshFilter',
182=>'WindZone', 41 => 'OcclusionPortal',
183=>'Cloth', 43 => 'Mesh',
184=>'SubstanceArchive', 45 => 'Skybox',
185=>'ProceduralMaterial', 47 => 'QualitySettings',
186=>'ProceduralTexture', 48 => 'Shader',
191=>'OffMeshLink', 49 => 'TextAsset',
192=>'OcclusionArea', 50 => 'Rigidbody2D',
193=>'Tree', 51 => 'Physics2DManager',
194=>'NavMeshObsolete', 53 => 'Collider2D',
195=>'NavMeshAgent', 54 => 'Rigidbody',
196=>'NavMeshSettings', 55 => 'PhysicsManager',
197=>'LightProbesLegacy', 56 => 'Collider',
198=>'ParticleSystem', 57 => 'Joint',
199=>'ParticleSystemRenderer', 58 => 'CircleCollider2D',
200=>'ShaderVariantCollection', 59 => 'HingeJoint',
205=>'LODGroup', 60 => 'PolygonCollider2D',
206=>'BlendTree', 61 => 'BoxCollider2D',
207=>'Motion', 62 => 'PhysicsMaterial2D',
208=>'NavMeshObstacle', 64 => 'MeshCollider',
210=>'TerrainInstance', 65 => 'BoxCollider',
212=>'SpriteRenderer', 66 => 'SpriteCollider2D',
213=>'Sprite', 68 => 'EdgeCollider2D',
214=>'CachedSpriteAtlas', 72 => 'ComputeShader',
215=>'ReflectionProbe', 74 => 'AnimationClip',
216=>'ReflectionProbes', 75 => 'ConstantForce',
218=>'Terrain', 76 => 'WorldParticleCollider',
220=>'LightProbeGroup', 78 => 'TagManager',
221=>'AnimatorOverrideController', 81 => 'AudioListener',
222=>'CanvasRenderer', 82 => 'AudioSource',
223=>'Canvas', 83 => 'AudioClip',
224=>'RectTransform', 84 => 'RenderTexture',
225=>'CanvasGroup', 87 => 'MeshParticleEmitter',
226=>'BillboardAsset', 88 => 'ParticleEmitter',
227=>'BillboardRenderer', 89 => 'Cubemap',
228=>'SpeedTreeWindAsset', 90 => 'Avatar',
229=>'AnchoredJoint2D', 91 => 'AnimatorController',
230=>'Joint2D', 92 => 'GUILayer',
231=>'SpringJoint2D', 93 => 'RuntimeAnimatorController',
232=>'DistanceJoint2D', 94 => 'ScriptMapper',
233=>'HingeJoint2D', 95 => 'Animator',
234=>'SliderJoint2D', 96 => 'TrailRenderer',
235=>'WheelJoint2D', 98 => 'DelayedCallManager',
238=>'NavMeshData', 102 => 'TextMesh',
240=>'AudioMixer', 104 => 'RenderSettings',
241=>'AudioMixerController', 108 => 'Light',
243=>'AudioMixerGroupController', 109 => 'CGProgram',
244=>'AudioMixerEffectController', 110 => 'BaseAnimationTrack',
245=>'AudioMixerSnapshotController', 111 => 'Animation',
246=>'PhysicsUpdateBehaviour2D', 114 => 'MonoBehaviour',
247=>'ConstantForce2D', 115 => 'MonoScript',
248=>'Effector2D', 116 => 'MonoManager',
249=>'AreaEffector2D', 117 => 'Texture3D',
250=>'PointEffector2D', 118 => 'NewAnimationTrack',
251=>'PlatformEffector2D', 119 => 'Projector',
252=>'SurfaceEffector2D', 120 => 'LineRenderer',
258=>'LightProbes', 121 => 'Flare',
271=>'SampleClip', 122 => 'Halo',
272=>'AudioMixerSnapshot', 123 => 'LensFlare',
273=>'AudioMixerGroup', 124 => 'FlareLayer',
290=>'AssetBundleManifest', 125 => 'HaloLayer',
1001=>'Prefab', 126 => 'NavMeshAreas',
1002=>'EditorExtensionImpl', 127 => 'HaloManager',
1003=>'AssetImporter', 128 => 'Font',
1004=>'AssetDatabase', 129 => 'PlayerSettings',
1005=>'Mesh3DSImporter', 130 => 'NamedObject',
1006=>'TextureImporter', 131 => 'GUITexture',
1007=>'ShaderImporter', 132 => 'GUIText',
1008=>'ComputeShaderImporter', 133 => 'GUIElement',
1011=>'AvatarMask', 134 => 'PhysicMaterial',
1020=>'AudioImporter', 135 => 'SphereCollider',
1026=>'HierarchyState', 136 => 'CapsuleCollider',
1027=>'GUIDSerializer', 137 => 'SkinnedMeshRenderer',
1028=>'AssetMetaData', 138 => 'FixedJoint',
1029=>'DefaultAsset', 140 => 'RaycastCollider',
1030=>'DefaultImporter', 141 => 'BuildSettings',
1031=>'TextScriptImporter', 142 => 'AssetBundle',
1032=>'SceneAsset', 143 => 'CharacterController',
1034=>'NativeFormatImporter', 144 => 'CharacterJoint',
1035=>'MonoImporter', 145 => 'SpringJoint',
1037=>'AssetServerCache', 146 => 'WheelCollider',
1038=>'LibraryAssetImporter', 147 => 'ResourceManager',
1040=>'ModelImporter', 148 => 'NetworkView',
1041=>'FBXImporter', 149 => 'NetworkManager',
1042=>'TrueTypeFontImporter', 150 => 'PreloadData',
1044=>'MovieImporter', 152 => 'MovieTexture',
1045=>'EditorBuildSettings', 153 => 'ConfigurableJoint',
1046=>'DDSImporter', 154 => 'TerrainCollider',
1048=>'InspectorExpandedState', 155 => 'MasterServerInterface',
1049=>'AnnotationManager', 156 => 'TerrainData',
1050=>'PluginImporter', 157 => 'LightmapSettings',
1051=>'EditorUserBuildSettings', 158 => 'WebCamTexture',
1052=>'PVRImporter', 159 => 'EditorSettings',
1053=>'ASTCImporter', 160 => 'InteractiveCloth',
1054=>'KTXImporter', 161 => 'ClothRenderer',
1101=>'AnimatorStateTransition', 162 => 'EditorUserSettings',
1102=>'AnimatorState', 163 => 'SkinnedCloth',
1105=>'HumanTemplate', 164 => 'AudioReverbFilter',
1107=>'AnimatorStateMachine', 165 => 'AudioHighPassFilter',
1108=>'PreviewAssetType', 166 => 'AudioChorusFilter',
1109=>'AnimatorTransition', 167 => 'AudioReverbZone',
1110=>'SpeedTreeImporter', 168 => 'AudioEchoFilter',
1111=>'AnimatorTransitionBase', 169 => 'AudioLowPassFilter',
1112=>'SubstanceImporter', 170 => 'AudioDistortionFilter',
1113=>'LightmapParameters', 171 => 'SparseTexture',
1120=>'LightmapSnapshot' 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'
}.freeze
end end
+5 -3
View File
@@ -1,7 +1,9 @@
# frozen_string_literal: true
require 'mikunyan/decoders/image_decoder' require 'mikunyan/decoders/image_decoder'
module Mikunyan module Mikunyan
# Module for helper classes for decoding object # Module for helper classes for decoding object
module DecodeHelper module DecodeHelper
end end
end end
+473 -471
View File
@@ -1,481 +1,483 @@
begin; require 'oily_png'; rescue LoadError; require 'chunky_png'; end # frozen_string_literal: true
begin
require 'oily_png'
rescue LoadError
require 'chunky_png'
end
require 'bin_utils' require 'bin_utils'
require 'mikunyan/decoders/native' require 'mikunyan/decoders/native'
module Mikunyan module Mikunyan
# Class for image decoding tools # Class for image decoding tools
class ImageDecoder class ImageDecoder
# Decode image from Mikunyan::ObjectValue # Decode image from Mikunyan::ObjectValue
# @param [Mikunyan::ObjectValue] object object to decode # @param [Mikunyan::ObjectValue] object object to decode
# @return [ChunkyPNG::Image,nil] decoded image # @return [ChunkyPNG::Image,nil] decoded image
def self.decode_object(object) def self.decode_object(object)
return nil unless object.class == ObjectValue return nil unless object.class == ObjectValue
endian = object.endian endian = object.endian
width = object['m_Width'] width = object['m_Width']
height = object['m_Height'] height = object['m_Height']
bin = object['image data'] bin = object['image data']
fmt = object['m_TextureFormat'] fmt = object['m_TextureFormat']
return nil unless width && height && bin && fmt return nil unless width && height && bin && fmt
width = width.value width = width.value
height = height.value height = height.value
bin = bin.value bin = bin.value
fmt = fmt.value fmt = fmt.value
if bin.size == 0 && object['m_StreamData'] if bin.empty? && object['m_StreamData']
bin = object['m_StreamData'].value bin = object['m_StreamData'].value
return nil unless bin return nil unless bin
end end
case fmt case fmt
when 1 when 1
decode_a8(width, height, bin) decode_a8(width, height, bin)
when 2 when 2
decode_argb4444(width, height, bin, endian) decode_argb4444(width, height, bin, endian)
when 3 when 3
decode_rgb24(width, height, bin) decode_rgb24(width, height, bin)
when 4 when 4
decode_rgba32(width, height, bin) decode_rgba32(width, height, bin)
when 5 when 5
decode_argb32(width, height, bin) decode_argb32(width, height, bin)
when 7 when 7
decode_rgb565(width, height, bin, endian) decode_rgb565(width, height, bin, endian)
when 9 when 9
decode_r16(width, height, bin) decode_r16(width, height, bin)
when 10 when 10
decode_dxt1(width, height, bin) decode_dxt1(width, height, bin)
when 12 when 12
decode_dxt5(width, height, bin) decode_dxt5(width, height, bin)
when 13 when 13
decode_rgba4444(width, height, bin, endian) decode_rgba4444(width, height, bin, endian)
when 14 when 14
decode_bgra32(width, height, bin) decode_bgra32(width, height, bin)
when 15 when 15
decode_rhalf(width, height, bin, endian) decode_rhalf(width, height, bin, endian)
when 16 when 16
decode_rghalf(width, height, bin, endian) decode_rghalf(width, height, bin, endian)
when 17 when 17
decode_rgbahalf(width, height, bin, endian) decode_rgbahalf(width, height, bin, endian)
when 18 when 18
decode_rfloat(width, height, bin, endian) decode_rfloat(width, height, bin, endian)
when 19 when 19
decode_rgfloat(width, height, bin, endian) decode_rgfloat(width, height, bin, endian)
when 20 when 20
decode_rgbafloat(width, height, bin, endian) decode_rgbafloat(width, height, bin, endian)
when 22 when 22
decode_rgb9e5float(width, height, bin, endian) decode_rgb9e5float(width, height, bin, endian)
when 34 when 34
decode_etc1(width, height, bin) decode_etc1(width, height, bin)
when 45 when 45
decode_etc2rgb(width, height, bin) decode_etc2rgb(width, height, bin)
when 46 when 46
decode_etc2rgba1(width, height, bin) decode_etc2rgba1(width, height, bin)
when 47 when 47
decode_etc2rgba8(width, height, bin) decode_etc2rgba8(width, height, bin)
when 48, 54 when 48, 54
decode_astc(width, height, 4, bin) decode_astc(width, height, 4, bin)
when 49, 55 when 49, 55
decode_astc(width, height, 5, bin) decode_astc(width, height, 5, bin)
when 50, 56 when 50, 56
decode_astc(width, height, 6, bin) decode_astc(width, height, 6, bin)
when 51, 57 when 51, 57
decode_astc(width, height, 8, bin) decode_astc(width, height, 8, bin)
when 52, 58 when 52, 58
decode_astc(width, height, 10, bin) decode_astc(width, height, 10, bin)
when 53, 59 when 53, 59
decode_astc(width, height, 12, bin) decode_astc(width, height, 12, bin)
when 62 when 62
decode_rg16(width, height, bin) decode_rg16(width, height, bin)
when 63 when 63
decode_r8(width, height, bin) decode_r8(width, height, bin)
else end
nil
end
end
# Decode image from RGBA4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from ARGB4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from RGB565 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb565(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_rgb565(bin, width * height, endian == :big)).flip
end
# Decode image from A8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_a8(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = BinUtils.get_int8(bin, i)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from R8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_r8(width, height, bin)
decode_a8(width, height, bin)
end
# Decode image from RG16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rg16(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i*2), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGB24 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb24(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, bin).flip
end
# Decode image from RGBA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba32(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, bin).flip
end
# Decode image from ARGB32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_be(bin, i*4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from BGRA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_bgra32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_le(bin, i*4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from R16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_r16(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)
c = f2i(r / 65535.0)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGB9e5 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb9e5float(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
n = endian == :little ? BinUtils.get_int32_le(bin, i*4) : BinUtils.get_int32_be(bin, i*4)
e = (n & 0xf8000000) >> 27
r = (n & 0x7fc0000) >> 9
g = (n & 0x3fe00) >> 9
b = n & 0x1ff
r = (r / 512r + 1) * (2**(e-15))
g = (g / 512r + 1) * (2**(e-15))
b = (b / 512r + 1) * (2**(e-15))
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b))
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from R Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rhalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*2) : BinUtils.get_int16_be(bin, i*2)))
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RG Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rghalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4) : BinUtils.get_int16_be(bin, i*4)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*4+2) : BinUtils.get_int16_be(bin, i*4+2)))
BinUtils.append_int8!(mem, r, g, 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbahalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8) : BinUtils.get_int16_be(bin, i*8)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+2) : BinUtils.get_int16_be(bin, i*8+2)))
b = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+4) : BinUtils.get_int16_be(bin, i*8+4)))
a = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i*8+6) : BinUtils.get_int16_be(bin, i*8+6)))
BinUtils.append_int8!(mem, r, g, b, a)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from R float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e' : 'g'
(width * height).times do |i|
c = f2i(bin.byteslice(i*4, 4).unpack(unpackstr)[0])
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RG float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e2' : 'g2'
(width * height).times do |i|
r, g = bin.byteslice(i*8, 8).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbafloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
unpackstr = endian == :little ? 'e4' : 'g4'
(width * height).times do |i|
r, g, b, a = bin.byteslice(i*16, 16).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from DXT1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt1(bin, width, height))
end
# Decode image from DXT5 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt5(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt5(bin, width, height))
end
# Decode image from ETC1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc1(bin, width, height))
end
# Decode image from ETC2 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgb(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2(bin, width, height))
end
# Decode image from ETC2 Alpha1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a1(bin, width, height))
end
# Decode image from ETC2 Alpha8 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba8(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a8(bin, width, height))
end
# Decode image from ASTC compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [Integer] blocksize block size
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_astc(width, height, blocksize, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_astc(bin, width, height, blocksize, blocksize))
end
# Create ASTC file data from ObjectValue
# @param [Mikunyan::ObjectValue,Hash] object target object
# @return [String,nil] created file
def self.create_astc_file(object)
astc_list = {
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12
}
width = object['m_Width']
height = object['m_Height']
fmt = object['m_TextureFormat']
bin = object['image data']
width = width.value if width.class == ObjectValue
height = height.value if height.class == ObjectValue
fmt = fmt.value if fmt.class == ObjectValue
bin = bin.value if bin.class == ObjectValue
if width && height && fmt && astc_list[fmt]
header = "\x13\xAB\xA1\x5C".force_encoding('ascii-8bit')
header << [astc_list[fmt], astc_list[fmt], 1].pack("C*")
header << [width].pack("V").byteslice(0, 3)
header << [height].pack("V").byteslice(0, 3)
header << "\x01\x00\x00"
header + bin
else
nil
end
end
private
# convert 16bit float
def self.n2f(n)
case n
when 0x0000
0.0
when 0x8000
-0.0
when 0x7c00
Float::INFINITY
when 0xfc00
-Float::INFINITY
else
s = n & 0x8000 != 0
e = n & 0x7c00
f = n & 0x03ff
case e
when 0x7c00
Float::NAN
when 0
(s ? -f : f) * 2.0**-24
else
(s ? -1 : 1) * (f / 1024.0 + 1) * (2.0 ** ((e >> 10)-15))
end
end
end
# [0.0,1.0] -> [0,255]
def self.f2i(d)
(d * 255).round.clamp(0, 255)
end
end end
# Decode image from RGBA4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)
c = ((c & 0xf000) << 12) | ((c & 0x0f00) << 8) | ((c & 0x00f0) << 4) | (c & 0x000f)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from ARGB4444 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb4444(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)
c = ((c & 0x0f00) << 16) | ((c & 0x00f0) << 12) | ((c & 0x000f) << 8) | ((c & 0xf000) >> 12)
BinUtils.append_int32_be!(mem, c << 4 | c)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from RGB565 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb565(width, height, bin, endian = :big)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_rgb565(bin, width * height, endian == :big)).flip
end
# Decode image from A8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_a8(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = BinUtils.get_int8(bin, i)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from R8 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_r8(width, height, bin)
decode_a8(width, height, bin)
end
# Decode image from RG16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rg16(width, height, bin)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
BinUtils.append_int16_int8_be!(mem, BinUtils.get_int16_be(bin, i * 2), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGB24 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb24(width, height, bin)
ChunkyPNG::Image.from_rgb_stream(width, height, bin).flip
end
# Decode image from RGBA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgba32(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, bin).flip
end
# Decode image from ARGB32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_argb32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_be(bin, i * 4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from BGRA32 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_bgra32(width, height, bin)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
c = BinUtils.get_int32_le(bin, i * 4)
BinUtils.append_int32_be!(mem, ((c & 0x00ffffff) << 8) | ((c & 0xff000000) >> 24))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from R16 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_r16(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)
c = f2i(r / 65535.0)
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGB9e5 binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgb9e5float(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
n = endian == :little ? BinUtils.get_int32_le(bin, i * 4) : BinUtils.get_int32_be(bin, i * 4)
e = (n & 0xf8000000) >> 27
r = (n & 0x7fc0000) >> 9
g = (n & 0x3fe00) >> 9
b = n & 0x1ff
r = (r / 512r + 1) * (2**(e - 15))
g = (g / 512r + 1) * (2**(e - 15))
b = (b / 512r + 1) * (2**(e - 15))
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b))
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from R Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rhalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
c = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 2) : BinUtils.get_int16_be(bin, i * 2)))
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RG Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rghalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 4) : BinUtils.get_int16_be(bin, i * 4)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 4 + 2) : BinUtils.get_int16_be(bin, i * 4 + 2)))
BinUtils.append_int8!(mem, r, g, 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA Half-float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbahalf(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
(width * height).times do |i|
r = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 8) : BinUtils.get_int16_be(bin, i * 8)))
g = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 8 + 2) : BinUtils.get_int16_be(bin, i * 8 + 2)))
b = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 8 + 4) : BinUtils.get_int16_be(bin, i * 8 + 4)))
a = f2i(n2f(endian == :little ? BinUtils.get_int16_le(bin, i * 8 + 6) : BinUtils.get_int16_be(bin, i * 8 + 6)))
BinUtils.append_int8!(mem, r, g, b, a)
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from R float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e' : 'g'
(width * height).times do |i|
c = f2i(bin.byteslice(i * 4, 4).unpack1(unpackstr))
BinUtils.append_int8!(mem, c, c, c)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RG float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgfloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 3)
unpackstr = endian == :little ? 'e2' : 'g2'
(width * height).times do |i|
r, g = bin.byteslice(i * 8, 8).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), 0)
end
ChunkyPNG::Image.from_rgb_stream(width, height, mem).flip
end
# Decode image from RGBA float binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @param [Symbol] endian endianness of binary
# @return [ChunkyPNG::Image] decoded image
def self.decode_rgbafloat(width, height, bin, endian = :big)
mem = String.new(capacity: width * height * 4)
unpackstr = endian == :little ? 'e4' : 'g4'
(width * height).times do |i|
r, g, b, a = bin.byteslice(i * 16, 16).unpack(unpackstr)
BinUtils.append_int8!(mem, f2i(r), f2i(g), f2i(b), f2i(a))
end
ChunkyPNG::Image.from_rgba_stream(width, height, mem).flip
end
# Decode image from DXT1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt1(bin, width, height))
end
# Decode image from DXT5 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_dxt5(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_dxt5(bin, width, height))
end
# Decode image from ETC1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc1(bin, width, height))
end
# Decode image from ETC2 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgb(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2(bin, width, height))
end
# Decode image from ETC2 Alpha1 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba1(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a1(bin, width, height))
end
# Decode image from ETC2 Alpha8 compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_etc2rgba8(width, height, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_etc2a8(bin, width, height))
end
# Decode image from ASTC compressed binary
# @param [Integer] width image width
# @param [Integer] height image height
# @param [Integer] blocksize block size
# @param [String] bin binary to decode
# @return [ChunkyPNG::Image] decoded image
def self.decode_astc(width, height, blocksize, bin)
ChunkyPNG::Image.from_rgba_stream(width, height, DecodeHelper.decode_astc(bin, width, height, blocksize, blocksize))
end
# Create ASTC file data from ObjectValue
# @param [Mikunyan::ObjectValue,Hash] object target object
# @return [String,nil] created file
def self.create_astc_file(object)
astc_list = {
48 => 4, 49 => 5, 50 => 6, 51 => 8, 52 => 10, 53 => 12,
54 => 4, 55 => 5, 56 => 6, 57 => 8, 58 => 10, 59 => 12
}
width = object['m_Width']
height = object['m_Height']
fmt = object['m_TextureFormat']
bin = object['image data']
width = width.value if width.class == ObjectValue
height = height.value if height.class == ObjectValue
fmt = fmt.value if fmt.class == ObjectValue
bin = bin.value if bin.class == ObjectValue
if width && height && fmt && astc_list[fmt]
header = "\x13\xAB\xA1\x5C".force_encoding('ascii-8bit')
header << [astc_list[fmt], astc_list[fmt], 1].pack('C*')
header << [width].pack('V').byteslice(0, 3)
header << [height].pack('V').byteslice(0, 3)
header << "\x01\x00\x00"
header + bin
end
end
private
# convert 16bit float
def self.n2f(n)
case n
when 0x0000
0.0
when 0x8000
-0.0
when 0x7c00
Float::INFINITY
when 0xfc00
-Float::INFINITY
else
s = n & 0x8000 != 0
e = n & 0x7c00
f = n & 0x03ff
case e
when 0x7c00
Float::NAN
when 0
(s ? -f : f) * 2.0**-24
else
(s ? -1 : 1) * (f / 1024.0 + 1) * (2.0**((e >> 10) - 15))
end
end
end
# [0.0,1.0] -> [0,255]
def self.f2i(d)
(d * 255).round.clamp(0, 255)
end
end
end end
+91 -89
View File
@@ -1,93 +1,95 @@
# frozen_string_literal: true
module Mikunyan module Mikunyan
# Class for representing decoded object # Class for representing decoded object
# @attr [String] name object name # @attr [String] name object name
# @attr [String] type object type name # @attr [String] type object type name
# @attr [Object] value object # @attr [Object] value object
# @attr [Symbol] endian endianness # @attr [Symbol] endian endianness
# @attr [Boolean] is_struct # @attr [Boolean] is_struct
class ObjectValue class ObjectValue
attr_accessor :name, :type, :value, :endian, :is_struct attr_accessor :name, :type, :value, :endian, :is_struct
# Constructor # Constructor
# @param [String] name object name # @param [String] name object name
# @param [String] type object type name # @param [String] type object type name
# @param [Symbol] endian endianness # @param [Symbol] endian endianness
# @param [Object] value object # @param [Object] value object
def initialize(name, type, endian, value = nil) def initialize(name, type, endian, value = nil)
@name = name @name = name
@type = type @type = type
@endian = endian @endian = endian
@value = value @value = value
@is_struct = false @is_struct = false
@attr = {} @attr = {}
end
# Return whether object is array or not
# @return [Boolean]
def array?
value && value.class == Array
end
# Return whether object is value or not
# @return [Boolean]
def value?
value && value.class != Array
end
# Return whether object is struct or not
# @return [Boolean]
def struct?
is_struct
end
# Return all keys
# @return [Array] list of keys
def keys
@attr.keys
end
# Return whether object contains key
# @param [String] key
# @return [Boolean]
def key?(key)
@attr.key?(key)
end
# Return value
# @return [Object] value
def []
@value
end
# Return value of selected index or key
# @param [Integer,String] i index or key
# @return [Object] value
def [](i)
if array? && i.class == Integer
@value[i]
else
@attr[i]
end
end
# Set value of selected key
# @param [String] name key
# @param [Object] value value
# @return [Object] value
def []=(name, value)
@attr[name] = value
end
# Return value of called key
# @param [String] name key
# @return [Object] value
def method_missing(name, *args)
@attr[name.to_s]
end
# Implementation of respond_to_missing?
def respond_to_missing?(symbol, include_private)
@attr.key?(symbol.to_s)
end
end end
# Return whether object is array or not
# @return [Boolean]
def array?
value && value.class == Array
end
# Return whether object is value or not
# @return [Boolean]
def value?
value && value.class != Array
end
# Return whether object is struct or not
# @return [Boolean]
def struct?
is_struct
end
# Return all keys
# @return [Array] list of keys
def keys
@attr.keys
end
# Return whether object contains key
# @param [String] key
# @return [Boolean]
def key?(key)
@attr.key?(key)
end
# Return value
# @return [Object] value
def []
@value
end
# Return value of selected index or key
# @param [Integer,String] i index or key
# @return [Object] value
def [](i)
if array? && i.class == Integer
@value[i]
else
@attr[i]
end
end
# Set value of selected key
# @param [String] name key
# @param [Object] value value
# @return [Object] value
def []=(name, value)
@attr[name] = value
end
# Return value of called key
# @param [String] name key
# @return [Object] value
def method_missing(name, *_args)
@attr[name.to_s]
end
# Implementation of respond_to_missing?
def respond_to_missing?(symbol, _include_private)
@attr.key?(symbol.to_s)
end
end
end end
+79 -77
View File
@@ -1,82 +1,84 @@
# frozen_string_literal: true
module Mikunyan module Mikunyan
# Class for representing TypeTree # Class for representing TypeTree
# @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes # @attr [Array<Mikunyan::TypeTree::Node>] nodes list of all nodes
class TypeTree class TypeTree
attr_accessor :nodes attr_accessor :nodes
# Struct for representing Node in TypeTree # Struct for representing Node in TypeTree
# @attr [String] version version string # @attr [String] version version string
# @attr [Integer] depth depth of node (>= 0) # @attr [Integer] depth depth of node (>= 0)
# @attr [Boolean] array? array node or not # @attr [Boolean] array? array node or not
# @attr [String] type type name # @attr [String] type type name
# @attr [String] name node (attribute) name # @attr [String] name node (attribute) name
# @attr [Integer] index index in node list # @attr [Integer] index index in node list
# @attr [Integer] flags flags of node # @attr [Integer] flags flags of node
Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags) Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags)
# Create TypeTree from binary string (new version) # Create TypeTree from binary string (new version)
# @param [Mikunyan::BinaryReader] br # @param [Mikunyan::BinaryReader] br
# @return [Mikunyan::TypeTree] created TypeTree # @return [Mikunyan::TypeTree] created TypeTree
def self.load(br) def self.load(br)
nodes = [] nodes = []
node_count = br.i32u node_count = br.i32u
buffer_size = br.i32u buffer_size = br.i32u
node_count.times do node_count.times do
node = Node.new(br.i16u, br.i8u, br.i8u != 0, br.i32, br.i32, br.i32, br.i32u, br.i32u) node = Node.new(br.i16u, br.i8u, br.i8u != 0, br.i32, br.i32, br.i32, br.i32u, br.i32u)
nodes << node nodes << node
end end
buffer = br.read(buffer_size) buffer = br.read(buffer_size)
nodes.each do |n| nodes.each do |n|
if n.type >= 0 n.type = if n.type >= 0
n.type = buffer.unpack("@#{n.type}Z*")[0] buffer.unpack1("@#{n.type}Z*")
else else
n.type = Mikunyan::STRING_TABLE[n.type + 2**31] Mikunyan::STRING_TABLE[n.type + 2**31]
end end
if n.name >= 0 n.name = if n.name >= 0
n.name = buffer.unpack("@#{n.name}Z*")[0] buffer.unpack1("@#{n.name}Z*")
else else
n.name = Mikunyan::STRING_TABLE[n.name + 2**31] Mikunyan::STRING_TABLE[n.name + 2**31]
end end
end end
r = TypeTree.new r = TypeTree.new
r.nodes = nodes r.nodes = nodes
r r
end
# Create TypeTree from binary string (legacy version)
# @param [Mikunyan::BinaryReader] br
# @return [Mikunyan::TypeTree] created TypeTree
def self.load_legacy(br)
nodes = []
stack = [0]
while stack.size > 0
depth = stack.pop
type = br.cstr
name = br.cstr
size = br.i32
index = br.i32u
is_array = (br.i32 != 0)
version = br.i32u
flags = br.i32u
child_count = br.i32u
child_count.times{ stack << depth + 1 }
nodes << Node.new(version, depth, is_array, type, name, size, index, flags)
end
r = TypeTree.new
r.nodes = nodes
r
end
# Create default TypeTree from hash string (if exists)
# @param [String] hash
# @return [Mikunyan::TypeTree,nil] created TypeTree
def self.load_default(hash)
hash_str = hash.unpack('H*')[0]
file = File.expand_path("../typetrees/#{hash_str}.dat", __FILE__)
return nil unless File.file?(file)
r = TypeTree.new
r.nodes = Marshal.load(File.binread(file))
r
end
end end
# Create TypeTree from binary string (legacy version)
# @param [Mikunyan::BinaryReader] br
# @return [Mikunyan::TypeTree] created TypeTree
def self.load_legacy(br)
nodes = []
stack = [0]
until stack.empty?
depth = stack.pop
type = br.cstr
name = br.cstr
size = br.i32
index = br.i32u
is_array = (br.i32 != 0)
version = br.i32u
flags = br.i32u
child_count = br.i32u
child_count.times{stack << depth + 1}
nodes << Node.new(version, depth, is_array, type, name, size, index, flags)
end
r = TypeTree.new
r.nodes = nodes
r
end
# Create default TypeTree from hash string (if exists)
# @param [String] hash
# @return [Mikunyan::TypeTree,nil] created TypeTree
def self.load_default(hash)
hash_str = hash.unpack1('H*')
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 end
+4 -2
View File
@@ -1,4 +1,6 @@
# frozen_string_literal: true
module Mikunyan module Mikunyan
# version string # version string
VERSION = "3.9.6" VERSION = '3.9.6'
end end
+18 -17
View File
@@ -1,31 +1,32 @@
# coding: utf-8 # frozen_string_literal: true
lib = File.expand_path("../lib", __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) lib = File.expand_path('lib', __dir__)
require "mikunyan/version" $:.unshift(lib) unless $:.include?(lib)
require 'mikunyan/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "mikunyan" spec.name = 'mikunyan'
spec.version = Mikunyan::VERSION spec.version = Mikunyan::VERSION
spec.authors = ["Ishotihadus"] spec.authors = ['Ishotihadus']
spec.email = ["hanachan.pao@gmail.com"] spec.email = ['hanachan.pao@gmail.com']
spec.summary = "Unity asset deserializer for Ruby" spec.summary = 'Unity asset deserializer for Ruby'
spec.description = "Library to deserialize Unity assetbundles and assets." spec.description = 'Library to deserialize Unity assetbundles and assets.'
spec.homepage = "https://github.com/Ishotihadus/mikunyan" spec.homepage = 'https://github.com/Ishotihadus/mikunyan'
spec.license = "MIT" spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0").reject do |f| spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/}) f.match(%r{^(test|spec|features)/})
end end
spec.bindir = "exe" spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^exe/}){|f| File.basename(f)}
spec.require_paths = ["lib"] spec.require_paths = ['lib']
spec.extensions = ["ext/decoders/native/extconf.rb"] spec.extensions = ['ext/decoders/native/extconf.rb']
spec.add_dependency 'extlz4', '~> 0'
spec.add_dependency 'extlzma', '~> 0'
spec.add_dependency 'bin_utils', '~> 0' spec.add_dependency 'bin_utils', '~> 0'
spec.add_dependency 'chunky_png', '~> 1' spec.add_dependency 'chunky_png', '~> 1'
spec.add_dependency 'extlz4', '~> 0'
spec.add_dependency 'extlzma', '~> 0'
spec.add_dependency 'json', '~> 2' spec.add_dependency 'json', '~> 2'
spec.add_development_dependency 'bundler', '~> 1' spec.add_development_dependency 'bundler', '~> 1'