mm/tools/parse_dl.py

1392 lines
57 KiB
Python

#!/usr/bin/env python3
import argparse, os, struct, ast
setcombine_a_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', '1', 'NOISE',
'', '', '', '',
'', '', '', '0',
]
setcombine_b_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', 'CENTER', 'K4',
'', '', '', '',
'', '', '', '0',
]
setcombine_c_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', 'SCALE', 'COMBINED_ALPHA',
'TEXEL0_ALPHA', 'TEXEL1_ALPHA', 'PRIMITIVE_ALPHA', 'SHADE_ALPHA',
'ENV_ALPHA', 'LOD_FRACTION', 'PRIM_LOD_FRAC', 'K5',
'', '', '', '',
'', '', '', '',
'', '', '', '',
'', '', '', '0',
]
setcombine_d_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', '1', '0',
'', '', '', '',
'', '', '', '',
]
setcombine_Aa_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', '1', '0',
]
setcombine_Ab_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', '1', '0',
]
setcombine_Ac_names = [
'LOD_FRACTION', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', 'PRIM_LOD_FRAC', '0',
]
setcombine_Ad_names = [
'COMBINED', 'TEXEL0', 'TEXEL1', 'PRIMITIVE',
'SHADE', 'ENVIRONMENT', '1', '0',
]
setcombine_predefined = {
'0, 0, 0, PRIMITIVE, 0, 0, 0, PRIMITIVE':'G_CC_PRIMITIVE',
'0, 0, 0, SHADE, 0, 0, 0, SHADE':'G_CC_SHADE',
'TEXEL0, 0, SHADE, 0, 0, 0, 0, SHADE':'G_CC_MODULATEI',
'TEXEL0, 0, SHADE, 0, TEXEL0, 0, SHADE, 0':'G_CC_MODULATEIA',
'TEXEL0, 0, SHADE, 0, 0, 0, 0, TEXEL0':'G_CC_MODULATEIDECALA',
'TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE':'G_CC_MODULATEI_PRIM',
'TEXEL0, 0, PRIMITIVE, 0, TEXEL0, 0, PRIMITIVE, 0':'G_CC_MODULATEIA_PRIM',
'TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, TEXEL0':'G_CC_MODULATEIDECALA_PRIM',
'0, 0, 0, TEXEL0, 0, 0, 0, SHADE':'G_CC_DECALRGB',
'0, 0, 0, TEXEL0, 0, 0, 0, TEXEL0':'G_CC_DECALRGBA',
'ENVIRONMENT, SHADE, TEXEL0, SHADE, 0, 0, 0, SHADE':'G_CC_BLENDI',
'ENVIRONMENT, SHADE, TEXEL0, SHADE, TEXEL0, 0, SHADE, 0':'G_CC_BLENDIA',
'ENVIRONMENT, SHADE, TEXEL0, SHADE, 0, 0, 0, TEXEL0':'G_CC_BLENDIDECALA',
'TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, SHADE':'G_CC_BLENDRGBA',
'TEXEL0, SHADE, TEXEL0_ALPHA, SHADE, 0, 0, 0, TEXEL0':'G_CC_BLENDRGBDECALA',
'1, 0, TEXEL0, SHADE, 0, 0, 0, SHADE':'G_CC_ADDRGB',
'1, 0, TEXEL0, SHADE, 0, 0, 0, TEXEL0':'G_CC_ADDRGBDECALA',
'ENVIRONMENT, 0, TEXEL0, SHADE, 0, 0, 0, SHADE':'G_CC_REFLECTRGB',
'ENVIRONMENT, 0, TEXEL0, SHADE, 0, 0, 0, TEXEL0':'G_CC_REFLECTRGBDECALA',
'PRIMITIVE, SHADE, TEXEL0, SHADE, 0, 0, 0, SHADE':'G_CC_HILITERGB',
'PRIMITIVE, SHADE, TEXEL0, SHADE, PRIMITIVE, SHADE, TEXEL0, SHADE':'G_CC_HILITERGBA',
'PRIMITIVE, SHADE, TEXEL0, SHADE, 0, 0, 0, TEXEL0':'G_CC_HILITERGBDECALA',
'0, 0, 0, SHADE, 0, 0, 0, TEXEL0':'G_CC_SHADEDECALA',
'PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, SHADE, 0':'G_CC_BLENDPE',
'PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, 0, 0, 0, TEXEL0':'G_CC_BLENDPEDECALA',
'ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, TEXEL0, 0, SHADE, 0':'_G_CC_BLENDPE',
'ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, 0, 0, 0, TEXEL0':'_G_CC_BLENDPEDECALA',
'PRIMITIVE, SHADE, TEXEL0, SHADE, 0, 0, 0, SHADE':'_G_CC_TWOCOLORTEX',
'PRIMITIVE, TEXEL0, LOD_FRACTION, TEXEL0, PRIMITIVE, TEXEL0, LOD_FRACTION, TEXEL0':'_G_CC_SPARSEST',
'TEXEL1, TEXEL0, PRIM_LOD_FRAC, TEXEL0, TEXEL1, TEXEL0, PRIM_LOD_FRAC, TEXEL0':'G_CC_TEMPLERP',
'TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0, TEXEL1, TEXEL0, LOD_FRACTION, TEXEL0':'G_CC_TRILERP',
'TEXEL0, 0, TEXEL1, 0, TEXEL0, 0, TEXEL1, 0':'G_CC_INTERFERENCE',
'TEXEL0, K4, K5, TEXEL0, 0, 0, 0, SHADE':'G_CC_1CYUV2RGB',
'TEXEL1, K4, K5, TEXEL1, 0, 0, 0, 0':'G_CC_YUV2RGB',
'0, 0, 0, COMBINED, 0, 0, 0, COMBINED':'G_CC_PASS2',
'COMBINED, 0, SHADE, 0, 0, 0, 0, SHADE':'G_CC_MODULATEI2',
'COMBINED, 0, SHADE, 0, COMBINED, 0, SHADE, 0':'G_CC_MODULATEIA2',
'COMBINED, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE':'G_CC_MODULATEI_PRIM2',
'COMBINED, 0, PRIMITIVE, 0, COMBINED, 0, PRIMITIVE, 0':'G_CC_MODULATEIA_PRIM2',
'G_CC_MODULATEI_PRIM2':'G_CC_MODULATERGB_PRIM2',
'G_CC_MODULATEIA_PRIM2':'G_CC_MODULATERGBA_PRIM2',
'0, 0, 0, COMBINED, 0, 0, 0, SHADE':'G_CC_DECALRGB2',
'ENVIRONMENT, SHADE, COMBINED, SHADE, 0, 0, 0, SHADE':'G_CC_BLENDI2',
'ENVIRONMENT, SHADE, COMBINED, SHADE, COMBINED, 0, SHADE, 0':'G_CC_BLENDIA2',
'TEXEL0, CENTER, SCALE, 0, 0, 0, 0, 0':'G_CC_CHROMA_KEY2',
'ENVIRONMENT, COMBINED, TEXEL0, COMBINED, 0, 0, 0, SHADE':'G_CC_HILITERGB2',
'ENVIRONMENT, COMBINED, TEXEL0, COMBINED, ENVIRONMENT, COMBINED, TEXEL0, COMBINED':'G_CC_HILITERGBA2',
'ENVIRONMENT, COMBINED, TEXEL0, COMBINED, 0, 0, 0, TEXEL0':'G_CC_HILITERGBDECALA2',
'ENVIRONMENT, COMBINED, TEXEL0, COMBINED, 0, 0, 0, COMBINED':'G_CC_HILITERGBPASSA2',
}
def read_file(name):
file_data=[]
try:
with open(name, 'rb') as f:
file_data = f.read()
except IOError:
print('failed to read file ' + name)
return file_data
def read_uint16_be(file_data, offset):
return struct.unpack('>h', file_data[offset:offset+2])[0]
def read_uint32_be(file_data, offset):
return struct.unpack('>I', file_data[offset:offset+4])[0]
def get_cmd(inst):
return (inst & 0b11111111000000000000000000000000) >> 24
def get_signed_imm(inst):
imm = get_imm(inst)
if (imm & (1 << 15)) != 0:
imm = -2**15 + (imm & 0b00000000000000000111111111111111)
return imm
def generate_output(self, path):
with open(path + '/out', 'w', newline='\n') as f:
f.write('#include <PR/gbi.h>\n'
'\n'
)
def parse_geometry_mode_flags(flags):
flag_strs = []
if flags & 0x00000001 == 0x00000001:
flag_strs.append('G_ZBUFFER')
if flags & 0x00000004 == 0x00000004:
flag_strs.append('G_SHADE')
if flags & 0x00000600 == 0x00000600:
flag_strs.append('G_CULL_BOTH')
elif flags & 0x00000600 == 0x00000200:
flag_strs.append('G_CULL_FRONT')
elif flags & 0x00000600 == 0x00000400:
flag_strs.append('G_CULL_BACK')
if flags & 0x00010000 == 0x00010000:
flag_strs.append('G_FOG')
if flags & 0x00020000 == 0x00020000:
flag_strs.append('G_LIGHTING')
if flags & 0x00040000 == 0x00040000:
flag_strs.append('G_TEXTURE_GEN')
if flags & 0x00080000 == 0x00080000:
flag_strs.append('G_TEXTURE_GEN_LINEAR')
if flags & 0x00200000 == 0x00200000:
flag_strs.append('G_SHADING_SMOOTH')
result = ''
for flag_str in flag_strs:
if result == '':
result = flag_str
else:
result += ' | ' + flag_str
return result
class Parser:
class VertexList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x10
class DisplayList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 8
class Matrix:
def __init__(self, addr):
self.addr = addr
def get_last_addr(self):
return self.addr + 0x40
class MeshHeader:
def __init__(self, addr, file_data, offset):
self.addr = addr
self.minx = read_uint16_be(file_data, offset+0)
self.miny = read_uint16_be(file_data, offset+2)
self.minz = read_uint16_be(file_data, offset+4)
self.maxx = read_uint16_be(file_data, offset+6)
self.maxy = read_uint16_be(file_data, offset+8)
self.maxz = read_uint16_be(file_data, offset+10)
self.num_vertices = read_uint16_be(file_data, offset+12)
self.vertices = read_uint32_be(file_data, offset+16)
self.num_polygons = read_uint16_be(file_data, offset+20)
self.polygons = read_uint32_be(file_data, offset+24)
self.polygon_attrs = read_uint32_be(file_data, offset+28)
self.cameras = read_uint32_be(file_data, offset+32)
self.num_waterboxes = read_uint16_be(file_data, offset+36)
self.waterboxes = read_uint32_be(file_data, offset+40)
def get_last_addr(self):
return self.addr + 0x2C
class MeshVertList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x6
class MeshPolyList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x10
class MeshPolyAttrList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x2
class MeshCameraList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x8
class MeshWaterboxList:
def __init__(self, addr, length):
self.addr = addr
self.length = length
def get_last_addr(self):
return self.addr + self.length * 0x10
def __init__(self, base_addr):
self.base_addr = base_addr
# TODO keep sorted, is_in_* functions can return early
self.vertex_lists = []
self.textures = [] # TODO check overlapping
self.dls = [] # TODO check overlapping
self.matrices = [] # TODO check overlapping
self.mesh_headers = [] # TODO check overlapping
self.mesh_verts = [] # TODO check overlapping
self.mesh_polys = [] # TODO check overlapping
self.mesh_attrs = [] # TODO check overlapping
self.mesh_cameras = [] # TODO check overlapping
self.mesh_waterboxes = [] # TODO check overlapping
self.pointers = []
self.zeros = set()
def add_vertex_list(self, addr, length):
#print('adding 0x{:08X} with length {}'.format(addr, length))
new_list = self.VertexList(addr, length)
overlapping_lists = []
for i in range(length):
if self.is_in_vertex_list(addr + i * 0x10):
list = self.get_vertex_list(addr + i * 0x10)
#print('Found overlapping list at 0x{:08X} with length {}'.format(list[0], list[1]))
overlapping_lists.append(list)
self.vertex_lists.remove(list)
end_addr = new_list.get_last_addr()
for list in overlapping_lists:
if list.addr < addr:
addr = list.addr
if list.get_last_addr() > end_addr:
end_addr = list.get_last_addr()
self.vertex_lists.append(self.VertexList(addr, (end_addr - addr) // 0x10))
def add_dl(self, addr, length):
self.dls.append(self.DisplayList(addr, length))
def add_matrix(self, addr):
self.matrices.append(self.Matrix(addr))
def add_mesh_header(self, addr, file_data, offset):
self.mesh_headers.append(self.MeshHeader(addr, file_data, offset))
def add_mesh_verts(self, addr, length):
self.mesh_verts.append(self.MeshVertList(addr, length))
def add_mesh_polys(self, addr, length):
self.mesh_polys.append(self.MeshPolyList(addr, length))
def add_mesh_attrs(self, addr, length):
self.mesh_attrs.append(self.MeshPolyAttrList(addr, length))
def add_mesh_cameras(self, addr, length):
self.mesh_cameras.append(self.MeshCameraList(addr, length))
def add_mesh_waterboxes(self, addr, length):
self.mesh_waterboxes.append(self.MeshWaterboxList(addr, length))
def is_in_vertex_list(self, addr):
for list in self.vertex_lists:
if (addr >= list.addr) and (addr < list.get_last_addr()):
return True
return False
def is_in_texture(self, addr):
for texture in self.textures:
if texture[1] == 0:
continue # size was not set
if (addr >= texture[0]) and (addr < (texture[0] + texture[1])):
return True
return False
def is_in_dl(self, addr):
for dl in self.dls:
if (addr >= dl.addr) and (addr < dl.get_last_addr()):
return True
return False
def is_in_matrix(self, addr):
for matrix in self.matrices:
if (addr >= matrix.addr) and (addr < matrix.get_last_addr()):
return True
return False
def is_in_mesh_header(self, addr):
for header in self.mesh_headers:
if (addr >= header.addr) and (addr < header.get_last_addr()):
return True
return False
def is_in_mesh_verts(self, addr):
for verts in self.mesh_verts:
if (addr >= verts.addr) and (addr < verts.get_last_addr()):
return True
return False
def is_in_mesh_polys(self, addr):
for polys in self.mesh_polys:
if (addr >= polys.addr) and (addr < polys.get_last_addr()):
return True
return False
def is_in_mesh_attrs(self, addr):
for attrs in self.mesh_attrs:
if (addr >= attrs.addr) and (addr < attrs.get_last_addr()):
return True
return False
def is_in_mesh_cameras(self, addr):
for cameras in self.mesh_cameras:
if (addr >= cameras.addr) and (addr < cameras.get_last_addr()):
return True
return False
def is_in_mesh_waterboxes(self, addr):
for waterboxes in self.mesh_waterboxes:
if (addr >= waterboxes.addr) and (addr < waterboxes.get_last_addr()):
return True
return False
def is_unknown(self, addr):
return not self.is_in_vertex_list(addr) and \
not self.is_in_texture(addr) and \
not self.is_in_dl(addr) and \
not self.is_in_matrix(addr) and \
not self.is_in_mesh_header(addr) and \
not self.is_in_mesh_verts(addr) and \
not self.is_in_mesh_polys(addr) and \
not self.is_in_mesh_attrs(addr) and \
not self.is_in_mesh_cameras(addr) and \
not self.is_in_mesh_waterboxes(addr)
def get_vertex_list(self, addr):
for list in self.vertex_lists:
if (addr >= list.addr) and (addr < list.get_last_addr()):
return list
return None
def get_texture(self, addr):
for texture in self.textures:
if texture[1] == 0:
continue # size was not set
if (addr >= texture[0]) and (addr < (texture[0] + texture[1])):
return texture
return None
def get_dl(self, addr):
for dl in self.dls:
if (addr >= dl.addr) and (addr < dl.get_last_addr()):
return dl
return None
def get_matrix(self, addr):
for matrix in self.matrices:
if (addr >= matrix.addr) and (addr < matrix.get_last_addr()):
return matrix
return None
def get_mesh_header(self, addr):
for header in self.mesh_headers:
if (addr >= header.addr) and (addr < header.get_last_addr()):
return header
return None
def get_mesh_verts(self, addr):
for verts in self.mesh_verts:
if (addr >= verts.addr) and (addr < verts.get_last_addr()):
return verts
return None
def get_mesh_polys(self, addr):
for polys in self.mesh_polys:
if (addr >= polys.addr) and (addr < polys.get_last_addr()):
return polys
return None
def get_mesh_attrs(self, addr):
for attrs in self.mesh_attrs:
if (addr >= attrs.addr) and (addr < attrs.get_last_addr()):
return attrs
return None
def get_mesh_cameras(self, addr):
for cameras in self.mesh_cameras:
if (addr >= cameras.addr) and (addr < cameras.get_last_addr()):
return cameras
return None
def get_mesh_waterboxes(self, addr):
for waterboxes in self.mesh_waterboxes:
if (addr >= waterboxes.addr) and (addr < waterboxes.get_last_addr()):
return waterboxes
return None
def parse_cmd(self, w0, w1):
invalid = (False, False, 'Invalid') # Valid, IsEndOfDl, Print
cmd = get_cmd(w0)
# TODO for fields that get shifted before they are placed in microcode (e.g. vertices) , we should check that the lower bits are 0 for validity
if cmd == 0x00: # G_NOOP
if w0 == 0:
if w1 == 0:
return (True, False, 'gsDPNoOp()')
else:
return (True, False, 'gsDPNoOpTag({})'.format(w1))
else:
return invalid
if cmd == 0x01: # G_VTX
if (w0 & 0xFFF00F01) == 0x01000000:
vaddr = w1
numv = (w0 & 0x000FF000) >> 12
vbidx = ((w0 & 0x000000FE) >> 1) - numv
if (numv < 1) or (numv > 32) or (vbidx < 0) or (vbidx > 31):
return invalid
self.add_vertex_list(vaddr, numv)
return (True, False, 'gsSPVertex(0x{:08X}, {}, {})'.format(vaddr, numv, vbidx))
else:
return invalid
if cmd == 0x02: # G_MODIFYVTX
where = (w0 & 0x00FF0000) >> 16
vbidx = (w0 & 0x0000FFFF) // 2
if vbidx > 31:
return invalid
if where == 0x10:
where_str = 'G_MWO_POINT_RGBA'
elif where == 0x14:
where_str = 'G_MWO_POINT_ST'
elif where == 0x18:
where_str = 'G_MWO_POINT_XYSCREEN'
elif where == 0x1C:
where_str = 'G_MWO_POINT_ZSCREEN'
else:
return invalid
return (True, False, 'gsSPModifyVertex({}, {}, 0x{:08X})'.format(vbidx, where_str, w1))
if cmd == 0x03: # G_CULLDL
if ((w0 & 0xFFFF0000) == 0x03000000) and ((w1 & 0xFFFF0000) == 0):
vfirst = (w0 & 0x0000FFFF) // 2
vlast = (w1 & 0x0000FFFF) // 2
if vfirst > 31 or vlast > 31:
return invalid
return (True, False, 'gsSPCullDisplayList({}, {})'.format(vfirst, vlast))
else:
return invalid
if cmd == 0x04: # G_BRANCH_Z
return (True, False, 'gsDPNoOp() # TODO G_BRANCH_Z')
if cmd == 0x05: # G_TRI1
if w1 == 0:
v0 = ((w0 & 0x00FF0000) >> 16) // 2
v1 = ((w0 & 0x0000FF00) >> 8) // 2
v2 = (w0 & 0x000000FF) // 2
if (v0 < 0) or (v0 > 31):
return invalid
if (v1 < 0) or (v1 > 31):
return invalid
if (v2 < 0) or (v2 > 31):
return invalid
return (True, False, 'gsSP1Triangle({}, {}, {}, 0)'.format(v0, v1, v2))
else:
return invalid
if cmd == 0x06: # G_TRI2
if (w1 & 0xFF000000) == 0:
v00 = ((w0 & 0x00FF0000) >> 16) // 2
v01 = ((w0 & 0x0000FF00) >> 8) // 2
v02 = (w0 & 0x000000FF) // 2
v10 = ((w1 & 0x00FF0000) >> 16) // 2
v11 = ((w1 & 0x0000FF00) >> 8) // 2
v12 = (w1 & 0x000000FF) // 2
if (v00 < 0) or (v00 > 31):
return invalid
if (v01 < 0) or (v01 > 31):
return invalid
if (v02 < 0) or (v02 > 31):
return invalid
if (v10 < 0) or (v10 > 31):
return invalid
if (v11 < 0) or (v11 > 31):
return invalid
if (v12 < 0) or (v12 > 31):
return invalid
return (True, False, 'gsSP2Triangles({}, {}, {}, 0, {}, {}, {}, 0)'.format(v00, v01, v02, v10, v11, v12))
else:
return invalid
if cmd == 0x07: # G_QUAD
if (w1 & 0xFF000000) == 0:
v00 = ((w0 & 0x00FF0000) >> 16) // 2
v01 = ((w0 & 0x0000FF00) >> 8) // 2
v02 = (w0 & 0x000000FF) // 2
v10 = ((w1 & 0x00FF0000) >> 16) // 2
v11 = ((w1 & 0x0000FF00) >> 8) // 2
v12 = (w1 & 0x000000FF) // 2
if (v00 < 0) or (v00 > 31):
return invalid
if (v01 < 0) or (v01 > 31):
return invalid
if (v02 < 0) or (v02 > 31):
return invalid
if (v10 < 0) or (v10 > 31):
return invalid
if (v11 < 0) or (v11 > 31):
return invalid
if (v12 < 0) or (v12 > 31):
return invalid
if (v00 != v10) or (v02 != v11):
return invalid
return (True, False, 'gsSPQuadrangle({}, {}, {}, {}, 0)'.format(v00, v01, v02, v12))
else:
return invalid
if cmd == 0xD6: # G_DMA_IO
return (True, False, 'gsDPNoOp() # TODO G_DMA_IO')
if cmd == 0xD7: # G_TEXTURE
if (w0 & 0xFFFFC001) == 0xD7000000:
level = (w0 & 0x00003800) >> 11
tile = (w0 & 0x00000700) >> 8
on = (w0 & 0x000000FE) >> 1
scaleS = (w1 & 0xFFFF0000) >> 16
scaleT = (w1 & 0x0000FFFF)
if on != 0 and on != 1:
return invalid
onStr = 'G_OFF' if on == 0 else 'G_ON'
if tile == 0:
tileStr = 'G_TX_RENDERTILE'
elif tile == 7:
tileStr = 'G_TX_LOADTILE'
else:
tileStr = str(tile)
return (True, False, 'gsSPTexture(0x{:04X}, 0x{:04X}, {}, {}, {})'.format(scaleS, scaleT, level, tileStr, onStr))
else:
return invalid
if cmd == 0xD8: # G_POPMTX
if w0 == 0xD8380002:
num = w1 // 64
return (True, False, 'sSPPopMatrixN(G_MTX_MODELVIEW, {})'.format(num))
else:
return invalid
if cmd == 0xD9: # G_GEOMETRYMODE
if w1 & 0xFF000000 == 0:
clear_str = parse_geometry_mode_flags(~w0)
set_str = parse_geometry_mode_flags(w1)
if (w0 == 0xD9FFFFFF):
return (True, False, 'gsSPSetGeometryMode({})'.format(set_str))
elif (w1 == 0):
return (True, False, 'gsSPClearGeometryMode({})'.format(clear_str))
elif (w0 == 0xD9000000):
return (True, False, 'gsSPLoadGeometryMode({})'.format(set_str))
else:
return (True, False, 'gsSPGeometryMode({}, {})'.format(clear_str, set_str))
else:
return invalid
if cmd == 0xDA: # G_MTX
if (w0 & 0xFFFFFF00) == 0xDA380000:
# TODO test other bits that should always be clear
push = 'G_MTX_NOPUSH' if (w0 & 0b001) != 0 else 'G_MTX_PUSH' # G_MTX_PUSH is flipped in the macro
mul_or_load = 'G_MTX_LOAD' if (w0 & 0b010) != 0 else 'G_MTX_MUL'
model_or_proj = 'G_MTX_PROJECTION' if (w0 & 0b100) != 0 else 'G_MTX_MODELVIEW'
self.add_matrix(w1)
return (True, False, 'gsSPMatrix(0x{:08X}, {} | {} | {})'.format(w1, push, mul_or_load, model_or_proj))
else:
return invalid
if cmd == 0xDB: # G_MOVEWORD
return (True, False, 'gsDPNoOp() # TODO G_MOVEWORD')
if cmd == 0xDC: # G_MOVEMEM
return (True, False, 'gsDPNoOp() # TODO G_MOVEMEM')
if cmd == 0xDD: # G_LOAD_UCODE
return (True, False, 'gsDPNoOp() # TODO G_LOAD_UCODE')
if cmd == 0xDE: # G_DL
if (w0 & 0xFF00FFFF) == 0xDE000000:
type = (w0 & 0x00FF0000) >> 16
if type == 0:
return (True, False, 'gsSPDisplayList(0x{:08X})'.format(w1))
elif type == 1:
return (True, True, 'gsSPBranchList(0x{:08X})'.format(w1))
else:
return invalid
return (True, False, 'gsSPVertex(0x{:08X}, {}, {})'.format(vaddr, numv, vbidx))
else:
return invalid
if cmd == 0xDF: # G_ENDDL
if w0 == 0xDF000000 and w1 == 0:
return (True, True, 'gsSPEndDisplayList()')
else:
return invalid
if cmd == 0xE0: # G_SPNOOP
if (w0 == 0xE0000000) and (w1 == 0):
return (True, False, 'gsSPNoOp()')
else:
return invalid
if cmd == 0xE1: # G_RDPHALF_1
return (True, False, 'gsDPNoOp() # TODO G_RDPHALF_1')
if cmd == 0xE2: # G_SETOTHERMODE_L
if (w0 & 0xFFFF0000) == 0xE2000000:
shift_base = (w0 & 0x0000FF00) >> 8
length = (w0 & 0x000000FF) + 1
shift = 32 - length - shift_base
data = w1 >> shift
if shift == 0: # G_MDSFT_ALPHACOMPARE
if data == 0:
type = 'G_AC_NONE'
elif data == 1:
type = 'G_AC_THRESHOLD'
elif data == 3:
type = 'G_AC_DITHER'
else:
return invalid
return (True, False, 'gsDPSetAlphaCompare({})'.format(type))
elif shift == 2: # G_MDSFT_ZSRCSEL
if data == 0:
src = 'G_ZS_PIXEL'
elif data == 1:
src = 'G_ZS_PRIM'
else:
return invalid
return (True, False, 'gsDPSetDepthSource({})'.format(src))
elif shift == 3: # G_MDSFT_RENDERMODE
##return (True, False, 'gDPSetRenderMode({})'.format(c0, c1))
return (True, False, 'gsDPNoOp() # TODO G_MDSFT_RENDERMODE')
else:
return invalid
return
else:
return invalid
if cmd == 0xE3: # G_SETOTHERMODE_H
if (w0 & 0xFFFF0000) == 0xE3000000:
shift_base = (w0 & 0x0000FF00) >> 8
length = (w0 & 0x000000FF) + 1
shift = 32 - length - shift_base
if shift < 0:
return invalid
data = w1 >> shift
if shift == 0: # G_MDSFT_BLENDMASK
return invalid # This is not supported
elif shift == 4: # G_MDSFT_ALPHADITHER
if length != 2:
return invalid
if data == 0:
type = 'G_AD_PATTERN'
elif data == 1:
type = 'G_AD_NOTPATTERN'
elif data == 2:
type = 'G_AD_NOISE'
elif data == 3:
type = 'G_AD_DISABLE'
else:
return invalid
return (True, False, 'gsDPSetAlphaDither({})'.format(mode))
elif shift == 6: # G_MDSFT_RGBDITHER
if length != 2:
return invalid
if data == 0:
type = 'G_CD_MAGICSQ'
elif data == 1:
type = 'G_CD_BAYER'
elif data == 2:
type = 'G_CD_NOISE'
elif data == 3:
type = 'G_CD_DISABLE'
else:
return invalid
return (True, False, 'gsDPSetColorDither({})'.format(mode))
elif shift == 8: # G_MDSFT_COMBKEY
if length != 1:
return invalid
if data == 0:
type = 'G_CK_NONE'
elif data == 1:
type = 'G_CK_KEY'
else:
return invalid
return (True, False, 'gsDPSetCombineKey({})'.format(type))
elif shift == 9: # G_MDSFT_TEXTCONV
if length != 3:
return invalid
if data == 0:
type = 'G_TC_CONV'
elif data == 5:
type = 'G_TC_FILTCONV'
elif data == 6:
type = 'G_TC_FILT'
else:
return invalid
return (True, False, 'gsDPSetTextureConvert({})'.format(type))
elif shift == 12: # G_MDSFT_TEXTFILT
if length != 2:
return invalid
if data == 0:
type = 'G_TF_POINT'
elif data == 2:
type = 'G_TF_AVERAGE'
elif data == 3:
type = 'G_TF_BILERP'
else:
return invalid
return (True, False, 'gsDPSetTextureFilter({})'.format(type))
elif shift == 14: # G_MDSFT_TEXTLUT
if length != 2:
return invalid
if data == 0:
type = 'G_TT_NONE'
elif data == 2:
type = 'G_TT_RGBA16'
elif data == 3:
type = 'G_TT_IA16'
else:
return invalid
return (True, False, 'gsDPSetTextureLUT({})'.format(type))
elif shift == 16: # G_MDSFT_TEXTLOD
if length != 1:
return invalid
if data == 0:
type = 'G_TL_TILE'
elif data == 1:
type = 'G_TL_LOD'
else:
return invalid
return (True, False, 'gsDPSetTextureLOD({})'.format(type))
elif shift == 17: # G_MDSFT_TEXTDETAIL
if length != 2:
return invalid
if data == 0:
type = 'G_TD_CLAMP'
elif data == 1:
type = 'G_TD_SHARPEN'
elif data == 2:
type = 'G_TD_DETAIL'
else:
return invalid
return (True, False, 'gsDPSetTextureDetail({})'.format(type))
elif shift == 19: # G_MDSFT_TEXTPERSP
if length != 1:
return invalid
if data == 0:
type = 'G_TP_NONE'
elif data == 1:
type = 'G_TP_PERSP'
else:
return invalid
return (True, False, 'gsDPSetTexturePersp({})'.format(type))
elif shift == 20: # G_MDSFT_CYCLETYPE
if length != 2:
return invalid
if data == 0:
type = 'G_CYC_1CYCLE'
elif data == 1:
type = 'G_CYC_2CYCLE'
elif data == 2:
type = 'G_CYC_COPY'
elif data == 3:
type = 'G_CYC_FILL'
else:
return invalid
return (True, False, 'gsDPSetCycleType({})'.format(type))
elif shift == 22: # G_MDSFT_COLORDITHER
return invalid # G_MDSFT_COLORDITHER was changed to G_MDSFT_RGBDITHER in HW version 2
elif shift == 23: # G_MDSFT_PIPELINE
if length != 1:
return invalid
if data == 0:
mode = 'G_PM_NPRIMITIVE'
elif data == 1:
mode = 'G_PM_1PRIMITIVE'
else:
return invalid
return (True, False, 'gsDPPipelineMode({})'.format(mode))
else:
return invalid
return
else:
return invalid
if cmd == 0xE4: # G_TEXRECT
return (True, False, 'gsDPNoOp() # TODO G_TEXRECT')
if cmd == 0xE5: # G_TEXRECTFLIP
return (True, False, 'gsDPNoOp() # TODO G_TEXRECTFLIP')
if cmd == 0xE6: # G_RDPLOADSYNC
if (w0 == 0xE6000000) and (w1 == 0):
return (True, False, 'gsDPLoadSync()')
else:
return invalid
if cmd == 0xE7: # G_RDPPIPESYNC
if (w0 == 0xE7000000) and (w1 == 0):
return (True, False, 'gsDPPipeSync()')
else:
return invalid
if cmd == 0xE8: # G_RDPTILESYNC
if (w0 == 0xE8000000) and (w1 == 0):
return (True, False, 'gsDPTileSync()')
else:
return invalid
if cmd == 0xE9: # G_RDPFULLSYNC
if (w0 == 0xE9000000) and (w1 == 0):
return (True, False, 'gsDPFullSync()')
else:
return invalid
if cmd == 0xEA: # G_SETKEYGB
return (True, False, 'gsDPNoOp() # TODO G_SETKEYGB')
if cmd == 0xEB: # G_SETKEYR
return (True, False, 'gsDPNoOp() # TODO G_SETKEYR')
if cmd == 0xEC: # G_SETCONVERT
return (True, False, 'gsDPNoOp() # TODO G_SETCONVERT')
if cmd == 0xED: # G_SETSCISSOR
return (True, False, 'gsDPNoOp() # TODO G_SETSCISSOR')
if cmd == 0xEE: # G_SETPRIMDEPTH
return (True, False, 'gsDPNoOp() # TODO G_SETPRIMDEPTH')
if cmd == 0xEF: # G_RDPSETOTHERMODE
return (True, False, 'gsDPNoOp() # TODO G_RDPSETOTHERMODE')
if cmd == 0xF0: # G_LOADTLUT
if (w0 == 0xF0000000) and ((w1 & 0xF8000FFF) == 0):
tile = (w1 & 0x07000000) >> 24
count = ((w1 & 0x00FFF000) >> 12) >> 2
# TODO comment
assert len(self.textures) > 0
if self.textures[-1][1] == 0:
size_in_bytes = (count * self.textures[-1][2] + 5) // 8 # +5 to round up to byte
self.textures[-1] = (self.textures[-1][0], size_in_bytes, self.textures[-1][2])
# else:
# assert False # TODO
return (True, False, 'gsDPLoadTLUTCmd({}, {})'.format(tile, count))
else:
return invalid
if cmd == 0xF1: # G_RDPHALF_2
return (True, False, 'gsDPNoOp() # TODO G_RDPHALF_2')
if cmd == 0xF2: # G_SETTILESIZE
if ((w0 & 0xFF000000) == 0xF2000000) and ((w1 & 0xF8000000) == 0):
uls = (w0 & 0x00FFF000) >> 12
ult = (w0 & 0x00000FFF)
tile = (w1 & 0x07000000) >> 24
lrs = (w1 & 0x00FFF000) >> 12
lrt = (w1 & 0x00000FFF)
return (True, False, 'gsDPSetTileSize({}, {}, {}, {}, {})'.format(tile, uls, ult, lrs, lrt))
else:
return invalid
if cmd == 0xF3: # G_LOADBLOCK
if (w1 & 0xF8000000) == 0:
uls = (w0 & 0x00FFF000) >> 12
ult = (w0 & 0x00000FFF)
tile = (w1 & 0x07000000) >> 16
texels = (w1 & 0x00FFF000) >> 12
dxt = (w1 & 0x00000FFF)
# TODO comment
assert len(self.textures) > 0
if self.textures[-1][1] == 0:
size_in_bytes = ((texels + 1) * self.textures[-1][2] + 5) // 8 # +5 to round up to byte
self.textures[-1] = (self.textures[-1][0], size_in_bytes, self.textures[-1][2])
# else:
# assert False # TODO
return (True, False, 'gsDPLoadBlock({}, {}, {}, {}, {})'.format(tile, uls, ult, texels, dxt))
else:
return invalid
if cmd == 0xF4: # G_LOADTILE
return (True, False, 'gsDPNoOp() # TODO G_LOADTILE')
if cmd == 0xF5: # G_SETTILE
# TODO check illegal combinations
if ((w0 & 0xFF040000) == 0xF5000000) and ((w1 & 0xF8000000) == 0):
fmt = (w0 & 0x00E00000) >> 21
siz = (w0 & 0x00180000) >> 19
line = (w0 & 0x0003FE00) >> 9
tmem = (w0 & 0x000001FF)
tile = (w1 & 0x07000000) >> 24
palette = (w1 & 0x00F00000) >> 20
cmT = (w1 & 0x000C0000) >> 18
maskT = (w1 & 0x0003C000) >> 14
shiftT = (w1 & 0x00003C00) >> 10
cmS = (w1 & 0x00000300) >> 8
maskS = (w1 & 0x000000F0) >> 4
shiftS = (w1 & 0x0000000F)
if fmt == 0:
fmt_str = 'G_IM_FMT_RGBA'
elif fmt == 1:
fmt_str = 'G_IM_FMT_YUV'
elif fmt == 2:
fmt_str = 'G_IM_FMT_CI'
elif fmt == 3:
fmt_str = 'G_IM_FMT_IA'
elif fmt == 4:
fmt_str = 'G_IM_FMT_I'
else:
return invalid
if siz == 0:
siz_str = 'G_IM_SIZ_4b'
elif siz == 1:
siz_str = 'G_IM_SIZ_8b'
elif siz == 2:
siz_str = 'G_IM_SIZ_16b'
elif siz == 3:
siz_str = 'G_IM_SIZ_32b'
else:
return invalid
mirror_t = 'G_TX_MIRROR' if (cmT & 0b01) != 0 else 'G_TX_NOMIRROR'
wrap_or_clamp_t = 'G_TX_CLAMP' if (cmT & 0b10) != 0 else 'G_TX_WRAP'
mirror_s = 'G_TX_MIRROR' if (cmS & 0b01) != 0 else 'G_TX_NOMIRROR'
wrap_or_clamp_s = 'G_TX_CLAMP' if (cmS & 0b10) != 0 else 'G_TX_WRAP'
return (True, False, 'gsDPSetTile({}, {}, {}, {}, {}, {}, {} | {}, {}, {}, {} | {}, {}, {})'.format(fmt_str, siz_str, line, tmem, tile, palette, mirror_t, wrap_or_clamp_t, maskT, shiftT, mirror_s, wrap_or_clamp_s, maskS, shiftS))
else:
return invalid
if cmd == 0xF6: # G_FILLRECT
if ((w0 & 0xFF003003) == 0xF60000) and ((w1 & 0xFF003003) == 0):
lrx = (w0 & 0x00FFC000) >> 14
lry = (w0 & 0x00000FFC) >> 2
ulx = (w1 & 0x00FFC000) >> 14
uly = (w1 & 0x00000FFC) >> 2
return (True, False, 'gsDPFillRectangle({}, {}, {}, {})'.format(ulx, uly, lrx, lry))
else:
return invalid
if cmd == 0xF7: # G_SETFILLCOLOR
if w0 == 0xF7000000:
return (True, False, 'gsDPSetFillColor(0x{:08X})'.format(w1))
else:
return invalid
if cmd == 0xF8: # G_SETFOGCOLOR
if w0 == 0xF8000000:
red = (w1 & 0xFF000000) >> 24
green = (w1 & 0x00FF0000) >> 16
blue = (w1 & 0x0000FF00) >> 8
alpha = (w1 & 0x000000FF)
return (True, False, 'gsDPSetFogColor({}, {}, {}, {})'.format(red, green, blue, alpha))
else:
return invalid
if cmd == 0xF9: # G_SETBLENDCOLOR
if w0 == 0xF9000000:
red = (w1 & 0xFF000000) >> 24
green = (w1 & 0x00FF0000) >> 16
blue = (w1 & 0x0000FF00) >> 8
alpha = (w1 & 0x000000FF)
return (True, False, 'gsDPSetBlendColor({}, {}, {}, {})'.format(red, green, blue, alpha))
else:
return invalid
if cmd == 0xFA: # G_SETPRIMCOLOR
if (w0 & 0xFFFF0000) == 0xFA000000:
minlevel = (w0 & 0x0000FF00) >> 8
lodfrac = (w0 & 0x000000FF)
red = (w1 & 0xFF000000) >> 24
green = (w1 & 0x00FF0000) >> 16
blue = (w1 & 0x0000FF00) >> 8
alpha = (w1 & 0x000000FF)
return (True, False, 'gsDPSetPrimColor({}, {}, {}, {}, {}, {})'.format(minlevel, lodfrac, red, green, blue, alpha))
else:
return invalid
if cmd == 0xFB: # G_SETENVCOLOR
if w0 == 0xFB000000:
red = (w1 & 0xFF000000) >> 24
green = (w1 & 0x00FF0000) >> 16
blue = (w1 & 0x0000FF00) >> 8
alpha = (w1 & 0x000000FF)
return (True, False, 'gsDPSetEnvColor({}, {}, {}, {})'.format(red, green, blue, alpha))
else:
return invalid
if cmd == 0xFC: # G_SETCOMBINE
a0 = (w0 & 0x00F00000) >> 20
c0 = (w0 & 0x000F8000) >> 15
Aa0 = (w0 & 0x00007000) >> 12
Ac0 = (w0 & 0x00000E00) >> 9
a1 = (w0 & 0x000001E0) >> 5
c1 = (w0 & 0x0000001F)
b0 = (w1 & 0xF0000000) >> 28
b1 = (w1 & 0x0F000000) >> 24
Aa1 = (w1 & 0x00E00000) >> 21
Ac1 = (w1 & 0x001C0000) >> 18
d0 = (w1 & 0x00038000) >> 15
Ab0 = (w1 & 0x00007000) >> 12
Ad0 = (w1 & 0x00000E00) >> 9
d1 = (w1 & 0x000001C0) >> 6
Ab1 = (w1 & 0x00000038) >> 3
Ad1 = (w1 & 0x00000007)
param_str_c0 = '{}, {}, {}, {}, {}, {}, {}, {}'.format(
setcombine_a_names[a0],
setcombine_b_names[b0],
setcombine_c_names[c0],
setcombine_d_names[d0],
setcombine_Aa_names[Aa0],
setcombine_Ab_names[Ab0],
setcombine_Ac_names[Ac0],
setcombine_Ad_names[Ad0],
)
if param_str_c0.startswith(',') or (', ,' in param_str_c0): # one of the names was empty, meaning it was invalid
return invalid
param_str_c1 = '{}, {}, {}, {}, {}, {}, {}, {}'.format(
setcombine_a_names[a1],
setcombine_b_names[b1],
setcombine_c_names[c1],
setcombine_d_names[d1],
setcombine_Aa_names[Aa1],
setcombine_Ab_names[Ab1],
setcombine_Ac_names[Ac1],
setcombine_Ad_names[Ad1],
)
if param_str_c1.startswith(',') or (', ,' in param_str_c1): # one of the names was empty, meaning it was invalid
return invalid
if (param_str_c0 in setcombine_predefined) and (param_str_c1 in setcombine_predefined):
return (True, False, 'gsDPSetCombineMode({}, {})'.format(setcombine_predefined[param_str_c0], setcombine_predefined[param_str_c1]))
else:
return (True, False, 'gsDPSetCombineLERP({}, {})'.format(param_str_c0, param_str_c1))
if cmd == 0xFD: # G_SETTIMG
if (w0 & 0xFF07F000) == 0xFD000000:
fmt = (w0 & 0x00E00000) >> 21
siz = (w0 & 0x00180000) >> 19
width = (w0 & 0x00000FFF) + 1
# TODO check valid fmt and siz combinations?
# TODO util functions
if fmt == 0:
fmt_str = 'G_IM_FMT_RGBA'
elif fmt == 1:
fmt_str = 'G_IM_FMT_YUV'
elif fmt == 2:
fmt_str = 'G_IM_FMT_CI'
elif fmt == 3:
fmt_str = 'G_IM_FMT_IA'
elif fmt == 4:
fmt_str = 'G_IM_FMT_I'
else:
return invalid
if siz == 0:
siz_str = 'G_IM_SIZ_4b'
siz_bits = 4
elif siz == 1:
siz_str = 'G_IM_SIZ_8b'
siz_bits = 8
elif siz == 2:
siz_str = 'G_IM_SIZ_16b'
siz_bits = 16
elif siz == 3:
siz_str = 'G_IM_SIZ_32b'
siz_bits = 32
elif siz == 5:
siz_str = 'G_IM_SIZ_DD'
siz_bits = 0
assert False, "TODO G_IM_SIZ_DD size"
else:
return invalid
self.textures.append((w1, 0, siz_bits))
return (True, False, 'gsDPSetTextureImage({}, {}, {}, 0x{:08X})'.format(fmt_str, siz_str, width, w1))
else:
return invalid
if cmd == 0xFE: # G_SETZIMG
if (w0 == 0xFE000000):
return (True, False, 'gsDPSetDepthImage(0x{:08X})'.format(w1))
else:
return invalid
if cmd == 0xFF: # G_SETCIMG
return (True, False, 'gsDPNoOp() # TODO G_SETCIMG')
return invalid
def find_dls(self, file_info):
is_in_dl = False
for i in range(len(file_info) - 1, -1, -1):
info = file_info[i]
addr = self.base_addr + i * 8
if self.is_in_vertex_list(addr) or self.is_in_texture(addr):
if is_in_dl: # previous dl ended and we are not in a new one
self.add_dl(addr + 8, (dl_end_addr - addr) // 8)
is_in_dl = False
elif not info[0]:
if is_in_dl: # previous dl ended and we are not in a new one
self.add_dl(addr + 8, (dl_end_addr - addr) // 8)
is_in_dl = False
if info[1] and not is_in_dl:
is_in_dl = True
dl_end_addr = addr
elif info[1] and is_in_dl: # previous dl ended and we are in a new one
self.add_dl(addr + 8, (dl_end_addr - addr) // 8)
dl_end_addr = addr
if is_in_dl:
self.add_dl(self.base_addr, (dl_end_addr - self.base_addr) // 8)
def find_mesh_headers(self, file_data):
i = 0
num_pointers = len(self.pointers)
while i < num_pointers:
if i + 2 < num_pointers:
base = self.pointers[i] # vertices pointer
# test for polygons and attributes
if base > 3 and self.pointers[i+1] == base + 2 and self.pointers[i+2] == base + 3:
# test for cameras and waterboxes, which may be null
if (i + 4 < num_pointers and self.pointers[i+3] == base + 4 and self.pointers[i+4] == base + 6) or \
(i + 3 < num_pointers and ((self.pointers[i+3] == base + 4 and base + 6 in self.zeros) or (self.pointers[i+3] == base + 6 and base + 4 in self.zeros))) or \
(base + 4 in self.zeros and base + 6 in self.zeros):
self.add_mesh_header(self.base_addr + (base - 4) * 4, file_data, (base - 4) * 4)
header = self.mesh_headers[-1]
self.add_mesh_verts(header.vertices, header.num_vertices)
self.add_mesh_polys(header.polygons, header.num_polygons)
self.add_mesh_attrs(header.polygon_attrs, (header.polygons - header.polygon_attrs) // 8) # TODO how is the length determined? This is just a guess
if header.cameras != 0:
self.add_mesh_cameras(header.cameras, (header.polygon_attrs - header.cameras) // 8) # TODO how is the length determined? This is just a guess
# TODO parse camera parameters, as it can have pointers we need to follow
if header.num_waterboxes != 0:
self.add_mesh_waterboxes(header.waterboxes, header.num_waterboxes)
pointers_to_consume = 3
if base + 4 not in self.zeros:
pointers_to_consume += 1
if base + 6 not in self.zeros:
pointers_to_consume += 1
i += pointers_to_consume # TODO remove pointers from list for future passes looking for different object types?
#print('Adding mesh at 0x{:08X}'.format(self.base_addr + (base - 4) * 4))
i += 1
def extract_models(self, dir, file_data):
if len(self.dls) == 0:
return
for dl in self.dls:
verts = dict()
global_vert_index = 1
active_verts = [None] * 32
tris = []
for i in range(dl.length):
w0 = read_uint32_be(file_data, dl.addr - self.base_addr + i * 8)
w1 = read_uint32_be(file_data, dl.addr - self.base_addr + i * 8 + 4)
cmd = get_cmd(w0)
if cmd == 0x01: # G_VTX
vaddr = w1
numv = (w0 & 0x000FF000) >> 12
vbidx = ((w0 & 0x000000FF) >> 1) - numv
for vertex_index in range(numv):
vert_addr = vaddr - self.base_addr + vertex_index * 0x10
if vert_addr not in verts:
x = read_uint16_be(file_data, vert_addr)
y = read_uint16_be(file_data, vert_addr + 2)
z = read_uint16_be(file_data, vert_addr + 4)
verts[vert_addr] = (x, y, z, global_vert_index)
global_vert_index += 1
active_verts[vbidx + vertex_index] = verts[vert_addr]
if cmd == 0x05: # G_TRI1
v0 = ((w0 & 0x00FF0000) >> 16) // 2
v1 = ((w0 & 0x0000FF00) >> 8) // 2
v2 = (w0 & 0x000000FF) // 2
tris.append((active_verts[v0][3], active_verts[v1][3], active_verts[v2][3]))
if cmd == 0x06: # G_TRI2
v00 = ((w0 & 0x00FF0000) >> 16) // 2
v01 = ((w0 & 0x0000FF00) >> 8) // 2
v02 = (w0 & 0x000000FF) // 2
v10 = ((w1 & 0x00FF0000) >> 16) // 2
v11 = ((w1 & 0x0000FF00) >> 8) // 2
v12 = (w1 & 0x000000FF) // 2
tris.append((active_verts[v00][3], active_verts[v01][3], active_verts[v02][3]))
tris.append((active_verts[v10][3], active_verts[v11][3], active_verts[v12][3]))
if len(verts) > 0 and len(tris) > 0:
with open('{}/0x{:08X}.obj'.format(dir, dl.addr), 'w') as f:
for addr, vert in sorted(verts.items(), key=lambda vert: vert[1][3]):
f.write('v {} {} {}\n'.format(vert[0], vert[1], vert[2]))
f.write('\n')
for tri in tris:
f.write('f {} {} {}\n'.format(tri[0], tri[1], tri[2]))
def parse(self, file_data):
file_info = []
for i in range(len(file_data) // 8):
w0 = read_uint32_be(file_data, i * 8)
w1 = read_uint32_be(file_data, i * 8 + 4)
file_info.append(self.parse_cmd(w0, w1))
self.find_dls(file_info)
for i in range(len(file_data) // 4):
if self.is_unknown(self.base_addr + i * 4):
word = read_uint32_be(file_data, i * 4)
offset = word - self.base_addr
if offset >= 0 and offset < 0x1000000:
self.pointers.append(i)
elif word == 0:
self.zeros.add(i)
self.find_mesh_headers(file_data)
return file_info
def print_info(self, file_data, file_info):
num_in_dls = 0
for dl in self.dls:
num_in_dls += dl.length
print('DLs:{}({}) Vertex Lists:{} Textures:{} Mesh Headers:{}'.format(len(self.dls), num_in_dls, len(self.vertex_lists), len(self.textures), len(self.mesh_headers)))
start_addr = self.base_addr
# for pointer in self.pointers:
# print('0x{:08X}'.format(self.base_addr + pointer*4))
# print('-----')
# for zero in self.zeros:
# print('0x{:08X}'.format(self.base_addr + zero*4))
# for texture in self.textures:
# print('0x{:08X} {}'.format(texture[0], texture[1]))
# TODO assert that vertex lists and textures start on the address
i = 0
while(i < len(file_info) * 2):
addr = start_addr + i*4
if self.is_in_vertex_list(addr):
list = self.get_vertex_list(addr)
print('0x{:08X}: Vertex[{}]'.format(addr, list.length))
# for vertex in range(list.length):
# x = read_uint16_be(file_data, list.addr - self.base_addr + vertex * 0x10)
# y = read_uint16_be(file_data, list.addr - self.base_addr + vertex * 0x10 + 2)
# z = read_uint16_be(file_data, list.addr - self.base_addr + vertex * 0x10 + 4)
# print('{} {} {}'.format(x, y, z))
i += list.length * 4
continue
elif self.is_in_texture(addr):
length = self.get_texture(addr)[1]
print('0x{:08X}: Texture(0x{:X} bytes)'.format(addr, length))
i += (length + 3) // 4 # +3 to align up to word
continue
elif self.is_in_dl(addr):
length = self.get_dl(addr).length
print('0x{:08X}: Dl[{}]'.format(addr, length))
for dl_cmd in range(length):
print(' {}'.format(file_info[i // 2 + dl_cmd][2]))
i += length * 2
continue
elif self.is_in_matrix(addr):
print('0x{:08X}: Matrix'.format(addr))
i += 16
continue
elif self.is_in_mesh_header(addr):
print('0x{:08X}: Mesh Header'.format(addr))
header = self.get_mesh_header(addr)
print(' min: {{{}, {}, {}}}'.format(header.minx, header.miny, header.minz))
print(' max: {{{}, {}, {}}}'.format(header.maxx, header.maxy, header.maxz))
print(' num_vertices: {}'.format(header.num_vertices))
print(' vertices: 0x{:08X}'.format(header.vertices))
print(' num_polygons: {}'.format(header.num_polygons))
print(' polygons: 0x{:08X}'.format(header.polygons))
print(' polygon_attrs: 0x{:08X}'.format(header.polygon_attrs))
print(' cameras: 0x{:08X}'.format(header.cameras))
print(' num_waterboxes: {}'.format(header.num_waterboxes))
print(' waterboxes: 0x{:08X}'.format(header.waterboxes))
i += 11
continue
elif self.is_in_mesh_verts(addr):
length = self.get_mesh_verts(addr).length
print('0x{:08X}: MeshVerts[{}]'.format(addr, length))
i += (length*6 + 3) // 4 # +3 to align up to word
continue
elif self.is_in_mesh_polys(addr):
length = self.get_mesh_polys(addr).length
print('0x{:08X}: MeshPolys[{}]'.format(addr, length))
i += length*4
continue
elif self.is_in_mesh_attrs(addr):
length = self.get_mesh_attrs(addr).length
print('0x{:08X}: MeshPolyAttrs[{}]'.format(addr, length))
i += length*2
continue
elif self.is_in_mesh_cameras(addr):
length = self.get_mesh_cameras(addr).length
print('0x{:08X}: Cameras[{}]'.format(addr, length))
i += length*2
continue
elif self.is_in_mesh_waterboxes(addr):
length = self.get_mesh_waterboxes(addr).length
print('0x{:08X}: Waterboxes[{}]'.format(addr, length))
i += length*4
continue
elif (i % 2) == 1:
print('pad 0x00000000') # TODO assert that it is actually 0
i += 1
else:
if (i % 4) == 2 and file_info[i // 2] == (True, False, 'gsDPNoOp()'):
print('pad 0x00000000')
print('pad 0x00000000')
else:
print('0x{:08X}: {}, {}, {}'.format(addr, file_info[i // 2][0], file_info[i // 2][1], file_info[i // 2][2]))
i += 2
continue
#self.extract_models('test/', file_data)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('input', help='input file to parse', metavar='file')
args = parser.parse_args()
file_data = read_file(args.input)
parser = Parser(0x06000000) # TODO take in base addr
file_info = parser.parse(file_data)
parser.print_info(file_data, file_info)