3 Remove the backup save file
Thomas Winwood edited this page 2023-09-27 08:49:05 +01:00

Remove the backup save file

THIS WILL BREAK SAVE COMPATIBILITY WITH VANILLA EMERALD AND ANY SAVE EDITING TOOLS SUCH AS PKHEX

The Generation III Pokémon games have a backup save file in case the primary one is corrupted, but in practice it's very unlikely for this to happen unless the player deliberately breaks it. We can reclaim it to get a lot of save space without needing to remove any gameplay features. However, be aware that while doing this leaves you relatively unconstrained by the size of the save file the save blocks still take up space in EWRAM. It may be a better idea to look into other ways to reduce the size of the save blocks before resorting to this. If you encounter link-time errors after increasing the size of the save blocks, look into ways to alleviate EWRAM pressure.

The files we'll be editing are include/save.h and src/save.c, so open those in your editor of choice.

include/save.h

Delete the line #define NUM_SAVE_SLOTS 2.

Change NUM_SECTORS_PER_SLOT from 14 to 28.

 #define SECTOR_ID_SAVEBLOCK2          0
 #define SECTOR_ID_SAVEBLOCK1_START    1
 #define SECTOR_ID_SAVEBLOCK1_END      4
 #define SECTOR_ID_PKMN_STORAGE_START  5
 #define SECTOR_ID_PKMN_STORAGE_END   13
-#define NUM_SECTORS_PER_SLOT         14
+#define NUM_SECTORS_PER_SLOT         28
-// Save Slot 1: 0-13;  Save Slot 2: 14-27
 #define SECTOR_ID_HOF_1              28
 #define SECTOR_ID_HOF_2              29
 #define SECTOR_ID_TRAINER_HILL       30
 #define SECTOR_ID_RECORDED_BATTLE    31
 #define SECTORS_COUNT                32

src/save.c

We have a few places where the constant NUM_SAVE_SLOTS was used; since we only have one save slot, we can remove them.

Remove the line sector += NUM_SECTORS_PER_SLOT * (gSaveCounter % NUM_SAVE_SLOTS); in the following five functions.

  • HandleWriteSector
  • HandleReplaceSector
  • WriteSectorSignatureByte_NoOffset
  • CopySectorSignatureByte
  • WriteSectorSignatureByte

