Table of Contents
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
.