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

116
Makefile
View File

@ -1,58 +1,58 @@
# # Compiler flags # # Compiler flags
CXXFLAGS := -std=c++11 -fno-rtti -fno-exceptions -fno-unwind-tables -Wall -Wextra -I $(CURDIR) -I $(CURDIR)/include -g -Os CXXFLAGS := -std=c++11 -fno-rtti -fno-exceptions -fno-unwind-tables -Wall -Wextra -I $(CURDIR) -I $(CURDIR)/include -g -Os
# Source files directory # Source files directory
SRC_DIR := src SRC_DIR := src
# Build directory # Build directory
BUILD_DIR := build BUILD_DIR := build
# Source files (add more as needed) # Source files (add more as needed)
SRCS := $(shell find $(SRC_DIR) -type f -name '*.cpp') SRCS := $(shell find $(SRC_DIR) -type f -name '*.cpp')
# Object files # Object files
OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS)) OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(BUILD_DIR)/%.o,$(SRCS))
PNG_SUPPORT ?= 1 PNG_SUPPORT ?= 1
# Add PNG support flag if PNG_SUPPORT is set to 1 # Add PNG support flag if PNG_SUPPORT is set to 1
ifeq ($(PNG_SUPPORT),1) ifeq ($(PNG_SUPPORT),1)
CXXFLAGS += -DPNG_SUPPORT CXXFLAGS += -DPNG_SUPPORT
MAKEOVERRIDES := PNG_SUPPORT=1 MAKEOVERRIDES := PNG_SUPPORT=1
endif endif
# Ensure necessary directories exist # Ensure necessary directories exist
# This function ensures the directory for the target exists # This function ensures the directory for the target exists
define make_directory define make_directory
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
endef endef
# Target executable # Target executable
TARGET := libpokemegb.a TARGET := libpokemegb.a
# Phony targets # Phony targets
.PHONY: all clean .PHONY: all clean
# Default target # Default target
all: $(TARGET) examples all: $(TARGET) examples
examples: $(TARGET) examples: $(TARGET)
$(MAKE) -C examples $(MAKECMDGOALS) $(MAKEOVERRIDES) $(MAKE) -C examples $(MAKECMDGOALS) $(MAKEOVERRIDES)
# Rule to build the target executable # Rule to build the target executable
$(TARGET): $(OBJS) $(TARGET): $(OBJS)
@echo "Creating static library $(TARGET)" @echo "Creating static library $(TARGET)"
ar rcs $@ $(OBJS) ar rcs $@ $(OBJS)
ranlib $(TARGET) ranlib $(TARGET)
# Rule to compile source files # Rule to compile source files
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR) $(C_FILE) $(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR) $(C_FILE)
$(make_directory) $(make_directory)
$(CXX) $(CXXFLAGS) -c $< -o $@ $(CXX) $(CXXFLAGS) -c $< -o $@
# Create the build directory if it doesn't exist # Create the build directory if it doesn't exist
$(BUILD_DIR): $(BUILD_DIR):
mkdir -p $(BUILD_DIR) mkdir -p $(BUILD_DIR)
# Clean rule # Clean rule
clean: clean:
make -C examples clean $(MAKEOVERRIDES) make -C examples clean $(MAKEOVERRIDES)
rm -rf $(BUILD_DIR) $(TARGET) rm -rf $(BUILD_DIR) $(TARGET)

View File

