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>
This commit is contained in:
Amber Burton
2026-07-12 11:09:06 -04:00
committed by GitHub
parent 1003c462a8
commit 2b423c36aa
7 changed files with 534 additions and 5 deletions
@@ -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;
}
@@ -0,0 +1,34 @@
#pragma once
#include <stdint.h>
#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
+6
View File
@@ -76,6 +76,7 @@
#include <functions.h>
#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;
}
+20
View File
@@ -6,6 +6,7 @@
#include <soh/Enhancements/cosmetics/authenticGfxPatches.h>
#include <soh/Enhancements/TimeDisplay/TimeDisplay.h>
#include "soh/Enhancements/randomizer/randomizer.h"
#include "soh/Enhancements/Restorations/GetItemManipulation.h"
#include <ship/Context.h>
extern "C" {
@@ -62,6 +63,18 @@ static const std::map<int32_t, const char*> timeTravelOptions = {
{ TIME_TRAVEL_ANY_MS, "Any Ocarina + Master Sword" },
};
static const std::map<int32_t, const char*> 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<int32_t, const char*> 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)
+3 -1
View File
@@ -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?
+6 -3
View File
@@ -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);
@@ -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);
}