Support reading and writing the trainers' money from the save file.

It was implemented to be used for the Pokéshop idea for PokeMe64
This commit is contained in:
Philippe Symons 2026-01-14 18:49:37 +01:00
parent 8bb1f6a7d3
commit 54d9caa6c2
10 changed files with 1317 additions and 1192 deletions

View File

@ -55,12 +55,18 @@ int main(int argc, char** argv)
{
Gen1GameReader gameReader(romReader, saveManager, gen1Type);
printf("%s", (gameReader.isMainChecksumValid()) ? "Game save valid!\n" : "Game save NOT valid!\n");
printf("OT: %u\n", gameReader.getTrainerID());
printf("Name: %s\n", gameReader.getTrainerName());
printf("Money: %u\n", gameReader.getTrainerMoney());
}
else if (gen2Type != Gen2GameType::INVALID)
{
Gen2GameReader gameReader(romReader, saveManager, gen2Type);
printf("%s", (gameReader.isMainChecksumValid()) ? "Main save valid!\n" : "Main save NOT valid!\n");
printf("%s", (gameReader.isBackupChecksumValid()) ? "Backup save valid!\n" : "Backup save NOT valid!\n");
printf("OT: %u\n", gameReader.getTrainerID());
printf("Name: %s\n", gameReader.getTrainerName());
printf("Money: %u\n", gameReader.getTrainerMoney());
}
free(romBuffer);

View File

@ -86,6 +86,17 @@ public:
*/
uint16_t getTrainerID() const;
/**
* @brief This function retrieves the amount of pokédollars the trainer currently has
*/
uint32_t getTrainerMoney() const;
/**
* @brief Sets the amount of pokédollars the trainer currently has
* NOTE: the value is capped to 16777215 as this is the maximum value that can be represented by 3 bytes
*/
void setTrainerMoney(uint32_t amount);
/**
* Retrieves the current map the player is in.
*/

View File