@ -1,73 +1,79 @@
#include "gen1/Gen1GameReader.h" #include "gen1/Gen1GameReader.h"
#include "gen2/Gen2GameReader.h" #include "gen2/Gen2GameReader.h"
#include "RomReader.h" #include "RomReader.h"
#include "SaveManager.h" #include "SaveManager.h"
#include "utils.h" #include "utils.h"
#include <cstdio> #include <cstdio>
#include <cstdint> #include <cstdint>
#include <cstdlib> #include <cstdlib>
static void print_usage() static void print_usage()
{ {
printf("Usage: checkSave <path/to/rom.gbc> <path/to/file.sav>\n"); printf("Usage: checkSave <path/to/rom.gbc> <path/to/file.sav>\n");
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
if(argc != 3) if(argc != 3)
{ {
print_usage(); print_usage();
return 1; return 1;
} }
uint8_t* romBuffer; uint8_t* romBuffer;
uint8_t* savBuffer; uint8_t* savBuffer;
uint32_t romFileSize; uint32_t romFileSize;
uint32_t savFileSize; uint32_t savFileSize;
printf("rom: %s, save: %s\n", argv[1], argv[2]); printf("rom: %s, save: %s\n", argv[1], argv[2]);
romBuffer = readFileIntoBuffer(argv[1], romFileSize); romBuffer = readFileIntoBuffer(argv[1], romFileSize);
if(!romBuffer) if(!romBuffer)
{ {
fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[1]); fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[1]);
return 1; return 1;
} }
savBuffer = readFileIntoBuffer(argv[2], savFileSize); savBuffer = readFileIntoBuffer(argv[2], savFileSize);
if(!savBuffer) if(!savBuffer)
{ {
fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[2]); fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[2]);
free(romBuffer); free(romBuffer);
romBuffer = nullptr; romBuffer = nullptr;
return 1; return 1;
} }
GameboyCartridgeHeader cartridgeHeader; GameboyCartridgeHeader cartridgeHeader;
BufferBasedRomReader romReader(romBuffer, romFileSize); BufferBasedRomReader romReader(romBuffer, romFileSize);
BufferBasedSaveManager saveManager(savBuffer, savFileSize); BufferBasedSaveManager saveManager(savBuffer, savFileSize);
readGameboyCartridgeHeader(romReader, cartridgeHeader); readGameboyCartridgeHeader(romReader, cartridgeHeader);
// check if we're dealing with gen 1 // check if we're dealing with gen 1
const Gen1GameType gen1Type = gen1_determineGameType(cartridgeHeader); const Gen1GameType gen1Type = gen1_determineGameType(cartridgeHeader);
const Gen2GameType gen2Type = gen2_determineGameType(cartridgeHeader); const Gen2GameType gen2Type = gen2_determineGameType(cartridgeHeader);
if (gen1Type != Gen1GameType::INVALID) if (gen1Type != Gen1GameType::INVALID)
{ {
Gen1GameReader gameReader(romReader, saveManager, gen1Type); Gen1GameReader gameReader(romReader, saveManager, gen1Type);
printf("%s", (gameReader.isMainChecksumValid()) ? "Game save valid!\n" : "Game save NOT valid!\n"); printf("%s", (gameReader.isMainChecksumValid()) ? "Game save valid!\n" : "Game save NOT valid!\n");
} printf("OT: %u\n", gameReader.getTrainerID());
else if (gen2Type != Gen2GameType::INVALID) printf("Name: %s\n", gameReader.getTrainerName());
{ printf("Money: %u\n", gameReader.getTrainerMoney());
Gen2GameReader gameReader(romReader, saveManager, gen2Type); }
printf("%s", (gameReader.isMainChecksumValid()) ? "Main save valid!\n" : "Main save NOT valid!\n"); else if (gen2Type != Gen2GameType::INVALID)
printf("%s", (gameReader.isBackupChecksumValid()) ? "Backup save valid!\n" : "Backup save NOT valid!\n"); {
} Gen2GameReader gameReader(romReader, saveManager, gen2Type);
printf("%s", (gameReader.isMainChecksumValid()) ? "Main save valid!\n" : "Main save NOT valid!\n");
free(romBuffer); printf("%s", (gameReader.isBackupChecksumValid()) ? "Backup save valid!\n" : "Backup save NOT valid!\n");
romBuffer = 0; printf("OT: %u\n", gameReader.getTrainerID());
printf("Name: %s\n", gameReader.getTrainerName());
free(savBuffer); printf("Money: %u\n", gameReader.getTrainerMoney());
savBuffer = 0; }
return 0; free(romBuffer);
romBuffer = 0;
free(savBuffer);
savBuffer = 0;
return 0;
} }

View File

