Created
August 18, 2020 12:39
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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