# Majora's Mask Gateway changes ## Introduction The build date of the newly-discovered Gateway rom is `00-08-08 09:25:41`. This is between the US retail version (`00-07-31`, referred to as `NE0`) and the first PAL version (`00-09-25`, referred to as `NP0`) ## Summary Most of the rom is the same as NE0. A number of patches/changes previously only known in NP0 and after are also present, surprising considering that this rom was built only 10 days after NE0. - Several unused functions are removed from various files, as they are in NP0. - Several patches from NP0 (in z_eventmgr, z_kaleido_setup), but not all. - The file choice screen is changed to only display a single file, and no copy/erase functionality. A new file (which we may guess is called `gateway_title_static`) contains 3 new textures for this, a `Start` button, and textures for `Please Select.`, `Press A button to decide.`, `Press B button to exit.`. The `Options` screen is changed to remove the Dolby logo. After entering a name, the game starts immediately `END` is pressed. - Other standard gateway-compliant changes (removing rumble capability, adding and enabling the pause/unpause code). - Some as yet not understood changes to the save system to avoid actually saving data. - Owl statues act as in the Japanese version: you cannot save using them. - The most striking difference is in the `message_data_static` file: somehow all the apostrophes are missing! There are also changes to the "Save and return to Dawn of the First Day" box, since it no longer saves. ## Detailed analysis Section titles are the segment name. ### Rom header Region byte is `'G'`. ### `code` N.B. the segment is called `code`, but there is executable code in many other segments, this is just the largest one that is always loaded. - z_collision_check: `Collider_SetTrisDim` removed (as in NP0) - z_demo: `CutsceneCmd_Misc`: `Sram_ResetSaveFromMoonCrash` in case `CS_MISC_RESET_SAVE_FROM_MOON_CRASH` replaced by `func_80144A94`/`func_80144924_neg` - z_eventmgr: CutsceneManager_Start: ```diff s32 csType = 0; +sCutsceneMgr.play->actorCtx.flags &= ~ACTORCTX_FLAG_PICTO_BOX_ON; if ((csId < 0) || (sCutsceneMgr.csId != CS_ID_NONE)) { return csId; } ``` (as in NP0) - z_kaleido_setup: KaleidoSetup_Update ```diff if (!(play->actorCtx.flags & ACTORCTX_FLAG_1) && !(play->actorCtx.flags & ACTORCTX_FLAG_PICTO_BOX_ON)) { + if (player->actor.id == ACTOR_PLAYER) { if ((play->actorCtx.unk268 == 0) && CHECK_BTN_ALL(input->press.button, BTN_START)) { [...] + } ``` (as in NP0) - z_rcp: last function removed (as in NP0) - z_rumble: ```diff void Rumble_Update(void* arg0) { RumbleManager_Update(&gRumbleMgr); - PadMgr_RumbleSet(gRumbleMgr.rumbleEnabled); } [...] s32 Rumble_ControllerOneHasRumblePak(void) { - return PadMgr_ControllerHasRumblePak(0); + return false; } ``` - z_sram: - Sram_ResetSaveFromMoonCrash: everything above ` gSaveContext.save.cutsceneIndex = cutsceneIndex;` replaced by ```c Sram_SaveEndOfCycle(&gSaveContext); gSaveContext.timerStates[TIMER_ID_MOON_CRASH] = TIMER_STATE_OFF; ``` Also appears to take gamestate instead of sramCtx. - `func_80145698` removed - `Sram_SaveSpecialEnterClockTown` stubbed - `Sram_SaveSpecialNewDay`: ```diff void Sram_SaveSpecialNewDay(PlayState* play) { s32 cutsceneIndex = gSaveContext.save.cutsceneIndex; s32 day; u16 time = gSaveContext.save.time; day = gSaveContext.save.day; CLEAR_WEEKEVENTREG(WEEKEVENTREG_84_20); Sram_SaveEndOfCycle(play); - func_8014546C(&play->sramCtx); gSaveContext.save.day = day; gSaveContext.save.time = time; gSaveContext.save.cutsceneIndex = cutsceneIndex; - func_80185F64(play->sramCtx.saveBuf, D_801C67C8[gSaveContext.fileNum * 2], D_801C67F0[gSaveContext.fileNum * 2]); } ``` - New file at offset 0xA15F0: copy of OoT's `z_ss_sram` with an extra read and write function at the bottom. https://decomp.me/scratch/glaGm - z_message: `Message_Update` ```diff case MSGMODE_NEW_CYCLE_0: play->state.unk_A3 = 1; sp44 = gSaveContext.save.cutscene; sp3E = gSaveContext.save.time; sp40 = gSaveContext.save.day; Sram_SaveEndOfCycle(play); gSaveContext.timerStates[3] = 0; - func_8014546C(sramCtx); gSaveContext.save.day = sp40; gSaveContext.save.time = sp3E; gSaveContext.save.cutscene = sp44; - if (gSaveContext.fileNum != 0xFF) { - func_80147008(sramCtx, D_801C67C8[gSaveContext.fileNum * 2], D_801C6818[gSaveContext.fileNum * 2]); - func_80147020(sramCtx); - } - msgCtx->msgMode = MSGMODE_NEW_CYCLE_1; + play->msgCtx.ocarinaMode = OCARINA_MODE_APPLY_SOT; + msgCtx->msgMode = MSGMODE_NEW_CYCLE_2; break; ``` - graph: function to conform to the gateway halting requirements, i.e. pause gameplay when joypad Up+Down is pressed and released, resume when joypad Left+Right is pressed and released. This is called near the top of `graph_main`(`Graph_UpdateGame`) ```c void func_800A1EF8_neg(GameState* game) { Input* input = game->input; u16 payload; if (CHECK_BTN_ALL(input->cur.button, U_JPAD | D_JPAD)) { SREG(20) = 2; // Set halt bit payload = 0xFFFF; func_80091BC4_neg(&payload); // Wait for Up or Down to be released do { usleep(1000000 / 60); game_get_controller(game); } while (CHECK_BTN_ALL(input->cur.button, U_JPAD | D_JPAD)); // Sit in this loop until Left and Right detected do { usleep(1000000 / 60); game_get_controller(game); } while (!CHECK_BTN_ALL(input->cur.button, L_JPAD | R_JPAD)); // clear halt bit payload = 0; func_80091BC4_neg(&payload); // Wait for Left or Right to be released do { usleep(1000000 / 60); game_get_controller(game); } while (CHECK_BTN_ALL(input->cur.button, L_JPAD | R_JPAD)); SREG(20) = 0; } } ``` (very similar to the OoT version but uses different macros and the ss_sram wrappers). `SREG(20)` disables audio updating. - padmgr: - `PadMgr_UpdateRumble`, `PadMgr_RumbleStop`, `PadMgr_RumbleReset`, `PadMgr_RumbleSetSingle`, `PadMgr_RumbleSet`, `PadMgr_ControllerHasRumblePak` all removed (rumble-related) - `Padmgr_ParseState`: ```diff - // If opposed directions on the D-Pad are pressed at the same time, mask both out - if ((input->cur.button & (D_JPAD | U_JPAD)) == (D_JPAD | U_JPAD)) { - input->cur.button &= ~(D_JPAD | U_JPAD); - } - if ((input->cur.button & (R_JPAD | L_JPAD)) == (R_JPAD | L_JPAD)) { - input->cur.button &= ~(R_JPAD | L_JPAD); - } ``` (removed to enable the Gateway pause combinations) - `Padmgr_HandleRetrace`: ```diff // Rumble Pak - if (gFaultMgr.msgId != 0) { - // If fault is active, no rumble - PadMgr_RumbleStop(); - } else if (gPadMgrPtr->rumbleOffTimer > 0) { - // If the rumble off timer is active, no rumble - --gPadMgrPtr->rumbleOffTimer; - PadMgr_RumbleStop(); - } else if (gPadMgrPtr->rumbleOnTimer == 0) { - // If the rumble on timer is inactive, no rumble - PadMgr_RumbleStop(); - } else if (!gPadMgrPtr->isResetting) { - // If not resetting, update rumble - PadMgr_UpdateRumble(); - --gPadMgrPtr->rumbleOnTimer; - } ``` - `Padmgr_HandlePreNMI`: ```diff void PadMgr_HandlePreNMI(void) { gPadMgrPtr->isResetting = true; - PadMgr_RumbleReset(); } ``` - sys_math3d: - Unused functions `func_8017A7B8`, `func_8017A7F8`, `func_8017A838` removed (individual cross product components) (as in NP0) - `func_8017B68C` removed (as in NP0) - sys_rumble: `RumbleManager_Update`: all function calls and ```c if (D_801D1E70) { for (i = 0; i < MAXCONTROLLERS; i++) { PadMgr_RumbleSetSingle(i, false); } } ``` removed. - c_keyframe: last function removed (as in NP0) ### Overlays - ovl_file_choose: - `FileSelect_DrawNameEntry`: `validName` block that uses z_sram functions replaced with ```c this->configMode = CM_NAME_ENTRY_TO_MAIN; this->actionTimer = 4; this->selectedFileIndex = this->buttonIndex; this->menuMode = FS_MENU_MODE_SELECT; this->nextTitleLabel = FS_TITLE_OPEN_FILE; this->selectMode = SM_FADE_OUT; func_801A3558_neg(0xF); // audio function ``` - `FileChoose_UpdateOptionsMenu` ```diff if (CHECK_BTN_ALL(input->press.button, BTN_B)) { play_sound(NA_SE_SY_FSEL_DECIDE_L); - Sram_WriteSaveOptionsToBuffer(sramCtx); - - if (!gSaveContext.flashSaveAvailable) { this->configMode = CM_OPTIONS_TO_MAIN; - } else { - Sram_SetFlashPagesDefault(sramCtx, gFlashSaveStartPages[8], gFlashSpecialSaveNumPages[8]); - Sram_StartWriteToFlashDefault(sramCtx); - this->configMode = CM_OPTIONS_WAIT_FOR_FLASH_SAVE; - } - func_801A3D98(gSaveContext.options.audioSetting); return; } ``` - `FileChoose_DrawOptionsImpl`: changes mostly to remove the Dolby logo, it seems. - Vertices for the dividers are loaded in one set of 12 at the top instead of loading 4 at a time. - 20 instead of 32 are loaded before drawing the headers, the final iteration of the loop is skipped (not drawing the Dolby logo). - 24 instead of 32 are loaded before drawing the audio settings. - 8 more are loaded separately before drawing the "Check Brightness" bars, and used separately (probably the `vtx` variable was just reset to 0). - `FileChoose_IncrementConfigMode`: ```diff void FileSelect_IncrementConfigMode(FileSelectState* this) { - this->configMode++; } ``` - `FileChoose_InitModeUpdate`: contents replaced by ```c if (gSaveContext.language > 2) { gSaveContext.language = 1; // English? } this->screenFillAlpha = 255; this->menuMode = FS_MENU_MODE_CONFIG; this->configMode = CM_FADE_IN_START; this->titleLabel = FS_TITLE_SELECT_FILE; this->nextTitleLabel = FS_TITLE_OPEN_FILE; ``` while not much like any other version, the language set is similar to what PAL versions do. (It is not clear when the language would be set to something other than 1 on the US version anyway.) - `FileChoose_UpdateMainMenu`: majority of the function is removed, all that remains is ```c void FileChoose_UpdateMainMenu(GameState* thisx) { FileSelectState* this = (FileSelectState*)thisx; SramContext* sramCtx = &this->sramCtx; Input* input = CONTROLLER1(&this->state); if (CHECK_BTN_ALL(input->press.button, BTN_START) || CHECK_BTN_ALL(input->press.button, BTN_A)) { if (this->buttonIndex == 0) { play_sound(NA_SE_SY_FSEL_DECIDE_L); this->configMode = CM_ROTATE_TO_NAME_ENTRY; this->kbdButton = FS_KBD_BTN_NONE; this->charPage = FS_CHAR_PAGE_HIRA; if (gSaveContext.options.language != 0) { this->charPage = FS_CHAR_PAGE_ENG; } this->kbdX = 0; this->kbdY = 0; this->charIndex = 0; this->charBgAlpha = 0; this->newFileNameCharCount = 0; this->nameEntryBoxPosX = 120; this->nameEntryBoxAlpha = 0; Lib_MemCpy(&this->fileNames[this->buttonIndex], &sEmptyName, ARRAY_COUNT(sEmptyName)); } else { play_sound(NA_SE_SY_FSEL_DECIDE_L); this->prevConfigMode = this->configMode; this->configMode = CM_MAIN_TO_OPTIONS; this->kbdButton = FS_KBD_BTN_HIRA; this->kbdX = 0; this->kbdY = 0; this->charBgAlpha = 0; this->newFileNameCharCount = 0; this->nameEntryBoxPosX = 120; this->actionTimer = 4; } } else if (CHECK_BTN_ALL(input->press.button, BTN_B)) { gSaveContext.gameMode = GAMEMODE_TITLE_SCREEN; STOP_GAMESTATE(&this->state); SET_NEXT_GAMESTATE(&this->state, TitleSetup_Init, sizeof(TitleSetupState)); } else { if (ABS(this->stickAdjY) > 30) { play_sound(NA_SE_SY_FSEL_CURSOR); arg0->buttonIndex ^= 1; } } } ``` - `FileChoose_SetWindowContentVtx`: changed, not clear how, function is too messy to work it out until both versions are matched. It contains some extra vtx setting, and `menuMode` section is essentially gone. - `FileChoose_DrawWindowContents`: very similar to OoT: - `sTitleLabels[this->titleLabel]` replaced by `D_2000800_neg` in the first `gDPLoadTextureBlock` - Next block (the `nextTitleLabel` one) is gone. - The file button/box loop is gone, replaced by a single iteration, that uses the `Start` texture. - sActionButtonTextures loop is gone. - The option buttons quadrangle uses different vertices. - `FileChoose_LoadGame` ```diff - gSaveContext.fileNum = this->buttonIndex; - Sram_OpenSave(this, &this->sramCtx); + Sram_InitNewSave(); + for (i = 0; i < 8; i++) { + gSaveContext.save.playerData.playerName[i] = this->fileNames[this->buttonIndex][i]; + } ``` - `FileChoose_Main`: config TextureBlock/TextureRectangle replaced by ```c if ((this->configMode < CM_MAIN_TO_OPTIONS) || (this->configMode > CM_OPTIONS_TO_MAIN)) { gDPLoadTextureBlock(POLY_OPA_DISP++, D_2001000_neg, G_IM_FMT_IA, G_IM_SIZ_8b, 144, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); gSPTextureRectangle(POLY_OPA_DISP++, 90 << 2, 204 << 2, (90 + 144) << 2, (204 + 16) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); } else if (this->configMode == CM_OPTIONS_MENU) { gDPLoadTextureBlock(POLY_OPA_DISP++, D_2001900_neg, G_IM_FMT_IA, G_IM_SIZ_8b, 144, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); gSPTextureRectangle(POLY_OPA_DISP++, 90 << 2, 204 << 2, (90 + 144) << 2, (204 + 16) << 2, G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); } ``` - `FileChoose_InitContext`: ```diff Sram_Alloc(&this->state, &this->sramCtx); - func_801457CC(&this->state, &this->sramCtx); + Sram_InitNewSave(); ``` - `FileChoose_Init`: ```diff - size = SEGMENT_ROM_SIZE(parameter_static); + size = SEGMENT_ROM_SIZE(gateway_title_static); this->parameterSegment = THA_AllocTailAlign16(&this->state.heap, size); - DmaMgr_SendRequest0(this->parameterSegment, SEGMENT_ROM_START(parameter_static), size); + DmaMgr_SendRequest0(this->parameterSegment, SEGMENT_ROM_START(gateway_title_static), size); - size = gObjectTable[OBJECT_MAG].vromEnd - gObjectTable[OBJECT_MAG].vromStart; - this->titleSegment = THA_AllocTailAlign16(&this->state.heap, size); - DmaMgr_SendRequest0(this->titleSegment, gObjectTable[OBJECT_MAG].vromStart, size); ``` - ovl_kaleido_scope: - `func_80821A04_ne0`/`func_80820604_neg`: `gSaveContext.buttonStatus[EQUIP_SLOT_B]` set to `BTN_DISABLED` instead of `BTN_ENABLED`. - `func_80828788_ne0`/`func_8082738C_neg`: ```diff pauseCtx->pageIndex = sPageSwitchNextPageIndex[pauseCtx->nextPageMode]; pauseCtx->mainState = PAUSE_MAIN_STATE_IDLE; pauseCtx->state++; // PAUSE_STATE_MAIN pauseCtx->alpha = 255; + gSaveContext.buttonStatus[EQUIP_SLOT_B] = BTN_DISABLED; func_80115844(play, DO_ACTION_RETURN); ``` - `KaleidoScope_Update`: ```diff if (pauseCtx->roll < -628.0f) { pauseCtx->roll = -628.0f; interfaceCtx->startAlpha = 255; sGameOverRectPosY = 66; sPauseMenuVerticalOffset = 0.0f; pauseCtx->alpha = 255; - if (gameOverCtx->state == GAMEOVER_INACTIVE) { - pauseCtx->state = PAUSE_STATE_GAMEOVER_SAVE_PROMPT; - } else { pauseCtx->state = PAUSE_STATE_GAMEOVER_CONTINUE_PROMPT; - } + gameOverCtx->state++; } ``` - ovl_En_Bombf: unused vector removed ```diff void EnBombf_Update(Actor* thisx, PlayState* play) { Vec3f sp8C = { 0.0f, 0.0f, 0.0f }; - Vec3f sp80 = { 0.0f, 0.1f, 0.0f }; Vec3f sp74 = { 0.0f, 0.0f, 0.0f }; Vec3f sp68; ``` (as in NP0) - ovl_En_Wood02: ```diff Gfx* D_808C4D70[] = { object_wood02_DL_007968, object_wood02_DL_007D38, object_wood02_DL_0080D0, NULL, NULL, NULL, - object_wood02_DL_007AD0, - object_wood02_DL_007E20, - object_wood02_DL_009340, - object_wood02_DL_000160, - object_wood02_DL_000440, - object_wood02_DL_000700, }; ``` These are displaylists for types that are unused in MM (bushes, for example). Nothing in the object is changed, and nothing in the code, so this will cause some nice out-of-bounds reads if unused types are spawned. (as in NP0) - ovl_En_Fishing: bss is 0x10 bytes smaller (reason unknown, although notably it is the same size as NJ0, the original Japanese release). - ovl_En_Dno: Unused function `func_80A7153C` stubbed. (as in NP0) - ovl_Obj_Warpstone: saving functionality disabled. This is done simply by swapping the text used when talking to it while "open" back to that used to the Japanese version, where you could not save at an owl statue. ### Asset files As in Ocarina of Time, a new file is added, `gateway_title_static`, with textures for a `Start` button, `Please Select.`, `Press A button to decide.`, `Press B button to exit.` (the file is identical to OoT's) The only other changed asset file is `message_data_static`: - 83 empty messages are removed (these consist only of the message header and the END character `\xBF`, there are 3 in NEG and 86 in NE0) - The message displayed on playing Song of Time differs: NE0: ``` [CL:RED]Save[CL:DFLT] and return to the [CL:RED]Dawn of[\n] the First Day[CL:DFLT]?[\n] [CL:GRN][CHOICE:2]Yes[\n] No[END_SQUARE] ``` NEG: ``` [CL:SIL]Ret[CL:DFLT]urn to the [CL:RED]Dawn of[\n] the First Day[CL:DFLT]?[\n] [CL:GRN][CHOICE:2]Yes[\n] No[END_SQUARE] ``` (the formatting appears to be a mistake) - A far more egregious mistake is that every `'` and `"` has been removed, e.g. NE0 ``` You always did say, "I cannot die[\n] until I've eaten 1000 tons of rock[\n] sirloin!"[ALT_END_SQUARE][END_SQUARE] ``` NEG ``` You always did say, I cannot die[\n] until Ive eaten 1000 tons of rock[\n] sirloin![ALT_END_SQUARE][END_SQUARE] ```