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); }