mirror of
				https://github.com/MarioSpore/Grinch-AP.git
				synced 2025-10-21 20:21:32 -06:00 
			
		
		
		
	
		
			
	
	
		
			733 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			733 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | # mzxrules 2018 | ||
|  | # In order to patch MQ to the existing data... | ||
|  | # | ||
|  | # Scenes: | ||
|  | # | ||
|  | # Ice Cavern (Scene 9) needs to have it's header altered to support MQ's path list. This | ||
|  | # expansion will delete the otherwise unused alternate headers command | ||
|  | # | ||
|  | # Transition actors will be patched over the old data, as the number of records is the same | ||
|  | # Path data will be appended to the end of the scene file. | ||
|  | # | ||
|  | # The size of a single path on file is NUM_POINTS * 6, rounded up to the nearest 4 byte boundary | ||
|  | # The total size consumed by the path data is NUM_PATHS * 8, plus the sum of all path file sizes | ||
|  | # padded to the nearest 0x10 bytes | ||
|  | # | ||
|  | # Collision: | ||
|  | # OoT's collision data consists of these elements: vertices, surface types, water boxes, | ||
|  | # camera behavior data, and polys. MQ's vertice and polygon geometry data is identical. | ||
|  | # However, the surface types and the collision exclusion flags bound to the polys have changed | ||
|  | # for some polygons, as well as the number of surface type records and camera type records. | ||
|  | # | ||
|  | # To patch collision, a flag denotes whether collision data cannot be written in place without | ||
|  | # expanding the size of the scene file. If true, the camera data is relocated to the end | ||
|  | # of the scene file, and the surface types are shifted down into where the camera types | ||
|  | # were situated. If false, the camera data isn't moved, but rather the surface type list | ||
|  | # will be shifted to the end of the camera data | ||
|  | # | ||
|  | # Rooms: | ||
|  | # | ||
|  | # Object file initialization data will be appended to the end of the room file. | ||
|  | # The total size consumed by the object file data is NUM_OBJECTS * 0x02, aligned to | ||
|  | # the nearest 0x04 bytes | ||
|  | # | ||
|  | # Actor spawn data will be appended to the end of the room file, after the objects. | ||
|  | # The total size consumed by the actor spawn data is NUM_ACTORS * 0x10 | ||
|  | # | ||
|  | # Finally: | ||
|  | # | ||
|  | # Scene and room files will be padded to the nearest 0x10 bytes | ||
|  | # | ||
|  | # Maps: | ||
|  | # Jabu Jabu's B1 map contains no chests in the vanilla layout. Because of this, | ||
|  | # the floor map data is missing a vertex pointer that would point within kaleido_scope. | ||
|  | # As such, if the file moves, the patch will break. | ||
|  | 
 | ||
|  | from .Utils import data_path | ||
|  | from .Rom import Rom | ||
|  | import json | ||
|  | from struct import pack, unpack | ||
|  | 
 | ||
|  | SCENE_TABLE = 0xB71440 | ||
|  | 
 | ||
|  | 
 | ||
|  | class File(object): | ||
|  |     def __init__(self, file): | ||
|  |         self.name = file['Name'] | ||
|  |         self.start = int(file['Start'], 16) if 'Start' in file else 0 | ||
|  |         self.end = int(file['End'], 16) if 'End' in file else self.start | ||
|  |         self.remap = file['RemapStart'] if 'RemapStart' in file else None | ||
|  |         self.from_file = self.start | ||
|  | 
 | ||
|  |         # used to update the file's associated dmadata record | ||
|  |         self.dma_key = self.start | ||
|  | 
 | ||
|  |         if self.remap is not None: | ||
|  |             self.remap = int(self.remap, 16) | ||
|  | 
 | ||
|  |     def __repr__(self): | ||
|  |         remap = "None" | ||
|  |         if self.remap is not None: | ||
|  |             remap = "{0:x}".format(self.remap) | ||
|  |         return "{0}: {1:x} {2:x}, remap {3}".format(self.name, self.start, self.end, remap) | ||
|  | 
 | ||
|  |     def relocate(self, rom:Rom): | ||
|  |         if self.remap is None: | ||
|  |             self.remap = rom.free_space() | ||
|  | 
 | ||
|  |         new_start = self.remap | ||
|  | 
 | ||
|  |         offset = new_start - self.start | ||
|  |         new_end = self.end + offset | ||
|  | 
 | ||
|  |         rom.buffer[new_start:new_end] = rom.buffer[self.start:self.end] | ||
|  |         self.start = new_start | ||
|  |         self.end = new_end | ||
|  |         update_dmadata(rom, self) | ||
|  | 
 | ||
