diff --git a/celeste_map_reader.rb b/celeste_map_reader.rb index 0d8ee37..f9c2341 100644 --- a/celeste_map_reader.rb +++ b/celeste_map_reader.rb @@ -1,5 +1,6 @@ require './rom' - +require './binwriter' +require 'json' # Value Types # :boolean, :u8, :s16, :s32, :float, :lookup, :bin, :rle @@ -63,22 +64,154 @@ class Element def [] name attributes[name.to_s] end + def to_h + { + 'name' => name, + 'package' => package, + 'attributes' => attributes, + 'attribute_types' => attributes_value_types, + 'children' => children.map(&:to_h) + } + end + def strings + strs = [] + strs << name + attributes.each do |k, v| + strs << k + strs << v if attributes_value_types[k].to_sym == :lookup + end + children.each do |c| + strs += c.strings + end + strs.compact.uniq + end + def self.from_h obj + element = self.new + element.name = obj['name'] + element.package = obj['package'] + element.attributes = obj['attributes'] + element.attributes_value_types = obj['attribute_types'] + element.children = obj['children'].map { |c| from_h c } + element + end end class CelesteMapReader - attr_accessor :rom, :package, :string_lookup, :root - def initialize fn - @rom = ROM.from_file(fn) - raise "Not a celeste map" unless rom.read_str_varlen == 'CELESTE MAP' - @package = rom.read_str_varlen - @string_lookup = (0...rom.read_u16_le).map { rom.read_str_varlen } - @root = read_element - @root.package = @package + attr_accessor :debug, :rom, :package, :string_lookup, :root, :writer + def initialize fn, fmt: :bin, debug: false + @debug = debug + case fmt + when :bin + @rom = ROM.from_file(fn) + raise "Not a celeste map" unless rom.read_str_varlen == 'CELESTE MAP' + @package = rom.read_str_varlen + @string_lookup = (0...rom.read_u16_le).map { rom.read_str_varlen } + @root = read_element + @root.package = @package + when :json + obj = File.open(fn, 'rb') { |f| JSON.parse(f.read) } + raise "Not a celeste map" unless obj['type'] == 'CELESTE MAP' + @root = Element.from_h(obj['root']) + @package = @root.package + else + raise "unknown fmt #{fmt}" + end end + + def write fn + @writer = BinWriter.new(fn) + @string_lookup = root.strings + writer.write_str_varlen 'CELESTE MAP' + writer.write_str_varlen root.package + writer.write_u16_le string_lookup.size + string_lookup.each do |s| + writer.write_str_varlen s + end + write_element root + writer.close + end + + def write_json fn + File.open(fn, 'wb') do |f| + f.write JSON.pretty_generate( + obj = { + 'type' => 'CELESTE MAP', + 'root' => root.to_h + } + ) + end + end + + def write_element element + writer.write_u16_le string_lookup.index(element.name) + writer.write_byte element.attributes.size + element.attributes.each do |k, v| + writer.write_u16_le string_lookup.index(k) + vt = element.attributes_value_types[k] + case vt.to_sym + when :boolean + writer.write_byte 0 + writer.write_bool v + when :u8 + writer.write_byte 1 + writer.write_byte v + when :s16 + writer.write_byte 2 + writer.write_s16_le v + when :s32 + writer.write_byte 3 + writer.write_s32_le v + when :float + writer.write_byte 4 + writer.write_f32_le v + when :lookup + writer.write_byte 5 + writer.write_u16_le string_lookup.index(v) + when :bin + writer.write_byte 6 + writer.write_varlen_le v.size + v.each do |b| + writer.write_byte b + end + when :rle + writer.write_byte 7 + rle = [] + count = 0 + lb = -1 + v.each do |b| + if b != lb + if lb >= 0 + rle << count + rle << lb + end + count = 0 + lb = b + end + count += 1 + end + if lb >= 0 + rle << count + rle << lb + end + writer.write_u16_le rle.size + rle.each do |b| + writer.write_byte b + end + else + raise "unknown value type #{vt} for key #{k} with value #{v}" + end + + end + writer.write_u16_le element.children.size + element.children.each do |child| + write_element child + end + end + def read_element pre=0 element = Element.new element.name = string_lookup[rom.read_u16_le] - # puts "#{" " * pre}<#{element.name}" + dputs "#{" " * pre}<#{element.name}" rom.read_byte.times do |i| key = string_lookup[rom.read_u16_le] value_type_enc = rom.read_byte @@ -88,30 +221,30 @@ class CelesteMapReader when 0 value = rom.read_bool if value - # puts "#{" " * (pre + 2)}#{key}" + dputs "#{" " * (pre + 2)}#{key}" else - # puts "#{" " * (pre + 2)}#{key}={#{value}}" + dputs "#{" " * (pre + 2)}#{key}={#{value}}" end value_type = :boolean when 1 value = rom.read_byte - # puts "#{" " * (pre + 2)}#{key}={#{value}}" + dputs "#{" " * (pre + 2)}#{key}={#{value}}" value_type = :u8 when 2 value = rom.read_s16_le - # puts "#{" " * (pre + 2)}#{key}={#{value}}" + dputs "#{" " * (pre + 2)}#{key}={#{value}}" value_type = :s16 when 3 value = rom.read_s32_le - # puts "#{" " * (pre + 2)}#{key}={#{value}}" + dputs "#{" " * (pre + 2)}#{key}={#{value}}" value_type = :s32 when 4 value = rom.read_f32_le - # puts "#{" " * (pre + 2)}#{key}={#{value}}" + dputs "#{" " * (pre + 2)}#{key}={#{value}}" value_type = :float when 5 value = string_lookup[rom.read_u16_le] - # puts "#{" " * (pre + 2)}#{key}=\"#{value}\"" + dputs "#{" " * (pre + 2)}#{key}=\"#{value}\"" value_type = :lookup when 6 count = rom.read_varlen_le @@ -121,8 +254,9 @@ class CelesteMapReader bin << rom.read_byte end # value = rom.read_str_varlen - # # puts "#{" " * (pre + 2)}#{key}=\"#{value}\"" - # puts "#{" " * (pre + 2)}#{key}={bin#{bin.inspect}}" + dputs "#{" " * (pre + 2)}#{key}=\"#{value}\"" + dputs "#{" " * (pre + 2)}#{key}={bin#{bin.inspect}}" + value = bin value_type = :bin when 7 count = rom.read_u16_le @@ -134,20 +268,25 @@ class CelesteMapReader num.times { bin << val } end value = bin - # puts "#{" " * (pre + 2)}#{key}={rle#{bin.inspect}}" + dputs "#{" " * (pre + 2)}#{key}={rle#{bin.inspect}}" value_type = :rle + else + raise "unknown value type byte #{value_type_enc} for key #{key}" end element.attributes[key] = value element.attributes_value_types[key] = value_type end - # puts "#{" " * pre}>" + dputs "#{" " * pre}>" rom.read_u16_le.times do |j| element.children << read_element(pre + 2) end - # puts "#{" " * pre}" + dputs "#{" " * pre}" element end def inspect "" end + def dputs s + puts s if @debug + end end