From 2b423c36aa0708a9e042b1afe8f252a6cf91c556 Mon Sep 17 00:00:00 2001 From: Amber Burton Date: Sun, 12 Jul 2026 11:09:06 -0400 Subject: [PATCH] GIM Restoration (#6865) All edge cases for bottle combinations and replacement/filling mirrors the real game, as well we produce the compass text, and does not render the item exactly as the original game does. (I would have preferred rendering the item since its only cosmetic but I couldn't find a way to do it). This also swaps the table redirect stuff for using the direct item id's which is a lot more clear and does not substitute anything for 'good enough' and covers all the edge case ids that have been documented. This is purely a cosmetic change to present the visual of the item without affecting the cutscene making any tricks for GIM still work and just looks more polished. This supplies effects of ALL game versions (including MZX build) as well as providing text table. To note: we still can crash with GIM even when returning item None EVEN at least with returning Kokiri Tunic (I can't really figure out why) and there are a few items in the table that use extra data that I don't know how to implement here just yet but the info is in the docs under "Variable". In the case of disabling the gim restoration, the game would still crash. This change adds a text id protection to prevent crashing, as well prevents invalid items from crashing directly, and also makes it so the game will no longer crash when having GIM disabled in the menu. Co-authored-by: serprex <159546+serprex@users.noreply.github.com> --- .../Restorations/GetItemManipulation.cpp | 461 ++++++++++++++++++ .../Restorations/GetItemManipulation.h | 34 ++ soh/soh/OTRGlobals.cpp | 6 + soh/soh/SohGui/SohMenuEnhancements.cpp | 20 + soh/src/code/z_message_PAL.c | 4 +- soh/src/code/z_parameter.c | 9 +- .../actors/ovl_player_actor/z_player.c | 5 +- 7 files changed, 534 insertions(+), 5 deletions(-) create mode 100644 soh/soh/Enhancements/Restorations/GetItemManipulation.cpp create mode 100644 soh/soh/Enhancements/Restorations/GetItemManipulation.h diff --git a/soh/soh/Enhancements/Restorations/GetItemManipulation.cpp b/soh/soh/Enhancements/Restorations/GetItemManipulation.cpp new file mode 100644 index 0000000000..960dd44c49 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/GetItemManipulation.cpp @@ -0,0 +1,461 @@ +#include "GetItemManipulation.h" +#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h" +#include "soh/Enhancements/item-tables/ItemTableManager.h" +#include "soh/OTRGlobals.h" +#include "soh/ShipInit.hpp" + +extern "C" { +#include "macros.h" +#include "variables.h" +extern PlayState* gPlayState; +GetItemID RetrieveGetItemIDFromItemID(ItemID itemID); +} + +// On N64, Get Item Manipulation (GIM) makes the player receive an item with a negative getItemId, +// so the vanilla lookup `sGetItemTable[getItemId - 1]` reads out of bounds below the table. The +// bytes it lands on are fixed player overlay data, so each negative getItemId deterministically +// resolves to the itemId below, indexed by getItemId + 128. getItemId is s8 on console, so +// [-128, -1] covers every value reachable there. +// +// Tables from: +// ItemID -> +// https://docs.google.com/spreadsheets/d/1SLJzamokLb7wDOaJh5x8DsxmMBy9oIYawyDN3dAWppw/edit?gid=870929937#gid=870929937 +// Text -> https://docs.google.com/spreadsheets/d/13N9lJqF5JSBZkVkVTvkrorJ8PlEBMowUH4nkrG93BuQ/edit?gid=0#gid=0 + +// Format for matrix below: +// [NTSC 1.0], [NTSC 1.1], [NTSC 1.2], [PAL1.0/1.1], [(GC U/J) / (MQ J)], [(GC E) / (MQ U/E)], [IQUE CHN/TWN], [MQ +// Debug], [MZX NTSC], [MZX PAL] +static const ItemID sGimItemIdsVer[40][10] = { + /* 80 */ { ITEM_POE, ITEM_MASK_GORON, ITEM_POE, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_BULLET_BAG_30, + ITEM_POE, ITEM_STICK }, + /* 81 */ + { ITEM_BOMB, ITEM_STICK, ITEM_BOMB, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_BEAN, + ITEM_BOMB, ITEM_ARROW_ICE }, + /* 82 */ + { ITEM_CHICKEN, ITEM_STICK, ITEM_MASK_KEATON, ITEM_ODD_MUSHROOM, ITEM_ODD_MUSHROOM, ITEM_ODD_MUSHROOM, + ITEM_ODD_MUSHROOM, ITEM_BOMB, ITEM_NONE, ITEM_ODD_MUSHROOM }, + /* 83 */ + { ITEM_BEAN, ITEM_BOMBS_10, ITEM_BEAN, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMB, ITEM_BEAN, + ITEM_BOMBS_5 }, + /* 84 */ + { ITEM_NUT, ITEM_MASK_GORON, ITEM_NUT, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_SONG_MINUET, ITEM_NUT, + ITEM_STICK }, + /* 85 */ + { ITEM_BOMB, ITEM_NONE, ITEM_BOMB, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_BOMBS_10, + ITEM_BOMB, ITEM_ARROW_ICE }, + /* 86 */ + { ITEM_PRESCRIPTION, ITEM_STICK, ITEM_EYEDROPS, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_TUNIC_GORON, + ITEM_NONE, ITEM_STICK }, + /* 87 */ + { ITEM_BOMBS_10, ITEM_BOOTS_KOKIRI, ITEM_BOMBS_10, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, + ITEM_SWORD_MASTER, ITEM_NONE, ITEM_BOMBS_10, ITEM_SWORD_MASTER }, + /* 88 */ + { ITEM_TUNIC_GORON, ITEM_STICK, ITEM_TUNIC_GORON, ITEM_BRACELET, ITEM_BRACELET, ITEM_BRACELET, ITEM_BRACELET, + ITEM_ARROW_FIRE, ITEM_TUNIC_GORON, ITEM_BRACELET }, + /* 89 */ + { ITEM_NONE, ITEM_MASK_KEATON, ITEM_NONE, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, + ITEM_SWORD_MASTER, ITEM_BOOTS_HOVER, ITEM_NONE, ITEM_SWORD_MASTER }, + /* 8A */ + { ITEM_ARROW_FIRE, ITEM_SHIELD_MIRROR, ITEM_ARROW_FIRE, ITEM_ODD_POTION, ITEM_ODD_POTION, ITEM_ODD_POTION, + ITEM_ODD_POTION, ITEM_STICK, ITEM_ARROW_FIRE, ITEM_ODD_POTION }, + /* 8B */ + { ITEM_BOOTS_HOVER, ITEM_BOOTS_IRON, ITEM_BOOTS_HOVER, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, + ITEM_STICK, ITEM_BOOTS_HOVER, ITEM_BOMBS_10 }, + /* 8C */ + { ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_ARROW_FIRE, ITEM_STICK, + ITEM_STICK }, + /* 8D */ + { ITEM_STICK, ITEM_BEAN, ITEM_STICK, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, + ITEM_MASK_KEATON, ITEM_STICK, ITEM_SWORD_MASTER }, + /* 8E */ + { ITEM_ARROW_FIRE, ITEM_POCKET_EGG, ITEM_ARROW_FIRE, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, + ITEM_BULLET_BAG_50, ITEM_ARROW_FIRE, ITEM_STICK }, + /* 8F */ + { ITEM_MASK_KEATON, ITEM_BOMB, ITEM_MASK_KEATON, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, + ITEM_BOMBS_5, ITEM_MASK_KEATON, ITEM_ARROW_ICE }, + /* 90 */ + { ITEM_LETTER_ZELDA, ITEM_NUT, ITEM_MASK_KEATON, ITEM_MASK_GORON, ITEM_MASK_GORON, ITEM_MASK_GORON, ITEM_MASK_GORON, + ITEM_ARROW_FIRE, ITEM_NONE, ITEM_MASK_GORON }, + /* 91 */ + { ITEM_BOMBS_5, ITEM_MASK_SKULL, ITEM_BOMBS_5, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, ITEM_SWORD_MASTER, + ITEM_SWORD_MASTER, ITEM_POTION_RED, ITEM_BOMBS_5, ITEM_SWORD_MASTER }, + /* 92 */ + { ITEM_ARROW_FIRE, ITEM_STICK, ITEM_ARROW_FIRE, ITEM_POCKET_EGG, ITEM_POCKET_EGG, ITEM_POCKET_EGG, ITEM_POCKET_EGG, + ITEM_STICK, ITEM_ARROW_FIRE, ITEM_POCKET_EGG }, + /* 93 */ + { ITEM_POTION_RED, ITEM_BOMB, ITEM_POTION_RED, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, + ITEM_ARROW_ICE, ITEM_POTION_RED, ITEM_BOMBS_10 }, + /* 94 */ + { ITEM_STICK, ITEM_MASK_SPOOKY, ITEM_STICK, ITEM_MASK_GORON, ITEM_MASK_GORON, ITEM_MASK_GORON, ITEM_MASK_GORON, + ITEM_STICK, ITEM_STICK, ITEM_MASK_GORON }, + /* 95 */ + { ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_MASK_KEATON, ITEM_MASK_KEATON, ITEM_MASK_KEATON, + ITEM_MASK_KEATON, ITEM_BOMB, ITEM_ARROW_ICE, ITEM_MASK_KEATON }, + /* 96 */ + { ITEM_STICK, ITEM_POE, ITEM_STICK, ITEM_MILK_BOTTLE, ITEM_MASK_SPOOKY, ITEM_BLUE_FIRE, ITEM_ARROW_LIGHT, + ITEM_SLINGSHOT, ITEM_STICK, ITEM_NONE }, + /* 97 */ + { ITEM_BOMB, ITEM_SWORD_MASTER, ITEM_BOMB, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, + ITEM_PRESCRIPTION, ITEM_BOMB, ITEM_BOMBS_5 }, + /* 98 */ + { ITEM_SLINGSHOT, ITEM_STICK, ITEM_SLINGSHOT, ITEM_POE, ITEM_POE, ITEM_POE, ITEM_POE, ITEM_NONE, ITEM_SLINGSHOT, + ITEM_POE }, + /* 99 */ + { ITEM_PRESCRIPTION, ITEM_NONE, ITEM_PRESCRIPTION, ITEM_BOW, ITEM_BOW, ITEM_BOW, ITEM_BOW, ITEM_BOMBS_5, + ITEM_PRESCRIPTION, ITEM_BOW }, + /* 9A */ + { ITEM_NONE, ITEM_SLINGSHOT, ITEM_NONE, ITEM_SLINGSHOT, ITEM_SLINGSHOT, ITEM_SLINGSHOT, ITEM_SLINGSHOT, + ITEM_SLINGSHOT, ITEM_NONE, ITEM_SLINGSHOT }, + /* 9B */ + { ITEM_BOMBS_5, ITEM_SCALE_GOLDEN, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, ITEM_BOMBS_5, + ITEM_BOMBS_10, ITEM_BOMBS_5, ITEM_BOMBS_5 }, + /* 9C */ + { ITEM_SLINGSHOT, ITEM_STICK, ITEM_SLINGSHOT, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_NUT, + ITEM_SLINGSHOT, ITEM_STICK }, + /* 9D */ + { ITEM_BOMBS_10, ITEM_MASK_KEATON, ITEM_BOMBS_10, ITEM_ARROWS_LARGE, ITEM_ARROWS_LARGE, ITEM_ARROWS_LARGE, + ITEM_ARROWS_LARGE, ITEM_ODD_POTION, ITEM_BOMBS_10, ITEM_ARROWS_LARGE }, + /* 9E */ + { ITEM_NUT, ITEM_NONE, ITEM_NUT, ITEM_BOMB, ITEM_BOMB, ITEM_BOMB, ITEM_BOMB, ITEM_STICK, ITEM_NUT, ITEM_BOMB }, + /* 9F */ + { ITEM_ODD_POTION, ITEM_BOMBS_10, ITEM_ODD_POTION, ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_SILVER, + ITEM_GAUNTLETS_SILVER, ITEM_GAUNTLETS_SILVER, ITEM_NUT, ITEM_ODD_POTION, ITEM_GAUNTLETS_SILVER }, + /* A0 */ + { ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, + ITEM_STICK }, + /* A1 */ + { ITEM_NUTS_5, ITEM_FROG, ITEM_NUTS_5, ITEM_MASK_KEATON, ITEM_MASK_KEATON, ITEM_MASK_KEATON, ITEM_MASK_KEATON, + ITEM_NONE, ITEM_NUT, ITEM_MASK_KEATON }, + /* A2 */ + { ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_NUT, ITEM_NUT, ITEM_NUT, ITEM_NUT, ITEM_SEEDS, ITEM_STICK, ITEM_NUT }, + /* A3 */ + { ITEM_NONE, ITEM_BOMBS_10, ITEM_NONE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, ITEM_ARROW_ICE, + ITEM_BOMBS_10, ITEM_NONE, ITEM_ARROW_ICE }, + /* A4 */ + { ITEM_SAW, ITEM_STICK, ITEM_PRESCRIPTION, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_NONE, + ITEM_STICK }, + /* A5 */ + { ITEM_BOMBS_10, ITEM_MASK_BUNNY, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, ITEM_BOMBS_10, + ITEM_BOW, ITEM_BOMBS_10, ITEM_BOMBS_10 }, + /* A6 */ + { ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, + ITEM_STICK }, + /* A7 */ + { ITEM_BOW, ITEM_STICK, ITEM_BOW, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_STICK, ITEM_BOW, ITEM_STICK } +}; + +// These values are consistent reguardless of version. +static const ItemID sGimItemIdsExt[88] = { + /* A8 */ ITEM_STICK, + /* A9 */ ITEM_STICK, + /* AA */ ITEM_NUT, + /* AB */ ITEM_NUT, + /* AC */ ITEM_STICK, + /* AD */ ITEM_TUNIC_GORON, + /* AE */ ITEM_STICK, + /* AF */ ITEM_TUNIC_GORON, + /* B0 */ ITEM_NONE, + /* B1 */ ITEM_TUNIC_GORON, + /* B2 */ ITEM_STICK, + /* B3 */ ITEM_TUNIC_GORON, + /* B4 */ ITEM_STICK, + /* B5 */ ITEM_TUNIC_GORON, + /* B6 */ ITEM_STICK, + /* B7 */ ITEM_TUNIC_GORON, + /* B8 */ ITEM_ARROW_LIGHT, + /* B9 */ ITEM_ARROW_LIGHT, + /* BA */ ITEM_POTION_BLUE, + /* BB */ ITEM_ARROW_LIGHT, + /* BC */ ITEM_POTION_BLUE, + /* BD */ ITEM_POTION_BLUE, + /* BE */ ITEM_BIG_POE, + /* BF */ ITEM_POTION_BLUE, + /* C0 */ ITEM_BIG_POE, + /* C1 */ ITEM_ARROW_LIGHT, + /* C2 */ ITEM_POTION_BLUE, + /* C3 */ ITEM_ARROW_LIGHT, + /* C4 */ ITEM_POTION_BLUE, + /* C5 */ ITEM_STICK, + /* C6 */ ITEM_MASK_BUNNY, + /* C7 */ ITEM_ARROW_FIRE, + /* C8 */ ITEM_POCKET_CUCCO, + /* C9 */ ITEM_ARROW_FIRE, + /* CA */ ITEM_POCKET_CUCCO, + /* CB */ ITEM_ARROW_FIRE, + /* CC */ ITEM_POCKET_EGG, + /* CD */ ITEM_ARROW_FIRE, + /* CE */ ITEM_POCKET_CUCCO, + /* CF */ ITEM_ARROW_FIRE, + /* D0 */ ITEM_STICK, + /* D1 */ ITEM_SHIELD_HYLIAN, + /* D2 */ ITEM_STICK, + /* D3 */ ITEM_TUNIC_GORON, + /* D4 */ ITEM_STICK, + /* D5 */ ITEM_TUNIC_KOKIRI, + /* D6 */ ITEM_STICK, + /* D7 */ ITEM_TUNIC_GORON, + /* D8 */ ITEM_STICK, + /* D9 */ ITEM_TUNIC_KOKIRI, + /* DA */ ITEM_STICK, + /* DB */ ITEM_NONE, + /* DC */ ITEM_NONE, + /* DD */ ITEM_NONE, + /* DE */ ITEM_STICK, + /* DF */ ITEM_STICK, + /* E0 */ ITEM_NONE, + /* E1 */ ITEM_NONE, + /* E2 */ ITEM_STICK, + /* E3 */ ITEM_STICK, + /* E4 */ ITEM_STICK, + /* E5 */ ITEM_STICK, + /* E6 */ ITEM_NONE, + /* E7 */ ITEM_NONE, + /* E8 */ ITEM_STICK, + /* E9 */ ITEM_ARROW_FIRE, + /* EA */ ITEM_LETTER_ZELDA, + /* EB */ ITEM_ARROW_FIRE, + /* EC */ ITEM_LETTER_ZELDA, + /* ED */ ITEM_ARROW_FIRE, + /* EE */ ITEM_POCKET_EGG, + /* EF */ ITEM_ARROW_FIRE, + /* F0 */ ITEM_POCKET_EGG, + /* F1 */ ITEM_ARROW_FIRE, + /* F2 */ ITEM_LETTER_ZELDA, + /* F3 */ ITEM_STICK, + /* F4 */ ITEM_STICK, + /* F5 */ ITEM_NONE /* variable */, + /* F6 */ ITEM_STICK, + /* F7 */ ITEM_SHIELD_HYLIAN, + /* F8 */ ITEM_STICK, + /* F9 */ ITEM_STICK, + /* FA */ ITEM_STICK, + /* FB */ ITEM_NONE /* variable */, + /* FC */ ITEM_STICK, + /* FD */ ITEM_STICK, + /* FE */ ITEM_STICK, + /* FF */ ITEM_STICK +}; + +// Text type seems universal? +static const uint16_t sGimTextIds[128] = { + /* 80 */ 0x40, + /* 81 */ 0x15, + /* 82 */ 0xA4, + /* 83 */ 0x25, + /* 84 */ 0xA7, + /* 85 */ 0x34, + /* 86 */ 0x81, + /* 87 */ 0x90, + /* 88 */ 0xC6, + /* 89 */ 0x3C, + /* 8A */ 0x00, + /* 8B */ 0x00, + /* 8C */ 0x00, + /* 8D */ 0xF0, + /* 8E */ 0x00, + /* 8F */ 0xBC, + /* 90 */ 0xCE, + /* 91 */ 0x04, + /* 92 */ 0x00, + /* 93 */ 0xD6, + /* 94 */ 0x20, + /* 95 */ 0x25, + /* 96 */ 0x01, + /* 97 */ 0x40, + /* 98 */ 0x19, + /* 99 */ 0x94, + /* 9A */ 0x62, + /* 9B */ 0x1C, + /* 9C */ 0x01, + /* 9D */ 0x00, + /* 9E */ 0xBF, + /* 9F */ 0x04, + /* A0 */ 0x4B, + /* A1 */ 0x04, + /* A2 */ 0xA4, + /* A3 */ 0x1C, + /* A4 */ 0xBD, + /* A5 */ 0x08, + /* A6 */ 0x00, + /* A7 */ 0x00, + /* A8 */ 0x00, + /* A9 */ 0x00, + /* AA */ 0x01, + /* AB */ 0x01, + /* AC */ 0x60, + /* AD */ 0x00, + /* AE */ 0xDE, + /* AF */ 0x00, + /* B0 */ 0x6C, + /* B1 */ 0x00, + /* B2 */ 0x10, + /* B3 */ 0x33, + /* B4 */ 0x88, + /* B5 */ 0x00, + /* B6 */ 0x70, + /* B7 */ 0x00, + /* B8 */ 0x67, + /* B9 */ 0x7C, + /* BA */ 0x67, + /* BB */ 0x7C, + /* BC */ 0x67, + /* BD */ 0x67, + /* BE */ 0x7C, + /* BF */ 0x67, + /* C0 */ 0x7C, + /* C1 */ 0x7C, + /* C2 */ 0x67, + /* C3 */ 0x7C, + /* C4 */ 0x67, + /* C5 */ 0x00, + /* C6 */ 0x00, + /* C7 */ 0x38, + /* C8 */ 0x00, + /* C9 */ 0x80, + /* CA */ 0x00, + /* CB */ 0x98, + /* CC */ 0x00, + /* CD */ 0x50, + /* CE */ 0x00, + /* CF */ 0x60, + /* D0 */ 0x70, + /* D1 */ 0xA6, + /* D2 */ 0x48, + /* D3 */ 0x00, + /* D4 */ 0xD8, + /* D5 */ 0x00, + /* D6 */ 0xEC, + /* D7 */ 0x00, + /* D8 */ 0x35, + /* D9 */ 0x00, + /* DA */ 0x5C, + /* DB */ 0xED, + /* DC */ 0x92, + /* DD */ 0x71, + /* DE */ 0x56, + /* DF */ 0xEA, + /* E0 */ 0x71, + /* E1 */ 0x5F, + /* E2 */ 0xEA, + /* E3 */ 0x0D, + /* E4 */ 0x56, + /* E5 */ 0xEA, + /* E6 */ 0x56, + /* E7 */ 0xEA, + /* E8 */ 0x00, + /* E9 */ 0x18, + /* EA */ 0x00, + /* EB */ 0xE0, + /* EC */ 0x00, + /* ED */ 0xF8, + /* EE */ 0x00, + /* EF */ 0x70, + /* F0 */ 0x00, + /* F1 */ 0xC8, + /* F2 */ 0x00, + /* F3 */ 0x00, + /* F4 */ 0x00, + /* F5 */ 0x00, + /* F6 */ 0x00, + /* F7 */ 0x00, + /* F8 */ 0x00, + /* F9 */ 0x00, + /* FA */ 0x00, + /* FB */ 0x00, + /* FC */ 0x00, + /* FD */ 0x00, + /* FE */ 0x00, + /* FF */ 0x00 +}; + +extern "C" GetItemEntry Gim_RetrieveOobGetItemEntry(int16_t getItemId) { + int32_t version = CVarGetInteger(CVAR_ENHANCEMENT("GetItemManipulation"), GIM_DISABLED); + + uint8_t tableOffset = getItemId + 128; + uint8_t mVersionOffset = 0; + + // SoH widened Player.getItemId from s8 to s16, so values below the console range can + // exist here; treat them (and the restoration being disabled) as an item table miss. + if (getItemId < -128) + return GET_ITEM_NONE; + + switch (version) { + case GIM_DISABLED: + return GET_ITEM_NONE; + + case GIM_NTSC_1_0: + mVersionOffset = 0; + break; + + case GIM_NTSC_1_1: + mVersionOffset = 1; + break; + + case GIM_NTSC_1_2: + mVersionOffset = 2; + break; + + case GIM_PAL_1_0: + case GIM_PAL_1_1: + mVersionOffset = 3; + break; + + case GIM_GC_U: + case GIM_GC_J: + case GIM_MQ_J: + mVersionOffset = 4; + break; + + case GIM_GC_E: + case GIM_MQ_U: + case GIM_MQ_E: + mVersionOffset = 5; + break; + + case GIM_IQUE_CHN: + case GIM_IQUE_TWN: + mVersionOffset = 6; + break; + + case GIM_MQ_DEBUG: + mVersionOffset = 7; + break; + + case GIM_MZX_NTSC: + mVersionOffset = 8; + break; + + case GIM_MZX_PAL: + mVersionOffset = 9; + break; + } + + // Only the received itemId of the console OOB read is documented. + GetItemID giId; + ItemID itemId; + + // Determine if the item id is universal or unique + if ((getItemId & 0xFF) <= 0xA7) { + itemId = sGimItemIdsVer[tableOffset][mVersionOffset]; + giId = RetrieveGetItemIDFromItemID(itemId); + } else { + itemId = sGimItemIdsExt[tableOffset - 40]; + giId = RetrieveGetItemIDFromItemID(itemId); + } + + GetItemEntry giEntry; + + // Safety in case we retrieved an incompatible GI item type + if (giId == GI_MAX) { + // We give them no item because we don't have a valid 'get item'. + // Allow the games original text value to pass through from text table though. + giEntry = GET_ITEM_NONE; + } else { + giEntry = ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, giId); + } + + // Fetch the correct textbox to use. + giEntry.textId = sGimTextIds[tableOffset]; + + return giEntry; +} diff --git a/soh/soh/Enhancements/Restorations/GetItemManipulation.h b/soh/soh/Enhancements/Restorations/GetItemManipulation.h new file mode 100644 index 0000000000..88ab9e5112 --- /dev/null +++ b/soh/soh/Enhancements/Restorations/GetItemManipulation.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include "soh/Enhancements/item-tables/ItemTableTypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + GIM_DISABLED, + GIM_NTSC_1_0, + GIM_NTSC_1_1, + GIM_NTSC_1_2, + GIM_PAL_1_0, + GIM_PAL_1_1, + GIM_GC_U, + GIM_GC_E, + GIM_GC_J, + GIM_MQ_U, + GIM_MQ_E, + GIM_MQ_J, + GIM_IQUE_CHN, + GIM_IQUE_TWN, + GIM_MQ_DEBUG, + GIM_MZX_NTSC, + GIM_MZX_PAL +} GimVersion; + +GetItemEntry Gim_RetrieveOobGetItemEntry(int16_t getItemId); + +#ifdef __cplusplus +} +#endif diff --git a/soh/soh/OTRGlobals.cpp b/soh/soh/OTRGlobals.cpp index 2069d4c4c3..c5e7164aa0 100644 --- a/soh/soh/OTRGlobals.cpp +++ b/soh/soh/OTRGlobals.cpp @@ -76,6 +76,7 @@ #include #include "Enhancements/item-tables/ItemTableManager.h" +#include "Enhancements/Restorations/GetItemManipulation.h" #include "Enhancements/Lang/Lang.h" #include "soh/SohGui/ImGuiUtils.h" #include "ActorDB.h" @@ -2349,6 +2350,11 @@ extern "C" ShopItemIdentity Randomizer_IdentifyShopItem(s32 sceneNum, u8 slotInd } extern "C" GetItemEntry ItemTable_Retrieve(int16_t getItemID) { + // A negative getItemId makes the vanilla lookup `sGetItemTable[getItemId - 1]` read out of + // bounds below the table (Get Item Manipulation); reproduce the console result of that read. + if (getItemID < 0) { + return Gim_RetrieveOobGetItemEntry(getItemID); + } GetItemEntry giEntry = ItemTableManager::Instance->RetrieveItemEntry(MOD_NONE, getItemID); return giEntry; } diff --git a/soh/soh/SohGui/SohMenuEnhancements.cpp b/soh/soh/SohGui/SohMenuEnhancements.cpp index 9af0303bd4..7afe4ff04f 100644 --- a/soh/soh/SohGui/SohMenuEnhancements.cpp +++ b/soh/soh/SohGui/SohMenuEnhancements.cpp @@ -6,6 +6,7 @@ #include #include #include "soh/Enhancements/randomizer/randomizer.h" +#include "soh/Enhancements/Restorations/GetItemManipulation.h" #include extern "C" { @@ -62,6 +63,18 @@ static const std::map timeTravelOptions = { { TIME_TRAVEL_ANY_MS, "Any Ocarina + Master Sword" }, }; +static const std::map getItemManipulationOptions = { + { GIM_DISABLED, "Disabled" }, { GIM_NTSC_1_0, "NTSC 1.0" }, + { GIM_NTSC_1_1, "NTSC 1.1" }, { GIM_NTSC_1_2, "NTSC 1.2" }, + { GIM_PAL_1_0, "PAL 1.0" }, { GIM_PAL_1_1, "PAL 1.1" }, + { GIM_GC_U, "GC U" }, { GIM_GC_E, "GC E" }, + { GIM_GC_J, "GC J" }, { GIM_MQ_U, "MQ U" }, + { GIM_MQ_E, "MQ E" }, { GIM_MQ_J, "MQ J" }, + { GIM_IQUE_CHN, "IQUE CHN" }, { GIM_IQUE_TWN, "IQUE TWN" }, + { GIM_MQ_DEBUG, "MQ DEBUG" }, { GIM_MZX_NTSC, "MZX NTSC 1.0" }, + { GIM_MZX_PAL, "MZX PAL 1.1" }, +}; + static const std::map sleepingWaterfallOptions = { { WATERFALL_ALWAYS, "Always" }, { WATERFALL_ONCE, "Once" }, @@ -1258,6 +1271,13 @@ void SohMenu::AddMenuEnhancements() { .Options(CheckboxOptions().Tooltip( "Restores a bug from NTSC 1.0/1.1 that allows you to obtain the eyeball frog from King Zora " "instead of the Zora Tunic by Holding Shield.")); + AddWidget(path, "Get Item Manipulation", WIDGET_CVAR_COMBOBOX) + .CVar(CVAR_ENHANCEMENT("GetItemManipulation")) + .Options(ComboboxOptions() + .ComboMap(getItemManipulationOptions) + .DefaultIndex(GIM_DISABLED) + .Tooltip("Restores Get Item Manipulation.\n" + "NTSC and PAL have separate tables.")); AddWidget(path, "Misc Restorations", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Fix L&Z Page Switch in Pause Menu", WIDGET_CVAR_CHECKBOX) diff --git a/soh/src/code/z_message_PAL.c b/soh/src/code/z_message_PAL.c index b049f7c2a3..60cbef0bda 100644 --- a/soh/src/code/z_message_PAL.c +++ b/soh/src/code/z_message_PAL.c @@ -2736,7 +2736,9 @@ void Message_OpenText(PlayState* play, u16 textId) { textId = 0xB; // Traded Giant's Knife for Biggoron Sword } else if (!IS_RANDO && (msgCtx->textId == 0xB4 && (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_CURSED_MAN_IN_SKULL_HOUSE)))) { - textId = 0xB5; // Destroyed Gold Skulltula + textId = 0xB5; // Destroyed Gold Skulltula + } else if (textId == 0x0) { // Invalid text probably from GIM or SRM + textId = 1; } // Ocarina Staff + Dialog if (textId == 0x4077 || // Pierre? diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index a807a42b15..6f53e192ba 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -2383,7 +2383,7 @@ u8 Item_Give(PlayState* play, u8 item) { } gSaveContext.inventory.items[temp + i] = item; - break; + return Return_Item(item, MOD_NONE, ITEM_NONE); } } } else { @@ -2393,11 +2393,14 @@ u8 Item_Give(PlayState* play, u8 item) { for (i = 0; i < 4; i++) { if (gSaveContext.inventory.items[temp + i] == ITEM_NONE) { gSaveContext.inventory.items[temp + i] = item; - break; + return Return_Item(item, MOD_NONE, ITEM_NONE); } } } - return Return_Item(item, MOD_NONE, ITEM_NONE); + + if (IS_RANDO) { + return Return_Item(item, MOD_NONE, ITEM_NONE); + } } else if ((item >= ITEM_WEIRD_EGG) && (item <= ITEM_CLAIM_CHECK)) { if (GameInteractor_Should(VB_POACHERS_SAW_SET_DEKU_NUT_UPGRADE_FLAG, item == ITEM_SAW)) { Flags_SetItemGetInf(ITEMGETINF_OBTAINED_NUT_UPGRADE_FROM_STAGE); diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index aeb1fac438..6ef4e4648c 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -14027,7 +14027,10 @@ s32 func_8084DFF4(PlayState* play, Player* this) { gSaveContext.bgsFlag = true; gSaveContext.swordHealth = 8; } - Item_Give(play, giEntry.itemId); + + // Prevent OOB Items from crashing game. + if (giEntry.itemId != ITEM_NONE) + Item_Give(play, giEntry.itemId); } else { Randomizer_Item_Give(play, giEntry); }