-
Notifications
You must be signed in to change notification settings - Fork 0
How to Backwards Compatability
If you're planning on making a hack and updating it with bugfixes and additional content in the future, you're going to have to deal with players wanting to migrate their save files forward to the new version of your hack. Which usually means dancing around making any major changes to the save file structures, and/or fretting when that does happen. This tutorial will provide advice on how to do save file versioning and forward migration, so you don't have to worry as much about making major changes to the save file.
Adding a version to the save data is simple and easy. If you've already released your hack and people's save files are out there, don't worry, it's not too late. This method is designed to handle the case where the old version of your game's save is unversion.
(If you haven't released your game yet and don't care about maintaining backwards compatibility with vanilla, you can skip this step.)
First, in order to maintain backwards compatibility, we will need to save off the structure of the previous save file. Create a new file include/save.v0.h and add header guards like the other include files have. Now head into your include/global.h file and copy save file structures over!
What structures you copy over depends on what kind of edits you plan to make to the save structure. At minimum, you'll want to be copying the entiery of struct SaveBlock2 and struct SaveBlock1. However, if you're planning on changing any of the substructures, you'll want to copy those over as well.
Importantly: every structure you copy, you should rename the structure by suffixing _v0. As in, struct SaveBlock2_v0 and struct SaveBlock1_v0. This indicates that these structures are for "version 0" of the save data. (Version 0 is the version before we implement save data versioning.)
From now on, the version that lives in global.h is the "current" version. (If you want to move the structs out of global.h and into, like save.v-current.h or something and just include that file in global.h, you can do that too. It will make future versioning easier to just save off a copy of this file. Do not include save.v0.h in global.h; it doesn't need to be globally seen.)
Now that the old version of the save structure is stored safely elsewhere in the codebase, we can add the version number to the save file! Make the following changes in global.h:
struct SaveBlock2
{
+ u8 _saveSentinel; // 0xFF
+ // u8 unused;
+ u16 saveVersion;
u8 playerName[PLAYER_NAME_LENGTH + 1];
u8 playerGender; // MALE, FEMALE
u8 specialSaveWarpFlags;Yes, we are adding the save version to the front of the save block. This will shift all the data down in the save block, so any offset comments you may have before the fields will be outdata, and you may just want to remove them.
Here's why we're doing this:
- In order to determine if a save needs to be updated, we need a version number. But the previous version of the save doesn't have a version number yet, so we go for the next best thing: would the save be invalid in the previous format?
- The value 0xFF is the "end of string" terminator character, and if we find that at the beginning of a string, that means the string is empty. A valid save file cannot have a player name that is an empty string.
- So in the future, when loading the save file, we check the first byte of the save structure, where normally the first letter of the player name would be. If it is NOT a 0xFF, then it is a version 0 save. If it IS a 0xFF, then we know there's a save version we can read.
- (Side note, due to struct padding rules, there's an unused byte, marked above. Feel free to put a game id or something in there, but that's outside the scope of this tutorial.)
Now that we have a field for save versions, time to fill that in!
In include/constants/global.h:
#define LANGUAGE_SPANISH 7
#define NUM_LANGUAGES 7
+#define SAVE_VERSION_0 0
+#define SAVE_VERSION_1 1
#define GAME_VERSION (VERSION_EMERALD)
#define GAME_LANGUAGE (LANGUAGE_ENGLISH)
+#define SAVE_VERSION (SAVE_VERSION_1)(Feel free to come up with more descriptive names. My hack names the save versions after a yearly release.)
In new_game.c:
void NewGameInitData(void)
{
if (gSaveFileStatus == SAVE_STATUS_EMPTY || gSaveFileStatus == SAVE_STATUS_CORRUPT)
RtcReset();
gDifferentSaveFile = TRUE;
+ gSaveBlock2Ptr->_saveSentinel = 0xFF;
+ gSaveBlock2Ptr->saveVersion = SAVE_VERSION;
gSaveBlock2Ptr->encryptionKey = 0;
ZeroPlayerPartyMons();
ZeroEnemyPartyMons();(TODO: )
In save.h:
#define SAVE_STATUS_EMPTY 0
#define SAVE_STATUS_OK 1
#define SAVE_STATUS_CORRUPT 2
#define SAVE_STATUS_NO_FLASH 4
+#define SAVE_STATUS_OUTDATED 10
#define SAVE_STATUS_ERROR 0xFFIn save.c:
static u8 TryLoadSaveSlot(u16 sectorId, struct SaveSectorLocation *locations)
{
u8 status;
gReadWriteSector = &gSaveDataBuffer;
if (sectorId != FULL_SAVE_SLOT)
{
// This function may not be used with a specific sector id
status = SAVE_STATUS_ERROR;
}
else
{
status = GetSaveValidStatus(locations);
CopySaveSlotData(FULL_SAVE_SLOT, locations);
}
+ if (status == 1) {
+ if (gSaveBlock2Ptr->gameVersion.sentinel != 0xFF)
+ status = SAVE_STATUS_OUTDATED;
+ if (gSaveBlock2Ptr->gameVersion.gameId != GAME_VERSION)
+ status = SAVE_STATUS_OUTDATED;
+ }
return status;
}In main_menu.c:
static void Task_MainMenuCheckSaveFile(u8 taskId)
{
@@ ...
switch (gSaveFileStatus)
{
case SAVE_STATUS_OK:
tMenuType = HAS_SAVED_GAME;
if (IsMysteryGiftEnabled())
tMenuType++;
gTasks[taskId].func = Task_MainMenuCheckBattery;
break;
case SAVE_STATUS_CORRUPT:
CreateMainMenuErrorWindow(gText_SaveFileErased);
tMenuType = HAS_NO_SAVED_GAME;
gTasks[taskId].func = Task_WaitForSaveFileErrorWindow;
break;
+ case SAVE_STATUS_OUTDATED:
+ CreateMainMenuErrorWindow(gText_SaveFileOld);
+ tMenuType = HAS_SAVED_GAME;
+ gTasks[taskId].func = Task_WaitForSaveFileErrorWindow;
+ break;
case SAVE_STATUS_ERROR:
CreateMainMenuErrorWindow(gText_SaveFileCorrupted);
gTasks[taskId].func = Task_WaitForSaveFileErrorWindow;In strings.c:
+const u8 gText_SaveFileOld[] = _("Your save file is for an older\nrelease of HACK NAME HERE.\pYour save will be updated to the new version.\nPlease back up your old save if\lyou wish to keep it.");