|  |     # The file will now refer to the new copy of the file | ||
|  |     def copy(self, rom:Rom): | ||
|  |         self.dma_key = None | ||
|  |         self.relocate(rom) | ||
|  | 
 | ||
|  | 
 | ||
|  | class CollisionMesh(object): | ||
|  |     def __init__(self, rom:Rom, start, offset): | ||
|  |         self.offset = offset | ||
|  |         self.poly_addr = rom.read_int32(start + offset + 0x18) | ||
|  |         self.polytypes_addr = rom.read_int32(start + offset + 0x1C) | ||
|  |         self.camera_data_addr = rom.read_int32(start + offset + 0x20) | ||
|  |         self.polytypes = (self.poly_addr - self.polytypes_addr) // 8 | ||
|  | 
 | ||
|  |     def write_to_scene(self, rom:Rom, start): | ||
|  |         addr = start + self.offset + 0x18 | ||
|  |         rom.write_int32s(addr, [self.poly_addr, self.polytypes_addr, self.camera_data_addr]) | ||
|  | 
 | ||
|  | 
 | ||
|  | class ColDelta(object): | ||
|  |     def __init__(self, delta): | ||
|  |         self.is_larger = delta['IsLarger'] | ||
|  |         self.polys = delta['Polys'] | ||
|  |         self.polytypes = delta['PolyTypes'] | ||
|  |         self.cams = delta['Cams'] | ||
|  | 
 | ||
|  | 
 | ||
|  | class Icon(object): | ||
|  |     def __init__(self, data): | ||
|  |         self.icon = data["Icon"]; | ||
|  |         self.count = data["Count"]; | ||
|  |         self.points = [IconPoint(x) for x in data["IconPoints"]] | ||
|  | 
 | ||
|  |     def write_to_minimap(self, rom:Rom, addr): | ||
|  |         rom.write_sbyte(addr, self.icon) | ||
|  |         rom.write_byte(addr + 1,  self.count) | ||
|  |         cur = 2 | ||
|  |         for p in self.points: | ||
|  |             p.write_to_minimap(rom, addr + cur) | ||
|  |             cur += 0x03 | ||
|  | 
 | ||
|  |     def write_to_floormap(self, rom:Rom, addr): | ||
|  |         rom.write_int16(addr, self.icon) | ||
|  |         rom.write_int32(addr + 0x10, self.count) | ||
|  | 
 | ||
|  |         cur = 0x14 | ||
|  |         for p in self.points: | ||
|  |             p.write_to_floormap(rom, addr + cur) | ||
|  |             cur += 0x0C | ||
|  | 
 | ||
|  | 
 | ||
|  | class IconPoint(object): | ||
|  |     def __init__(self, point): | ||
|  |         self.flag = point["Flag"] | ||
|  |         self.x = point["x"] | ||
|  |         self.y = point["y"] | ||
|  | 
 | ||
|  |     def write_to_minimap(self, rom:Rom, addr): | ||
|  |         rom.write_sbyte(addr, self.flag) | ||
|  |         rom.write_byte(addr+1, self.x) | ||
|  |         rom.write_byte(addr+2, self.y) | ||
|  | 
 | ||
|  |     def write_to_floormap(self, rom:Rom, addr): | ||
|  |         rom.write_int16(addr, self.flag) | ||
|  |         rom.write_f32(addr + 4, float(self.x)) | ||
|  |         rom.write_f32(addr + 8, float(self.y)) | ||
|  | 
 | ||
|  | 
 | ||
|  | class Scene(object): | ||
|  |     def __init__(self, scene): | ||
|  |         self.file = File(scene['File']) | ||
|  |         self.id = scene['Id'] | ||
|  |         self.transition_actors = [convert_actor_data(x) for x in scene['TActors']] | ||
|  |         self.rooms = [Room(x) for x in scene['Rooms']] | ||
|  |         self.paths = [] | ||
|  |         self.coldelta = ColDelta(scene["ColDelta"]) | ||
|  |         self.minimaps = [[Icon(icon) for icon in minimap['Icons']] for minimap in scene['Minimaps']] | ||
|  |         self.floormaps = [[Icon(icon) for icon in floormap['Icons']] for floormap in scene['Floormaps']] | ||
|  |         temp_paths = scene['Paths'] | ||
|  |         for item in temp_paths: | ||
|  |             self.paths.append(item['Points']) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def write_data(self, rom:Rom): | ||
|  |         # write floormap and minimap data | ||
|  |         self.write_map_data(rom) | ||
|  | 
 | ||
