diff --git a/README.md b/README.md index 93507f9..ac01a5e 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ obj.key You can get png file directly from Texture2D asset. Output object's class is `ChunkyPNG::Image`. -Acceptable format is basic texture formats (1, 2, 3, 4, 5, 7 and 13) and ETC_RGB4 (34). +Only some basic texture formats (1--5, 7, 9, 13--20, 22, 62, and 63) and ETC_RGB4 (34) are available. ```ruby require 'mikunyan/decoders' @@ -118,6 +118,8 @@ img = Mikunyan::ImageDecoder.decode_object(obj) img.save('mikunyan.png') ``` +Mikunyan cannot decode ASTC files. Use `Mikunyan::ImageDecoder.create_astc_file` instead. + ### Json / YAML Outputer `mikunyan-json` is an executable command for converting unity3d to json. diff --git a/lib/mikunyan.rb b/lib/mikunyan.rb index dae3e45..86037a6 100644 --- a/lib/mikunyan.rb +++ b/lib/mikunyan.rb @@ -6,3 +6,7 @@ require "mikunyan/binary_reader" require "mikunyan/object_value" require "mikunyan/type_tree" require "mikunyan/constants" + +# Module for deserializing Unity Assets and AssetBundles +module Mikunyan +end diff --git a/lib/mikunyan/asset.rb b/lib/mikunyan/asset.rb index 039d272..dae625d 100644 --- a/lib/mikunyan/asset.rb +++ b/lib/mikunyan/asset.rb @@ -1,21 +1,133 @@ 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] klasses defined classes + # @attr_reader [Array] objects included objects + # @attr_reader [Array] add_ids ? + # @attr_reader [Array] 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] list of all path IDs + def path_ids + @objects.map{|e| e.path_id} + end + + # Returns list of containers + # @return [Array,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 @@ -108,97 +220,6 @@ module Mikunyan 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] @@ -267,5 +288,42 @@ module Mikunyan 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 diff --git a/lib/mikunyan/asset_bundle.rb b/lib/mikunyan/asset_bundle.rb index 1c65de4..97899ee 100644 --- a/lib/mikunyan/asset_bundle.rb +++ b/lib/mikunyan/asset_bundle.rb @@ -1,19 +1,33 @@ require 'extlz4' module Mikunyan + # Class for representing Unity AssetBundle + # @attr_reader [String] signature file signature (UnityRaw or UnityFS) + # @attr_reader [Integer] format file format number + # @attr_reader [String] unity_version version string of Unity to use this AssetBundle + # @attr_reader [String] generator_version version string of generator + # @attr_reader [Array] assets included Assets class AssetBundle - attr_accessor :signature, :format, :unity_version, :generator_version, :assets + attr_reader :signature, :format, :unity_version, :generator_version, :assets + # Load AssetBundle from binary string + # @param [String] bin binary data + # @return [Mikunyan::AssetBundle] deserialized AssetBundle object def self.load(bin) r = AssetBundle.new - r.load(bin) + r.send(:load, bin) 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 @@ -31,8 +45,6 @@ module Mikunyan end end - private - def load_unity_raw(br) @assets = [] @@ -48,8 +60,7 @@ module Mikunyan asset_size = br.i32u br.jmp(asset_pos + asset_header_size - 4) asset_data = br.read(asset_size) - asset = Asset.new(asset_name) - asset.load(asset_data) + asset = Asset.load(asset_data, asset_name) @assets << asset end end @@ -77,8 +88,7 @@ module Mikunyan blocks.each{|b| raw_data << uncompress(br.read(b[:c]), b[:u], b[:f])} asset_blocks.each do |b| - asset = Asset.new(b[:name]) - asset.load(raw_data.byteslice(b[:offset], b[:size])) + asset = Asset.load(raw_data.byteslice(b[:offset], b[:size]), b[:name]) @assets << asset end end diff --git a/lib/mikunyan/binary_reader.rb b/lib/mikunyan/binary_reader.rb index d5758c4..1e8beeb 100644 --- a/lib/mikunyan/binary_reader.rb +++ b/lib/mikunyan/binary_reader.rb @@ -1,9 +1,16 @@ require 'bin_utils' module Mikunyan + # Class for manipulating binary string + # @attr [Symbol] endian endianness + # @attr [Integer] pos position + # @attr [Integer] length data size class BinaryReader attr_accessor :endian, :pos, :length + # Constructor + # @param [String] data binary string + # @param [Symbol] endian endianness def initialize(data, endian = :big) @data = data @pos = 0 @@ -11,104 +18,131 @@ module Mikunyan @endian = endian end + # Returns whether little endian or not + # @return [Boolean] def little? @endian == :little end - def jmp(pos = 0) + # Jump to given position + # @param [Integer] pos position + def jmp(pos=0) @pos = pos end - def adv(size = 0) + # 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 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 diff --git a/lib/mikunyan/constants.rb b/lib/mikunyan/constants.rb index d15e0a4..4142c13 100644 --- a/lib/mikunyan/constants.rb +++ b/lib/mikunyan/constants.rb @@ -1,4 +1,5 @@ module Mikunyan + private STRING_TABLE = { 0=>'AABB', 5=>'AnimationClip', diff --git a/lib/mikunyan/decoders/image_decoder.rb b/lib/mikunyan/decoders/image_decoder.rb index 48214c3..e3930bf 100644 --- a/lib/mikunyan/decoders/image_decoder.rb +++ b/lib/mikunyan/decoders/image_decoder.rb @@ -3,10 +3,11 @@ require 'bin_utils' require 'fiddle' module Mikunyan + # Class for image decoding tools class ImageDecoder - Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]] - Etc1SubblockTable = [[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1]] - + # Decode image from Mikunyan::ObjectValue + # @param [Mikunyan::ObjectValue] object object to decode + # @return [ChunkyPNG::Image,nil] decoded image def self.decode_object(object) return nil unless object.class == ObjectValue @@ -66,8 +67,12 @@ module Mikunyan end end - # 4 bit integer per color - + # 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| @@ -78,6 +83,12 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) 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| @@ -88,8 +99,12 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) end - # 5-6 bit integer per color - + # 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) mem = String.new(capacity: width * height * 3) (width * height).times do |i| @@ -102,8 +117,11 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) end - # 8 bit integer per color - + # 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| @@ -113,10 +131,20 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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| @@ -125,14 +153,29 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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) 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) 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| @@ -142,6 +185,11 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) 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| @@ -151,8 +199,12 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) end - # 16 bit integer per color - + # 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| @@ -163,8 +215,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) end - # 9 bit float per color - + # 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| @@ -181,8 +237,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) end - # 16 bit (half) float per color - + # 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| @@ -192,6 +252,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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| @@ -202,6 +268,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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| @@ -214,8 +286,12 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) end - # 32 bit float per color - + # 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' @@ -226,6 +302,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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' @@ -236,6 +318,12 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(width, height, mem) 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' @@ -246,8 +334,11 @@ module Mikunyan ChunkyPNG::Image.from_rgba_stream(width, height, mem) end - # other formats - + # 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) bw = (width + 3) / 4 bh = (height + 3) / 4 @@ -263,6 +354,9 @@ module Mikunyan ChunkyPNG::Image.from_rgb_stream(bw * 4, bh * 4, mem.to_str).crop!(0, 0, width, height) 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, @@ -290,6 +384,9 @@ module Mikunyan private + Etc1ModifierTable = [[2, 8], [5, 17], [9, 29], [13, 42], [18, 60], [24, 80], [33, 106], [47, 183]] + Etc1SubblockTable = [[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1]] + def self.decode_etc1_block(bin) colors = [] codes = [bin >> 37 & 7, bin >> 34 & 7] diff --git a/lib/mikunyan/object_value.rb b/lib/mikunyan/object_value.rb index 3d21591..a65ae55 100644 --- a/lib/mikunyan/object_value.rb +++ b/lib/mikunyan/object_value.rb @@ -1,7 +1,18 @@ module Mikunyan + # Class for representing decoded object + # @attr [String] name object name + # @attr [String] type object type name + # @attr [Object] value object + # @attr [Symbol] endian endianness + # @attr [Boolean] is_struct class ObjectValue attr_accessor :name, :type, :value, :endian, :is_struct + # Constructor + # @param [String] name object name + # @param [String] type object type name + # @param [Symbol] endian endianness + # @param [Object] value object def initialize(name, type, endian, value = nil) @name = name @type = type @@ -11,46 +22,70 @@ module Mikunyan @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 - def [](name) - if array? && name.class == Integer - @value[name] + # 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[name] + @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 diff --git a/lib/mikunyan/type_tree.rb b/lib/mikunyan/type_tree.rb index 18abb53..4ea2d01 100644 --- a/lib/mikunyan/type_tree.rb +++ b/lib/mikunyan/type_tree.rb @@ -1,8 +1,22 @@ module Mikunyan + # Class for representing TypeTree + # @attr [Array] nodes list of all nodes class TypeTree attr_accessor :nodes + + # Struct for representing Node in TypeTree + # @attr [String] version version string + # @attr [Integer] depth depth of node (>= 0) + # @attr [Boolean] array? array node or not + # @attr [String] type type name + # @attr [String] name node (attribute) name + # @attr [Integer] index index in node list + # @attr [Integer] flags flags of node Node = Struct.new(:version, :depth, :array?, :type, :name, :size, :index, :flags) + # Create TypeTree from binary string (new version) + # @param [Mikunyan::BinaryReader] br + # @return [Mikunyan::TypeTree] created TypeTree def self.load(br) nodes = [] node_count = br.i32u @@ -28,6 +42,9 @@ module Mikunyan 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] @@ -49,6 +66,9 @@ module Mikunyan 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__) diff --git a/lib/mikunyan/version.rb b/lib/mikunyan/version.rb index 49eba6b..af98074 100644 --- a/lib/mikunyan/version.rb +++ b/lib/mikunyan/version.rb @@ -1,3 +1,4 @@ module Mikunyan - VERSION = "3.9.0" + # version string + VERSION = "3.9.0" end