219 lines
6.2 KiB
Python
219 lines
6.2 KiB
Python
from dataclasses import dataclass
|
|
import sys
|
|
import struct
|
|
from pprint import pprint, pformat
|
|
import textwrap
|
|
|
|
with open(sys.argv[1], "rb") as f:
|
|
mem = memoryview(f.read())
|
|
|
|
@dataclass
|
|
class buf_parser:
|
|
mem: memoryview
|
|
address: int
|
|
|
|
def u32(buf):
|
|
value, = struct.unpack("<I", buf.mem[buf.address:buf.address+4])
|
|
buf.address += 4
|
|
return value
|
|
|
|
def f32(buf):
|
|
value, = struct.unpack("<f", buf.mem[buf.address:buf.address+4])
|
|
buf.address += 4
|
|
return value
|
|
|
|
@dataclass
|
|
class tile:
|
|
flags: set[str]
|
|
x: int
|
|
y: int
|
|
|
|
def region_array_tile(value):
|
|
last_region = (value >> 31) & 1
|
|
z_clear = (value >> 30) & 1
|
|
pre_sort = (value >> 29) & 1
|
|
flush_accumulate = (value >> 28) & 1
|
|
tile_y_position = (value >> 8) & 0x3f
|
|
tile_x_position = (value >> 2) & 0x3f
|
|
|
|
flags = set()
|
|
if last_region:
|
|
flags.add("last_region")
|
|
if z_clear:
|
|
flags.add("z_clear")
|
|
if pre_sort:
|
|
flags.add("pre_sort")
|
|
if flush_accumulate:
|
|
flags.add("flush_accumulate")
|
|
return tile(flags, tile_x_position, tile_y_position)
|
|
|
|
@dataclass
|
|
class region_array_entry:
|
|
tile: int
|
|
opaque_list_pointer: int
|
|
modifier_volume_list_pointer: int
|
|
translucent_list_pointer: int
|
|
translucent_modifier_volume_list_pointer: int
|
|
punch_through_list_pointer: int
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def parse_region_array_entry(buf):
|
|
entry = region_array_entry()
|
|
entry.tile = region_array_tile(u32(buf))
|
|
entry.opaque_list_pointer = u32(buf)
|
|
entry.modifier_volume_list_pointer = u32(buf)
|
|
entry.translucent_list_pointer = u32(buf)
|
|
entry.translucent_modifier_volume_list_pointer = u32(buf)
|
|
entry.punch_through_list_pointer = u32(buf)
|
|
return entry
|
|
|
|
def parse_region_array(mem, address):
|
|
buf = buf_parser(mem, address)
|
|
while True:
|
|
entry = parse_region_array_entry(buf)
|
|
yield entry
|
|
if "last_region" in entry.tile.flags:
|
|
break
|
|
|
|
@dataclass
|
|
class object_list_array:
|
|
number: int
|
|
shadow: int
|
|
skip: int
|
|
start: int
|
|
|
|
def print_array(value):
|
|
number = (value >> 25) & 0b1111
|
|
shadow = (value >> 24) & 1
|
|
skip = (value >> 21) & 0b111
|
|
start = value & ((1 << 21) - 1)
|
|
print(f' number: {number}')
|
|
print(f' shadow: {shadow}')
|
|
print(f' skip: {skip}')
|
|
print(f' start: {start}')
|
|
return object_list_array(number, shadow, skip, start)
|
|
|
|
@dataclass
|
|
class isp_tsp_instruction_word:
|
|
depth_compare_mode: str
|
|
culling_mode: str
|
|
z_write_disable: int
|
|
texture: int
|
|
offset: int
|
|
gouraud_shading: int
|
|
_16bit_uv: int
|
|
cache_bypass: int
|
|
dcalc_ctrl: int
|
|
|
|
def parse_isp_tsp_instruction_word(value):
|
|
def depth_compare_mode(value):
|
|
value = (value >> 29) & 0b111
|
|
if value == 0: return "never"
|
|
if value == 1: return "less"
|
|
if value == 2: return "equal"
|
|
if value == 3: return "less_or_equal"
|
|
if value == 4: return "greater"
|
|
if value == 5: return "not_equal"
|
|
if value == 6: return "greater_or_equal"
|
|
if value == 7: return "always"
|
|
|
|
def culling_mode(value):
|
|
value = (value >> 27) & 0b11
|
|
if value == 0: return "no_culling"
|
|
if value == 1: return "cull_if_small"
|
|
if value == 2: return "cull_if_negative"
|
|
if value == 3: return "cull_if_positive"
|
|
|
|
z_write_disable = (value >> 26) & 1
|
|
texture = (value >> 25) & 1
|
|
offset = (value >> 24) & 1
|
|
gouraud_shading = (value >> 23) & 1
|
|
_16bit_uv = (value >> 22) & 1
|
|
cache_bypass = (value >> 21) & 1
|
|
dcalc_ctrl = (value >> 20) & 1
|
|
print(hex(value))
|
|
return isp_tsp_instruction_word(
|
|
depth_compare_mode(value),
|
|
culling_mode(value),
|
|
z_write_disable,
|
|
texture,
|
|
offset,
|
|
gouraud_shading,
|
|
_16bit_uv,
|
|
cache_bypass,
|
|
dcalc_ctrl,
|
|
)
|
|
|
|
def print_params(mem, ol_array, num_vertices):
|
|
# skip
|
|
|
|
# This field specifies the data size (* 32 bits) for one vertex in the
|
|
# ISP/TSP Parameters. Normally, the actual data size is "skip + 3,"
|
|
|
|
buf = buf_parser(mem, ol_array.start)
|
|
isp_tsp = u32(buf)
|
|
tsp = u32(buf)
|
|
texture = u32(buf)
|
|
print(textwrap.indent(pformat(parse_isp_tsp_instruction_word(isp_tsp)), ' '))
|
|
|
|
vertex_size = (ol_array.skip + 3) * 4
|
|
while num_vertices > 0:
|
|
start = buf.address
|
|
x = f32(buf)
|
|
y = f32(buf)
|
|
z = f32(buf)
|
|
base_color = u32(buf)
|
|
print(f"{x:7.3f} {y:7.3f} {z:7.3f} {base_color:x}")
|
|
buf.address = start + vertex_size
|
|
num_vertices -= 1
|
|
|
|
def parse_object_list_data(mem, value):
|
|
if ((value >> 31) & 1) == 0:
|
|
print(' triangle:', hex(value))
|
|
elif ((value >> 29) & 0b111) == 0b100:
|
|
print(' triangle array:', hex(value))
|
|
ol_array = print_array(value)
|
|
print_params(mem, ol_array, 3)
|
|
elif ((value >> 29) & 0b111) == 0b101:
|
|
print(' quad array:', hex(value))
|
|
ol_array = print_array(value)
|
|
print_params(mem, ol_array, 4)
|
|
elif ((value >> 29) & 0b111) == 0b111:
|
|
print(' block link:', hex(value))
|
|
end_of_list = (value >> 28) & 1
|
|
if end_of_list:
|
|
print(' [end of list]')
|
|
return end_of_list
|
|
else:
|
|
assert False, value
|
|
|
|
def parse_object_list(mem, address):
|
|
if (address & (1 << 31)) != 0:
|
|
return
|
|
buf = buf_parser(mem, address)
|
|
while True:
|
|
end_of_list = parse_object_list_data(mem, u32(buf))
|
|
if end_of_list:
|
|
break
|
|
|
|
def parse_object_lists(mem, region_array_entry):
|
|
print(region_array_entry.tile)
|
|
parse_object_list(mem, region_array_entry.opaque_list_pointer)
|
|
parse_object_list(mem, region_array_entry.modifier_volume_list_pointer)
|
|
parse_object_list(mem, region_array_entry.translucent_list_pointer)
|
|
parse_object_list(mem, region_array_entry.translucent_modifier_volume_list_pointer)
|
|
parse_object_list(mem, region_array_entry.punch_through_list_pointer)
|
|
|
|
|
|
region_array = 0x10_0000
|
|
framebuffer = 0x11_0000
|
|
region_array_entries = list(parse_region_array(mem, region_array))
|
|
|
|
for region_array_entry in region_array_entries:
|
|
parse_object_lists(mem, region_array_entry)
|
|
|
|
with open('framebuffer.data', 'wb') as f:
|
|
f.write(mem[framebuffer:framebuffer + 640 * 480 * 4])
|