2018-03-20 00:14:08 +00:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
|
|
|
|
require 'pp'
|
2018-03-20 02:21:29 +00:00
|
|
|
require 'digest/md5'
|
2018-03-20 00:14:08 +00:00
|
|
|
|
|
|
|
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)
|
2018-03-25 00:18:27 +00:00
|
|
|
hdr = new(*data[0..SIZE - 1].unpack("VVVVV"))
|
2018-03-24 03:00:31 +00:00
|
|
|
pp hdr
|
2018-03-20 00:14:08 +00:00
|
|
|
hdr.validate!(data.bytes.size)
|
|
|
|
hdr
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(*entries)
|
|
|
|
@num_sprites, @dir_offset, @dir_size, @data_offset, @data_size = *entries
|
|
|
|
end
|
|
|
|
|
2018-03-25 00:18:27 +00:00
|
|
|
def to_data
|
|
|
|
[
|
|
|
|
@num_sprites,
|
|
|
|
@dir_offset,
|
|
|
|
@dir_size,
|
|
|
|
@data_offset,
|
|
|
|
@data_size,
|
|
|
|
].pack("VVVVV")
|
|
|
|
end
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
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
|
|
|
|
|
2018-03-25 00:18:27 +00:00
|
|
|
def to_data
|
|
|
|
[rel_offset, sprite_size].pack("VV")
|
|
|
|
end
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
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
|
|
|
|
|
2018-03-24 03:00:31 +00:00
|
|
|
pp entries
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
new(entries)
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(entries)
|
|
|
|
@entries = entries
|
|
|
|
end
|
|
|
|
|
2018-03-25 00:18:27 +00:00
|
|
|
def to_data
|
|
|
|
entries.map(&:to_data).join("")
|
|
|
|
end
|
|
|
|
|
|
|
|
# Convert the directory into an Array of sprites
|
2018-03-20 00:14:08 +00:00
|
|
|
def realize(rel_data)
|
2018-03-21 05:08:24 +00:00
|
|
|
entries.map { |entry| Sprite.parse(rel_data[entry.sprite_range]) }
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-21 05:08:24 +00:00
|
|
|
class SpriteHeader
|
|
|
|
SIZE = 24 # Seems to be, anyway. Based on the size here vs. the size in the dir
|
|
|
|
|
|
|
|
attr_reader(
|
|
|
|
:unknown0, # Possibly magic data? It's the same for every sprite in jungtil.obj
|
|
|
|
:width, # Low nibble comes to 63 in jungtil.obj which would work for a 64x64 tile
|
|
|
|
:height,
|
|
|
|
:unknown8,
|
|
|
|
:size, # Number of bytes of pixel data following this header
|
|
|
|
:unknown16,
|
|
|
|
:unknown20,
|
|
|
|
)
|
2018-03-20 00:14:08 +00:00
|
|
|
|
|
|
|
def self.parse(rel_data)
|
2018-03-21 05:08:24 +00:00
|
|
|
new(*rel_data[0...SIZE].unpack("VvvVVVV"))
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(*args)
|
|
|
|
@unknown0,
|
|
|
|
@width,
|
|
|
|
@height,
|
|
|
|
@unknown8,
|
|
|
|
@size,
|
|
|
|
@unknown16,
|
|
|
|
@unknown20 = *args
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
|
2018-03-25 00:18:27 +00:00
|
|
|
def to_data
|
|
|
|
[
|
|
|
|
@unknown0,
|
|
|
|
@width,
|
|
|
|
@height,
|
|
|
|
@unknown8,
|
|
|
|
@size,
|
|
|
|
@unknown16,
|
|
|
|
@unknown20
|
|
|
|
].pack("VvvVVVV")
|
|
|
|
end
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
def pixel_range
|
2018-03-21 05:08:24 +00:00
|
|
|
SIZE...(SIZE+size)
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-21 05:08:24 +00:00
|
|
|
class Sprite
|
2018-03-24 03:00:31 +00:00
|
|
|
attr_reader :header, :records, :raw
|
2018-03-21 05:08:24 +00:00
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
def self.parse(rel_data)
|
|
|
|
hdr = SpriteHeader.parse(rel_data)
|
|
|
|
sprite_pixels = rel_data[hdr.pixel_range]
|
|
|
|
|
2018-03-24 03:00:31 +00:00
|
|
|
records = sprite_pixels.split("\x00")
|
2018-03-24 21:47:34 +00:00
|
|
|
records.map! { |record| record += "\x00" }
|
2018-03-24 03:00:31 +00:00
|
|
|
|
|
|
|
new(hdr, records, rel_data)
|
2018-03-21 05:08:24 +00:00
|
|
|
end
|
|
|
|
|
2018-03-24 03:00:31 +00:00
|
|
|
def initialize(header, records, raw = nil)
|
2018-03-21 05:08:24 +00:00
|
|
|
@header = header
|
2018-03-24 03:00:31 +00:00
|
|
|
@records = records
|
2018-03-21 05:08:24 +00:00
|
|
|
@raw = raw
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
2018-03-25 00:18:27 +00:00
|
|
|
|
|
|
|
# raw is optional, so don't use it here
|
|
|
|
def to_data
|
|
|
|
header.to_data + records.join("")
|
|
|
|
end
|
|
|
|
|
|
|
|
def total_size
|
|
|
|
SpriteHeader::SIZE + records.join("").size
|
|
|
|
end
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class Parsed
|
|
|
|
attr_reader :header
|
|
|
|
attr_reader :directory
|
|
|
|
attr_reader :sprites
|
2018-03-20 02:21:29 +00:00
|
|
|
attr_accessor :filename
|
2018-03-20 00:14:08 +00:00
|
|
|
|
|
|
|
def initialize(header, directory, sprites)
|
|
|
|
@header = header
|
|
|
|
@directory = directory
|
|
|
|
@sprites = sprites
|
|
|
|
end
|
2018-03-25 00:18:27 +00:00
|
|
|
|
|
|
|
def to_data
|
|
|
|
dir_padding = "\x00"*(header.dir_offset - Header::SIZE)
|
|
|
|
data_padding = "" # for now, assume all is well
|
|
|
|
|
|
|
|
[
|
|
|
|
header.to_data,
|
|
|
|
dir_padding,
|
|
|
|
directory.to_data,
|
|
|
|
data_padding,
|
|
|
|
sprites.map { |sprite| sprite.to_data },
|
|
|
|
].join("")
|
|
|
|
end
|
2018-03-20 00:14:08 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
def hex(num, leading=0)
|
2018-03-20 02:21:29 +00:00
|
|
|
return " "*(leading) if num.nil?
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
"%0#{leading}x"%num
|
|
|
|
end
|
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
VISIBLE_CHARS = ("0".ord)..("z".ord)
|
|
|
|
def text(num)
|
|
|
|
return " " if num.nil?
|
|
|
|
if VISIBLE_CHARS.cover?(num)
|
|
|
|
num.chr
|
|
|
|
else
|
|
|
|
" "
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def ascii(num)
|
|
|
|
return " " if num.nil?
|
|
|
|
|
|
|
|
"%3d"%num
|
|
|
|
end
|
|
|
|
|
|
|
|
def header!(blocksize=8)
|
|
|
|
nums = (0...blocksize).to_a
|
|
|
|
hdr = " #{nums.join(" ")} #{nums.join("")}"
|
|
|
|
|
|
|
|
puts "-" * (hdr.size + 1)
|
|
|
|
puts hdr
|
|
|
|
puts "-" * (hdr.size + 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
def display(data, blocksize=8, skip=0, header: false)
|
|
|
|
bytes = data.bytes[skip..-1]
|
2018-03-20 00:14:08 +00:00
|
|
|
nrows = (bytes.count / blocksize)
|
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
0.upto(nrows) do |i|
|
2018-03-21 22:49:46 +00:00
|
|
|
header!(blocksize) if header && (i%16==0 || i == skip)
|
2018-03-20 00:14:08 +00:00
|
|
|
block = bytes[(i*blocksize)...(i*blocksize+blocksize)]
|
2018-03-20 02:21:29 +00:00
|
|
|
|
|
|
|
block.concat([nil]*(blocksize-block.size)) if block.size < blocksize
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
out = [
|
2018-03-24 03:00:31 +00:00
|
|
|
"0x#{hex(i*blocksize, 4)}",
|
2018-03-20 02:21:29 +00:00
|
|
|
block.map { |b| hex(b, 2) }, # hex
|
2018-03-24 21:47:34 +00:00
|
|
|
# " | " + block.map { |b| text(b) }.join("") + " |", # ascii
|
|
|
|
# block.map { |b| ascii(b) } ,# decimal bytes
|
2018-03-24 03:00:31 +00:00
|
|
|
"",# decimal 2-bytes
|
|
|
|
# decimal 4-bytes
|
2018-03-20 00:14:08 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
puts out.flatten.join(' ')
|
2018-03-20 02:21:29 +00:00
|
|
|
# puts Digest::MD5.hexdigest(data[skip..-1])
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
def load_obj(filename)
|
|
|
|
Obj.parse(File.read(filename).force_encoding("BINARY"))
|
|
|
|
end
|
|
|
|
|
|
|
|
def dump(filename, spriteno = -1)
|
|
|
|
obj = load_obj(filename)
|
2018-03-21 05:08:24 +00:00
|
|
|
# pp obj
|
2018-03-20 00:14:08 +00:00
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
if spriteno == -1
|
|
|
|
puts "Dumping all sprites for #{filename}"
|
2018-03-24 03:00:31 +00:00
|
|
|
obj.sprites.each_with_index { |sprite, i| puts "Sprite #{i}\n" ; display(sprite.raw, 8, header: true) }
|
2018-03-20 02:21:29 +00:00
|
|
|
else
|
|
|
|
puts "Dumping sprite #{spriteno} for #{filename}"
|
|
|
|
display(obj.sprites[spriteno])
|
|
|
|
end
|
|
|
|
end
|
2018-03-20 00:14:08 +00:00
|
|
|
|
|
|
|
# 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.
|
|
|
|
#
|
|
|
|
# Tried so far, with no success:
|
2018-03-20 00:32:39 +00:00
|
|
|
# * DEFLATE / ZLIB
|
2018-03-20 00:14:08 +00:00
|
|
|
# * LZO
|
2018-03-20 00:32:39 +00:00
|
|
|
# * LZOP
|
2018-03-21 05:08:24 +00:00
|
|
|
# * RLE (maybe?)
|
|
|
|
#
|
|
|
|
# Maybe try:
|
|
|
|
# * RLE8: https://www.fileformat.info/format/bmp/corion-rle8.htm
|
2018-03-20 00:32:39 +00:00
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
def decompress(filename)
|
2018-03-21 21:26:07 +00:00
|
|
|
puts "\nAttempting custom decompression of #{filename}..."
|
2018-03-20 00:14:08 +00:00
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
obj = load_obj(filename)
|
2018-03-20 00:14:08 +00:00
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
obj.sprites.each_with_index do |sprite, i|
|
2018-03-21 21:26:07 +00:00
|
|
|
hdr = sprite.header
|
|
|
|
print "Sprite %02d... x=#{hdr.width} y=#{hdr.height}"%i
|
2018-03-20 00:14:08 +00:00
|
|
|
|
2018-03-21 05:08:24 +00:00
|
|
|
decompressed = []
|
2018-03-24 03:00:31 +00:00
|
|
|
data = sprite.raw[24..-1].bytes
|
2018-03-21 05:08:24 +00:00
|
|
|
hdr = sprite.header
|
|
|
|
|
2018-03-21 21:26:07 +00:00
|
|
|
loop do
|
|
|
|
break if data.empty?
|
|
|
|
|
|
|
|
right = data.index(0)
|
|
|
|
|
|
|
|
if right.nil?
|
|
|
|
print "error! end of chunk not found"
|
|
|
|
break
|
2018-03-20 02:21:29 +00:00
|
|
|
end
|
2018-03-21 05:08:24 +00:00
|
|
|
|
2018-03-21 21:26:07 +00:00
|
|
|
rec = data.shift(right)
|
|
|
|
_ = data.shift(1) # drop the record separator
|
|
|
|
|
2018-03-21 22:49:46 +00:00
|
|
|
decompressed << rec
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
2018-03-21 22:49:46 +00:00
|
|
|
puts ": #{decompressed.size} records. Sprite pixels: #{hdr.width*hdr.height}"
|
2018-03-20 02:21:29 +00:00
|
|
|
|
2018-03-21 22:49:46 +00:00
|
|
|
puts "WARNING: #{data.size} bytes left over" if data.size > 0
|
|
|
|
|
|
|
|
header!
|
|
|
|
decompressed.each_with_index do |line, y|
|
|
|
|
x = line.size
|
|
|
|
padding = (sprite.header.width - x) / 2
|
|
|
|
display((["\x00"]*padding + line.map(&:chr)).join(""), line.size+padding.size)
|
|
|
|
end
|
2018-03-20 02:21:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Print the first 128 bytes of sprites in a friendly format. Permits into-obj comparisons
|
|
|
|
def compare(filenames)
|
|
|
|
objs = filenames.map { |filename| load_obj(filename).tap { |obj| obj.filename = filename } }
|
|
|
|
|
|
|
|
objs.each do |obj|
|
|
|
|
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
sprites =
|
|
|
|
if spriteno == -1
|
|
|
|
else
|
|
|
|
|
|
|
|
|
|
|
|
end
|
2018-03-20 00:14:08 +00:00
|
|
|
end
|
2018-03-21 21:26:07 +00:00
|
|
|
require 'set'
|
|
|
|
def correlate(filenames)
|
|
|
|
objs = filenames.map { |f| load_obj(f) }
|
|
|
|
|
|
|
|
results = Hash.new { |h,k| h[k] = Set.new }
|
|
|
|
|
|
|
|
|
|
|
|
objs.each do |obj|
|
|
|
|
obj.sprites.each do |spr|
|
|
|
|
results[spr.header.unknown0 & 0xffff] << spr.header.width
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
puts "Unique widths for u0,1"
|
|
|
|
pp results
|
|
|
|
end
|
2018-03-20 00:14:08 +00:00
|
|
|
|
2018-03-21 05:08:24 +00:00
|
|
|
def sprites(filename)
|
|
|
|
obj = load_obj(filename)
|
|
|
|
|
|
|
|
puts filename + ":"
|
|
|
|
obj.sprites.each_with_index do |spr, i|
|
|
|
|
hdr = spr.header
|
|
|
|
px = hdr.width * hdr.height
|
2018-03-24 03:00:31 +00:00
|
|
|
pp hdr
|
|
|
|
pp spr
|
2018-10-13 02:39:23 +01:00
|
|
|
|
|
|
|
data = spr.to_data
|
2018-03-21 05:08:24 +00:00
|
|
|
|
2018-03-21 21:26:07 +00:00
|
|
|
puts "%s %03d: %02x %02x %02x %02x %3d %3d %5d %5d %.2f %02x %02x ... %02x"%[
|
|
|
|
filename,
|
2018-03-21 05:08:24 +00:00
|
|
|
i,
|
2018-03-21 21:26:07 +00:00
|
|
|
hdr.unknown0 & 0x000000ff,
|
|
|
|
(hdr.unknown0 & 0x0000ff00) >> 8,
|
|
|
|
(hdr.unknown0 & 0x00ff0000) >> 16,
|
|
|
|
(hdr.unknown0 & 0xff000000) >> 24,
|
2018-03-21 05:08:24 +00:00
|
|
|
hdr.width,
|
|
|
|
hdr.height,
|
|
|
|
px,
|
|
|
|
hdr.size,
|
2018-03-21 21:26:07 +00:00
|
|
|
(hdr.size*8) / px.to_f,
|
2018-10-13 02:39:23 +01:00
|
|
|
data.bytes[0],
|
|
|
|
data.bytes[1],
|
|
|
|
data.bytes[-1],
|
2018-03-21 05:08:24 +00:00
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-24 21:47:34 +00:00
|
|
|
def sprite(filename, i)
|
|
|
|
obj = load_obj(filename)
|
|
|
|
|
|
|
|
puts filename + ":"
|
|
|
|
spr = obj.sprites[i]
|
|
|
|
raise "No sprite #{i}" unless spr
|
|
|
|
|
|
|
|
# Show the header
|
|
|
|
display(spr.raw[0...24], 4, header: true)
|
|
|
|
|
|
|
|
spr.records.each_with_index do |record, i|
|
|
|
|
str =
|
|
|
|
if record.size > 40
|
|
|
|
record[0...20].bytes.map { |b| hex(b, 2) }.join(" ") +
|
|
|
|
" ... " +
|
|
|
|
record[-20..-1].bytes.map { |b| hex(b, 2) }.join(" ")
|
|
|
|
else
|
|
|
|
record.bytes.map { |b| hex(b, 2) }.join(" ")
|
|
|
|
end
|
|
|
|
|
|
|
|
puts "%4d: %4d bytes: %s"%[i,record.size, str]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-25 00:18:27 +00:00
|
|
|
# Build a test sprite to investigate the color palette
|
|
|
|
def build(filename)
|
|
|
|
sprite_records = [
|
|
|
|
# 63 in each sprite except the last, which has just 3
|
|
|
|
1.upto(63).map { |b| "\x01#{b.chr}\x00" },
|
|
|
|
64.upto(126).map { |b| "\x01#{b.chr}\x00" },
|
|
|
|
127.upto(189).map { |b| "\x01#{b.chr}\x00" },
|
|
|
|
190.upto(252).map { |b| "\x01#{b.chr}\x00" },
|
|
|
|
253.upto(255).map { |b| "\x01#{b.chr}\x00" },
|
|
|
|
]
|
|
|
|
|
|
|
|
sprites = sprite_records.map do |records|
|
|
|
|
data = records.join("")
|
|
|
|
header = Obj::SpriteHeader.new(
|
|
|
|
0, # Stolen from blank.obj
|
|
|
|
1, # width of 1
|
|
|
|
records.size, # height = number of records
|
|
|
|
0, # padding?
|
|
|
|
data.size,
|
|
|
|
0, # padding?
|
|
|
|
0, # padding?
|
|
|
|
)
|
|
|
|
|
|
|
|
Obj::Sprite.new(
|
|
|
|
header,
|
|
|
|
records,
|
|
|
|
header.to_data + data,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
dir_block_offset = 32 # hardcoded
|
|
|
|
dir_block_size = 8 *sprites.size
|
|
|
|
data_block_offset = dir_block_offset + dir_block_size
|
|
|
|
data_block_size = sprites.inject(0) { |x, spr| x + spr.total_size }
|
|
|
|
|
|
|
|
header = Obj::Header.new(
|
|
|
|
sprites.size,
|
|
|
|
dir_block_offset,
|
|
|
|
dir_block_size,
|
|
|
|
data_block_offset,
|
|
|
|
data_block_size,
|
|
|
|
)
|
|
|
|
|
|
|
|
offset = 0
|
|
|
|
directory = Obj::SpriteDir.new(
|
|
|
|
sprites.map { |sprite|
|
|
|
|
puts "offset #{offset} => #{sprite.total_size}"
|
|
|
|
entry = Obj::DirEntry.new(offset, sprite.total_size)
|
|
|
|
offset += sprite.total_size
|
|
|
|
entry
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
pp directory
|
|
|
|
|
|
|
|
built = Obj::Parsed.new(
|
|
|
|
header,
|
|
|
|
directory,
|
|
|
|
sprites
|
|
|
|
)
|
|
|
|
|
|
|
|
File.open(filename, "w") { |f| f.write(built.to_data) }
|
|
|
|
end
|
|
|
|
|
2020-04-15 00:27:43 +01:00
|
|
|
def unknown16(filenames)
|
|
|
|
objs = filenames.map { |f| load_obj(f) }
|
|
|
|
results = Set.new
|
|
|
|
|
|
|
|
objs.each do |obj|
|
|
|
|
obj.sprites.each do |spr|
|
|
|
|
results << spr.header.unknown16
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
puts "Unique widths for u16,4"
|
|
|
|
pp results
|
|
|
|
end
|
|
|
|
|
2018-03-20 02:21:29 +00:00
|
|
|
case command = ARGV.shift
|
2020-04-15 00:27:43 +01:00
|
|
|
when "unknown16" then
|
|
|
|
unknown16(ARGV)
|
2018-03-21 05:08:24 +00:00
|
|
|
when "sprites" then
|
|
|
|
ARGV.each { |filename| sprites(filename) }
|
2018-03-24 21:47:34 +00:00
|
|
|
when "sprite" then
|
|
|
|
sprite(ARGV[0], ARGV[1].to_i)
|
2018-03-20 02:21:29 +00:00
|
|
|
when "dump" then
|
|
|
|
ARGV.each { |filename| dump(filename) }
|
|
|
|
when "compare" then
|
2018-03-21 21:26:07 +00:00
|
|
|
compare(ARGV)
|
2018-03-20 02:21:29 +00:00
|
|
|
when "decompress" then
|
|
|
|
ARGV.each { |filename| decompress(filename) }
|
2018-03-21 21:26:07 +00:00
|
|
|
when "correlate" then
|
|
|
|
correlate(ARGV)
|
2018-03-25 00:18:27 +00:00
|
|
|
when "build" then
|
|
|
|
build(ARGV[0])
|
2018-03-20 02:21:29 +00:00
|
|
|
else
|
|
|
|
puts "Unrecognized command #{command}"
|
|
|
|
exit(1)
|
|
|
|
end
|