def apply_unvisited_room_names(version: EchoesDolVersion, dol_file: DolFile, enabled: bool): # In CAutoMapper::Update, the function checks for `mwInfo.IsMapped` then `mwInfo.IsAreaVisited` and if both are # false, sets a variable to false. This variable indicates if the room name is displayed used. dol_file.write_instructions(version.unvisited_room_names_address, [ li(r28, 1 if enabled else 0), ])
def apply_reverse_energy_tank_heal_patch(sd2_base: int, addresses: DangerousEnergyTankAddresses, active: bool, game: RandovaniaGame, dol_file: DolFile, ): if game == RandovaniaGame.PRIME2: health_offset = 0x14 refill_item = 0x29 patch_offset = 0x90 elif game == RandovaniaGame.PRIME3: health_offset = 0xc refill_item = 0x12 patch_offset = 0x138 else: raise ValueError(f"Unsupported game: {game}") if active: patch = [ lfs(f0, (addresses.small_number_float - sd2_base), r2), stfs(f0, health_offset, r30), ori(r0, r0, 0), ori(r0, r0, 0), ] else: patch = [ or_(r3, r30, r30), li(r4, refill_item), li(r5, 9999), bl(addresses.incr_pickup), ] dol_file.write_instructions(addresses.incr_pickup + patch_offset, patch)
def apply_patches(game_root: Path, game_specific: EchoesGameSpecific, user_preferences: EchoesUserPreferences, default_items: dict): dol_file = DolFile(_get_dol_path(game_root)) version = find_version_for_dol(dol_file, ALL_VERSIONS_PATCHES) if not isinstance(version, BasePrimeDolVersion): return dol_file.set_editable(True) with dol_file: all_prime_dol_patches.apply_remote_execution_patch( version.string_display, dol_file) all_prime_dol_patches.apply_energy_tank_capacity_patch( version.health_capacity, game_specific, dol_file) all_prime_dol_patches.apply_reverse_energy_tank_heal_patch( version.sda2_base, version.dangerous_energy_tank, game_specific.dangerous_energy_tank, version.game, dol_file) if isinstance(version, EchoesDolVersion): echoes_dol_patches.apply_game_options_patch( version.game_options_constructor_address, user_preferences, dol_file) echoes_dol_patches.apply_beam_cost_patch( version.beam_cost_addresses, game_specific, dol_file) echoes_dol_patches.apply_safe_zone_heal_patch( version.safe_zone, version.sda2_base, game_specific, dol_file) echoes_dol_patches.apply_starting_visor_patch( version.starting_beam_visor, default_items, dol_file)
def apply_remote_execution_patch(patch_addresses: StringDisplayPatchAddresses, dol_file: DolFile): patch = [ *remote_execution_patch_start(), *remote_execution_patch_end(), ] dol_file.write_instructions(patch_addresses.update_hint_state, patch)
def apply_patches(game_root: Path, patches_data: EchoesDolPatchesData): dol_file = DolFile(_get_dol_path(game_root)) version = typing.cast( EchoesDolVersion, find_version_for_dol(dol_file, echoes_dol_versions.ALL_VERSIONS)) dol_file.set_editable(True) with dol_file: all_prime_dol_patches.apply_remote_execution_patch( version.string_display, dol_file) all_prime_dol_patches.apply_energy_tank_capacity_patch( version.health_capacity, patches_data.energy_per_tank, dol_file) all_prime_dol_patches.apply_reverse_energy_tank_heal_patch( version.sda2_base, version.dangerous_energy_tank, patches_data.dangerous_energy_tank, version.game, dol_file) echoes_dol_patches.apply_fixes(version, dol_file) echoes_dol_patches.apply_unvisited_room_names( version, dol_file, patches_data.unvisited_room_names) echoes_dol_patches.apply_teleporter_sounds( version, dol_file, patches_data.teleporter_sounds) echoes_dol_patches.apply_game_options_patch( version.game_options_constructor_address, patches_data.user_preferences, dol_file) echoes_dol_patches.apply_beam_cost_patch( version.beam_cost_addresses, patches_data.beam_configuration, dol_file) echoes_dol_patches.apply_safe_zone_heal_patch( version.safe_zone, version.sda2_base, patches_data.safe_zone_heal_per_second, dol_file) echoes_dol_patches.apply_starting_visor_patch( version.starting_beam_visor, patches_data.default_items, dol_file)
def apply_safe_zone_heal_patch(patch_addresses: SafeZoneAddresses, sda2_base: int, heal_per_second: float, dol_file: DolFile): offset = patch_addresses.heal_per_frame_constant - sda2_base dol_file.write(patch_addresses.heal_per_frame_constant, struct.pack(">f", heal_per_second / 60)) dol_file.write_instructions(patch_addresses.increment_health_fmr, [lfs(f1, offset, r2)])
def apply_energy_tank_capacity_patch(patch_addresses: HealthCapacityAddresses, game_specific: EchoesGameSpecific, dol_file: DolFile): """ Patches the base health capacity and the energy tank capacity with matching values. """ tank_capacity = game_specific.energy_per_tank dol_file.write(patch_addresses.base_health_capacity, struct.pack(">f", tank_capacity - 1)) dol_file.write(patch_addresses.energy_tank_capacity, struct.pack(">f", tank_capacity))
def apply_safe_zone_heal_patch(patch_addresses: SafeZoneAddresses, sda2_base: int, game_specific: EchoesGameSpecific, dol_file: DolFile): heal_per_second = game_specific.safe_zone_heal_per_second offset = patch_addresses.heal_per_frame_constant - sda2_base dol_file.write(patch_addresses.heal_per_frame_constant, struct.pack(">f", heal_per_second / 60)) dol_file.write(patch_addresses.increment_health_fmr, lfs(f1, offset, r2))
def apply_fixes(version: EchoesDolVersion, dol_file: DolFile): dol_file.symbols[ "CMapWorldInfo::IsAnythingSet"] = version.anything_set_address dol_file.write_instructions("CMapWorldInfo::IsAnythingSet", [ li(r3, 1), blr(), ]) return None
def apply_energy_tank_capacity_patch(patch_addresses: HealthCapacityAddresses, energy_per_tank: int, dol_file: DolFile): """ Patches the base health capacity and the energy tank capacity with matching values. """ tank_capacity = float(energy_per_tank) dol_file.write(patch_addresses.base_health_capacity, struct.pack(">f", tank_capacity - 1)) dol_file.write(patch_addresses.energy_tank_capacity, struct.pack(">f", tank_capacity))
def find_version_for_dol(dol_file: DolFile, all_versions: Iterable[DolVersion]) -> DolVersion: dol_file.set_editable(False) with dol_file: for version in all_versions: build_string = dol_file.read(version.build_string_address, len(version.build_string)) if build_string == version.build_string: return version raise RuntimeError(f"Unsupported game version")
def apply_teleporter_sounds(version: EchoesDolVersion, dol_file: DolFile, enabled: bool): dol_file.symbols[ "CWorldTransManager::SfxStart"] = version.cworldtransmanager_sfxstart if enabled: inst = stwu(r1, -0x20, r1) else: inst = blr() dol_file.write_instructions("CWorldTransManager::SfxStart", [inst])
def apply_game_options_patch(game_options_constructor_offset: int, user_preferences: EchoesUserPreferences, dol_file: DolFile): patch = [ # Unknown purpose, but keep for safety stw(r31, 0x1c, r1), or_(r31, r3, r3), # For a later function call we don't touch addi(r3, r1, 0x8), ] for i, preference_name in enumerate(_PREFERENCES_ORDER): value = getattr(user_preferences, preference_name) if isinstance(value, Enum): value = value.value patch.extend([ li(r0, value), stw(r0, (0x04 * i), r31), ]) flag_values = [ getattr(user_preferences, flag_name) if flag_name is not None else False for flag_name in _FLAGS_ORDER ] bit_mask = int("".join(str(int(flag)) for flag in flag_values), 2) patch.extend([ li(r0, bit_mask), stb(r0, 0x04 * len(_PREFERENCES_ORDER), r31), li(r0, 0), stw(r0, 0x2c, r31), stw(r0, 0x30, r31), stw(r0, 0x34, r31), ]) instructions_space = 34 instructions_to_fill = instructions_space - len(patch) if instructions_to_fill < 0: raise RuntimeError( f"Our patch ({len(patch)}) is bigger than the space we have ({instructions_space})." ) for i in range(instructions_to_fill): patch.append(nop()) dol_file.write_instructions(game_options_constructor_offset + 8 * 4, patch)
def dol_file(tmp_path): section_size = 150 data = bytearray(b"\x00" * (0x100 + section_size)) data[0:4] = struct.pack(">L", 0x100) data[0x48:0x48 + 4] = struct.pack(">L", 0x2000) data[0x90:0x90 + 4] = struct.pack(">L", section_size) tmp_path.joinpath("test.dol").write_bytes(data) dol_file = DolFile(tmp_path.joinpath("test.dol")) return dol_file
def dol_file(tmp_path): section_size = 0x1C2 sections = [Section(0, 0, 0)] * _NUM_SECTIONS sections[0] = Section(0x100, base_address=0x2000, size=section_size) data = bytearray(b"\x00" * (0x100 + section_size)) data[0:0x100] = DolHeader(tuple(sections), 0, 0, 0).as_bytes() tmp_path.joinpath("test.dol").write_bytes(data) dol_file = DolFile(tmp_path.joinpath("test.dol")) return dol_file
def apply_starting_visor_patch(addresses: StartingBeamVisorAddresses, default_items: dict, dol_file: DolFile): visor_order = ["Combat Visor", "Echo Visor", "Scan Visor", "Dark Visor"] beam_order = ["Power Beam", "Dark Beam", "Light Beam", "Annihilator Beam"] default_visor = visor_order.index(default_items["visor"]) default_beam = beam_order.index(default_items["beam"]) # Patch CPlayerState constructor with default values dol_file.write_instructions( addresses.player_state_constructor_clean + 0x54, [ bl(addresses.health_info_constructor), li(r0, default_beam), stw(r0, 0xc, r30), # xc_currentBeam li(r0, default_visor), stw(r0, 0x30, r30), # x30_currentVisor stw(r0, 0x34, r30), # x34_transitioningVisor li(r3, 0), ]) # Patch CPlayerState constructor for loading save files dol_file.write_instructions( addresses.player_state_constructor_decode + 0x5C, [ li(r0, default_visor), stw(r0, 0x30, r30), stw(r0, 0x34, r30), ]) # Patch EnterMorphBallState's unconditional call for StartTransitionToVisor, but only if default visor isn't combat if default_visor == 0: patch = [bl(addresses.start_transition_to_visor)] else: patch = [nop()] dol_file.write_instructions(addresses.enter_morph_ball_state + 0xEC, patch)
def apply_fixes(version: EchoesDolVersion, dol_file: DolFile): resource_database = default_database.game_description_for(RandovaniaGame.PRIME2).resource_database dol_file.symbols["CMapWorldInfo::IsAnythingSet"] = version.anything_set_address dol_file.write_instructions("CMapWorldInfo::IsAnythingSet", [ li(r3, 1), blr(), ]) dol_file.write_instructions(version.rs_debugger_printf_loop_address, [ nop(), ]) for item in ["Double Damage", "Unlimited Missiles", "Unlimited Beam Ammo"]: index = resource_database.get_item_by_name(item).index dol_file.write(version.powerup_should_persist + index, b"\x01")
def apply_starting_visor_patch(addresses: StartingBeamVisorAddresses, default_items: dict, dol_file: DolFile): visor_order = ["Combat Visor", "Echo Visor", "Scan Visor", "Dark Visor"] beam_order = ["Power Beam", "Dark Beam", "Light Beam", "Annihilator Beam"] default_visor = visor_order.index(default_items["visor"]) default_beam = beam_order.index(default_items["beam"]) # Patch CPlayerState constructor with default values dol_file.write_instructions( addresses.player_state_constructor_clean + 0x54, [ bl(addresses.health_info_constructor), li(r0, default_beam), stw(r0, 0xc, r30), # xc_currentBeam li(r0, default_visor), stw(r0, 0x30, r30), # x30_currentVisor stw(r0, 0x34, r30), # x34_transitioningVisor li(r3, 0), ]) # Patch CPlayerState constructor for loading save files dol_file.write_instructions( addresses.player_state_constructor_decode + 0x5C, [ li(r0, default_visor), stw(r0, 0x30, r30), stw(r0, 0x34, r30), ]) # Patch EnterMorphBallState's call for StartTransitionToVisor to use the new default visor dol_file.write_instructions(addresses.enter_morph_ball_state + 0xE8, [ li(r4, default_visor), ]) # Patch CPlayerState::ResetVisor so elevators use the new default visor dol_file.write_instructions(addresses.reset_visor, [ li(r0, default_visor), ])
def apply_fixes(version: EchoesDolVersion, dol_file: DolFile): resource_database = default_database.resource_database_for( RandovaniaGame.METROID_PRIME_ECHOES) dol_file.symbols[ "CMapWorldInfo::IsAnythingSet"] = version.anything_set_address dol_file.write_instructions("CMapWorldInfo::IsAnythingSet", [ li(r3, 1), blr(), ]) dol_file.write_instructions(version.rs_debugger_printf_loop_address, [ nop(), ]) from randovania.games.prime2.exporter.patch_data_factory import item_id_for_item_resource for item in ["Double Damage", "Unlimited Missiles", "Unlimited Beam Ammo"]: index = item_id_for_item_resource( resource_database.get_item_by_name(item)) dol_file.write(version.powerup_should_persist + index, b"\x01")
def apply_string_display_patch(patch_addresses: StringDisplayPatchAddresses, dol_file: DolFile): message_receiver_string_ref = struct.pack( ">I", patch_addresses.message_receiver_string_ref) address_wstring_constructor = struct.pack( ">I", patch_addresses.wstring_constructor) address_display_hud_memo = struct.pack(">I", patch_addresses.display_hud_memo) # setup stack # stwu r1,-0x2C(r1) # mfspr r0,LR # stw r0,0x30(r1) # # return if displayed # lbz r4,0x2(r3) # cmpwi r4,0x0 # beq end # # otherwise set displayed # li r6,0x0 # stb r6,0x2(r3) # # setup CHUDMemoParms # lis r5,0x4100 # 8.0f # li r7,0x1 # li r9,0x9 # stw r5,0x10(r1) # display time (seconds) # stb r7,0x14(r1) # clear memo window # stb r6,0x15(r1) # fade out only # stb r6,0x16(r1) # hint memo # stb r7,0x17(r1) # fade in text # stw r9,0x18(r1) # unk # # setup wstring # addi r3,r1,0x1C # lis r4,0x803a # string pointer # ori r4,r4,0x6380 # lis r12,0x802f # wstring_l constructor # ori r12,r12,0xf3dc # mtspr CTR,r12 # bctrl # rstl::wstring_l # # r3 = wstring # addi r4,r1,0x10 # lis r12,0x8006 # DisplayHudMemo address # ori r12,r12,0xb3c8 # mtspr CTR,r12 # bctrl # CSamusHud::DisplayHudMemo # end: # lwz r0,0x30(r1) # mtspr LR,r0 # addi r1,r1,0x2C # blr message_receiver_patch = [ 0x94, 0x21, 0xFF, 0xD4, 0x7C, 0x08, 0x02, 0xA6, 0x90, 0x01, 0x00, 0x30, 0x88, 0x83, 0x00, 0x02, 0x2C, 0x04, 0x00, 0x00, 0x41, 0x82, 0x00, 0x60, 0x38, 0xC0, 0x00, 0x00, 0x98, 0xC3, 0x00, 0x02, 0x3C, 0xA0, 0x41, 0x00, 0x38, 0xE0, 0x00, 0x01, 0x39, 0x20, 0x00, 0x09, 0x90, 0xA1, 0x00, 0x10, 0x98, 0xE1, 0x00, 0x14, 0x98, 0xC1, 0x00, 0x15, 0x98, 0xC1, 0x00, 0x16, 0x98, 0xE1, 0x00, 0x17, 0x91, 0x21, 0x00, 0x18, 0x38, 0x61, 0x00, 0x1C, 0x3C, 0x80, message_receiver_string_ref[0], message_receiver_string_ref[1], 0x60, 0x84, message_receiver_string_ref[2], message_receiver_string_ref[3], 0x3D, 0x80, address_wstring_constructor[0], address_wstring_constructor[1], 0x61, 0x8C, address_wstring_constructor[2], address_wstring_constructor[3], 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x38, 0x81, 0x00, 0x10, 0x3D, 0x80, address_display_hud_memo[0], address_display_hud_memo[1], 0x61, 0x8C, address_display_hud_memo[2], address_display_hud_memo[3], 0x7D, 0x89, 0x03, 0xA6, 0x4E, 0x80, 0x04, 0x21, 0x80, 0x01, 0x00, 0x30, 0x7C, 0x08, 0x03, 0xA6, 0x38, 0x21, 0x00, 0x2C, 0x4E, 0x80, 0x00, 0x20, ] dol_file.write(patch_addresses.update_hint_state, message_receiver_patch)
def apply_beam_cost_patch(patch_addresses: BeamCostAddresses, game_specific: EchoesGameSpecific, dol_file: DolFile): uncharged_costs = [] charged_costs = [] combo_costs = [] missile_costs = [] ammo_types = [] for beam_config in game_specific.beam_configurations: uncharged_costs.append(beam_config.uncharged_cost) charged_costs.append(beam_config.charged_cost) combo_costs.append(beam_config.combo_ammo_cost) missile_costs.append(beam_config.combo_missile_cost) ammo_types.append(( beam_config.ammo_a.index if beam_config.ammo_a is not None else -1, beam_config.ammo_b.index if beam_config.ammo_b is not None else -1, )) uncharged_costs_patch = struct.pack(">llll", *uncharged_costs) charged_costs_patch = struct.pack(">llll", *charged_costs) combo_costs_patch = struct.pack(">llll", *combo_costs) missile_costs_patch = struct.pack(">llll", *missile_costs) # TODO: patch CPlayerGun::IsOutOfAmmoToShoot # The following patch also changes the fact that the game doesn't check if there's enough ammo for Power Beam # we start our patch right after the `addi r3,r31,0x0` ammo_type_patch_offset = 0x40 ammo_type_patch = [ 0x81, 0x59, 0x07, 0x74, # lwz r10,0x774(r25) # r10 = get current beam 0x55, 0x4a, 0x10, 0x3a, # rlwinm r10,r10,0x2,0x0,0x1d # r10 *= 4 0x7c, 0x03, 0x50, 0x2e, # lwzx r0,r3,r10 # r0 = BeamIdToUnchargedShotAmmoCost[currentBeam] *stw(r0, 0x0, r29), # *outBeamAmmoCost = r0 *lwz(r10, 0x774, r25), # r10 = get current beam *addi(r10, r10, 0x1), # r10 = r10 + 1 0x7d, 0x49, 0x03, 0xa6, # mtspr CTR,r10 # count_register = r10 # Power Beam 0x42, 0x00, 0x00, 0x10, # bdnz dark_beam # if (--count_register > 0) goto *li(r3, ammo_types[0][0]), *li(r9, ammo_types[0][1]), 0x42, 0x80, 0x00, 0x2c, # b update_out_beam_type # Dark Beam 0x42, 0x00, 0x00, 0x10, # bdnz dark_beam # if (--count_register > 0) goto *li(r3, ammo_types[1][0]), *li(r9, ammo_types[1][1]), 0x42, 0x80, 0x00, 0x1c, # b update_out_beam_type # Light Beam 0x42, 0x00, 0x00, 0x10, # bdnz light_beam # if (--count_register > 0) goto *li(r3, ammo_types[2][0]), *li(r9, ammo_types[2][1]), 0x42, 0x80, 0x00, 0x0c, # b update_out_beam_type # Annihilator Beam *li(r3, ammo_types[3][0]), *li(r9, ammo_types[3][1]), # update_out_beam_type *stw(r3, 0x0, r27), # *outBeamAmmoTypeA = r3 *stw(r9, 0x0, r28), # *outBeamAmmoTypeB = r9 0x42, 0x80, 0x00, 0x18, # b body_end # jump to the code for getting the charged/combo costs and then check if has ammo # The address in question is at 0x801ccd64 for NTSC ] dol_file.write(patch_addresses.uncharged_cost, uncharged_costs_patch) dol_file.write(patch_addresses.charged_cost, charged_costs_patch) dol_file.write(patch_addresses.charge_combo_ammo_cost, combo_costs_patch) dol_file.write(patch_addresses.charge_combo_missile_cost, missile_costs_patch) dol_file.write( patch_addresses.get_beam_ammo_type_and_costs + ammo_type_patch_offset, ammo_type_patch)
def apply_remote_execution_patch(patch_addresses: StringDisplayPatchAddresses, dol_file: DolFile): dol_file.write_instructions(patch_addresses.update_hint_state, remote_execution_patch())
def apply_beam_cost_patch(patch_addresses: BeamCostAddresses, beam_configuration: BeamConfiguration, dol_file: DolFile): uncharged_costs = [] charged_costs = [] combo_costs = [] missile_costs = [] ammo_types = [] for beam_config in beam_configuration.all_beams: uncharged_costs.append(beam_config.uncharged_cost) charged_costs.append(beam_config.charged_cost) combo_costs.append(beam_config.combo_ammo_cost) missile_costs.append(beam_config.combo_missile_cost) ammo_types.append(( beam_config.ammo_a, beam_config.ammo_b, )) # The following patch also changes the fact that the game doesn't check if there's enough ammo for Power Beam # we start our patch right after the `addi r3,r31,0x0` ammo_type_patch_offset = 0x40 offset_to_body_end = 0xB4 ammo_type_patch = [ lwz(r10, 0x774, r25), # r10 = get current beam rlwinm(r10, r10, 0x2, 0x0, 0x1d), # r10 *= 4 lwzx(r0, r3, r10), # r0 = BeamIdToUnchargedShotAmmoCost[currentBeam] stw(r0, 0x0, r29), # *outBeamAmmoCost = r0 lwz(r10, 0x774, r25), # r10 = get current beam addi(r10, r10, 0x1), # r10 = r10 + 1 mtspr(CTR, r10), # count_register = r10 # Power Beam bdnz("dark_beam"), # if (--count_register > 0) goto li(r3, ammo_types[0][0]), li(r9, ammo_types[0][1]), b("update_out_beam_type"), # Dark Beam bdnz("light_beam").with_label("dark_beam" ), # if (--count_register > 0) goto li(r3, ammo_types[1][0]), li(r9, ammo_types[1][1]), b("update_out_beam_type"), # Light Beam bdnz("annihilator_beam").with_label( "light_beam"), # if (--count_register > 0) goto li(r3, ammo_types[2][0]), li(r9, ammo_types[2][1]), b("update_out_beam_type"), # Annihilator Beam li(r3, ammo_types[3][0]).with_label("annihilator_beam"), li(r9, ammo_types[3][1]), # update_out_beam_type stw(r3, 0x0, r27).with_label("update_out_beam_type"), # *outBeamAmmoTypeA = r3 stw(r9, 0x0, r28), # *outBeamAmmoTypeB = r9 b(patch_addresses.get_beam_ammo_type_and_costs + offset_to_body_end), # jump to the code for getting the charged/combo costs and then check if has ammo # The address in question is at 0x801ccd64 for NTSC ] # FIXME: depend on version dol_file.symbols[ "BeamIdToChargedShotAmmoCost"] = patch_addresses.uncharged_cost dol_file.symbols[ "BeamIdToUnchargedShotAmmoCost"] = patch_addresses.charged_cost dol_file.symbols[ "BeamIdToChargeComboAmmoCost"] = patch_addresses.charge_combo_ammo_cost dol_file.symbols[ "g_ChargeComboMissileCosts"] = patch_addresses.charge_combo_missile_cost dol_file.symbols[ "CPlayerGun::IsOutOfAmmoToShoot"] = patch_addresses.is_out_of_ammo_to_shoot dol_file.symbols["CPlayerGun::GetPlayer"] = patch_addresses.gun_get_player dol_file.symbols[ "CPlayerState::GetItemAmount"] = patch_addresses.get_item_amount uncharged_costs_patch = struct.pack(">llll", *uncharged_costs) charged_costs_patch = struct.pack(">llll", *charged_costs) combo_costs_patch = struct.pack(">llll", *combo_costs) missile_costs_patch = struct.pack(">llll", *missile_costs) dol_file.write("BeamIdToChargedShotAmmoCost", uncharged_costs_patch) dol_file.write("BeamIdToUnchargedShotAmmoCost", charged_costs_patch) dol_file.write("BeamIdToChargeComboAmmoCost", combo_costs_patch) dol_file.write("g_ChargeComboMissileCosts", missile_costs_patch) dol_file.write_instructions( patch_addresses.get_beam_ammo_type_and_costs + ammo_type_patch_offset, ammo_type_patch) dol_file.write_instructions( "CPlayerGun::IsOutOfAmmoToShoot", _is_out_of_ammo_patch(dol_file.symbols, ammo_types))