@ -76,6 +76,7 @@ typedef struct Gen1LocalizationRomOffsets
typedef struct Gen1LocalizationSRAMOffsets
{
uint32_t trainerID;
uint32_t trainerMoney;
uint32_t currentMap;
uint32_t dexSeen;
uint32_t dexOwned;

View File

@ -131,6 +131,18 @@ public:
*/
const char *getTrainerName() const;
/**
* @brief This function retrieves the amount of pokédollars the trainer currently has
*/
uint32_t getTrainerMoney() const;
/**
* @brief Sets the amount of pokédollars the trainer currently has
* NOTE: the value is capped to 16777215 as this is the maximum value that can be represented by 3 bytes
*/
void setTrainerMoney(uint32_t amount);
/**
* @brief Get the rival name from the save file
* Note: the resulting const char* does not need to be free'd.

View File

@ -69,6 +69,7 @@ typedef struct Gen2LocalizationSRAMOffsets
uint32_t currentBox;
uint32_t dexSeen;
uint32_t dexOwned;
uint32_t trainerMoney;
uint32_t party;
uint32_t eventFlags;
uint32_t mainChecksum;

View File

@ -10,7 +10,7 @@
/**
* @brief This function calculates the main data checksum
*/
uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLanguage localization)
static uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLanguage localization)
{
Gen1Checksum checksum;
const uint32_t checksummedDataStart = 0x2598;
@ -30,7 +30,7 @@ uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLan
return checksum.get();
}
uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankIndex)
static uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankIndex)
{
Gen1Checksum checksum;
const uint16_t checksummedDataStart = 0x0;
@ -49,6 +49,50 @@ uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankInd
return checksum.get();
}
/**
* @brief This function decodes a binary coded decimal number stored in big endian format.
* (see: https://en.wikipedia.org/wiki/Binary-coded_decimal)
* It assumes 4 bits per digit and all digits are decimal (0-9)
*
* @param bcdData the buffer containing the Binary coded decimal data
* @param numDigits number of digits in the buffer
*/
static uint32_t decodeBigEndianBinaryCodedDecimalNumber(const uint8_t *bcdData, uint8_t numDigits)
{
uint32_t result = 0;
// The + 1 is because the division always rounds down.
// but we want to make sure that if there's an odd number of digits, we still process the last one
for(uint8_t i=0; i < (numDigits + 1) / 2; ++i)
{
const uint8_t upperNibble = (bcdData[i] >> 4);
const uint8_t lowerNibble = (bcdData[i] & 0xF);
result = (result * 100) + (upperNibble * 10) + lowerNibble;
}
return result;
}
/**
* @brief This function encodes a binary coded decimal number in big endian format.
* (see: https://en.wikipedia.org/wiki/Binary-coded_decimal)
* The output will use 4 bits per digit and all digits are decimal (0-9)
*
* @param bcdData a buffer with sufficient size to hold the binary encoded decimal data
* @param numDigits number of digits to encode
*/
static void encodeBigEndianBinaryCodedDecimalNumber(uint32_t value, uint8_t *outBcdData, uint8_t numDigits)
{
// since we're doing big endian, but we're extracting the digits from least significant to most significant,
// we need to fill the output buffer backwards
for(int8_t i = numDigits - 1; i >= 0; i -= 2)
{
const uint8_t lowerNibble = value % 10;
value /= 10;
const uint8_t upperNibble = value % 10;
value /= 10;
outBcdData[i] = (upperNibble << 4) | lowerNibble;
}
}
Gen1GameReader::Gen1GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language)
: romReader_(romReader)
, saveManager_(saveManager)
@ -347,6 +391,30 @@ uint16_t Gen1GameReader::getTrainerID() const
return result;
}
uint32_t Gen1GameReader::getTrainerMoney() const
{
const uint32_t savOffset = gen1_getSRAMOffsets(localization_).trainerMoney;
saveManager_.seek(savOffset);
uint8_t encodedAmount[3];
saveManager_.read(encodedAmount, 3);
// for gen 1, money is stored as a big-endian BCD number (source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#Main_Data)
return decodeBigEndianBinaryCodedDecimalNumber(encodedAmount, 6);
}
void Gen1GameReader::setTrainerMoney(uint32_t amount)
{
const uint32_t savOffset = gen1_getSRAMOffsets(localization_).trainerMoney;
saveManager_.seek(savOffset);
// for gen 1, money is stored as a big-endian BCD number (source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#Main_Data)
uint8_t encodedAmount[3];
encodeBigEndianBinaryCodedDecimalNumber(amount, encodedAmount, 6);
saveManager_.write(encodedAmount, 3);
}
Gen1Maps Gen1GameReader::getCurrentMap() const
{
uint8_t result;

View File

@ -151,6 +151,7 @@ static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsY[] = {
static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsInternational = {
.trainerID = 0x2605,
.trainerMoney = 0x25F3,
.currentMap = 0x260A,
.dexSeen = 0x25B6,
.dexOwned = 0x25A3,
@ -162,6 +163,7 @@ static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsInternational = {
static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsJapan = {
.trainerID = 0x25FB,
.trainerMoney = 0x25EE,
.currentMap = 0x2600,
.dexSeen = 0x25B1,
.dexOwned = 0x259E,

View File

@ -642,6 +642,24 @@ const char *Gen2GameReader::getTrainerName() const
return result;
}
uint32_t Gen2GameReader::getTrainerMoney() const
{
uint32_t result = 0;
const uint32_t savOffset = gen2_getSRAMOffsets(gameType_, localization_).trainerMoney;
saveManager_.seek(savOffset);
saveManager_.readUint24(result, Endianness::BIG);
return result;
}
void Gen2GameReader::setTrainerMoney(uint32_t amount)
{
const uint32_t savOffset = gen2_getSRAMOffsets(gameType_, localization_).trainerMoney;
saveManager_.seek(savOffset);
saveManager_.writeUint24(amount, Endianness::BIG);
}
const char *Gen2GameReader::getRivalName() const
{
static char result[20];

View File

@ -222,6 +222,7 @@ static const Gen2LocalizationSRAMOffsets g2_dummySRAMOffsets = {
.currentBox = 0,
.dexSeen = 0,
.dexOwned = 0,
.trainerMoney = 0,
.party = 0,
.eventFlags = 0,
.mainChecksum = 0,
@ -243,6 +244,7 @@ static const Gen2LocalizationSRAMOffsets g2_internationalSRAMOffsetsGS = {
.currentBox = 0x2D6C,
.dexSeen = 0x2A6C,
.dexOwned = 0x2A4C,
.trainerMoney = 0x23DB,
.party = 0x288A,
.eventFlags = 0x261F,
.mainChecksum = 0x2D69,
@ -264,6 +266,7 @@ static const Gen2LocalizationSRAMOffsets g2_internationalSRAMOffsetsC = {
.currentBox = 0x2D10,
.dexSeen = 0x2A47,
.dexOwned = 0x2A27,
.trainerMoney = 0x23DC,
.party = 0x2865,
.eventFlags = 0x2600,
.mainChecksum = 0x2D0D,
@ -297,6 +300,7 @@ static const Gen2LocalizationSRAMOffsets g2_localizationSRAMOffsetsGS[] = {
.currentBox = 0x2DAE,
.dexSeen = 0x2AAE,
.dexOwned = 0x2A8E,
.trainerMoney = 0x23D3,
.party = 0x28CC,
.eventFlags = 0x25F7,
.mainChecksum = 0x2DAB,
@ -318,6 +322,7 @@ static const Gen2LocalizationSRAMOffsets g2_localizationSRAMOffsetsGS[] = {
.currentBox = 0x2D10,
.dexSeen = 0x29EE,
.dexOwned = 0x29CE,
.trainerMoney = 0x23BC,
.party = 0x283E,
.eventFlags = 0x2600,
.mainChecksum = 0x2D0D,
@ -354,6 +359,7 @@ static const Gen2LocalizationSRAMOffsets g2_localizationSRAMOffsetsC[] = {
.currentBox = 0x2D10,
.dexSeen = 0x29CA,
.dexOwned = 0x29AA,
.trainerMoney = 0x23BE,
.party = 0x281A,
.eventFlags = 0x25E2,
.mainChecksum = 0x2D0D,