|  |         # move file to remap address | ||
|  |         if self.file.remap is not None: | ||
|  |             self.file.relocate(rom) | ||
|  | 
 | ||
|  |         start = self.file.start | ||
|  |         headcur = self.file.start | ||
|  | 
 | ||
|  |         room_list_offset = 0 | ||
|  | 
 | ||
|  |         code = rom.read_byte(headcur) | ||
|  |         loop = 0x20 | ||
|  |         while loop > 0 and code != 0x14: #terminator | ||
|  |             loop -= 1 | ||
|  | 
 | ||
|  |             if code == 0x03: #collision | ||
|  |                 col_mesh_offset = rom.read_int24(headcur + 5) | ||
|  |                 col_mesh = CollisionMesh(rom, start, col_mesh_offset) | ||
|  |                 self.patch_mesh(rom, col_mesh); | ||
|  | 
 | ||
|  |             elif code == 0x04: #rooms | ||
|  |                 room_list_offset = rom.read_int24(headcur + 5) | ||
|  | 
 | ||
|  |             elif code == 0x0D: #paths | ||
|  |                 path_offset = self.append_path_data(rom) | ||
|  |                 rom.write_int32(headcur + 4, path_offset) | ||
|  | 
 | ||
|  |             elif code == 0x0E: #transition actors | ||
|  |                 t_offset = rom.read_int24(headcur + 5) | ||
|  |                 addr = self.file.start + t_offset | ||
|  |                 write_actor_data(rom, addr, self.transition_actors) | ||
|  | 
 | ||
|  |             headcur += 8 | ||
|  |             code = rom.read_byte(headcur) | ||
|  | 
 | ||
|  |         # update file references | ||
|  |         self.file.end = align16(self.file.end) | ||
|  |         update_dmadata(rom, self.file) | ||
|  |         update_scene_table(rom, self.id, self.file.start, self.file.end) | ||
|  | 
 | ||
|  |         # write room file data | ||
|  |         for room in self.rooms: | ||
|  |             room.write_data(rom) | ||
|  |             if self.id == 6 and room.id == 6: | ||
|  |                 patch_spirit_temple_mq_room_6(rom, room.file.start) | ||
|  | 
 | ||
|  |         cur = self.file.start + room_list_offset | ||
|  |         for room in self.rooms: | ||
|  |             rom.write_int32s(cur, [room.file.start, room.file.end]) | ||
|  |             cur += 0x08 | ||
|  | 
 | ||
|  | 
 | ||
|  |     def write_map_data(self, rom:Rom): | ||
|  |         if self.id >= 10: | ||
|  |             return | ||
|  | 
 | ||
|  |         # write floormap | ||
|  |         floormap_indices = 0xB6C934 | ||
|  |         floormap_vrom = 0xBC7E00 | ||
|  |         floormap_index = rom.read_int16(floormap_indices + (self.id * 2)) | ||
|  |         floormap_index //= 2 # game uses texture index, where two textures are used per floor | ||
|  | 
 | ||
|  |         cur = floormap_vrom + (floormap_index * 0x1EC) | ||
|  |         for floormap in self.floormaps: | ||
|  |             for icon in floormap: | ||
|  |                 Icon.write_to_floormap(icon, rom, cur) | ||
|  |                 cur += 0xA4 | ||
|  | 
 | ||
|  | 
 | ||
|  |         # fixes jabu jabu floor B1 having no chest data | ||
|  |         if self.id == 2: | ||
|  |             cur = floormap_vrom + (0x08 * 0x1EC + 4) | ||
|  |             kaleido_scope_chest_verts = 0x803A3DA0 # hax, should be vram 0x8082EA00 | ||
|  |             rom.write_int32s(cur, [0x17, kaleido_scope_chest_verts, 0x04]) | ||
|  | 
 | ||
|  |         # write minimaps | ||
|  |         map_mark_vrom = 0xBF40D0 | ||
|  |         map_mark_vram = 0x808567F0 | ||
|  |         map_mark_array_vram = 0x8085D2DC # ptr array in map_mark_data to minimap "marks" | ||
|  | 
 | ||
|  |         array_vrom = map_mark_array_vram - map_mark_vram + map_mark_vrom | ||
|  |         map_mark_scene_vram = rom.read_int32(self.id * 4 + array_vrom) | ||
|  |         mark_vrom = map_mark_scene_vram - map_mark_vram + map_mark_vrom | ||
|  | 
 | ||