@ -1,204 +1,215 @@
#ifndef GEN1GAMEREADER_H #ifndef GEN1GAMEREADER_H
#define GEN1GAMEREADER_H #define GEN1GAMEREADER_H
#include "gen1/Gen1SpriteDecoder.h" #include "gen1/Gen1SpriteDecoder.h"
#include "gen1/Gen1IconDecoder.h" #include "gen1/Gen1IconDecoder.h"
#include "gen1/Gen1PlayerPokemonStorage.h" #include "gen1/Gen1PlayerPokemonStorage.h"
#include "gen1/Gen1DistributionPokemon.h" #include "gen1/Gen1DistributionPokemon.h"
#include "gen1/Gen1Localization.h" #include "gen1/Gen1Localization.h"
#include "gen1/Gen1Maps.h" #include "gen1/Gen1Maps.h"
class IRomReader; class IRomReader;
class ISaveManager; class ISaveManager;
class Gen1GameReader class Gen1GameReader
{ {
public: public:
Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language = Gen1LocalizationLanguage::MAX); Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language = Gen1LocalizationLanguage::MAX);
/** /**
* @brief Retrieves the current game cartridges'/roms' language * @brief Retrieves the current game cartridges'/roms' language
*/ */
Gen1LocalizationLanguage getGameLanguage() const; Gen1LocalizationLanguage getGameLanguage() const;
/** /**
* @brief get the name of a pokémon based on an index number * @brief get the name of a pokémon based on an index number
* Note: you don't own the returned pointer. The data will get overwritten on the next call to this function, * Note: you don't own the returned pointer. The data will get overwritten on the next call to this function,
* so make sure to strdup() it if you need to store it for later * so make sure to strdup() it if you need to store it for later
* *
* @return const char* * @return const char*
*/ */
const char* getPokemonName(uint8_t index) const; const char* getPokemonName(uint8_t index) const;
/** /**
* @brief Gets the pokedex number of a pokemon at the given internal index * @brief Gets the pokedex number of a pokemon at the given internal index
*/ */
uint8_t getPokemonNumber(uint8_t index) const; uint8_t getPokemonNumber(uint8_t index) const;
/** /**
* @brief This function returns the Gen1PokemonIconType for a pokemon at the given index * @brief This function returns the Gen1PokemonIconType for a pokemon at the given index
*/ */
Gen1PokemonIconType getPokemonIconType(uint8_t index) const; Gen1PokemonIconType getPokemonIconType(uint8_t index) const;
/** /**
* @brief With this function, you can check if the index is valid and not referring to missingno * @brief With this function, you can check if the index is valid and not referring to missingno
*/ */
bool isValidIndex(uint8_t index) const; bool isValidIndex(uint8_t index) const;
/** /**
* @brief This function reads the pokemon stats at the given index * @brief This function reads the pokemon stats at the given index
* *
* @return whether or not this operation was successful * @return whether or not this operation was successful
*/ */
bool readPokemonStatsForIndex(uint8_t index, Gen1PokeStats& stats) const; bool readPokemonStatsForIndex(uint8_t index, Gen1PokeStats& stats) const;
/** /**
* @brief Get the index of the color palette based on the pokedex number of a certain pokemon * @brief Get the index of the color palette based on the pokedex number of a certain pokemon
*/ */
uint8_t getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber); uint8_t getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber);
/** /**
* @brief This function reads the color palette with the given ID into outColorPalette * @brief This function reads the color palette with the given ID into outColorPalette
* outColorPalette should be a pointer to an uint16_t array with 4 elements * outColorPalette should be a pointer to an uint16_t array with 4 elements
* *
* Based on: https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I) * Based on: https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I)
* *
* @param outColorPalette * @param outColorPalette
*/ */
void readColorPalette(uint8_t paletteId, uint16_t* outColorPalette); void readColorPalette(uint8_t paletteId, uint16_t* outColorPalette);
/** /**
* @brief Get the trainer name from the save file * @brief Get the trainer name from the save file
* Note: the resulting const char* does not need to be free'd. * Note: the resulting const char* does not need to be free'd.
* However, it needs to be either used rightaway or strdup()'d, because the data will get overwritten on the next call to this function. * However, it needs to be either used rightaway or strdup()'d, because the data will get overwritten on the next call to this function.
*/ */
const char* getTrainerName() const; const char* getTrainerName() const;
/** /**
* @brief Get the rival name from the save file * @brief Get the rival name from the save file
* Note: the resulting const char* does not need to be free'd. * Note: the resulting const char* does not need to be free'd.
* However, it needs to be either used rightaway or strdup()'d, because the data will get overwritten on the next call to this function. * However, it needs to be either used rightaway or strdup()'d, because the data will get overwritten on the next call to this function.
*/ */
const char* getRivalName() const; const char* getRivalName() const;
/** /**
* @brief Get the player ID from the save file * @brief Get the player ID from the save file
*/ */
uint16_t getTrainerID() const; uint16_t getTrainerID() const;
/** /**
* Retrieves the current map the player is in. * @brief This function retrieves the amount of pokédollars the trainer currently has
*/ */
Gen1Maps getCurrentMap() const; uint32_t getTrainerMoney() const;
/** /**
* @brief Returns a Gen1Party instance, which can be used to retrieve the information about the pokemon currently in the trainers' party * @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
Gen1Party getParty(); */
void setTrainerMoney(uint32_t amount);
/**
* @brief Returns a Gen1Box instance, which can be used to retrieve information about the pokemon currently in the Trainers' PC box at the specified index /**
*/ * Retrieves the current map the player is in.
Gen1Box getBox(uint8_t boxIndex); */
Gen1Maps getCurrentMap() const;
/**
* @brief Returns the 0-based index of the currently selected PC box for pokemon /**
*/ * @brief Returns a Gen1Party instance, which can be used to retrieve the information about the pokemon currently in the trainers' party
uint8_t getCurrentBoxIndex(); */
Gen1Party getParty();
/**
* @brief This function returns whether the pokemon with the given pokedexNumber has been SEEN/OWNED. /**
*/ * @brief Returns a Gen1Box instance, which can be used to retrieve information about the pokemon currently in the Trainers' PC box at the specified index
bool getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const; */
Gen1Box getBox(uint8_t boxIndex);
/**
* @brief This function sets the pokedex flag for the given pokedexNumber /**
*/ * @brief Returns the 0-based index of the currently selected PC box for pokemon
void setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const; */
uint8_t getCurrentBoxIndex();
/**
* @brief This function returns the counter value of the given dex flag /**
*/ * @brief This function returns whether the pokemon with the given pokedexNumber has been SEEN/OWNED.
uint8_t getPokedexCounter(PokedexFlag dexFlag) const; */
bool getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
/**
* @brief This function decodes a sprite at the given bank and pointer. /**
* Returns a pointer to a buffer containing the decoded gameboy sprite in gameboy sprite format. * @brief This function sets the pokedex flag for the given pokedexNumber
* The size of the returned buffer is SPRITE_BITPLANE_BUFFER_SIZE_IN_BYTES * 2 */
* void setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
* WARNING: this function returns a pointer to an internal buffer of the Gen1SpriteDecoder. It will get overwritten on the next call to this function.
* If you want to keep the content around for longer, make a copy of this data. /**
*/ * @brief This function returns the counter value of the given dex flag
uint8_t* decodeSprite(uint8_t bankIndex, uint16_t pointer); */
uint8_t getPokedexCounter(PokedexFlag dexFlag) const;
/**
* @brief This function decodes the given pokemon icon and returns an internal buffer /**
* Note that this returns a 16x16 buffer in gameboy format with tiles in vertical order. * @brief This function decodes a sprite at the given bank and pointer.
* You need to feed it to an instance of SpriteRenderer to convert it to a useful format * Returns a pointer to a buffer containing the decoded gameboy sprite in gameboy sprite format.
* * The size of the returned buffer is SPRITE_BITPLANE_BUFFER_SIZE_IN_BYTES * 2
* WARNING: this function returns a buffer to an internal buffer of Gen1IconDecoder. That means it will get overwritten on the next call to this function. *
* If you want to keep the content around for longer, make a copy of this data * WARNING: this function returns a pointer to an internal buffer of the Gen1SpriteDecoder. It will get overwritten on the next call to this function.
*/ * If you want to keep the content around for longer, make a copy of this data.
uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame = true); */
uint8_t* decodeSprite(uint8_t bankIndex, uint16_t pointer);
/**
* @brief Adds a pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the /**
* first ingame PC box with open slots * @brief This function decodes the given pokemon icon and returns an internal buffer
* * Note that this returns a 16x16 buffer in gameboy format with tiles in vertical order.
* @param originalTrainerID optional parameter. If not specified (=null), the original trainer id string will be set to the players' * You need to feed it to an instance of SpriteRenderer to convert it to a useful format
* @param nickname optional parameter. If not specified (= null), the pokemon species name will be used instead. *
* * WARNING: this function returns a buffer to an internal buffer of Gen1IconDecoder. That means it will get overwritten on the next call to this function.
* @return * If you want to keep the content around for longer, make a copy of this data
* 0xFF - Could not add pokemon (probably no space left) */
* 0xFE - Added to players' party uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame = true);
* 0x0 - 0xB - Added to box at index <value>
*/ /**
uint8_t addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID = 0, const char* nickname = 0); * @brief Adds a pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the
* first ingame PC box with open slots
/** *
* @brief Adds a distribution pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the * @param originalTrainerID optional parameter. If not specified (=null), the original trainer id string will be set to the players'
* first ingame PC box with open slots * @param nickname optional parameter. If not specified (= null), the pokemon species name will be used instead.
* *
* @param nickname optional parameter. If not specified (= null), the pokemon species name will be used instead. * @return
* * 0xFF - Could not add pokemon (probably no space left)
* @return * 0xFE - Added to players' party
* 0xFF - Could not add pokemon (probably no space left) * 0x0 - 0xB - Added to box at index <value>
* 0xFE - Added to players' party */
* 0x0 - 0xB - Added to box at index <value> uint8_t addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID = 0, const char* nickname = 0);
*/
uint8_t addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname = 0); /**
* @brief Adds a distribution pokemon to the save. Tries to add it to the party first. If there's no more room there, it tries to add it to the
/** * first ingame PC box with open slots
* @brief This function checks whether the main checksum of bank 1 is valid *
*/ * @param nickname optional parameter. If not specified (= null), the pokemon species name will be used instead.
bool isMainChecksumValid(); *
* @return
/** * 0xFF - Could not add pokemon (probably no space left)
* @brief Updates the main data checksum in bank 1 * 0xFE - Added to players' party
*/ * 0x0 - 0xB - Added to box at index <value>
void updateMainChecksum(); */
/** uint8_t addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname = 0);
* @brief Checks whether the whole bank checksum of the given memory bank is valid.
* As you may or may not know, bank 2 and bank 3 contain the PC Pokemon boxes of the player /**
* These memory banks have 2 types of checksums: a whole memory bank checksum (which is this one) * @brief This function checks whether the main checksum of bank 1 is valid
* And a checksum per box. (which will be dealt with in the Gen1Box class) */
* bool isMainChecksumValid();
* So yeah, this function is about the whole memory bank checksums in memory bank 2 and 3
*/ /**
bool isWholeBoxBankValid(uint8_t bankIndex); * @brief Updates the main data checksum in bank 1
*/
/** void updateMainChecksum();
* @brief This function updates the checksum value of the whole box bank (bank 2 or 3) /**
*/ * @brief Checks whether the whole bank checksum of the given memory bank is valid.
void updateWholeBoxBankChecksum(uint8_t bankIndex); * As you may or may not know, bank 2 and bank 3 contain the PC Pokemon boxes of the player
protected: * These memory banks have 2 types of checksums: a whole memory bank checksum (which is this one)
private: * And a checksum per box. (which will be dealt with in the Gen1Box class)
IRomReader& romReader_; *
ISaveManager& saveManager_; * So yeah, this function is about the whole memory bank checksums in memory bank 2 and 3
Gen1SpriteDecoder spriteDecoder_; */
Gen1IconDecoder iconDecoder_; bool isWholeBoxBankValid(uint8_t bankIndex);
Gen1GameType gameType_;
Gen1LocalizationLanguage localization_; /**
}; * @brief This function updates the checksum value of the whole box bank (bank 2 or 3)
*/
void updateWholeBoxBankChecksum(uint8_t bankIndex);
protected:
private:
IRomReader& romReader_;
ISaveManager& saveManager_;
Gen1SpriteDecoder spriteDecoder_;
Gen1IconDecoder iconDecoder_;
Gen1GameType gameType_;
Gen1LocalizationLanguage localization_;
};
#endif #endif

