Skip to content

Instantly share code, notes, and snippets.

@Dragorn421
Created August 18, 2020 12:39
Show Gist options
  • Save Dragorn421/63ce9441e593f6a8e3ea271ee770e543 to your computer and use it in GitHub Desktop.
Save Dragorn421/63ce9441e593f6a8e3ea271ee770e543 to your computer and use it in GitHub Desktop.
Python script for dumping polytypes from objects (using a CloudModding table) and scenes (using 0x03 commands)
"""
usage: python3 oot_dump_polytypes.py
prerequisites:
zzrtl/oot_names.tsv
have run oot_dump.rtl (with zzrtl) in zzrtl/
output: polytypes_dump.tsv
"""
import struct
# oot_names.tsv
scene_names = {}
object_names = {}
actor_names = {}
with open('zzrtl/oot_names.tsv') as f:
for line in f:
while line and line[-1] in '\r\n':
line = line[:-1]
parts = line.split('\t')
if parts[0] == 'index':
if parts[1:] != ['inhex','scene','object','actor','particle (TODO)']:
print('unexpected header line (starts with index\t), skipping', line)
continue
index = int(parts[0])
inhex = int(parts[1][2:], 16)
if index != inhex:
raise ValueError('line={!r}, index={}, inhex={}'.format(line, index, inhex))
scene, object, actor = parts[2:5]
if scene:
scene_names[index] = scene
if object:
object_names[index] = object
if actor:
actor_names[index] = actor
actor_mesh_collision_headers = (
('Object','0002','0041B0','00D0',),
('Object','0003','004E98','00FF',),
('Object','0003','005FB8','012A',),
('Object','000E','005FC8','000A',),
('Object','0019','0250A8','0059',),
('Object','001C','01D9D0','002E',),
('Object','002A','000E94','003E',),
('Object','002B','001DDC','003F',),
('Object','002B','003CE0','0058',),
('Object','002B','004F30','005C',),
('Object','002C','00DA10','0040',),
('Object','002C','00E2CC','006F',),
('Object','002C','00D054','0041',),
('Object','002F','0005E0','004A',),
('Object','002F','000280','004A',),
('Object','0036','006460','000F',),
('Object','0038','000118','0054',),
('Object','0040','001830','005A',),
('Object','0040','000A1C','005A',),
('Object','0059','0003F0','0064',),
('Object','0059','003590','0064',),
('Object','0059','000998','0064',),
('Object','0059','0073F0','01BB',),
('Object','0059','001DE8','01BA',),
('Object','0061','000658','006E',),
('Object','0069','00A938','00F7',),
('Object','0069','010E10','00F7',),
('Object','0069','0131C4','00F7',),
('Object','0069','004780','00AE',),
('Object','0069','0044D0','00AE',),
('Object','006A','000EE8','00D5',),
('Object','006B','002594','01C7',),
('Object','0070','0043D0','0068',),
('Object','0072','0035F8','0087',),
('Object','0072','00221C','0086',),
('Object','0072','0063B8','0088',),
('Object','0072','0089E0','00E3',),
('Object','0072','0087AC','0089',),
('Object','0074','0039D4','01A8',),
('Object','0076','0000C0','0026',),
('Object','008D','000C2C','00BE',),
('Object','008D','001830','00AE',),
('Object','0096','005CF8','00E6',),
('Object','0096','005580','00C8',),
('Object','0096','005048','00C8',),
('Object','0096','008CE0','00C8',),
('Object','0099','007860','0093',),
('Object','009A','00169C','0092',),
('Object','009C','000D68','00AC',),
('Object','00A1','0128D8','009C',),
('Object','00A1','0133EC','009C',),
('Object','00A2','000428','009D',),
('Object','00AE','00283C','00B8',),
('Object','00AF','002154','015C',),
('Object','00AF','000368','01C3',),
('Object','00AF','000534','01C4',),
('Object','00AF','00261C','00B9',),
('Object','00AF','002FE4','00B9',),
('Object','00B1','000A38','01A9',),
('Object','00E2','0180F8','0166',),
('Object','00F0','000348','0107',),
('Object','00F0','0004D0','00F8',),
('Object','00F1','0004A8','018E',),
('Object','00F9','00075C','0103',),
('Object','0100','001438','0108',),
('Object','0112','000C98','011F',),
('Object','011C','000730','012D',),
('Object','011C','000578','012D',),
('Object','0125','007564','0136',),
('Object','0130','000DB8','0148',),
('Object','0161','000918','018D',),
('Object','0161','0012C0','018D',),
('Object','0162','00238C','0191',),
('Object','0162','0011EC','0190',),
('Object','0166','000AF0','0194',),
('Object','0166','000908','0194',),
('Object','016C','000D48','0169',),
('Object','016C','001430','0169',),
('Object','016F','001A58','019F',),
('Object','0170','000B70','01A0',),
('Object','0181','001C58','019D',),
('Object','0181','001DA8','0100',),
('Object','018A','000118','0059',),
('Object','0190','000B30','01D1',),
('Actor', '0106','0024C0','0106',),
('Actor', '016B','004C20','016B',),
)
class MeshCollision:
def __init__(self):
pass
def load_from_header(self, *, data, offset):
header = struct.unpack_from('>hhhhhhHxxIHxxIIIHxxI', data, offset)
minx, miny, minz, maxx, maxy, maxz, vertex_array_length, vertex_array_segment_offset, polygon_array_length, polygon_array_segment_offset, polytypes_table_segment_offset, cameradata_segment_offset, waterbox_array_length, waterbox_segment_offset = header
print(data[offset:offset+0x2C].hex())
print(struct.pack('>hhhhhhHxxIHxxIIIHxxI', *header).hex())
print('polygon_array_segment_offset = 0x{:08X}'.format(polygon_array_segment_offset))
if len(set(offset >> 24 for offset in (vertex_array_segment_offset, polygon_array_segment_offset, polytypes_table_segment_offset, cameradata_segment_offset, waterbox_segment_offset) if offset != 0)) != 1:
print('Offsets', tuple('0x{:08X}'.format(offset) for offset in (vertex_array_segment_offset, polygon_array_segment_offset, polytypes_table_segment_offset, cameradata_segment_offset, waterbox_segment_offset)), 'use different segments. probably wrong')
print(set(offset >> 24 for offset in (vertex_array_segment_offset, polygon_array_segment_offset, polytypes_table_segment_offset, cameradata_segment_offset, waterbox_segment_offset)))
return None
polygon_array_segment = polygon_array_segment_offset >> 24
if polygon_array_segment == 0:
print('Skipping because polygon_array_segment = 0x{:02X}'.format(polygon_array_segment))
return None
polytypes_table_segment = polytypes_table_segment_offset >> 24
if polytypes_table_segment == 0:
print('Skipping because polytypes_table_segment = 0x{:02X}'.format(polytypes_table_segment))
return None
polygon_array_offset = polygon_array_segment_offset & 0xFFFFFF
print('polytypes_table_segment_offset = 0x{:08X}'.format(polytypes_table_segment_offset))
polytypes_table_offset = polytypes_table_segment_offset & 0xFFFFFF
polytype_indices = {}
for i in range(polygon_array_length):
polytype_index = struct.unpack_from('>H', data, polygon_array_offset + i * 0x10 + 0x0)[0]
polytype_use_count = polytype_indices.get(polytype_index, 0)
polytype_indices[polytype_index] = polytype_use_count + 1
print('polytype_indices =', polytype_indices)
polytype_lines = []
for polytype_index, polytype_use_count in polytype_indices.items():
polytype_line = ('{} 0x{:02X}{:02X}_{:02X}{:02X}_{:02X}{:02X}_{:02X}{:02X}'
.format(polytype_use_count,
*(data[polytypes_table_offset + polytype_index * 8 + i] for i in range(8))))
polytype_hi, polytype_lo = struct.unpack_from('>II', data, polytypes_table_offset + polytype_index * 8)
print(polytype_line)
print('polytype_hi = {:08X}'.format(polytype_hi))
print('polytype_lo = {:08X}'.format(polytype_lo))
polytype_line = '{} {}'.format(
polytype_line,
' '.join(
'{}'.format(v) for v in (
polytype_hi >> 31 & 1, # epona
polytype_hi >> 30 & 1, # -1 unit
polytype_hi >> 26 & 0xF, # floor
polytype_hi >> 21 & 0x1F, # wall
polytype_hi >> 18 & 7, # unused
polytype_hi >> 13 & 0x1F, # special
polytype_hi >> 8 & 0x1F, # exit
polytype_hi & 0xFF, # camera
polytype_lo >> 28 & 0b1111, # padding
1 if polytype_lo & 0x8000000 else 0, # wall_dmg
polytype_lo >> 21 & 0x3F, # conv_dir
polytype_lo >> 18 & 7, # conv_speed
polytype_lo >> 17 & 1, # hookshot
polytype_lo >> 11 & 0x3F, # echo
polytype_lo >> 6 & 0x1F, # lighting
polytype_lo >> 4 & 3, # slope
polytype_lo & 0xF, # sound
)
))
print(polytype_line)
polytype_lines.append(polytype_line)
return polytype_lines
output = []
for type, id, offset, actor in actor_mesh_collision_headers:
id = int(id, 16)
offset = int(offset, 16)
actor = int(actor, 16)
print('actor', actor, actor_names[actor])
if type == 'Object':
from_name = object_names[id]
print('data from object', id, )
filepath = 'zzrtl/object/{} - {}/zobj.zobj'.format(id, from_name)
elif type == 'Actor':
from_name = actor_names[id]
print('data from actor', id, from_name)
filepath = 'zzrtl/actor/{} - {}/actor.zovl'.format(id, from_name)
else:
raise ValueError('Unknown type {}'.format(type))
print('filepath =', filepath)
print('offset = 0x{:X}'.format(offset))
with open(filepath, 'rb') as f:
bytes = f.read()
mesh_collision = MeshCollision()
polytype_lines = mesh_collision.load_from_header(data=bytes, offset=offset)
if polytype_lines is None:
output.append('error\t{}\t{}\t{}\t{}\t{}\n'.format(type, id, from_name, actor, actor_names[actor]))
else:
for polytype_line in polytype_lines:
output.append('ok\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(type, id, from_name, actor, actor_names[actor], polytype_line.replace(' ', '\t')))
for scene_id, scene_name in scene_names.items():
mesh_collision_header_segment_offset = None
print('scene', scene_id, scene_name)
filepath = 'zzrtl/scene/{} - {}/scene.zscene'.format(scene_id, scene_name)
print('filepath =', filepath)
with open(filepath, 'rb') as f:
bytes = f.read()
header_command_index = 0
while True:
command_id, lower_word = struct.unpack_from('>BxxxI', bytes, header_command_index * 8)
if command_id == 0x03:
mesh_collision_header_segment_offset = lower_word
print('0x03: mesh header at 0x{:08X}'.format(mesh_collision_header_segment_offset))
mesh_collision_header_segment = mesh_collision_header_segment_offset >> 24
if mesh_collision_header_segment != 2:
raise ValueError('Unexpected segment 0x{:02X} (expected 2)'.format(mesh_collision_header_segment))
mesh_collision_header_offset = mesh_collision_header_segment_offset & 0xFFFFFF
mesh_collision = MeshCollision()
polytype_lines = mesh_collision.load_from_header(data=bytes, offset=mesh_collision_header_offset)
if polytype_lines is None:
output.append('error\tScene\t{}\t{}\n'.format(scene_id, scene_name))
else:
for polytype_line in polytype_lines:
output.append('ok\tScene\t{}\t{}\t\t\t{}\n'.format(scene_id, scene_name, polytype_line.replace(' ', '\t')))
elif command_id == 0x14:
break
header_command_index += 1
if mesh_collision_header_segment_offset is None:
print('No 0x03 command in scene', scene_id, scene_name)
output.append('error\tScene\t{}\t{}\n'.format(scene_id, scene_name))
with open('polytypes_dump.tsv', 'w') as f:
f.write('status\tsource type\tsource id\tsource name\tused by actor id\tused by actor name\tuse count\traw polytype data\tepona\t-1 unit\tfloor\twall\tunused\tspecial\texit\tcamera\tpadding\twall_dmg\tconv_dir\tconv_speed\thookshot\techo\tlighting\tslope\tsound\n')
f.writelines(output)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment