From ca3c5efbfe3523712d2edda6805bec819c362e14 Mon Sep 17 00:00:00 2001 From: Madeline Lim Date: Sat, 19 May 2018 21:29:39 -0700 Subject: [PATCH] Initial commit --- README.md | 1 + celeste_map_reader.rb | 153 ++++++++++++++++++++++++++++++++++++++++++ rom.rb | 151 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 README.md create mode 100644 celeste_map_reader.rb create mode 100644 rom.rb diff --git a/README.md b/README.md new file mode 100644 index 0000000..e05f13e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Celeste Reverse Engineering tools in Ruby diff --git a/celeste_map_reader.rb b/celeste_map_reader.rb new file mode 100644 index 0000000..0d8ee37 --- /dev/null +++ b/celeste_map_reader.rb @@ -0,0 +1,153 @@ +require './rom' + +# Value Types +# :boolean, :u8, :s16, :s32, :float, :lookup, :bin, :rle + +class Element + attr_accessor :package, :name, :attributes, :children, :attributes_value_types; + def initialize + @attributes = {} + @attributes_value_types = {} + @children = [] + @package = nil + @name = nil + end + def inspect + disp + end + def disp pre=0 + fend = children.size == 0 ? (attributes.size == 0 ? " />" : "/>") : ">" + pres = ' ' * pre + fstart = if attributes.size == 0 + "#{pres}<#{name}#{fend}" + else + "#{pres}<#{name}\n#{attributes_disp(pre)}\n#{pres}#{fend}" + end + if children.size > 0 + "#{fstart}\n#{children_disp(pre)}#{pres}\n" + else + "#{fstart}\n" + end + end + def attributes_disp pre=0 + pres = ' ' * (pre + 2) + attributes.map do |k, v| + value_type = attributes_value_types[k] + case value_type + when :boolean + if v + "#{pres}#{k}" + else + "#{pres}#{k}={#{v}}" + end + when :u8, :s16, :s32, :float + "#{pres}#{k}={#{v}}" + when :lookup + "#{pres}#{k}=\"#{v}\"" + when :bin + "#{pres}#{k}={bin#{v.inspect}}" + when :rle + "#{pres}#{k}={rle#{v.inspect}}" + end + end.join("\n") + end + def children_disp pre=0 + children.map { |c| c.disp(pre + 2) }.join("\n") + end + def get_children_by_name child_name + children.select { |c| c.name == child_name } + end + def find_child_by_name child_name + children.detect { |c| c.name == child_name } + end + def [] name + attributes[name.to_s] + 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 + end + def read_element pre=0 + element = Element.new + element.name = string_lookup[rom.read_u16_le] + # puts "#{" " * pre}<#{element.name}" + rom.read_byte.times do |i| + key = string_lookup[rom.read_u16_le] + value_type_enc = rom.read_byte + value = nil + value_type = nil + case value_type_enc + when 0 + value = rom.read_bool + if value + # puts "#{" " * (pre + 2)}#{key}" + else + # puts "#{" " * (pre + 2)}#{key}={#{value}}" + end + value_type = :boolean + when 1 + value = rom.read_byte + # puts "#{" " * (pre + 2)}#{key}={#{value}}" + value_type = :u8 + when 2 + value = rom.read_s16_le + # puts "#{" " * (pre + 2)}#{key}={#{value}}" + value_type = :s16 + when 3 + value = rom.read_s32_le + # puts "#{" " * (pre + 2)}#{key}={#{value}}" + value_type = :s32 + when 4 + value = rom.read_f32_le + # puts "#{" " * (pre + 2)}#{key}={#{value}}" + value_type = :float + when 5 + value = string_lookup[rom.read_u16_le] + # puts "#{" " * (pre + 2)}#{key}=\"#{value}\"" + value_type = :lookup + when 6 + count = rom.read_varlen_le + base = rom.tell + bin = [] + while rom.tell < base + count + bin << rom.read_byte + end + # value = rom.read_str_varlen + # # puts "#{" " * (pre + 2)}#{key}=\"#{value}\"" + # puts "#{" " * (pre + 2)}#{key}={bin#{bin.inspect}}" + value_type = :bin + when 7 + count = rom.read_u16_le + base = rom.tell + bin = [] + while rom.tell < base + count + num = rom.read_byte + val = rom.read_byte + num.times { bin << val } + end + value = bin + # puts "#{" " * (pre + 2)}#{key}={rle#{bin.inspect}}" + value_type = :rle + end + element.attributes[key] = value + element.attributes_value_types[key] = value_type + end + # puts "#{" " * pre}>" + rom.read_u16_le.times do |j| + element.children << read_element(pre + 2) + end + # puts "#{" " * pre}" + element + end + def inspect + "" + end +end diff --git a/rom.rb b/rom.rb new file mode 100644 index 0000000..49abe0b --- /dev/null +++ b/rom.rb @@ -0,0 +1,151 @@ +# General purpose binary reader + +# 8 16 32 64 +# C S L Q - unsigned +# c s l q - unsigned +# < LE +# > BE +# A - binary string +# z - null terminated +# H - hex string + +class ROM + def initialize str + @rom = str + @cur = 0 + @base = 0 + end + + def self.from_file fn + data = File.open(fn, "rb") { |io| io.read } + ROM.new data + end + + def set_base pos + @base = pos + end + + def seek pos + @cur = pos + @base + end + + def seek_rel pos + @cur += pos + end + + def tell + @cur - @base + end + + def read_str len + r = @rom[@cur..(@cur + len - 1)].unpack("A#{len}").first + @cur += len + r + end + + def read_str_varlen + len = read_varlen_le + r = @rom[@cur..(@cur + len - 1)].unpack("A#{len}").first + @cur += len + r + end + + def read_byte + r = @rom[@cur].ord + @cur += 1 + r + end + + def read_bool + read_byte == 1 + end + + def read_s8 + r = @rom[@cur].unpack("c").first + @cur += 1 + r + end + + def read_u16_le + r = @rom[@cur..(@cur + 3)].unpack('S<').first + @cur += 2 + r + end + + def read_s16_le + r = @rom[@cur..(@cur + 3)].unpack('s<').first + @cur += 2 + r + end + + def read_u16_be + r = @rom[@cur..(@cur + 3)].unpack('S>').first + @cur += 2 + r + end + + def read_u32_le + r = @rom[@cur..(@cur + 3)].unpack('L<').first + @cur += 4 + r + end + + def read_s32_le + r = @rom[@cur..(@cur + 3)].unpack('l<').first + @cur += 4 + r + end + + def read_u32_be + r = @rom[@cur..(@cur + 3)].unpack('L>').first + @cur += 4 + r + end + + def read_f32_le + r = @rom[@cur..(@cur + 3)].unpack('e').first + @cur += 4 + r + end + + def read_bin len + r = @rom[@cur..(@cur + len - 1)] + @cur += len + r + end + + def read_binswap len + togo = len + bin_data = "".b + while togo > 0 + r = @rom[@cur..(@cur + 3)].unpack('L>') + bin_data += r.pack('L<') + @cur += 4 + togo -= 4 + end + bin_data + end + + def read_varlen_le + val = 0 + r = read_byte + val = r & 0x7F + return val if r < 0x80 + r = read_byte << 7 + val + r + end + + def read_varlen_be + val = 0 + r = read_byte + val = r & 0x7F + return val if r < 0x80 + r = read_byte + val = (val << 8) + r + val + end + + def msg str + puts "%08X(%08X): %s" % [@cur, tell, str] + end +end