Пример #1
0
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),
    ])
Пример #2
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)
Пример #3
0
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)
Пример #4
0
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)
Пример #5
0
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)
Пример #6
0
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)])
Пример #7
0
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))
Пример #8
0
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))
Пример #9
0
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
Пример #10
0
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))
Пример #11
0
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")
Пример #12
0
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])
Пример #13
0
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)
Пример #14
0
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
Пример #15
0
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
Пример #16
0
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)
Пример #17
0
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")
Пример #18
0
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),
    ])
Пример #19
0
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")
Пример #20
0
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)
Пример #21
0
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)
Пример #22
0
def apply_remote_execution_patch(patch_addresses: StringDisplayPatchAddresses,
                                 dol_file: DolFile):
    dol_file.write_instructions(patch_addresses.update_hint_state,
                                remote_execution_patch())
Пример #23
0
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))