|  |         cur = mark_vrom | ||
|  |         for minimap in self.minimaps: | ||
|  |             for icon in minimap: | ||
|  |                 Icon.write_to_minimap(icon, rom, cur) | ||
|  |                 cur += 0x26 | ||
|  | 
 | ||
|  | 
 | ||
|  |     def patch_mesh(self, rom:Rom, mesh:CollisionMesh): | ||
|  |         start = self.file.start | ||
|  | 
 | ||
|  |         final_cams = [] | ||
|  | 
 | ||
|  |         # build final camera data | ||
|  |         for cam in self.coldelta.cams: | ||
|  |             data = cam['Data'] | ||
|  |             pos = cam['PositionIndex'] | ||
|  |             if pos < 0: | ||
|  |                 final_cams.append((data, 0)) | ||
|  |             else: | ||
|  |                 addr = start + (mesh.camera_data_addr & 0xFFFFFF) | ||
|  |                 seg_off = rom.read_int32(addr + (pos * 8) + 4) | ||
|  |                 final_cams.append((data, seg_off)) | ||
|  | 
 | ||
|  |         types_move_addr = 0 | ||
|  | 
 | ||
|  |         # if data can't fit within the old mesh space, append camera data | ||
|  |         if self.coldelta.is_larger: | ||
|  |             types_move_addr = mesh.camera_data_addr | ||
|  | 
 | ||
|  |             # append to end of file | ||
|  |             self.write_cam_data(rom, self.file.end, final_cams) | ||
|  |             mesh.camera_data_addr = get_segment_address(2, self.file.end - self.file.start) | ||
|  |             self.file.end += len(final_cams) * 8 | ||
|  | 
 | ||
|  |         else: | ||
|  |             types_move_addr = mesh.camera_data_addr + (len(final_cams) * 8) | ||
|  | 
 | ||
|  |             # append in place | ||
|  |             addr = self.file.start + (mesh.camera_data_addr & 0xFFFFFF) | ||
|  |             self.write_cam_data(rom, addr, final_cams) | ||
|  | 
 | ||
|  |         # if polytypes needs to be moved, do so | ||
|  |         if (types_move_addr != mesh.polytypes_addr): | ||
|  |             a_start = self.file.start + (mesh.polytypes_addr & 0xFFFFFF) | ||
|  |             b_start = self.file.start + (types_move_addr & 0xFFFFFF) | ||
|  |             size = mesh.polytypes * 8 | ||
|  | 
 | ||
|  |             rom.buffer[b_start:b_start + size] = rom.buffer[a_start:a_start + size] | ||
|  |             mesh.polytypes_addr = types_move_addr | ||
|  | 
 | ||
|  |         # patch polytypes | ||
|  |         for item in self.coldelta.polytypes: | ||
|  |             id = item['Id'] | ||
|  |             high = item['High'] | ||
|  |             low = item['Low'] | ||
|  |             addr = self.file.start + (mesh.polytypes_addr & 0xFFFFFF) + (id * 8) | ||
|  |             rom.write_int32s(addr, [high, low]) | ||
|  | 
 | ||
|  |         # patch poly data | ||
|  |         for item in self.coldelta.polys: | ||
|  |             id = item['Id'] | ||
|  |             t = item['Type'] | ||
|  |             flags = item['Flags'] | ||
|  | 
 | ||
|  |             addr = self.file.start + (mesh.poly_addr & 0xFFFFFF) + (id * 0x10) | ||
|  |             vert_bit =  rom.read_byte(addr + 0x02) & 0x1F # VertexA id data | ||
|  |             rom.write_int16(addr, t) | ||
|  |             rom.write_byte(addr + 0x02, (flags << 5) + vert_bit) | ||
|  | 
 | ||
|  |         # Write Mesh to Scene | ||
|  |         mesh.write_to_scene(rom, self.file.start) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def write_cam_data(self, rom:Rom, addr, cam_data): | ||
|  | 
 | ||
|  |         for item in cam_data: | ||
|  |             data, pos = item | ||
|  |             rom.write_int32s(addr, [data, pos]) | ||
|  |             addr += 8 | ||
|  | 
 | ||
|  | 
 | ||
|  |     # appends path data to the end of the rom | ||
|  |     # returns segment address to path data | ||
|  |     def append_path_data(self, rom:Rom): | ||
|  |         start = self.file.start | ||
|  |         cur = self.file.end | ||
|  |         records = [] | ||
|  | 
 | ||
|  |         for path in self.paths: | ||
|  |             nodes = len(path) | ||
|  |             offset = get_segment_address(2, cur - start) | ||
|  |             records.append((nodes, offset)) | ||
|  | 
 | ||
|  |             #flatten | ||
|  |             points = [x for points in path for x in points] | ||
|  |             rom.write_int16s(cur, points) | ||
|  |             path_size = align4(len(path) * 6) | ||
|  |             cur += path_size | ||
|  | 
 | ||
|  |         records_offset = get_segment_address(2, cur - start) | ||
|  |         for node, offset in records: | ||
|  |             rom.write_byte(cur, node) | ||
|  |             rom.write_int32(cur + 4, offset) | ||
|  |             cur += 8 | ||
|  | 
 | ||
|  |         self.file.end = cur | ||
|  |         return records_offset | ||
|  | 
 | ||
|  | 
 | ||
|  | class Room(object): | ||
|  |     def __init__(self, room): | ||
|  |         self.file = File(room['File']) | ||
|  |         self.id = room['Id'] | ||
|  |         self.objects = [int(x, 16) for x in room['Objects']] | ||
|  |         self.actors = [convert_actor_data(x) for x in room['Actors']] | ||
|  | 
 | ||
|  |     def write_data(self, rom:Rom): | ||
|  |         # move file to remap address | ||
|  |         if self.file.remap is not None: | ||
|  |             self.file.relocate(rom) | ||
|  | 
 | ||
|  |         headcur = self.file.start | ||
|  | 
 | ||
|  |         code = rom.read_byte(headcur) | ||
|  |         loop = 0x20 | ||
|  |         while loop != 0 and code != 0x14: #terminator | ||
|  |             loop -= 1 | ||
|  | 
 | ||
|  |             if code == 0x01: # actors | ||
|  |                 offset = self.file.end - self.file.start | ||
|  |                 write_actor_data(rom, self.file.end, self.actors) | ||
|  |                 self.file.end += len(self.actors) * 0x10 | ||
|  | 
 | ||
|  |                 rom.write_byte(headcur + 1, len(self.actors)) | ||
|  |                 rom.write_int32(headcur + 4, get_segment_address(3, offset)) | ||
|  | 
 | ||
|  |             elif code == 0x0B: # objects | ||
|  |                 offset = self.append_object_data(rom, self.objects) | ||
|  | 
 | ||
|  |                 rom.write_byte(headcur + 1, len(self.objects)) | ||
|  |                 rom.write_int32(headcur + 4, get_segment_address(3, offset)) | ||
|  | 
 | ||
|  |             headcur += 8 | ||
|  |             code = rom.read_byte(headcur) | ||
|  | 
 | ||
|  |         # update file reference | ||
|  |         self.file.end = align16(self.file.end) | ||
|  |         update_dmadata(rom, self.file) | ||
|  | 
 | ||
|  | 
 | ||
|  |     def append_object_data(self, rom:Rom, objects): | ||
|  |         offset = self.file.end - self.file.start | ||
|  |         cur = self.file.end | ||
|  |         rom.write_int16s(cur, objects) | ||
|  | 
 | ||
|  |         objects_size = align4(len(objects) * 2) | ||
|  |         self.file.end += objects_size | ||
|  |         return offset | ||
|  | 
 | ||
|  | 
 | ||
|  | def patch_files(rom:Rom, mq_scenes:list): | ||
|  | 
 | ||
|  |     data = get_json() | ||
|  |     scenes = [Scene(x) for x in data] | ||
|  |     for scene in scenes: | ||
|  |         if scene.id in mq_scenes: | ||
|  |             if scene.id == 9: | ||
|  |                 patch_ice_cavern_scene_header(rom) | ||
|  |             scene.write_data(rom) | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_json(): | ||
|  |     with open(data_path('mqu.json'), 'r') as stream: | ||
|  |         data = json.load(stream) | ||
|  |     return data | ||
|  | 
 | ||
|  | 
 | ||
|  | def convert_actor_data(str): | ||
|  |     spawn_args = str.split(" ") | ||
|  |     return [ int(x,16) for x in spawn_args ] | ||
|  | 
 | ||
|  | 
 | ||
|  | def get_segment_address(base, offset): | ||
|  |     offset &= 0xFFFFFF | ||
|  |     base *= 0x01000000 | ||
|  |     return base + offset | ||
|  | 
 | ||
|  | 
 | ||
|  | def patch_ice_cavern_scene_header(rom): | ||
|  |     rom.buffer[0x2BEB000:0x2BEB038] = rom.buffer[0x2BEB008:0x2BEB040] | ||
|  |     rom.write_int32s(0x2BEB038, [0x0D000000, 0x02000000]) | ||
|  | 
 | ||
