lib/mikunyan/asset.rb in mikunyan-3.9.0 vs lib/mikunyan/asset.rb in mikunyan-3.9.1
- old
+ new
@@ -1,23 +1,135 @@
module Mikunyan
+ # Class for representing Unity Asset
+ # @attr_reader [String] name Asset name
+ # @attr_reader [Integer] format file format number
+ # @attr_reader [String] generator_version version string of generator
+ # @attr_reader [Integer] target_platform target platform number
+ # @attr_reader [Symbol] endian data endianness (:little or :big)
+ # @attr_reader [Array<Mikunyan::Asset::Klass>] klasses defined classes
+ # @attr_reader [Array<Mikunyan::Asset::ObjectData>] objects included objects
+ # @attr_reader [Array<Integer>] add_ids ?
+ # @attr_reader [Array<Mikunyan::Asset::Reference>] references reference data
class Asset
- attr_accessor :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
+ attr_reader :name, :format, :generator_version, :target_platform, :endian, :klasses, :objects, :add_ids, :references
+
+ # Struct for representing Asset class definition
+ # @attr [Integer] class_id class ID
+ # @attr [Integer,nil] script_id script ID
+ # @attr [String] hash hash value (16 or 32 bytes)
+ # @attr [Mikunyan::TypeTree, nil] type_tree given TypeTree
Klass = Struct.new(:class_id, :script_id, :hash, :type_tree)
+
+ # Struct for representing Asset object information
+ # @attr [Integer] path_id path ID
+ # @attr [Integer] offset data offset
+ # @attr [Integer] size data size
+ # @attr [Integer,nil] type_id type ID
+ # @attr [Integer,nil] class_id class ID
+ # @attr [Integer,nil] class_idx class definition index
+ # @attr [Boolean] destroyed? destroyed or not
+ # @attr [String] data binary data of object
ObjectData = Struct.new(:path_id, :offset, :size, :type_id, :class_id, :class_idx, :destroyed?, :data)
+
+ # Struct for representing Asset reference information
+ # @attr [String] path path
+ # @attr [String] guid GUID (16 bytes)
+ # @attr [Integer] type ?
+ # @attr [String] file_path Asset name
Reference = Struct.new(:path, :guid, :type, :file_path)
+ # Load Asset from binary string
+ # @param [String] bin binary data
+ # @param [String] name Asset name
+ # @return [Mikunyan::Asset] deserialized Asset object
+ def self.load(bin, name)
+ r = Asset.new(name)
+ r.send(:load, bin)
+ r
+ end
+
+ # Load Asset from file
+ # @param [String] file file name
+ # @param [String] name Asset name (automatically generated if not specified)
+ # @return [Mikunyan::Asset] deserialized Asset object
+ def self.file(file, name=nil)
+ name = File.basename(name, '.*') unless name
+ Asset.load(File.binread(file), name)
+ end
+
+ # Returns list of all path IDs
+ # @return [Array<Integer>] list of all path IDs
+ def path_ids
+ @objects.map{|e| e.path_id}
+ end
+
+ # Returns list of containers
+ # @return [Array<Hash>,nil] list of all containers
+ def containers
+ obj = parse_object(1)
+ return nil unless obj && obj.m_Container && obj.m_Container.array?
+ obj.m_Container.value.map do |e|
+ {:name => e.first.value, :preload_index => e.second.preloadIndex.value, :path_id => e.second.asset.m_PathID.value}
+ end
+ end
+
+ # Parse object of given path ID
+ # @param [Integer,ObjectData] path_id path ID or object
+ # @return [Mikunyan::ObjectValue,nil] parsed object
+ def parse_object(path_id)
+ if path_id.class == Integer
+ obj = @objects.find{|e| e.path_id == path_id}
+ return nil unless obj
+ elsif path_id.class == ObjectData
+ obj = path_id
+ else
+ return nil
+ end
+
+ klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
+ type_tree = Asset.parse_type_tree(klass)
+ return nil unless type_tree
+
+ parse_object_private(BinaryReader.new(obj.data, @endian), type_tree)
+ end
+
+ # Parse object of given path ID and simplify it
+ # @param [Integer,ObjectData] path_id path ID or object
+ # @return [Hash,nil] parsed object
+ def parse_object_simple(path_id)
+ Asset.object_simplify(parse_object(path_id))
+ end
+
+ # Returns object type name string
+ # @param [Integer,ObjectData] path_id path ID or object
+ # @return [String,nil] type name
+ def object_type(path_id)
+ if path_id.class == Integer
+ obj = @objects.find{|e| e.path_id == path_id}
+ return nil unless obj
+ elsif path_id.class == ObjectData
+ obj = path_id
+ else
+ return nil
+ end
+ klass = (obj.class_idx ? @klasses[obj.class_idx] : @klasses.find{|e| e.class_id == obj.class_id} || @klasses.find{|e| e.class_id == obj.type_id})
+ if klass && klass.type_tree && klass.type_tree.nodes[0]
+ klass.type_tree.nodes[0].type
+ elsif klass
+ Mikunyan::CLASS_ID[klass.class_id]
+ else
+ nil
+ end
+ end
+
+ private
+
def initialize(name)
@name = name
@endian = :big
end
- def self.file(file, name)
- r = Asset.new(name)
- r.load(File.binread(file))
- r
- end
-
def load(bin)
br = BinaryReader.new(bin)
metadata_size = br.i32u
size = br.i32u
@format = br.i32u
@@ -106,101 +218,10 @@
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]
@@ -228,11 +249,10 @@
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'
@@ -265,8 +285,45 @@
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