View File

@ -1,90 +1,91 @@
#ifndef _GEN1LOCALIZATION_H #ifndef _GEN1LOCALIZATION_H
#define _GEN1LOCALIZATION_H #define _GEN1LOCALIZATION_H
#include "gen1/Gen1Common.h" #include "gen1/Gen1Common.h"
/** /**
* @brief This enum defines the supported localization languages for gen 1. * @brief This enum defines the supported localization languages for gen 1.
* MAX is invalid. It is defined to be used in a for loop if needed. * MAX is invalid. It is defined to be used in a for loop if needed.
* It can also be used as an invalid value to force Gen1GameReader to identify * It can also be used as an invalid value to force Gen1GameReader to identify
* the localization language * the localization language
*/ */
enum class Gen1LocalizationLanguage enum class Gen1LocalizationLanguage
{ {
ENGLISH, ENGLISH,
FRENCH, FRENCH,
SPANISH, SPANISH,
GERMAN, GERMAN,
ITALIAN, ITALIAN,
JAPANESE, JAPANESE,
MAX MAX
}; };
/** /**
* @brief Different pokémon localizations unfortunately also store data at different locations in the rom. * @brief Different pokémon localizations unfortunately also store data at different locations in the rom.
* This is because stuff shifts around because of the different text. * This is because stuff shifts around because of the different text.
* *
* So to deal with different localizations of the same game, we need to keep track of the various rom offsets we need * So to deal with different localizations of the same game, we need to keep track of the various rom offsets we need
* for each of them. * for each of them.
* *
*/ */
typedef struct Gen1LocalizationRomOffsets typedef struct Gen1LocalizationRomOffsets
{ {
/** /**
* @brief The rom offset at which the species structs can be found containing the base stats * @brief The rom offset at which the species structs can be found containing the base stats
* of the various pokémon. https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_species_data_structure_(Generation_I) * of the various pokémon. https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_species_data_structure_(Generation_I)
*/ */
uint32_t stats; uint32_t stats;
/** /**
* @brief For some reason, in the Pokémon Blue and Red versions, the mew species(stats) struct is stored separately. * @brief For some reason, in the Pokémon Blue and Red versions, the mew species(stats) struct is stored separately.
* This does not apply to Pokémon Yellow * This does not apply to Pokémon Yellow
*/ */
uint32_t statsMew; uint32_t statsMew;
/** /**
* @brief This is the rom offset at which the pokedex numbers of pokémon are stored in pokemon index order * @brief This is the rom offset at which the pokedex numbers of pokémon are stored in pokemon index order
*/ */
uint32_t numbers; uint32_t numbers;
/** /**
* @brief This is the rom offset at which we can find the encoded pokémon names. * @brief This is the rom offset at which we can find the encoded pokémon names.
* These can be decoded with gen1_decodePokeText() * These can be decoded with gen1_decodePokeText()
*/ */
uint32_t names; uint32_t names;
/** /**
* @brief This is the rom offset at which we can find the party menu icon type for each pokémon in pokedex number order. * @brief This is the rom offset at which we can find the party menu icon type for each pokémon in pokedex number order.
*/ */
uint32_t iconTypes; uint32_t iconTypes;
/** /**
* @brief This is the rom offset at which we can find the MonPartySpritePointers pointer table * @brief This is the rom offset at which we can find the MonPartySpritePointers pointer table
* This defines where the icon sprites are stored in the rom (and the number of tiles) * This defines where the icon sprites are stored in the rom (and the number of tiles)
* *
* see https://github.com/pret/pokered/blob/master/data/icon_pointers.asm * see https://github.com/pret/pokered/blob/master/data/icon_pointers.asm
*/ */
uint32_t icons; uint32_t icons;
/** /**
* @brief This is the rom offset at which a palette index is stored for each pokémon in pokedex number order. * @brief This is the rom offset at which a palette index is stored for each pokémon in pokedex number order.
*/ */
uint32_t paletteIndices; uint32_t paletteIndices;
/** /**
* @brief This is the rom offset at which each pokémon sprite color palette is stored in palette index order * @brief This is the rom offset at which each pokémon sprite color palette is stored in palette index order
*/ */
uint32_t palettes; uint32_t palettes;
} Gen1LocalizationRomOffsets; } Gen1LocalizationRomOffsets;
typedef struct Gen1LocalizationSRAMOffsets typedef struct Gen1LocalizationSRAMOffsets
{ {
uint32_t trainerID; uint32_t trainerID;
uint32_t currentMap; uint32_t trainerMoney;
uint32_t dexSeen; uint32_t currentMap;
uint32_t dexOwned; uint32_t dexSeen;
uint32_t rivalName; uint32_t dexOwned;
uint32_t party; uint32_t rivalName;
uint32_t currentBoxIndex; uint32_t party;
uint32_t checksum; uint32_t currentBoxIndex;
} Gen1LocalizationSRAMOffsets; uint32_t checksum;
} Gen1LocalizationSRAMOffsets;
const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language);
const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language); const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language);
const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language);
#endif #endif