|  | 
 | ||
|  | def patch_spirit_temple_mq_room_6(rom:Rom, room_addr): | ||
|  |     cur = room_addr | ||
|  | 
 | ||
|  |     actor_list_addr = 0 | ||
|  |     cmd_actors_offset = 0 | ||
|  | 
 | ||
|  |     # scan for actor list and header end | ||
|  |     code = rom.read_byte(cur) | ||
|  |     while code != 0x14: #terminator | ||
|  |         if code == 0x01: # actors | ||
|  |             actor_list_addr = rom.read_int32(cur + 4) | ||
|  |             cmd_actors_offset = cur - room_addr | ||
|  | 
 | ||
|  |         cur += 8 | ||
|  |         code = rom.read_byte(cur) | ||
|  | 
 | ||
|  |     cur += 8 | ||
|  | 
 | ||
|  |     # original header size | ||
|  |     header_size = cur - room_addr | ||
|  | 
 | ||
|  |     # set alternate header data location | ||
|  |     alt_data_off = header_size + 8 | ||
|  | 
 | ||
|  |     # set new alternate header offset | ||
|  |     alt_header_off = align16(alt_data_off + (4 * 3)) # alt header record size * num records | ||
|  | 
 | ||
|  |     # write alternate header data | ||
|  |     # the first 3 words are mandatory. the last 3 are just to make the binary | ||
|  |     # cleaner to read | ||
|  |     rom.write_int32s(room_addr + alt_data_off, | ||
|  |                     [0, get_segment_address(3, alt_header_off), 0, 0, 0, 0]) | ||
|  | 
 | ||
|  |     # clone header | ||
|  |     a_start = room_addr | ||
|  |     a_end = a_start + header_size | ||
|  |     b_start = room_addr + alt_header_off | ||
|  |     b_end = b_start + header_size | ||
|  | 
 | ||
|  |     rom.buffer[b_start:b_end] = rom.buffer[a_start:a_end] | ||
|  | 
 | ||
|  |     # make the child header skip the first actor, | ||
|  |     # which avoids the spawning of the block while in the hole | ||
|  |     cmd_addr = room_addr + cmd_actors_offset | ||
|  |     actor_list_addr += 0x10 | ||
|  |     actors = rom.read_byte(cmd_addr + 1) | ||
|  |     rom.write_byte(cmd_addr+1, actors - 1) | ||
|  |     rom.write_int32(cmd_addr + 4, actor_list_addr) | ||
|  | 
 | ||
|  |     # move header | ||
|  |     rom.buffer[a_start + 8:a_end + 8] = rom.buffer[a_start:a_end] | ||
|  | 
 | ||
|  |     # write alternate header command | ||
|  |     seg = get_segment_address(3, alt_data_off) | ||
|  |     rom.write_int32s(room_addr, [0x18000000, seg]) | ||
|  | 
 | ||
|  | 
 | ||
|  | def verify_remap(scenes): | ||
|  |     def test_remap(file:File): | ||
|  |         if file.remap is not None: | ||
|  |             if file.start < file.remap: | ||
|  |                 return False | ||
|  |         return True | ||
|  |     print("test code: verify remap won't corrupt data") | ||
|  | 
 | ||
|  |     for scene in scenes: | ||
|  |         file = scene.file | ||
|  |         result = test_remap(file) | ||
|  |         print("{0} - {1}".format(result, file)) | ||
|  | 
 | ||
|  |         for room in scene.rooms: | ||
|  |             file = room.file | ||
|  |             result = test_remap(file) | ||
|  |             print("{0} - {1}".format(result, file)) | ||
|  | 
 | ||
|  | 
 | ||
|  | def update_dmadata(rom:Rom, file:File): | ||
|  |     key, start, end, from_file = file.dma_key, file.start, file.end, file.from_file | ||
|  |     rom.update_dmadata_record(key, start, end, from_file) | ||
|  |     file.dma_key = file.start | ||
|  | 
 | ||
|  | def update_scene_table(rom:Rom, sceneId, start, end): | ||
|  |     cur = sceneId * 0x14 + SCENE_TABLE | ||
|  |     rom.write_int32s(cur, [start, end]) | ||
|  | 
 | ||
|  | 
 | ||
|  | def write_actor_data(rom:Rom, cur, actors): | ||
|  |     for actor in actors: | ||
|  |         rom.write_int16s(cur, actor) | ||
|  |         cur += 0x10 | ||
|  | 
 | ||
