SaveMenu: call LoadStandardMenuHeader farcall DisplaySaveInfoOnSave call SpeechTextbox2bpp call UpdateSprites farcall CopyTilemapAtOnce ld hl, WouldYouLikeToSaveTheGameText call SaveTheGame_yesorno jr nz, .refused call AskOverwriteSaveFile jr c, .refused call PauseGameLogic call _SavingDontTurnOffThePower call ResumeGameLogic call ExitMenu and a ret .refused call ExitMenu farcall CopyTilemapAtOnce scf ret AutoSaveGameInOverworld: call PauseGameLogic ; Prevent joypad interrupts xor a ldh [hJoypadReleased], a ldh [hJoypadPressed], a ldh [hJoypadSum], a ldh [hJoypadDown], a ; Signal that we're saving inside a level ld a, TRUE ld [wSaveFileInOverworld], a call SaveGameData call ResumeGameLogic ret AutoSaveGameOutsideOverworld: call PauseGameLogic ; Prevent joypad interrupts xor a ldh [hJoypadReleased], a ldh [hJoypadPressed], a ldh [hJoypadSum], a ldh [hJoypadDown], a ; Signal that we're not saving inside a level xor a ; FALSE ld [wSaveFileInOverworld], a call SaveGameData call ResumeGameLogic ret SaveAfterLinkTrade: call PauseGameLogic call SavePokemonData call SaveChecksum call SaveBackupPokemonData call SaveBackupChecksum farcall BackupPartyMonMail call ResumeGameLogic ret ChangeBoxSaveGame: push de ld hl, ChangeBoxSaveText call MenuTextbox call YesNoBox call ExitMenu jr c, .refused call AskOverwriteSaveFile jr c, .refused call PauseGameLogic call SavingDontTurnOffThePower call SaveBox pop de ld a, e ld [wCurBox], a call LoadBox call SavedTheGame call ResumeGameLogic and a ret .refused pop de ret Link_SaveGame: call AskOverwriteSaveFile jr c, .refused call PauseGameLogic call _SavingDontTurnOffThePower call ResumeGameLogic and a .refused ret MoveMonWOMail_SaveGame: call PauseGameLogic push de call SaveBox pop de ld a, e ld [wCurBox], a call LoadBox call ResumeGameLogic ret MoveMonWOMail_InsertMon_SaveGame: call PauseGameLogic push de call SaveBox pop de ld a, e ld [wCurBox], a ld a, TRUE ld [wSaveFileExists], a call ValidateSave call SaveOptions call SavePlayerData call SavePokemonData call SaveChecksum call ValidateBackupSave call SaveBackupOptions call SaveBackupPlayerData call SaveBackupPokemonData call SaveBackupChecksum farcall BackupPartyMonMail call LoadBox call ResumeGameLogic ld de, SFX_SAVE call PlaySFX ld c, 24 call DelayFrames ret StartMoveMonWOMail_SaveGame: ld hl, MoveMonWOMailSaveText call MenuTextbox call YesNoBox call ExitMenu jr c, .refused call AskOverwriteSaveFile jr c, .refused call PauseGameLogic call _SavingDontTurnOffThePower call ResumeGameLogic and a ret .refused scf ret PauseGameLogic: ld a, TRUE ld [wGameLogicPaused], a ret ResumeGameLogic: xor a ; FALSE ld [wGameLogicPaused], a ret AddHallOfFameEntry: ld a, BANK(sHallOfFame) call OpenSRAM ld hl, sHallOfFame + HOF_LENGTH * (NUM_HOF_TEAMS - 1) - 1 ld de, sHallOfFame + HOF_LENGTH * NUM_HOF_TEAMS - 1 ld bc, HOF_LENGTH * (NUM_HOF_TEAMS - 1) .loop ld a, [hld] ld [de], a dec de dec bc ld a, c or b jr nz, .loop ld hl, wHallOfFamePokemonList ld de, sHallOfFame ld bc, wHallOfFamePokemonListEnd - wHallOfFamePokemonList + 1 call CopyBytes call CloseSRAM ; This vc_hook causes the Virtual Console to set [sMobileEventIndex] and [sMobileEventIndexBackup] ; to MOBILE_EVENT_OBJECT_GS_BALL, which enables you to get the GS Ball, take it to Kurt, and ; encounter Celebi. It assumes that sMobileEventIndex and sMobileEventIndexBackup are at their ; original addresses. vc_hook Enable_GS_Ball_mobile_event vc_assert BANK(sMobileEventIndex) == $1 && sMobileEventIndex == $be3c, \ "sMobileEventIndex is no longer located at 01:be3c." vc_assert BANK(sMobileEventIndexBackup) == $1 && sMobileEventIndexBackup == $be44, \ "sMobileEventIndexBackup is no longer located at 01:be44." vc_assert MOBILE_EVENT_OBJECT_GS_BALL == $0b, \ "MOBILE_EVENT_OBJECT_GS_BALL is no longer equal to $0b." ret AskOverwriteSaveFile: ld a, [wSaveFileExists] and a jr z, .erase call CompareLoadedAndSavedPlayerID jr z, .yoursavefile ld hl, AnotherSaveFileText call SaveTheGame_yesorno jr nz, .refused jr .erase .yoursavefile ld hl, AlreadyASaveFileText call SaveTheGame_yesorno jr nz, .refused jr .ok .erase call ErasePreviousSave .ok and a ret .refused scf ret SaveTheGame_yesorno: ld b, BANK(WouldYouLikeToSaveTheGameText) call MapTextbox call LoadMenuTextbox lb bc, 0, 7 call PlaceYesNoBox ld a, [wMenuCursorY] dec a call CloseWindow push af pop af and a ret CompareLoadedAndSavedPlayerID: ld a, BANK(sPlayerData) call OpenSRAM ld hl, sPlayerData + (wPlayerID - wPlayerData) ld a, [hli] ld c, [hl] ld b, a call CloseSRAM ld a, [wPlayerID] cp b ret nz ld a, [wPlayerID + 1] cp c ret _SavingDontTurnOffThePower: call SavingDontTurnOffThePower SavedTheGame: ld a, TRUE ld [wSaveFileInOverworld], a call SaveGameData ; wait 32 frames ld c, 32 call DelayFrames ; copy the original text speed setting to the stack ld a, [wOptions] push af ; set text speed to medium ld a, TEXT_DELAY_MED ld [wOptions], a ; saved the game! ld hl, SavedTheGameText call PrintText1bpp ; restore the original text speed setting pop af ld [wOptions], a ld de, SFX_SAVE call WaitPlaySFX call WaitSFX ; wait 30 frames ld c, 30 call DelayFrames ret SaveGameData: ld a, TRUE ld [wSaveFileExists], a call ValidateSave call SaveOptions call SavePlayerData call SavePokemonData call SaveBox call SaveChecksum call ValidateBackupSave call SaveBackupOptions call SaveBackupPlayerData call SaveBackupPokemonData call SaveBackupChecksum call UpdateStackTop farcall BackupPartyMonMail ret UpdateStackTop: ; sStackTop appears to be unused. ; It could have been used to debug stack overflow during saving. call FindStackTop ld a, BANK(sStackTop) call OpenSRAM ld a, [sStackTop + 0] ld e, a ld a, [sStackTop + 1] ld d, a or e jr z, .update ld a, e sub l ld a, d sbc h jr c, .done .update ld a, l ld [sStackTop + 0], a ld a, h ld [sStackTop + 1], a .done call CloseSRAM ret FindStackTop: ; Find the furthest point that sp has traversed to. ; This is distinct from the current value of sp. ld hl, wStackBottom .loop ld a, [hl] or a ret nz inc hl jr .loop SavingDontTurnOffThePower: ; Prevent joypad interrupts xor a ldh [hJoypadReleased], a ldh [hJoypadPressed], a ldh [hJoypadSum], a ldh [hJoypadDown], a ; Save the text speed setting to the stack ld a, [wOptions] push af ; Set the text speed to medium ld a, TEXT_DELAY_MED ld [wOptions], a ; SAVING... DON'T TURN OFF THE POWER. ld hl, SavingDontTurnOffThePowerText call PrintText1bpp ; Restore the text speed setting pop af ld [wOptions], a ; Wait for 16 frames ld c, 16 call DelayFrames ret ErasePreviousSave: call EraseBoxes call EraseHallOfFame call EraseLinkBattleStats ld a, BANK(sStackTop) call OpenSRAM xor a ld [sStackTop + 0], a ld [sStackTop + 1], a call CloseSRAM ld a, $1 ld [wSavedAtLeastOnce], a ret EraseLinkBattleStats: ld a, BANK(sLinkBattleStats) call OpenSRAM ld hl, sLinkBattleStats ld bc, sLinkBattleStatsEnd - sLinkBattleStats xor a call ByteFill jp CloseSRAM EraseHallOfFame: ld a, BANK(sHallOfFame) call OpenSRAM ld hl, sHallOfFame ld bc, sHallOfFameEnd - sHallOfFame xor a call ByteFill jp CloseSRAM HallOfFame_InitSaveIfNeeded: ld a, [wSavedAtLeastOnce] and a ret nz call ErasePreviousSave ret ValidateSave: ld a, BANK(sCheckValue1) ; aka BANK(sCheckValue2) call OpenSRAM ld a, SAVE_CHECK_VALUE_1 ld [sCheckValue1], a ld a, SAVE_CHECK_VALUE_2 ld [sCheckValue2], a jp CloseSRAM SaveOptions: ld a, BANK(sOptions) call OpenSRAM ld hl, wOptions ld de, sOptions ld bc, wOptionsEnd - wOptions call CopyBytes ld a, [wOptions] and ~(1 << NO_TEXT_SCROLL) ld [sOptions], a jp CloseSRAM SavePlayerData: ld a, BANK(sPlayerData) call OpenSRAM ld hl, wPlayerData ld de, sPlayerData ld bc, wPlayerDataEnd - wPlayerData call CopyBytes ld hl, wCurMapData ld de, sCurMapData ld bc, wCurMapDataEnd - wCurMapData call CopyBytes jp CloseSRAM SavePokemonData: ld a, BANK(sPokemonData) call OpenSRAM ld hl, wPokemonData ld de, sPokemonData ld bc, wPokemonDataEnd - wPokemonData call CopyBytes call CloseSRAM ret SaveBox: call GetBoxAddress call SaveBoxAddress ret SaveChecksum: ld hl, sGameData ld bc, sGameDataEnd - sGameData ld a, BANK(sGameData) call OpenSRAM call Checksum ld a, e ld [sChecksum + 0], a ld a, d ld [sChecksum + 1], a call CloseSRAM ret ValidateBackupSave: ld a, BANK(sBackupCheckValue1) ; aka BANK(sBackupCheckValue2) call OpenSRAM ld a, SAVE_CHECK_VALUE_1 ld [sBackupCheckValue1], a ld a, SAVE_CHECK_VALUE_2 ld [sBackupCheckValue2], a call CloseSRAM ret SaveBackupOptions: ld a, BANK(sBackupOptions) call OpenSRAM ld hl, wOptions ld de, sBackupOptions ld bc, wOptionsEnd - wOptions call CopyBytes call CloseSRAM ret SaveBackupPlayerData: ld a, BANK(sBackupPlayerData) call OpenSRAM ld hl, wPlayerData ld de, sBackupPlayerData ld bc, wPlayerDataEnd - wPlayerData call CopyBytes ld hl, wCurMapData ld de, sBackupCurMapData ld bc, wCurMapDataEnd - wCurMapData call CopyBytes call CloseSRAM ret SaveBackupPokemonData: ld a, BANK(sBackupPokemonData) call OpenSRAM ld hl, wPokemonData ld de, sBackupPokemonData ld bc, wPokemonDataEnd - wPokemonData call CopyBytes call CloseSRAM ret SaveBackupChecksum: ld hl, sBackupGameData ld bc, sBackupGameDataEnd - sBackupGameData ld a, BANK(sBackupGameData) call OpenSRAM call Checksum ld a, e ld [sBackupChecksum + 0], a ld a, d ld [sBackupChecksum + 1], a call CloseSRAM ret TryLoadSaveFile: call VerifyChecksum jr nz, .backup call LoadPlayerData call LoadPokemonData call LoadBox farcall RestorePartyMonMail call ValidateBackupSave call SaveBackupOptions call SaveBackupPlayerData call SaveBackupPokemonData call SaveBackupChecksum and a ret .backup call VerifyBackupChecksum jr nz, .corrupt call LoadBackupPlayerData call LoadBackupPokemonData call LoadBox farcall RestorePartyMonMail call ValidateSave call SaveOptions call SavePlayerData call SavePokemonData call SaveChecksum and a ret .corrupt ld a, [wOptions] push af set NO_TEXT_SCROLL, a ld [wOptions], a ld hl, SaveFileCorruptedText call PrintText1bpp pop af ld [wOptions], a scf ret TryLoadSaveData: xor a ; FALSE ld [wSaveFileExists], a call CheckPrimarySaveFile ld a, [wSaveFileExists] and a jr z, .backup ld a, BANK(sPlayerData) call OpenSRAM ld hl, sPlayerData + wStartDay - wPlayerData ld de, wStartDay ld bc, 8 call CopyBytes ld hl, sPlayerData + wStatusFlags - wPlayerData ld de, wStatusFlags ld a, [hl] ld [de], a call CloseSRAM ret .backup call CheckBackupSaveFile ld a, [wSaveFileExists] and a jr z, .corrupt ld a, BANK(sBackupPlayerData) call OpenSRAM ld hl, sBackupPlayerData + wStartDay - wPlayerData ld de, wStartDay ld bc, 8 call CopyBytes ld hl, sBackupPlayerData + wStatusFlags - wPlayerData ld de, wStatusFlags ld a, [hl] ld [de], a call CloseSRAM ret .corrupt ld hl, DefaultOptions ld de, wOptions ld bc, wOptionsEnd - wOptions call CopyBytes ret INCLUDE "data/default_options.asm" CheckPrimarySaveFile: ld a, BANK(sCheckValue1) ; aka BANK(sCheckValue2) call OpenSRAM ld a, [sCheckValue1] cp SAVE_CHECK_VALUE_1 jr nz, .nope ld a, [sCheckValue2] cp SAVE_CHECK_VALUE_2 jr nz, .nope ld hl, sOptions ld de, wOptions ld bc, wOptionsEnd - wOptions call CopyBytes call CloseSRAM ld a, TRUE ld [wSaveFileExists], a .nope call CloseSRAM ret CheckBackupSaveFile: ld a, BANK(sBackupCheckValue1) ; aka BANK(sBackupCheckValue2) call OpenSRAM ld a, [sBackupCheckValue1] cp SAVE_CHECK_VALUE_1 jr nz, .nope ld a, [sBackupCheckValue2] cp SAVE_CHECK_VALUE_2 jr nz, .nope ld hl, sBackupOptions ld de, wOptions ld bc, wOptionsEnd - wOptions call CopyBytes ld a, $2 ld [wSaveFileExists], a .nope call CloseSRAM ret LoadPlayerData: ld a, BANK(sPlayerData) call OpenSRAM ld hl, sPlayerData ld de, wPlayerData ld bc, wPlayerDataEnd - wPlayerData call CopyBytes ld hl, sCurMapData ld de, wCurMapData ld bc, wCurMapDataEnd - wCurMapData call CopyBytes call CloseSRAM ret LoadPokemonData: ld a, BANK(sPokemonData) call OpenSRAM ld hl, sPokemonData ld de, wPokemonData ld bc, wPokemonDataEnd - wPokemonData call CopyBytes call CloseSRAM ret LoadBox: call GetBoxAddress call LoadBoxAddress ret VerifyChecksum: ld hl, sGameData ld bc, sGameDataEnd - sGameData ld a, BANK(sGameData) call OpenSRAM call Checksum ld a, [sChecksum + 0] cp e jr nz, .fail ld a, [sChecksum + 1] cp d .fail push af call CloseSRAM pop af ret LoadBackupPlayerData: ld a, BANK(sBackupPlayerData) call OpenSRAM ld hl, sBackupPlayerData ld de, wPlayerData ld bc, wPlayerDataEnd - wPlayerData call CopyBytes ld hl, sBackupCurMapData ld de, wCurMapData ld bc, wCurMapDataEnd - wCurMapData call CopyBytes call CloseSRAM ret LoadBackupPokemonData: ld a, BANK(sBackupPokemonData) call OpenSRAM ld hl, sBackupPokemonData ld de, wPokemonData ld bc, wPokemonDataEnd - wPokemonData call CopyBytes call CloseSRAM ret VerifyBackupChecksum: ld hl, sBackupGameData ld bc, sBackupGameDataEnd - sBackupGameData ld a, BANK(sBackupGameData) call OpenSRAM call Checksum ld a, [sBackupChecksum + 0] cp e jr nz, .fail ld a, [sBackupChecksum + 1] cp d .fail push af call CloseSRAM pop af ret GetBoxAddress: ld a, [wCurBox] cp NUM_BOXES jr c, .ok xor a ld [wCurBox], a .ok ld e, a ld d, 0 ld hl, BoxAddresses rept 5 add hl, de endr ld a, [hli] push af ld a, [hli] ld e, a ld a, [hli] ld d, a ld a, [hli] ld h, [hl] ld l, a pop af ret SaveBoxAddress: ; Save box via wBoxPartialData. ; We do this in three steps because the size of wBoxPartialData is less than ; the size of sBox. push hl ; Load the first part of the active box. push af push de ld a, BANK(sBox) call OpenSRAM ld hl, sBox ld de, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM pop de pop af ; Save it to the target box. push af push de call OpenSRAM ld hl, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM ; Load the second part of the active box. ld a, BANK(sBox) call OpenSRAM ld hl, sBox + (wBoxPartialDataEnd - wBoxPartialData) ld de, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM pop de pop af ld hl, (wBoxPartialDataEnd - wBoxPartialData) add hl, de ld e, l ld d, h ; Save it to the next part of the target box. push af push de call OpenSRAM ld hl, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM ; Load the third and final part of the active box. ld a, BANK(sBox) call OpenSRAM ld hl, sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2 ld de, wBoxPartialData ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e call CopyBytes call CloseSRAM pop de pop af ld hl, (wBoxPartialDataEnd - wBoxPartialData) add hl, de ld e, l ld d, h ; Save it to the final part of the target box. call OpenSRAM ld hl, wBoxPartialData ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e call CopyBytes call CloseSRAM pop hl ret LoadBoxAddress: ; Load box via wBoxPartialData. ; We do this in three steps because the size of wBoxPartialData is less than ; the size of sBox. push hl ld l, e ld h, d ; Load part 1 push af push hl call OpenSRAM ld de, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM ld a, BANK(sBox) call OpenSRAM ld hl, wBoxPartialData ld de, sBox ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM pop hl pop af ld de, (wBoxPartialDataEnd - wBoxPartialData) add hl, de ; Load part 2 push af push hl call OpenSRAM ld de, wBoxPartialData ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM ld a, BANK(sBox) call OpenSRAM ld hl, wBoxPartialData ld de, sBox + (wBoxPartialDataEnd - wBoxPartialData) ld bc, (wBoxPartialDataEnd - wBoxPartialData) call CopyBytes call CloseSRAM pop hl pop af ; Load part 3 ld de, (wBoxPartialDataEnd - wBoxPartialData) add hl, de call OpenSRAM ld de, wBoxPartialData ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e call CopyBytes call CloseSRAM ld a, BANK(sBox) call OpenSRAM ld hl, wBoxPartialData ld de, sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2 ld bc, sBoxEnd - (sBox + (wBoxPartialDataEnd - wBoxPartialData) * 2) ; $8e call CopyBytes call CloseSRAM pop hl ret EraseBoxes: ld hl, BoxAddresses ld c, NUM_BOXES .next push bc ld a, [hli] call OpenSRAM ld a, [hli] ld e, a ld a, [hli] ld d, a xor a ld [de], a inc de ld a, -1 ld [de], a inc de ld bc, sBoxEnd - (sBox + 2) .clear xor a ld [de], a inc de dec bc ld a, b or c jr nz, .clear ld a, [hli] ld e, a ld a, [hli] ld d, a ld a, -1 ld [de], a inc de xor a ld [de], a call CloseSRAM pop bc dec c jr nz, .next ret BoxAddresses: table_width 5, BoxAddresses for n, 1, NUM_BOXES + 1 db BANK(sBox{d:n}) ; aka BANK(sBox{d:n}End) dw sBox{d:n}, sBox{d:n}End endr assert_table_length NUM_BOXES Checksum: ld de, 0 .loop ld a, [hli] add e ld e, a ld a, 0 adc d ld d, a dec bc ld a, b or c jr nz, .loop ret WouldYouLikeToSaveTheGameText: text_far _WouldYouLikeToSaveTheGameText text_end SavingDontTurnOffThePowerText: text_far _SavingDontTurnOffThePowerText text_end SavedTheGameText: text_far _SavedTheGameText text_end AlreadyASaveFileText: text_far _AlreadyASaveFileText text_end AnotherSaveFileText: text_far _AnotherSaveFileText text_end SaveFileCorruptedText: text_far _SaveFileCorruptedText text_end ChangeBoxSaveText: text_far _ChangeBoxSaveText text_end MoveMonWOMailSaveText: text_far _MoveMonWOMailSaveText text_end