Ejemplo n.º 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),
    ])
Ejemplo n.º 2
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)
Ejemplo n.º 3
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)
Ejemplo n.º 4
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)])
Ejemplo n.º 5
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
Ejemplo n.º 6
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])
Ejemplo n.º 7
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)
Ejemplo n.º 8
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")
Ejemplo n.º 9
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)
Ejemplo n.º 10
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")
Ejemplo n.º 11
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),
    ])
Ejemplo n.º 12
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))
Ejemplo n.º 13
0
def apply_remote_execution_patch(patch_addresses: StringDisplayPatchAddresses,
                                 dol_file: DolFile):
    dol_file.write_instructions(patch_addresses.update_hint_state,
                                remote_execution_patch())