|  | def align4(value): | ||
|  |     return ((value + 3) // 4) * 4 | ||
|  | 
 | ||
|  | def align16(value): | ||
|  |     return ((value + 0xF) // 0x10) * 0x10 | ||
|  | 
 | ||
|  | # This function inserts space in a ovl section at the section's offset | ||
|  | # The section size is expanded | ||
|  | # Every relocation entry in the section after the offet is moved accordingly | ||
|  | # Every relocation value that is after the inserted space is increased accordingly | ||
|  | def insert_space(rom, file, vram_start, insert_section, insert_offset, insert_size): | ||
|  |     sections = [] | ||
|  |     val_hi = {} | ||
|  |     adr_hi = {} | ||
|  | 
 | ||
|  |     # get the ovl header | ||
|  |     cur = file.end - rom.read_int32(file.end - 4) | ||
|  |     section_total = 0 | ||
|  |     for i in range(0, 4): | ||
|  |         # build the section offsets | ||
|  |         section_size = rom.read_int32(cur) | ||
|  |         sections.append(section_total) | ||
|  |         section_total += section_size | ||
|  | 
 | ||
|  |         # increase the section to be expanded | ||
|  |         if insert_section == i: | ||
|  |             rom.write_int32(cur, section_size + insert_size) | ||
|  | 
 | ||
|  |         cur += 4 | ||
|  | 
 | ||
|  |     # calculate the insert address in vram | ||
|  |     insert_vram = sections[insert_section] + insert_offset + vram_start | ||
|  |     insert_rom = sections[insert_section] + insert_offset + file.start | ||
|  | 
 | ||
|  |     # iterate over the relocation table | ||
|  |     relocate_count = rom.read_int32(cur) | ||
|  |     cur += 4 | ||
|  |     for i in range(0, relocate_count): | ||
|  |         entry = rom.read_int32(cur) | ||
|  | 
 | ||
|  |         # parse relocation entry | ||
|  |         section = ((entry & 0xC0000000) >> 30) - 1 | ||
|  |         type = (entry & 0x3F000000) >> 24 | ||
|  |         offset = entry & 0x00FFFFFF | ||
|  | 
 | ||
|  |         # calculate relocation address in rom | ||
|  |         address = file.start + sections[section] + offset | ||
|  | 
 | ||
|  |         # move relocation if section is increased and it's after the insert | ||
|  |         if insert_section == section and offset >= insert_offset: | ||
|  |             # rebuild new relocation entry | ||
|  |             rom.write_int32(cur, | ||
|  |                 ((section + 1) << 30) | | ||
|  |                 (type << 24) | | ||
|  |                 (offset + insert_size)) | ||
|  | 
 | ||
|  |         # value contains the vram address | ||
|  |         value = rom.read_int32(address) | ||
|  |         raw_value = value | ||
|  |         if type == 2: | ||
|  |             # Data entry: value is the raw vram address | ||
|  |             pass | ||
|  |         elif type == 4: | ||
|  |             # Jump OP: Get the address from a Jump instruction | ||
|  |             value = 0x80000000 | (value & 0x03FFFFFF) << 2 | ||
|  |         elif type == 5: | ||
|  |             # Load High: Upper half of an address load | ||
|  |             reg = (value >> 16) & 0x1F | ||
|  |             val_hi[reg] = (value & 0x0000FFFF) << 16 | ||
|  |             adr_hi[reg] = address | ||
|  |             # Do not process, wait until the lower half is read | ||
|  |             value = None | ||
|  |         elif type == 6: | ||
|  |             # Load Low: Lower half of the address load | ||
|  |             reg = (value >> 21) & 0x1F | ||
|  |             val_low = value & 0x0000FFFF | ||
|  |             val_low = unpack('h', pack('H', val_low))[0] | ||
|  |             # combine with previous load high | ||
|  |             value = val_hi[reg] + val_low | ||
|  |         else: | ||
|  |             # unknown. OoT does not use any other types | ||
|  |             value = None | ||
|  | 
 | ||
|  |         # update the vram values if it's been moved | ||
|  |         if value != None and value >= insert_vram: | ||
|  |             # value = new vram address | ||
|  |             new_value = value + insert_size | ||
|  | 
 | ||
|  |             if type == 2: | ||
|  |                 # Data entry: value is the raw vram address | ||
|  |                 rom.write_int32(address, new_value) | ||
|  |             elif type == 4: | ||
|  |                 # Jump OP: Set the address in the Jump instruction | ||
|  |                 op = rom.read_int32(address) & 0xFC000000 | ||
|  |                 new_value = (new_value & 0x0FFFFFFC) >> 2 | ||
|  |                 new_value = op | new_value | ||
|  |                 rom.write_int32(address, new_value) | ||
|  |             elif type == 6: | ||
|  |                 # Load Low: Lower half of the address load | ||
|  |                 op = rom.read_int32(address) & 0xFFFF0000 | ||
|  |                 new_val_low = new_value & 0x0000FFFF | ||
|  |                 rom.write_int32(address, op | new_val_low) | ||
|  | 
 | ||
|  |                 # Load High: Upper half of an address load | ||
|  |                 op = rom.read_int32(adr_hi[reg]) & 0xFFFF0000 | ||
|  |                 new_val_hi = (new_value & 0xFFFF0000) >> 16 | ||
|  |                 if new_val_low >= 0x8000: | ||
|  |                     # add 1 if the lower part is negative for borrow | ||
|  |                     new_val_hi += 1 | ||
|  |                 rom.write_int32(adr_hi[reg], op | new_val_hi) | ||
|  | 
 | ||
|  |         cur += 4 | ||
|  | 
 | ||
|  |     # Move rom bytes | ||
|  |     rom.buffer[(insert_rom + insert_size):(file.end + insert_size)] = rom.buffer[insert_rom:file.end] | ||
|  |     rom.buffer[insert_rom:(insert_rom + insert_size)] = [0] * insert_size | ||
|  |     file.end += insert_size | ||
|  | 
 | ||
|  | 
 | ||
|  | def add_relocations(rom, file, addresses): | ||
|  |     relocations = [] | ||
|  |     sections = [] | ||
|  |     header_size = rom.read_int32(file.end - 4) | ||
|  |     header = file.end - header_size | ||
|  |     cur = header | ||
|  | 
 | ||
|  |     # read section sizes and build offsets | ||
|  |     section_total = 0 | ||
|  |     for i in range(0, 4): | ||
|  |         section_size = rom.read_int32(cur) | ||
|  |         sections.append(section_total) | ||
|  |         section_total += section_size | ||
|  |         cur += 4 | ||
|  | 
 | ||
|  |     # get all entries in relocation table | ||
|  |     relocate_count = rom.read_int32(cur) | ||
|  |     cur += 4 | ||
|  |     for i in range(0, relocate_count): | ||
|  |         relocations.append(rom.read_int32(cur)) | ||
|  |         cur += 4 | ||
|  | 
 | ||
|  |     # create new enties | ||
|  |     for address in addresses: | ||
|  |         if isinstance(address, tuple): | ||
|  |             # if type provided use it | ||
|  |             type, address = address | ||
|  |         else: | ||
|  |             # Otherwise, try to infer type from value | ||
|  |             value = rom.read_int32(address) | ||
|  |             op = value >> 26 | ||
|  |             type = 2 # default: data | ||
|  |             if op == 0x02 or op == 0x03: # j or jal | ||
|  |                 type = 4 | ||
|  |             elif op == 0x0F: # lui | ||
|  |                 type = 5 | ||
|  |             elif op == 0x08: # addi | ||
|  |                 type = 6 | ||
|  | 
 | ||
|  |         # Calculate section and offset | ||
|  |         address = address - file.start | ||
|  |         section = 0 | ||
|  |         for section_start in sections: | ||
|  |             if address >= section_start: | ||
|  |                 section += 1 | ||
|  |             else: | ||
|  |                 break | ||
|  |         offset = address - sections[section - 1] | ||
|  | 
 | ||
|  |         # generate relocation entry | ||
|  |         relocations.append((section << 30) | ||
|  |                         | (type << 24) | ||
|  |                         | (offset & 0x00FFFFFF)) | ||
|  | 
 | ||
|  |     # Rebuild Relocation Table | ||
|  |     cur = header + 0x10 | ||
|  |     relocations.sort(key = lambda val: val & 0xC0FFFFFF) | ||
|  |     rom.write_int32(cur, len(relocations)) | ||
|  |     cur += 4 | ||
|  |     for relocation in relocations: | ||
|  |         rom.write_int32(cur, relocation) | ||
|  |         cur += 4 | ||
|  | 
 | ||
|  |     # Add padded 0? | ||
|  |     rom.write_int32(cur, 0) | ||
|  |     cur += 4 | ||
|  | 
 | ||
|  |     # Update Header and File size | ||
|  |     new_header_size = (cur + 4) - header | ||
|  |     rom.write_int32(cur, new_header_size) | ||
|  |     file.end += (new_header_size - header_size) |