View File

@ -131,6 +131,18 @@ public:
*/ */
const char *getTrainerName() const; 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 * @brief Get the rival name from the save file
* Note: the resulting const char* does not need to be free'd. * 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 currentBox;
uint32_t dexSeen; uint32_t dexSeen;
uint32_t dexOwned; uint32_t dexOwned;
uint32_t trainerMoney;
uint32_t party; uint32_t party;
uint32_t eventFlags; uint32_t eventFlags;
uint32_t mainChecksum; uint32_t mainChecksum;

File diff suppressed because it is too large Load Diff

View File

@ -1,197 +1,199 @@
#include "gen1/Gen1Localization.h" #include "gen1/Gen1Localization.h"
static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsRGB[] = { static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsRGB[] = {
// English // English
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x41024, .numbers = 0x41024,
.names = 0x1c21e, .names = 0x1c21e,
.iconTypes = 0x7190D, .iconTypes = 0x7190D,
.icons = 0x717C0, .icons = 0x717C0,
.paletteIndices = 0x725C9, .paletteIndices = 0x725C9,
.palettes = 0x72660 .palettes = 0x72660
}, },
// French // French
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x40FAA, .numbers = 0x40FAA,
.names = 0x1C21E, .names = 0x1C21E,
.iconTypes = 0x718DE, .iconTypes = 0x718DE,
.icons = 0x71791, .icons = 0x71791,
.paletteIndices = 0x7259A, .paletteIndices = 0x7259A,
.palettes = 0x72631 .palettes = 0x72631
}, },
// Spanish // Spanish
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x40FB4, .numbers = 0x40FB4,
.names = 0x1C21E, .names = 0x1C21E,
.iconTypes = 0x718FD, .iconTypes = 0x718FD,
.icons = 0x717B0, .icons = 0x717B0,
.paletteIndices = 0x725B9, .paletteIndices = 0x725B9,
.palettes = 0x72650 .palettes = 0x72650
}, },
// German // German
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x40F96, .numbers = 0x40F96,
.names = 0x1C21E, .names = 0x1C21E,
.iconTypes = 0x718E7, .iconTypes = 0x718E7,
.icons = 0x7179A, .icons = 0x7179A,
.paletteIndices = 0x725A3, .paletteIndices = 0x725A3,
.palettes = 0x7263A .palettes = 0x7263A
}, },
// Italian // Italian
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x40FB6, .numbers = 0x40FB6,
.names = 0x1C21E, .names = 0x1C21E,
.iconTypes = 0x7194D, .iconTypes = 0x7194D,
.icons = 0x71800, .icons = 0x71800,
.paletteIndices = 0x72609, .paletteIndices = 0x72609,
.palettes = 0x726A0 .palettes = 0x726A0
}, },
// Japanese // Japanese
{ {
.stats = 0x38000, .stats = 0x38000,
.statsMew = 0x4200, .statsMew = 0x4200,
.numbers = 0x4279A, .numbers = 0x4279A,
.names = 0x39068, .names = 0x39068,
.iconTypes = 0x71DD1, .iconTypes = 0x71DD1,
.icons = 0x71C84, .icons = 0x71C84,
.paletteIndices = 0x72A1F, .paletteIndices = 0x72A1F,
.palettes = 0x72AB6 .palettes = 0x72AB6
} }
}; };
static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsBlueJpn = { static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsBlueJpn = {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x425B, .statsMew = 0x425B,
.numbers = 0x42784, .numbers = 0x42784,
.names = 0x39446, .names = 0x39446,
.iconTypes = 0x71DC1, .iconTypes = 0x71DC1,
.icons = 0x71C74, .icons = 0x71C74,
.paletteIndices = 0x72A0E, .paletteIndices = 0x72A0E,
.palettes = 0x72AA5 .palettes = 0x72AA5
}; };
static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsY[] = { static const Gen1LocalizationRomOffsets g1_localizationROMOffsetsY[] = {
// English // English
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x410B1, .numbers = 0x410B1,
.names = 0xE8000, .names = 0xE8000,
.iconTypes = 0x719BA, .iconTypes = 0x719BA,
.icons = 0x7184D, .icons = 0x7184D,
.paletteIndices = 0x72922, .paletteIndices = 0x72922,
.palettes = 0x72AF9 .palettes = 0x72AF9
}, },
// French // French
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x41036, .numbers = 0x41036,
.names = 0xE8000, .names = 0xE8000,
.iconTypes = 0x7198B, .iconTypes = 0x7198B,
.icons = 0x7181E, .icons = 0x7181E,
.paletteIndices = 0x728F3, .paletteIndices = 0x728F3,
.palettes = 0x72ACA .palettes = 0x72ACA
}, },
// Spanish // Spanish
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x41041, .numbers = 0x41041,
.names = 0xE8000, .names = 0xE8000,
.iconTypes = 0x719AA, .iconTypes = 0x719AA,
.icons = 0x7183D, .icons = 0x7183D,
.paletteIndices = 0x72912, .paletteIndices = 0x72912,
.palettes = 0x72AE9 .palettes = 0x72AE9
}, },
// German // German
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x41023, .numbers = 0x41023,
.names = 0xE8000, .names = 0xE8000,
.iconTypes = 0x71999, .iconTypes = 0x71999,
.icons = 0x7182c, .icons = 0x7182c,
.paletteIndices = 0x72901, .paletteIndices = 0x72901,
.palettes = 0x72AD8 .palettes = 0x72AD8
}, },
// Italian // Italian
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x41043, .numbers = 0x41043,
.names = 0xE8000, .names = 0xE8000,
.iconTypes = 0x719FA, .iconTypes = 0x719FA,
.icons = 0x7188D, .icons = 0x7188D,
.paletteIndices = 0x72962, .paletteIndices = 0x72962,
.palettes = 0x72B39 .palettes = 0x72B39
}, },
// Japanese // Japanese
{ {
.stats = 0x383DE, .stats = 0x383DE,
.statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow .statsMew = 0x0, // IRRELEVANT because mew is included in the stats list for Yellow
.numbers = 0x4282D, .numbers = 0x4282D,
.names = 0x39462, .names = 0x39462,
.iconTypes = 0x71911, .iconTypes = 0x71911,
.icons = 0x717A4, .icons = 0x717A4,
.paletteIndices = 0x72650, .paletteIndices = 0x72650,
.palettes = 0x726E7 .palettes = 0x726E7
} }
}; };
static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsInternational = { static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsInternational = {
.trainerID = 0x2605, .trainerID = 0x2605,
.currentMap = 0x260A, .trainerMoney = 0x25F3,
.dexSeen = 0x25B6, .currentMap = 0x260A,
.dexOwned = 0x25A3, .dexSeen = 0x25B6,
.rivalName = 0x25F6, .dexOwned = 0x25A3,
.party = 0x2F2C, .rivalName = 0x25F6,
.currentBoxIndex = 0x284C, .party = 0x2F2C,
.checksum = 0x3523 .currentBoxIndex = 0x284C,
}; .checksum = 0x3523
};
static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsJapan = {
.trainerID = 0x25FB, static const Gen1LocalizationSRAMOffsets g1_sRAMOffsetsJapan = {
.currentMap = 0x2600, .trainerID = 0x25FB,
.dexSeen = 0x25B1, .trainerMoney = 0x25EE,
.dexOwned = 0x259E, .currentMap = 0x2600,
.rivalName = 0x25F1, .dexSeen = 0x25B1,
.party = 0x2ED5, .dexOwned = 0x259E,
.currentBoxIndex = 0x2842, .rivalName = 0x25F1,
.checksum = 0x3594 .party = 0x2ED5,
}; .currentBoxIndex = 0x2842,
.checksum = 0x3594
const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language) };
{
if(language == Gen1LocalizationLanguage::JAPANESE && gameType == Gen1GameType::BLUE) const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language)
{ {
return g1_localizationROMOffsetsBlueJpn; if(language == Gen1LocalizationLanguage::JAPANESE && gameType == Gen1GameType::BLUE)
} {
else return g1_localizationROMOffsetsBlueJpn;
{ }
switch(gameType) else
{ {
case Gen1GameType::RED: switch(gameType)
case Gen1GameType::BLUE: {
case Gen1GameType::GREEN: case Gen1GameType::RED:
return g1_localizationROMOffsetsRGB[(uint8_t)language]; case Gen1GameType::BLUE:
default: case Gen1GameType::GREEN:
return g1_localizationROMOffsetsY[(uint8_t)language]; return g1_localizationROMOffsetsRGB[(uint8_t)language];
} default:
} return g1_localizationROMOffsetsY[(uint8_t)language];
} }
}
const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language) }
{
return (language != Gen1LocalizationLanguage::JAPANESE) ? g1_sRAMOffsetsInternational : g1_sRAMOffsetsJapan; const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language)
{
return (language != Gen1LocalizationLanguage::JAPANESE) ? g1_sRAMOffsetsInternational : g1_sRAMOffsetsJapan;
} }

View File

@ -642,6 +642,24 @@ const char *Gen2GameReader::getTrainerName() const
return result; 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 const char *Gen2GameReader::getRivalName() const
{ {
static char result[20]; static char result[20];

View File

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