diff --git a/scripts/try-uncompress b/scripts/try-uncompress new file mode 100755 index 0000000..8ea30f1 --- /dev/null +++ b/scripts/try-uncompress @@ -0,0 +1,204 @@ +#!/usr/bin/env ruby + +require 'pp' + +module Obj + class Header + SIZE = 5*4 # 5x 32-bit little-endian integers + + attr_reader( + :num_sprites, # Number of entries in the directory. Always dir_size/8? + :dir_offset, # Offset of the sprite directory. Also the size of the main header + :dir_size, # Number of bytes allocated to the sprite directory. Always num_sprites*8? + :data_offset, # Number of bytes allocated to the sprites themselves + :data_size # Total size of the sprite data + # JUNGTIL.obj has 0x0000 0x0000 0x0000 following. Probably just padding to + # allow the sprite directory to start at a word boundary. + ) + + def self.parse(data) + hdr = new(*data[0..SIZE - 1].unpack("V*")) + pp hdr + hdr.validate!(data.bytes.size) + hdr + end + + def initialize(*entries) + @num_sprites, @dir_offset, @dir_size, @data_offset, @data_size = *entries + end + + def validate!(overall_size) + raise "Directory overlaps EOF" if overall_size < dir_size + dir_offset + raise "Data overlaps EOF" if overall_size < data_offset + data_size + raise "Bad dir_size or num_sprites" if num_sprites * DirEntry::SIZE != dir_size + raise "Directory overlaps data" if data_range.cover?(dir_offset+1) + raise "Data overlaps directory" if dir_range.cover?(data_offset+1) + + true + end + + def dir_range + dir_offset...(dir_offset+dir_size) + end + + def data_range + data_offset...(data_offset+data_size) + end + end + + class DirEntry + SIZE = 8 + + attr_reader :rel_offset # Relative to the main header's data_offset + attr_reader :sprite_size + + def self.parse(rel_data) + new(*rel_data.unpack("VV")) + end + + def initialize(rel_offset, sprite_size) + @rel_offset = rel_offset + @sprite_size = sprite_size + end + + def sprite_range + rel_offset...(rel_offset+sprite_size) + end + end + + class SpriteDir + attr_reader :entries + + def self.parse(rel_data) + count = rel_data.bytes.size + num_entries = count / DirEntry::SIZE + trailing = count%DirEntry::SIZE + + raise "SpriteDir block has #{trailing} trailing bytes" unless trailing == 0 + + entries = 0.upto(num_entries-1).map do |n| + rel_offset = n * DirEntry::SIZE + DirEntry.parse(rel_data.byteslice(rel_offset, DirEntry::SIZE)) + end + + pp entries + + new(entries) + end + + def initialize(entries) + @entries = entries + end + + # Convert the directory into an Array of bytes. Until we work out how to + # parse sprites, anyway... + def realize(rel_data) + entries.map { |entry| rel_data[entry.sprite_range] } + end + end + +=begin + SpriteHeader = Struct.new( + :unknown0, # Possibly magic data? It's the same for every sprite in jungtil.obj + :maybe_dimension, # Low nibble comes to 63 in jungtil.obj which would work for a 64x64 tile + :size, # Number of bytes of pixel data following this header + ) do + SIZE = 4*6 # Seems to be, anyway. Based on + + def self.parse(rel_data) + new(*sprite_data[0..SIZE-1]).unpack("V*") + end + + def pixel_range + SIZE...size # maybe,anyway + end + end + + Sprite = Struct.new( + :header, :data + ) do + def self.parse(rel_data) + hdr = SpriteHeader.parse(rel_data) + sprite_pixels = rel_data[hdr.pixel_range] + + Sprite.new(hdr, sprite_pixels) + end + end +=end + + class Parsed + attr_reader :header + attr_reader :directory + attr_reader :sprites + + def initialize(header, directory, sprites) + @header = header + @directory = directory + @sprites = sprites + end + end + + def self.parse(data) + hdr = Header.parse(data) + dir = SpriteDir.parse(data[hdr.dir_range]) + sprites = dir.realize(data[hdr.data_range]) + + Parsed.new(hdr, dir, sprites) + end +end + +parsed = Obj.parse(File.read("orig/Obj/jungtil.obj").force_encoding("BINARY")) + +def hex(num, leading=0) + "%0#{leading}x"%num +end + +def display(data, blocksize=8, skip=0) + bytes = data.bytes + nrows = (bytes.count / blocksize) + + skip.upto(nrows) do |i| + block = bytes[(i*blocksize)...(i*blocksize+blocksize)] + out = [ + "0x#{hex(i*blocksize, 4)}", + block.map { |b| hex(b, 2) }, + ] + + puts out.flatten.join(' ') + end +end + + +puts "Dumping blank sprite" +display(parsed.sprites[0], 8, 0) + +# The per-sprite data in jungtil.obj is too small to represent a 64x64 block, +# even at 1bpp, so try some decompression algorithms to see what comes out. +# Step through every byte of each sprite so we manage if some header is present +# +# Tried so far, with no success: +# * LZO + +puts "\nAttempting decompression..." + +require 'lzo' + +parsed.sprites.each_with_index do |sprite, i| + print "Sprite %02d..."%i + + (0...sprite.size).each do |offset| + block = sprite.byteslice(offset, sprite.size-offset) + + begin + decompressed = LZO.decompress(block) + puts "succeeded! sprite=#{i} offset=#{offset} decompressed_size=#{decompressed.size}" + puts "data:" + puts decompressed.inspect + exit 0 + rescue => err + end + end + + puts "failed" +end +