In the functions CopySaveSlotData and GetSaveBlocksPointersBaseOffset, remove the slotOffset variable.

 // sectorId arg is ignored, this always reads the full save slot
 static u8 CopySaveSlotData(u16 sectorId, struct SaveSectorLocation *locations)
 {
     u16 i;
     u16 checksum;
-    u16 slotOffset = NUM_SECTORS_PER_SLOT * (gSaveCounter % NUM_SAVE_SLOTS);
     u16 id;
 
     for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
     {
-        ReadFlashSector(i + slotOffset, gReadWriteSector);
+        ReadFlashSector(i, gReadWriteSector);
 u16 GetSaveBlocksPointersBaseOffset(void)
 {
-    u16 i, slotOffset;
+    u16 i;
     struct SaveSector* sector;
 
     sector = gReadWriteSector = &gSaveDataBuffer;
     if (gFlashMemoryPresent != TRUE)
         return 0;
     UpdateSaveAddresses();
     GetSaveValidStatus(gRamSaveSectorLocations);
-    slotOffset = NUM_SECTORS_PER_SLOT * (gSaveCounter % NUM_SAVE_SLOTS);
     for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
     {
-        ReadFlashSector(i + slotOffset, gReadWriteSector);
+        ReadFlashSector(i, gReadWriteSector);

Finally, we need to alter the code which checked the integrity of both save slots. Replace the function GetSaveValidStatus like so.

static u8 GetSaveValidStatus(const struct SaveSectorLocation *locations)
{
    u16 i;
    u16 checksum;
    u32 saveSlotCounter = 0;
    u32 validSectorFlags = 0;
    bool8 signatureValid = FALSE;
    u8 saveSlotStatus;

    for (i = 0; i < NUM_SECTORS_PER_SLOT; i++)
    {
        ReadFlashSector(i, gReadWriteSector);
        if (gReadWriteSector->signature == SECTOR_SIGNATURE)
        {
            signatureValid = TRUE;
            checksum = CalculateChecksum(gReadWriteSector->data, locations[gReadWriteSector->id].size);
            if (gReadWriteSector->checksum == checksum)
            {
                saveSlotCounter = gReadWriteSector->counter;
                validSectorFlags |= 1 << gReadWriteSector->id;
            }
        }
    }

    if (signatureValid)
    {
        if (validSectorFlags == (1 << NUM_SECTORS_PER_SLOT) - 1)
            return SAVE_STATUS_OK;
        else
            return SAVE_STATUS_ERROR;
    }
    else
    {
        // No sectors have the correct signature, treat it as empty
        return SAVE_STATUS_EMPTY;
    }
}

How do I use this new save space?

If you now run a build you should get a working copy of Pokémon Emerald, but we haven't made use of the free space. By way of an example, I've expanded both struct SaveBlock1 and struct SaveBlock2 to fill the available space while keeping struct PokemonStorage (the number of boxes) the same size.

In include/save.h:

-#define SECTOR_ID_SAVEBLOCK2          0
-#define SECTOR_ID_SAVEBLOCK1_START    1
-#define SECTOR_ID_SAVEBLOCK1_END      4
-#define SECTOR_ID_PKMN_STORAGE_START  5
-#define SECTOR_ID_PKMN_STORAGE_END   13
+#define SECTOR_ID_SAVEBLOCK2_START    0
+#define SECTOR_ID_SAVEBLOCK2_END      4
+#define SECTOR_ID_SAVEBLOCK1_START    5
+#define SECTOR_ID_SAVEBLOCK1_END     18
+#define SECTOR_ID_PKMN_STORAGE_START 19
+#define SECTOR_ID_PKMN_STORAGE_END   27
#define NUM_SECTORS_PER_SLOT         28
#define SECTOR_ID_HOF_1              28
#define SECTOR_ID_HOF_2              29
#define SECTOR_ID_TRAINER_HILL       30
#define SECTOR_ID_RECORDED_BATTLE    31
#define SECTORS_COUNT                32

In src/save.c:

 struct
 {
     u16 offset;
     u16 size;
 } static const sSaveSlotLayout[NUM_SECTORS_PER_SLOT] =
 {
-    SAVEBLOCK_CHUNK(struct SaveBlock2, 0), // SECTOR_ID_SAVEBLOCK2
+    SAVEBLOCK_CHUNK(struct SaveBlock2, 0), // SECTOR_ID_SAVEBLOCK2_START
+    SAVEBLOCK_CHUNK(struct SaveBlock2, 1),
+    SAVEBLOCK_CHUNK(struct SaveBlock2, 2),
+    SAVEBLOCK_CHUNK(struct SaveBlock2, 3),
+    SAVEBLOCK_CHUNK(struct SaveBlock2, 4), // SECTOR_ID_SAVEBLOCK2_END
 
-    SAVEBLOCK_CHUNK(struct SaveBlock1, 0), // SECTOR_ID_SAVEBLOCK1_START
-    SAVEBLOCK_CHUNK(struct SaveBlock1, 1),
-    SAVEBLOCK_CHUNK(struct SaveBlock1, 2),
-    SAVEBLOCK_CHUNK(struct SaveBlock1, 3), // SECTOR_ID_SAVEBLOCK1_END
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  0), // SECTOR_ID_SAVEBLOCK1_START
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  1),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  2),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  3),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  4),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  5),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  6),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  7),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  8),
+    SAVEBLOCK_CHUNK(struct SaveBlock1,  9),
+    SAVEBLOCK_CHUNK(struct SaveBlock1, 10),
+    SAVEBLOCK_CHUNK(struct SaveBlock1, 11),
+    SAVEBLOCK_CHUNK(struct SaveBlock1, 12),
+    SAVEBLOCK_CHUNK(struct SaveBlock1, 13), // SECTOR_ID_SAVEBLOCK1_END
 
     SAVEBLOCK_CHUNK(struct PokemonStorage, 0), // SECTOR_ID_PKMN_STORAGE_START
     SAVEBLOCK_CHUNK(struct PokemonStorage, 1),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 2),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 3),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 4),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 5),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 6),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 7),
     SAVEBLOCK_CHUNK(struct PokemonStorage, 8), // SECTOR_ID_PKMN_STORAGE_END
 };

 // These will produce an error if a save struct is larger than the space
 // alloted for it in the flash.
-STATIC_ASSERT(sizeof(struct SaveBlock2) <= SECTOR_DATA_SIZE, SaveBlock2FreeSpace);
+STATIC_ASSERT(sizeof(struct SaveBlock2) <= SECTOR_DATA_SIZE * (SECTOR_ID_SAVEBLOCK2_END - SECTOR_ID_SAVEBLOCK2_START + 1), SaveBlock2FreeSpace);
 STATIC_ASSERT(sizeof(struct SaveBlock1) <= SECTOR_DATA_SIZE * (SECTOR_ID_SAVEBLOCK1_END - SECTOR_ID_SAVEBLOCK1_START + 1), SaveBlock1FreeSpace);
 STATIC_ASSERT(sizeof(struct PokemonStorage) <= SECTOR_DATA_SIZE * (SECTOR_ID_PKMN_STORAGE_END - SECTOR_ID_PKMN_STORAGE_START + 1), PokemonStorageFreeSpace);

Since struct SaveBlock2 can now cover more than one sector, its static assert needed to be changed to reflect its size. As implied by the comments here, it's important to ensure that the number of sectors you allocate to each chunk of the save match the constant definitions in include/save.h.