forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
820 lines
32 KiB
Python
820 lines
32 KiB
Python
"""Apply Patch data to the ROM."""
|
|
|
|
import json
|
|
import os
|
|
from datetime import datetime as Datetime
|
|
from datetime import timezone
|
|
import time
|
|
from tempfile import mktemp
|
|
from randomizer.Enums.Settings import (
|
|
BananaportRando,
|
|
CBRando,
|
|
CrownEnemyDifficulty,
|
|
DamageAmount,
|
|
FasterChecksSelected,
|
|
FungiTimeSetting,
|
|
GalleonWaterSetting,
|
|
HardModeSelected,
|
|
HardBossesSelected,
|
|
MiscChangesSelected,
|
|
ProgressiveHintItem,
|
|
PuzzleRando,
|
|
RemovedBarriersSelected,
|
|
ShockwaveStatus,
|
|
ShuffleLoadingZones,
|
|
SlamRequirement,
|
|
WinConditionComplex,
|
|
WrinklyHints,
|
|
)
|
|
from randomizer.Enums.Transitions import Transitions
|
|
from randomizer.Enums.Types import Types
|
|
import randomizer.ItemPool as ItemPool
|
|
from randomizer.Enums.Items import Items
|
|
from randomizer.Enums.Switches import Switches
|
|
from randomizer.Enums.SwitchTypes import SwitchType
|
|
from randomizer.Enums.Kongs import Kongs
|
|
from randomizer.Enums.Levels import Levels
|
|
from randomizer.Enums.Maps import Maps
|
|
from randomizer.Enums.ScriptTypes import ScriptTypes
|
|
from randomizer.Lists.EnemyTypes import Enemies, EnemySelector
|
|
from randomizer.Lists.HardMode import HardSelector
|
|
from randomizer.Lists.Multiselectors import QoLSelector, RemovedBarrierSelector, FasterCheckSelector
|
|
from randomizer.Patching.BananaPlacer import randomize_cbs
|
|
from randomizer.Patching.BananaPortRando import randomize_bananaport, move_bananaports
|
|
from randomizer.Patching.BarrelRando import randomize_barrels
|
|
from randomizer.Patching.CoinPlacer import randomize_coins
|
|
from randomizer.Patching.Cosmetics.TextRando import writeBootMessages
|
|
from randomizer.Patching.Cosmetics.Puzzles import updateMillLeverTexture, updateCryptLeverTexture, updateDiddyDoors
|
|
from randomizer.Patching.CosmeticColors import (
|
|
applyHelmDoorCosmetics,
|
|
applyKongModelSwaps,
|
|
showWinCondition,
|
|
)
|
|
from randomizer.Patching.CratePlacer import randomize_melon_crate
|
|
from randomizer.Patching.CrownPlacer import randomize_crown_pads
|
|
from randomizer.Patching.DoorPlacer import place_door_locations, remove_existing_indicators, alterStoryCutsceneWarps
|
|
from randomizer.Patching.EnemyRando import randomize_enemies
|
|
from randomizer.Patching.EntranceRando import (
|
|
enableTriggerText,
|
|
filterEntranceType,
|
|
randomize_entrances,
|
|
placeLevelOrder,
|
|
)
|
|
from randomizer.Patching.FairyPlacer import PlaceFairies
|
|
from randomizer.Patching.ItemRando import place_randomized_items, alterTextboxRequirements
|
|
from randomizer.Patching.KasplatLocationRando import randomize_kasplat_locations
|
|
from randomizer.Patching.KongRando import apply_kongrando_cosmetic
|
|
from randomizer.Patching.Library.Generic import setItemReferenceName, addNewScript, IsItemSelected, getIceTrapCount, getProgHintBarrierItem, getHintRequirementBatch
|
|
from randomizer.Patching.MiscSetupChanges import (
|
|
randomize_setup,
|
|
updateKrushaMoveNames,
|
|
updateRandomSwitches,
|
|
updateSwitchsanity,
|
|
remove5DSCameraPoint,
|
|
)
|
|
from randomizer.Patching.MoveLocationRando import place_pregiven_moves, randomize_moves, parseMoveBlock
|
|
from randomizer.Patching.Patcher import LocalROM
|
|
from randomizer.Patching.PhaseRando import randomize_helm, randomize_krool
|
|
from randomizer.Patching.PriceRando import randomize_prices
|
|
from randomizer.Patching.PuzzleRando import randomize_puzzles, shortenCastleMinecart
|
|
from randomizer.Patching.ShopRandomizer import ApplyShopRandomizer
|
|
from randomizer.Patching.UpdateHints import (
|
|
PushHints,
|
|
replaceIngameText,
|
|
wipeHints,
|
|
PushItemLocations,
|
|
PushHelpfulHints,
|
|
PushHintTiedRegions,
|
|
)
|
|
from randomizer.Patching.ASMPatcher import patchAssembly
|
|
from randomizer.Patching.MirrorMode import ApplyMirrorMode
|
|
from randomizer.CompileHints import getHelmOrderHint
|
|
|
|
# from randomizer.Spoiler import Spoiler
|
|
|
|
|
|
class BooleanProperties:
|
|
"""Class to store data relating to boolean properties."""
|
|
|
|
def __init__(self, check, offset, target=1):
|
|
"""Initialize with given data."""
|
|
self.check = check
|
|
self.offset = offset
|
|
self.target = target
|
|
|
|
|
|
def writeMultiselector(
|
|
enabled: bool,
|
|
enabled_selections: list,
|
|
selector: list[dict],
|
|
selection_enum,
|
|
data_length: int,
|
|
ROM_COPY: LocalROM,
|
|
write_start: int,
|
|
):
|
|
"""Write multiselector choices to ROM."""
|
|
if enabled:
|
|
force = len(enabled_selections) == 0
|
|
write_data = [0] * data_length
|
|
for item in selector:
|
|
if item["shift"] >= 0:
|
|
if force or selection_enum[item["value"]] in enabled_selections:
|
|
offset = int(item["shift"] >> 3)
|
|
check = int(item["shift"] % 8)
|
|
write_data[offset] |= 0x80 >> check
|
|
ROM_COPY.seek(write_start)
|
|
for byte_data in write_data:
|
|
ROM_COPY.writeMultipleBytes(byte_data, 1)
|
|
|
|
|
|
def encPass(spoiler) -> int:
|
|
"""Encrypt the password."""
|
|
# Try to import randomizer.Encryption encrypt function, if we can pass all args to it.
|
|
try:
|
|
from randomizer.Encryption import encrypt
|
|
|
|
return encrypt(spoiler)
|
|
except Exception as e:
|
|
print(e)
|
|
return 0, 0
|
|
|
|
|
|
def patching_response(spoiler):
|
|
"""Apply the patch data to the ROM in the local server to be returned to the client."""
|
|
# Make sure we re-load the seed id
|
|
spoiler.settings.set_seed()
|
|
|
|
# Write date to ROM for debugging purposes
|
|
try:
|
|
temp_json = json.loads(spoiler.json)
|
|
except Exception:
|
|
temp_json = {"Settings": {}}
|
|
dt = Datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
temp_json["Settings"]["Generation Timestamp"] = dt
|
|
spoiler.json = json.dumps(temp_json, indent=4)
|
|
ROM_COPY = LocalROM()
|
|
ROM_COPY.seek(0x1FFF200)
|
|
ROM_COPY.writeBytes(dt.encode("ascii"))
|
|
# Initialize Text Changes
|
|
spoiler.text_changes = {}
|
|
|
|
# Starting index for our settings
|
|
sav = spoiler.settings.rom_data
|
|
|
|
# Shuffle Levels
|
|
flut_items = []
|
|
if spoiler.settings.shuffle_loading_zones == ShuffleLoadingZones.levels:
|
|
ROM_COPY.seek(sav + 0)
|
|
ROM_COPY.write(1)
|
|
|
|
# Update Level Order
|
|
vanilla_lobby_entrance_order = [
|
|
Transitions.IslesMainToJapesLobby,
|
|
Transitions.IslesMainToAztecLobby,
|
|
Transitions.IslesMainToFactoryLobby,
|
|
Transitions.IslesMainToGalleonLobby,
|
|
Transitions.IslesMainToForestLobby,
|
|
Transitions.IslesMainToCavesLobby,
|
|
Transitions.IslesMainToCastleLobby,
|
|
Transitions.IslesMainToHelmLobby,
|
|
]
|
|
vanilla_lobby_exit_order = [
|
|
Transitions.IslesJapesLobbyToMain,
|
|
Transitions.IslesAztecLobbyToMain,
|
|
Transitions.IslesFactoryLobbyToMain,
|
|
Transitions.IslesGalleonLobbyToMain,
|
|
Transitions.IslesForestLobbyToMain,
|
|
Transitions.IslesCavesLobbyToMain,
|
|
Transitions.IslesCastleLobbyToMain,
|
|
Transitions.IslesHelmLobbyToMain,
|
|
]
|
|
level_order = []
|
|
for level in vanilla_lobby_entrance_order:
|
|
level_order.append(vanilla_lobby_exit_order.index(spoiler.shuffled_exit_data[int(level)].reverse))
|
|
placeLevelOrder(spoiler, level_order, ROM_COPY)
|
|
|
|
vanilla_key_order = [0x1A, 0x4A, 0x8A, 0xA8, 0xEC, 0x124, 0x13D, 0x17C]
|
|
if Types.Key not in spoiler.settings.shuffled_location_types:
|
|
# Append to FLUT
|
|
for index, vanilla_key in enumerate(vanilla_key_order):
|
|
level_index_in_slot = level_order[index]
|
|
flut_items.append(
|
|
[
|
|
vanilla_key_order[level_index_in_slot],
|
|
vanilla_key,
|
|
]
|
|
)
|
|
# Re-write FLUT
|
|
written_flut = flut_items.copy() # Making a FLUT copy so that the flut sent to item rando isn't getting a double terminator
|
|
written_flut.append([0xFFFF, 0xFFFF])
|
|
ROM_COPY.seek(0x1FF2000)
|
|
for flut in sorted(written_flut, key=lambda x: x[0]):
|
|
for flag in flut:
|
|
ROM_COPY.writeMultipleBytes(flag, 2)
|
|
|
|
# Unlock All Kongs
|
|
kong_items = [Items.Donkey, Items.Diddy, Items.Lanky, Items.Tiny, Items.Chunky]
|
|
starting_kongs = []
|
|
if spoiler.settings.starting_kongs_count == 5:
|
|
ROM_COPY.seek(sav + 0x02C)
|
|
ROM_COPY.write(0x1F)
|
|
starting_kongs = kong_items.copy()
|
|
else:
|
|
bin_value = 0
|
|
for x in spoiler.settings.starting_kong_list:
|
|
bin_value |= 1 << x
|
|
starting_kongs.append(kong_items[x])
|
|
ROM_COPY.seek(sav + 0x02C)
|
|
ROM_COPY.write(bin_value)
|
|
for kong in starting_kongs:
|
|
setItemReferenceName(spoiler, kong, 0, "Starting Kong")
|
|
|
|
boolean_props = [
|
|
BooleanProperties(spoiler.settings.fast_start_beginning_of_game, 0x2E), # Fast Start Game
|
|
BooleanProperties(spoiler.settings.enable_tag_anywhere, 0x30), # Tag Anywhere
|
|
BooleanProperties(spoiler.settings.fps_display, 0x96), # FPS Display
|
|
BooleanProperties(spoiler.settings.no_melons, 0x128), # No Melon Drops
|
|
BooleanProperties(spoiler.settings.bonus_barrel_auto_complete, 0x126), # Auto-Complete Bonus Barrels
|
|
BooleanProperties(spoiler.settings.warp_to_isles, 0x135), # Warp to Isles
|
|
BooleanProperties(spoiler.settings.perma_death, 0x14D), # Permadeath
|
|
BooleanProperties(spoiler.settings.disable_tag_barrels, 0x14F), # Disable Tag Spawning
|
|
BooleanProperties(spoiler.settings.ice_traps_damage, 0x150), # Enable Ice Trap Damage
|
|
BooleanProperties(spoiler.settings.shorten_boss, 0x13B), # Shorten Boss Fights
|
|
BooleanProperties(spoiler.settings.fast_warps, 0x13A), # Fast Warps
|
|
BooleanProperties(spoiler.settings.auto_keys, 0x15B), # Auto-Turn Keys
|
|
BooleanProperties(spoiler.settings.tns_location_rando, 0x10E), # T&S Portal Location Rando
|
|
BooleanProperties(IsItemSelected(spoiler.settings.cb_rando_enabled, spoiler.settings.cb_rando_list_selected, Levels.DKIsles), 0x10B), # 5 extra medal handling
|
|
BooleanProperties(spoiler.settings.helm_hurry, 0xAE), # Helm Hurry
|
|
BooleanProperties(spoiler.settings.wrinkly_available, 0x52), # Remove Wrinkly Kong Checks
|
|
BooleanProperties(
|
|
spoiler.settings.bananaport_rando in (BananaportRando.crossmap_coupled, BananaportRando.crossmap_decoupled),
|
|
0x47,
|
|
), # Parent Map Filter
|
|
BooleanProperties(spoiler.settings.shop_indicator, 0x134, 2), # Shop Indicator
|
|
BooleanProperties(spoiler.settings.open_lobbies, 0x14C, 0xFF), # Open Lobbies
|
|
BooleanProperties(spoiler.settings.item_reward_previews, 0x101, 255), # Bonus Matches Contents
|
|
BooleanProperties(spoiler.settings.portal_numbers, 0x11E), # Portal Numbers
|
|
BooleanProperties(spoiler.settings.sprint_barrel_requires_sprint, 0x2F), # Sprint Barrel requires OSprint
|
|
BooleanProperties(spoiler.settings.fix_lanky_tiny_prod, 0x114), # Fix Lanky Tiny Prod
|
|
BooleanProperties(spoiler.settings.enemy_kill_crown_timer, 0x35), # Enemy crown timer reduction
|
|
]
|
|
|
|
for prop in boolean_props:
|
|
if prop.check:
|
|
ROM_COPY.seek(sav + prop.offset)
|
|
ROM_COPY.write(prop.target)
|
|
|
|
# Fast Hideout
|
|
ROM_COPY.seek(sav + 0x031)
|
|
# The HelmSetting enum is indexed to allow this.
|
|
ROM_COPY.write(int(spoiler.settings.helm_setting))
|
|
|
|
# Crown Door & Coin Door
|
|
# Crown Door
|
|
ROM_COPY.seek(sav + 0x4C)
|
|
ROM_COPY.write(int(spoiler.settings.crown_door_item))
|
|
ROM_COPY.write(spoiler.settings.crown_door_item_count)
|
|
# Coin Door
|
|
ROM_COPY.seek(sav + 0x4E)
|
|
ROM_COPY.write(int(spoiler.settings.coin_door_item))
|
|
ROM_COPY.write(spoiler.settings.coin_door_item_count)
|
|
|
|
if spoiler.settings.switchsanity:
|
|
for slot in spoiler.settings.switchsanity_data:
|
|
ROM_COPY.seek(sav + spoiler.settings.switchsanity_data[slot].rom_offset)
|
|
pad_kong = spoiler.settings.switchsanity_data[slot].kong
|
|
pad_type = spoiler.settings.switchsanity_data[slot].switch_type
|
|
if slot == Switches.IslesMonkeyport:
|
|
if pad_kong == Kongs.lanky:
|
|
ROM_COPY.writeMultipleBytes(2, 1)
|
|
elif pad_kong == Kongs.donkey:
|
|
ROM_COPY.writeMultipleBytes(1, 1)
|
|
elif slot == Switches.IslesHelmLobbyGone:
|
|
if pad_type == SwitchType.MiscActivator:
|
|
if pad_kong == Kongs.donkey:
|
|
ROM_COPY.writeMultipleBytes(6, 1)
|
|
elif pad_kong == Kongs.diddy:
|
|
ROM_COPY.writeMultipleBytes(7, 1)
|
|
elif pad_type != SwitchType.PadMove:
|
|
ROM_COPY.writeMultipleBytes(int(pad_kong) + 1, 1)
|
|
else:
|
|
ROM_COPY.writeMultipleBytes(int(pad_kong) + 1, 1)
|
|
|
|
slam_req_values = {
|
|
SlamRequirement.green: 1,
|
|
SlamRequirement.blue: 2,
|
|
SlamRequirement.red: 3,
|
|
}
|
|
ROM_COPY.seek(sav + 0x1E3)
|
|
ROM_COPY.write(slam_req_values[spoiler.settings.chunky_phase_slam_req_internal])
|
|
|
|
# Camera unlocked
|
|
given_moves = []
|
|
if spoiler.settings.shockwave_status == ShockwaveStatus.start_with:
|
|
given_moves.extend([39, 40]) # 39 = Camera, 40 = Shockwave
|
|
setItemReferenceName(spoiler, Items.CameraAndShockwave, 0, "Extra Training")
|
|
move_bitfields = [0] * 6
|
|
for move in given_moves:
|
|
offset = int(move >> 3)
|
|
check = int(move % 8)
|
|
move_bitfields[offset] |= 0x80 >> check
|
|
for offset, value in enumerate(move_bitfields):
|
|
ROM_COPY.seek(sav + 0xD5 + offset)
|
|
ROM_COPY.writeMultipleBytes(value, 1)
|
|
|
|
# Free Trade Agreement
|
|
if spoiler.settings.free_trade_items:
|
|
ROM_COPY.seek(sav + 0x113)
|
|
old = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
ROM_COPY.seek(sav + 0x113)
|
|
ROM_COPY.write(old | 0x80)
|
|
if spoiler.settings.free_trade_blueprints:
|
|
ROM_COPY.seek(sav + 0x113)
|
|
old = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
ROM_COPY.seek(sav + 0x113)
|
|
ROM_COPY.write(old | 0x40)
|
|
writeMultiselector(
|
|
spoiler.settings.quality_of_life,
|
|
spoiler.settings.misc_changes_selected,
|
|
QoLSelector,
|
|
MiscChangesSelected,
|
|
4,
|
|
ROM_COPY,
|
|
sav + 0x0B0,
|
|
)
|
|
writeMultiselector(
|
|
spoiler.settings.remove_barriers_enabled,
|
|
spoiler.settings.remove_barriers_selected,
|
|
RemovedBarrierSelector,
|
|
RemovedBarriersSelected,
|
|
2,
|
|
ROM_COPY,
|
|
sav + 0x1DE,
|
|
)
|
|
writeMultiselector(
|
|
spoiler.settings.faster_checks_enabled,
|
|
spoiler.settings.faster_checks_selected,
|
|
FasterCheckSelector,
|
|
FasterChecksSelected,
|
|
2,
|
|
ROM_COPY,
|
|
sav + 0x1E0,
|
|
)
|
|
writeMultiselector(
|
|
spoiler.settings.hard_mode and len(spoiler.settings.hard_mode_selected) > 0,
|
|
spoiler.settings.hard_mode_selected,
|
|
HardSelector,
|
|
HardModeSelected,
|
|
1,
|
|
ROM_COPY,
|
|
sav + 0x0C6,
|
|
)
|
|
|
|
is_dw = IsItemSelected(spoiler.settings.hard_mode, spoiler.settings.hard_mode_selected, HardModeSelected.donk_in_the_dark_world, False)
|
|
is_sky = IsItemSelected(spoiler.settings.hard_mode, spoiler.settings.hard_mode_selected, HardModeSelected.donk_in_the_sky, False)
|
|
if is_dw and is_sky:
|
|
# Memory challenge
|
|
ROM_COPY.seek(sav + 0x0C6)
|
|
old = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
ROM_COPY.seek(sav + 0x0C6)
|
|
ROM_COPY.write(old | 0x2)
|
|
|
|
keys = 0xFF
|
|
if spoiler.settings.k_rool_vanilla_requirement:
|
|
keys = 0x84 # 8765 4321 bitfield, only enable the keys 3 and 8 bits, meaning 0b1000 0100, which is 0x84
|
|
ROM_COPY.seek(sav + 0x1DD)
|
|
ROM_COPY.write(keys)
|
|
|
|
# Damage amount
|
|
damage_multipliers = {
|
|
DamageAmount.default: 1,
|
|
DamageAmount.double: 2,
|
|
DamageAmount.quad: 4,
|
|
DamageAmount.ohko: 12,
|
|
}
|
|
ROM_COPY.seek(sav + 0x097)
|
|
ROM_COPY.write(damage_multipliers[spoiler.settings.damage_amount])
|
|
|
|
ROM_COPY.seek(sav + 0x0C5)
|
|
ROM_COPY.write(int(Types.Enemies in spoiler.settings.shuffled_location_types))
|
|
|
|
ROM_COPY.seek(sav + 0x0C2)
|
|
hints_in_pool_handler = 0
|
|
if Types.Hint in spoiler.settings.shuffled_location_types:
|
|
hints_in_pool_handler = 1
|
|
if spoiler.settings.progressive_hint_item != ProgressiveHintItem.off:
|
|
hints_in_pool_handler = 2
|
|
ROM_COPY.write(int(hints_in_pool_handler))
|
|
|
|
# Progressive Hints
|
|
count = 0
|
|
if spoiler.settings.progressive_hint_item != ProgressiveHintItem.off:
|
|
count = spoiler.settings.progressive_hint_count
|
|
ROM_COPY.seek(sav + 0x0C3)
|
|
ROM_COPY.write(getProgHintBarrierItem(spoiler.settings.progressive_hint_item))
|
|
for x in range(10):
|
|
ROM_COPY.seek(sav + 0x98 + (x * 2))
|
|
ROM_COPY.writeMultipleBytes(getHintRequirementBatch(x, count), 2)
|
|
ROM_COPY.seek(sav + 0x115)
|
|
ROM_COPY.writeMultipleBytes(count, 1)
|
|
|
|
# Microhints
|
|
ROM_COPY.seek(sav + 0x102)
|
|
# The MicrohintsEnabled enum is indexed to allow this.
|
|
ROM_COPY.write(int(spoiler.settings.microhints_enabled))
|
|
|
|
# Cutscene Skip Setting
|
|
ROM_COPY.seek(sav + 0x116)
|
|
# The MicrohintsEnabled enum is indexed to allow this.
|
|
ROM_COPY.write(int(spoiler.settings.more_cutscene_skips))
|
|
|
|
# Helm Hurry
|
|
helm_hurry_bonuses = [
|
|
spoiler.settings.helmhurry_list_starting_time,
|
|
spoiler.settings.helmhurry_list_golden_banana,
|
|
spoiler.settings.helmhurry_list_blueprint,
|
|
spoiler.settings.helmhurry_list_company_coins,
|
|
spoiler.settings.helmhurry_list_move,
|
|
spoiler.settings.helmhurry_list_banana_medal,
|
|
spoiler.settings.helmhurry_list_rainbow_coin,
|
|
spoiler.settings.helmhurry_list_boss_key,
|
|
spoiler.settings.helmhurry_list_battle_crown,
|
|
spoiler.settings.helmhurry_list_bean,
|
|
spoiler.settings.helmhurry_list_pearl,
|
|
spoiler.settings.helmhurry_list_kongs,
|
|
spoiler.settings.helmhurry_list_fairies,
|
|
spoiler.settings.helmhurry_list_colored_bananas,
|
|
spoiler.settings.helmhurry_list_ice_traps,
|
|
]
|
|
ROM_COPY.seek(sav + 0xE2)
|
|
for bonus in helm_hurry_bonuses:
|
|
if bonus < 0:
|
|
bonus += 65536
|
|
ROM_COPY.writeMultipleBytes(bonus, 2)
|
|
|
|
# Activate Bananaports
|
|
ROM_COPY.seek(sav + 0x138)
|
|
# The ActivateAllBananaports enum is indexed to allow this.
|
|
ROM_COPY.write(int(spoiler.settings.activate_all_bananaports))
|
|
|
|
# Fast GBs - Change jetpac text
|
|
if IsItemSelected(spoiler.settings.faster_checks_enabled, spoiler.settings.faster_checks_selected, FasterChecksSelected.jetpac):
|
|
cranky_index = 8
|
|
data = {"textbox_index": 2, "mode": "replace", "search": "5000", "target": "2500"}
|
|
if cranky_index in spoiler.text_changes:
|
|
spoiler.text_changes[8].append(data)
|
|
else:
|
|
spoiler.text_changes[8] = [data]
|
|
|
|
if IsItemSelected(spoiler.settings.hard_bosses, spoiler.settings.hard_bosses_selected, HardBossesSelected.kut_out_phase_rando, False):
|
|
# KKO Phase Order
|
|
for phase_slot in range(3):
|
|
ROM_COPY.seek(sav + 0x17B + phase_slot)
|
|
ROM_COPY.write(spoiler.settings.kko_phase_order[phase_slot])
|
|
|
|
# Win Condition
|
|
win_con_table = {
|
|
WinConditionComplex.beat_krool: {
|
|
"index": 0,
|
|
},
|
|
WinConditionComplex.get_key8: {
|
|
"index": 1,
|
|
},
|
|
WinConditionComplex.krem_kapture: {
|
|
"index": 2,
|
|
},
|
|
WinConditionComplex.dk_rap_items: {
|
|
"index": 4,
|
|
},
|
|
WinConditionComplex.req_bean: {
|
|
"index": 3,
|
|
"item": 0xA,
|
|
},
|
|
WinConditionComplex.req_bp: {
|
|
"index": 3,
|
|
"item": 4,
|
|
},
|
|
WinConditionComplex.req_companycoins: {
|
|
"index": 3,
|
|
"item": 8,
|
|
},
|
|
WinConditionComplex.req_crown: {
|
|
"index": 3,
|
|
"item": 7,
|
|
},
|
|
WinConditionComplex.req_fairy: {
|
|
"index": 3,
|
|
"item": 5,
|
|
},
|
|
WinConditionComplex.req_gb: {
|
|
"index": 3,
|
|
"item": 3,
|
|
},
|
|
WinConditionComplex.req_pearl: {
|
|
"index": 3,
|
|
"item": 0xB,
|
|
},
|
|
WinConditionComplex.req_key: {
|
|
"index": 3,
|
|
"item": 6,
|
|
},
|
|
WinConditionComplex.req_medal: {
|
|
"index": 3,
|
|
"item": 9,
|
|
},
|
|
WinConditionComplex.req_rainbowcoin: {
|
|
"index": 3,
|
|
"item": 0xC,
|
|
},
|
|
}
|
|
win_con = spoiler.settings.win_condition_item
|
|
win_con_data = win_con_table.get(win_con, None)
|
|
if win_con_data is not None:
|
|
ROM_COPY.seek(sav + 0x11D)
|
|
ROM_COPY.write(win_con_data["index"])
|
|
if "item" in win_con_data:
|
|
ROM_COPY.seek(sav + 0xC0)
|
|
ROM_COPY.write(win_con_data["item"])
|
|
ROM_COPY.write(spoiler.settings.win_condition_count)
|
|
|
|
# Fungi Time of Day
|
|
fungi_times = (FungiTimeSetting.day, FungiTimeSetting.night, FungiTimeSetting.dusk, FungiTimeSetting.progressive)
|
|
progressive_removals = [5, 4] # Day Switch, Night Switch
|
|
dusk_removals = {
|
|
Maps.FungiForest: [
|
|
5, # Day Switch
|
|
4, # Night Switch
|
|
0xC, # Day Gate - Mill Front Entry
|
|
0xE, # Day Gate - Punch Door
|
|
0x12, # Day Gate - Snide Area
|
|
8, # Night Gate - Mill Lanky Attic
|
|
0xB, # Night Gate - Mill Winch Attic
|
|
0xD, # Night Gate - Dark Attic
|
|
0x11, # Night Gate - Thornvine Area
|
|
0x2A, # Night Gate - Mill GB
|
|
0x53, # Night Gate - Owl Tree Diddy Coins
|
|
0x48, # Night Gate - Beanstalk T&S
|
|
0x1F1, # Night Gate - Mushroom Night Door
|
|
0x46, # Night Gate - Crown Trapdoor
|
|
],
|
|
Maps.ForestGiantMushroom: [0x11], # Night Gate - GMush Interior
|
|
Maps.ForestMillFront: [0xB], # Night Gate - Mill Front
|
|
Maps.ForestMillBack: [
|
|
0xF, # Night Gate - Mill Rear
|
|
0x2, # Night Gate - Spider Web
|
|
],
|
|
}
|
|
time_val = spoiler.settings.fungi_time_internal
|
|
if time_val in fungi_times:
|
|
ROM_COPY.seek(sav + 0x1DB)
|
|
ROM_COPY.write(fungi_times.index(time_val))
|
|
if time_val == FungiTimeSetting.progressive:
|
|
addNewScript(ROM_COPY, Maps.FungiForest, progressive_removals, ScriptTypes.DeleteItem)
|
|
elif time_val == FungiTimeSetting.dusk:
|
|
for map_val in dusk_removals:
|
|
addNewScript(ROM_COPY, map_val, dusk_removals[map_val], ScriptTypes.DeleteItem)
|
|
|
|
# Galleon Water Level
|
|
if spoiler.settings.galleon_water_internal == GalleonWaterSetting.raised:
|
|
ROM_COPY.seek(sav + 0x1DC)
|
|
ROM_COPY.writeMultipleBytes(1, 1)
|
|
|
|
if spoiler.settings.fast_start_beginning_of_game:
|
|
# Write a null move to this spot if fast start beginning of game is on
|
|
ROM_COPY.seek(spoiler.settings.move_location_data + (125 * 6))
|
|
ROM_COPY.writeMultipleBytes(7, 2)
|
|
ROM_COPY.writeMultipleBytes(0, 4)
|
|
|
|
# ROM Flags
|
|
rom_flags = 0
|
|
rom_flags |= 0x80 if spoiler.settings.enable_plandomizer else 0
|
|
rom_flags |= 0x40 if spoiler.settings.generate_spoilerlog else 0
|
|
rom_flags |= 0x20 if spoiler.settings.has_password else 0
|
|
rom_flags |= 0x10 if spoiler.settings.archipelago else 0
|
|
if spoiler.settings.archipelago:
|
|
# Write spoiler.settings.player_name to ROM ASCII only
|
|
ROM_COPY.seek(0x1FF3000)
|
|
# Player name
|
|
player_name = spoiler.settings.player_name[:16]
|
|
# if we're shot on characters, pad with null bytes if we're short on characters
|
|
if len(player_name) < 16:
|
|
player_name += "\0" * (16 - len(player_name))
|
|
# Convert playername to a bytestring and write it to the ROM
|
|
bytestring = str(player_name).encode("ascii")
|
|
ROM_COPY.writeBytes(bytestring)
|
|
ROM_COPY.seek(sav + 0xC4)
|
|
ROM_COPY.writeMultipleBytes(rom_flags, 1)
|
|
password = None
|
|
if spoiler.settings.has_password:
|
|
ROM_COPY.seek(sav + 0x1B0)
|
|
byte_data, password = encPass(spoiler)
|
|
ROM_COPY.writeMultipleBytes(byte_data, 4)
|
|
|
|
# Ice Trap Count
|
|
ROM_COPY.seek(sav + 0x14E)
|
|
ice_trap_count = max(16, getIceTrapCount(spoiler.settings))
|
|
ROM_COPY.writeMultipleBytes(ice_trap_count, 1)
|
|
|
|
# Mill Levers
|
|
if spoiler.settings.mill_levers[0] > 0:
|
|
mill_text = ""
|
|
for x in range(5):
|
|
if spoiler.settings.mill_levers[x] > 0:
|
|
mill_text += str(spoiler.settings.mill_levers[x])
|
|
# Change default wrinkly hint
|
|
if spoiler.settings.wrinkly_hints == WrinklyHints.off:
|
|
if (
|
|
IsItemSelected(
|
|
spoiler.settings.faster_checks_enabled,
|
|
spoiler.settings.faster_checks_selected,
|
|
FasterChecksSelected.forest_mill_conveyor,
|
|
)
|
|
or spoiler.settings.puzzle_rando_difficulty != PuzzleRando.off
|
|
):
|
|
wrinkly_index = 41
|
|
data = {"textbox_index": 21, "mode": "replace", "search": "21132", "target": mill_text}
|
|
if wrinkly_index in spoiler.text_changes:
|
|
spoiler.text_changes[41].append(data)
|
|
else:
|
|
spoiler.text_changes[41] = [data]
|
|
|
|
# Diddy R&D Codes
|
|
enable_code = False
|
|
encoded_codes = []
|
|
for code in spoiler.settings.diddy_rnd_doors:
|
|
value = 0
|
|
if sum(code) > 0: # Has a non-zero element
|
|
enable_code = True
|
|
for subindex in range(4):
|
|
shift = 12 - (subindex << 2)
|
|
shifted = (code[subindex] & 3) << shift
|
|
value |= shifted
|
|
encoded_codes.append(value)
|
|
if enable_code:
|
|
ROM_COPY.seek(sav + 0x1B8)
|
|
for code in encoded_codes:
|
|
ROM_COPY.writeMultipleBytes(code, 2)
|
|
|
|
keys_turned_in = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
if len(spoiler.settings.krool_keys_required) > 0:
|
|
for key in spoiler.settings.krool_keys_required:
|
|
key_index = key - 4
|
|
if key_index in keys_turned_in:
|
|
keys_turned_in.remove(key_index)
|
|
key_bitfield = 0
|
|
for key in keys_turned_in:
|
|
key_bitfield = key_bitfield | (1 << key)
|
|
ROM_COPY.seek(sav + 0x127)
|
|
ROM_COPY.write(key_bitfield)
|
|
|
|
if spoiler.settings.rareware_gb_fairies != 20:
|
|
ROM_COPY.seek(sav + 0x36)
|
|
ROM_COPY.write(spoiler.settings.rareware_gb_fairies)
|
|
|
|
ROM_COPY.seek(sav + 0x1EB)
|
|
ROM_COPY.write(spoiler.settings.mermaid_gb_pearls)
|
|
|
|
if spoiler.settings.medal_cb_req != 75:
|
|
ROM_COPY.seek(sav + 0x112)
|
|
ROM_COPY.write(spoiler.settings.medal_cb_req)
|
|
|
|
if len(spoiler.settings.enemies_selected) == 0 and (spoiler.settings.enemy_rando or spoiler.settings.crown_enemy_difficulty != CrownEnemyDifficulty.vanilla):
|
|
lst = []
|
|
for enemy in EnemySelector:
|
|
lst.append(Enemies[enemy["value"]])
|
|
spoiler.settings.enemies_selected = lst
|
|
|
|
if spoiler.settings.random_starting_region:
|
|
ROM_COPY.seek(sav + 0x10C)
|
|
ROM_COPY.write(spoiler.settings.starting_region["map"])
|
|
ROM_COPY.write(spoiler.settings.starting_region["exit"])
|
|
if spoiler.settings.alter_switch_allocation:
|
|
ROM_COPY.seek(sav + 0x103)
|
|
ROM_COPY.write(1)
|
|
for x in range(7): # Shouldn't need index 8 since Helm has no slam switches in it
|
|
ROM_COPY.seek(sav + 0x104 + x)
|
|
ROM_COPY.write(spoiler.settings.switch_allocation[x])
|
|
|
|
# Helm Required Minigames - Always set to 2 for now
|
|
ROM_COPY.seek(sav + 0x2D)
|
|
ROM_COPY.write(int(spoiler.settings.helm_room_bonus_count))
|
|
|
|
if spoiler.settings.wrinkly_hints != WrinklyHints.off:
|
|
getHelmOrderHint(spoiler)
|
|
randomize_entrances(spoiler, ROM_COPY)
|
|
randomize_moves(spoiler, ROM_COPY)
|
|
randomize_prices(spoiler, ROM_COPY)
|
|
randomize_krool(spoiler, ROM_COPY)
|
|
randomize_helm(spoiler, ROM_COPY)
|
|
randomize_barrels(spoiler, ROM_COPY)
|
|
move_bananaports(spoiler, ROM_COPY) # Has to be before randomize_bananaport
|
|
randomize_bananaport(spoiler, ROM_COPY)
|
|
randomize_kasplat_locations(spoiler, ROM_COPY)
|
|
randomize_enemies(spoiler, ROM_COPY)
|
|
apply_kongrando_cosmetic(spoiler, ROM_COPY)
|
|
randomize_setup(spoiler, ROM_COPY)
|
|
randomize_puzzles(spoiler, ROM_COPY)
|
|
randomize_cbs(spoiler, ROM_COPY)
|
|
randomize_coins(spoiler, ROM_COPY)
|
|
ApplyShopRandomizer(spoiler, ROM_COPY)
|
|
showWinCondition(spoiler.settings, ROM_COPY)
|
|
remove5DSCameraPoint(spoiler, ROM_COPY)
|
|
alterTextboxRequirements(spoiler)
|
|
spoiler.arcade_item_reward = Items.NintendoCoin
|
|
spoiler.jetpac_item_reward = Items.RarewareCoin
|
|
place_randomized_items(spoiler, flut_items.copy(), ROM_COPY) # Has to be after kong rando cosmetic and moves
|
|
# Arcade detection for colorblind mode
|
|
arcade_item_index = 0
|
|
potion_pools = [
|
|
ItemPool.DonkeyMoves,
|
|
ItemPool.DiddyMoves,
|
|
ItemPool.LankyMoves,
|
|
ItemPool.TinyMoves,
|
|
ItemPool.ChunkyMoves,
|
|
ItemPool.ImportantSharedMoves + ItemPool.JunkSharedMoves + ItemPool.TrainingBarrelAbilities() + ItemPool.ClimbingAbilities() + [Items.Shockwave, Items.Camera, Items.CameraAndShockwave],
|
|
]
|
|
for index, lst in enumerate(potion_pools):
|
|
if spoiler.arcade_item_reward in lst:
|
|
arcade_item_index = 1 + index
|
|
ROM_COPY.seek(sav + 0x15A)
|
|
ROM_COPY.writeMultipleBytes(arcade_item_index, 1)
|
|
# Other funcs
|
|
place_pregiven_moves(spoiler, ROM_COPY)
|
|
remove_existing_indicators(spoiler, ROM_COPY)
|
|
place_door_locations(spoiler, ROM_COPY)
|
|
randomize_crown_pads(spoiler, ROM_COPY)
|
|
randomize_melon_crate(spoiler, ROM_COPY)
|
|
PlaceFairies(spoiler, ROM_COPY)
|
|
filterEntranceType(ROM_COPY)
|
|
updateKrushaMoveNames(spoiler)
|
|
updateSwitchsanity(spoiler, ROM_COPY)
|
|
updateRandomSwitches(spoiler, ROM_COPY) # Has to be after all setup changes that may alter the item type of slam switches
|
|
PushItemLocations(spoiler, ROM_COPY)
|
|
parseMoveBlock(spoiler, ROM_COPY) # Has to be after anything which messes with the move block, in this case, randomize_moves and place_randomized_items
|
|
|
|
if spoiler.settings.wrinkly_hints != WrinklyHints.off:
|
|
wipeHints()
|
|
PushHints(spoiler, ROM_COPY)
|
|
if spoiler.settings.dim_solved_hints:
|
|
PushHelpfulHints(spoiler, ROM_COPY)
|
|
if Types.Hint in spoiler.settings.shuffled_location_types and spoiler.settings.progressive_hint_item == ProgressiveHintItem.off:
|
|
PushHintTiedRegions(spoiler, ROM_COPY)
|
|
|
|
writeBootMessages(ROM_COPY, spoiler.settings.random)
|
|
enableTriggerText(spoiler, ROM_COPY)
|
|
shortenCastleMinecart(spoiler, ROM_COPY)
|
|
alterStoryCutsceneWarps(spoiler, ROM_COPY)
|
|
|
|
if "PYTEST_CURRENT_TEST" not in os.environ:
|
|
replaceIngameText(spoiler, ROM_COPY)
|
|
updateMillLeverTexture(spoiler.settings, ROM_COPY)
|
|
updateCryptLeverTexture(spoiler.settings, ROM_COPY)
|
|
updateDiddyDoors(spoiler.settings, ROM_COPY)
|
|
applyHelmDoorCosmetics(spoiler.settings, ROM_COPY)
|
|
applyKongModelSwaps(spoiler.settings, ROM_COPY)
|
|
|
|
patchAssembly(ROM_COPY, spoiler)
|
|
ApplyMirrorMode(spoiler.settings, ROM_COPY)
|
|
|
|
# Apply Hash
|
|
order = 0
|
|
for count in spoiler.settings.seed_hash:
|
|
ROM_COPY.seek(sav + 0x129 + order)
|
|
ROM_COPY.write(count)
|
|
order += 1
|
|
|
|
# Create a dummy time to attach to the end of the file name non decimal
|
|
str(time.time()).replace(".", "")
|
|
if "PYTEST_CURRENT_TEST" not in os.environ:
|
|
created_tempfile = mktemp()
|
|
delta_tempfile = mktemp()
|
|
# Write the LocalROM.rom bytesIo to a file
|
|
with open(created_tempfile, "wb") as f:
|
|
f.write(ROM_COPY.rom.getvalue())
|
|
|
|
import pyxdelta
|
|
|
|
pyxdelta.run("dk64.z64", created_tempfile, delta_tempfile)
|
|
# Read the patch file
|
|
with open(delta_tempfile, "rb") as f:
|
|
patch = f.read()
|
|
# Delete the patch.z64 file
|
|
os.remove(created_tempfile)
|
|
os.remove(delta_tempfile)
|
|
else:
|
|
patch = None
|
|
del ROM_COPY
|
|
return patch, password
|
|
|
|
|
|
def FormatSpoiler(value):
|
|
"""Format the values passed to the settings table into a more readable format.
|
|
|
|
Args:
|
|
value (str) or (bool)
|
|
"""
|
|
string = str(value)
|
|
formatted = string.replace("_", " ")
|
|
result = formatted.title()
|
|
return result
|