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

View File

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

View File

@ -1,204 +1,215 @@
#ifndef GEN1GAMEREADER_H
#define GEN1GAMEREADER_H
#include "gen1/Gen1SpriteDecoder.h"
#include "gen1/Gen1IconDecoder.h"
#include "gen1/Gen1PlayerPokemonStorage.h"
#include "gen1/Gen1DistributionPokemon.h"
#include "gen1/Gen1Localization.h"
#include "gen1/Gen1Maps.h"
class IRomReader;
class ISaveManager;
class Gen1GameReader
{
public:
Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language = Gen1LocalizationLanguage::MAX);
/**
* @brief Retrieves the current game cartridges'/roms' language
*/
Gen1LocalizationLanguage getGameLanguage() const;
/**
* @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,
* so make sure to strdup() it if you need to store it for later
*
* @return const char*
*/
const char* getPokemonName(uint8_t index) const;
/**
* @brief Gets the pokedex number of a pokemon at the given internal index
*/
uint8_t getPokemonNumber(uint8_t index) const;
/**
* @brief This function returns the Gen1PokemonIconType for a pokemon at the given index
*/
Gen1PokemonIconType getPokemonIconType(uint8_t index) const;
/**
* @brief With this function, you can check if the index is valid and not referring to missingno
*/
bool isValidIndex(uint8_t index) const;
/**
* @brief This function reads the pokemon stats at the given index
*
* @return whether or not this operation was successful
*/
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
*/
uint8_t getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber);
/**
* @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
*
* Based on: https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I)
*
* @param outColorPalette
*/
void readColorPalette(uint8_t paletteId, uint16_t* outColorPalette);
/**
* @brief Get the trainer name from the save file
* 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.
*/
const char* getTrainerName() const;
/**
* @brief Get the rival name from the save file
* 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.
*/
const char* getRivalName() const;
/**
* @brief Get the player ID from the save file
*/
uint16_t getTrainerID() const;
/**
* Retrieves the current map the player is in.
*/
Gen1Maps getCurrentMap() const;
/**
* @brief Returns a Gen1Party instance, which can be used to retrieve the information about the pokemon currently in the trainers' party
*/
Gen1Party getParty();
/**
* @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
*/
Gen1Box getBox(uint8_t boxIndex);
/**
* @brief Returns the 0-based index of the currently selected PC box for pokemon
*/
uint8_t getCurrentBoxIndex();
/**
* @brief This function returns whether the pokemon with the given pokedexNumber has been SEEN/OWNED.
*/
bool getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
/**
* @brief This function sets the pokedex flag for the given pokedexNumber
*/
void setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
/**
* @brief This function returns the counter value of the given dex flag
*/
uint8_t getPokedexCounter(PokedexFlag dexFlag) 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.
* The size of the returned buffer is SPRITE_BITPLANE_BUFFER_SIZE_IN_BYTES * 2
*
* 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* decodeSprite(uint8_t bankIndex, uint16_t pointer);
/**
* @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.
* You need to feed it to an instance of SpriteRenderer to convert it to a useful format
*
* 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
*/
uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame = true);
/**
* @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
*
* @param originalTrainerID optional parameter. If not specified (=null), the original trainer id string will be set to the players'
* @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)
* 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);
/**
* @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
*
* @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)
* 0xFE - Added to players' party
* 0x0 - 0xB - Added to box at index <value>
*/
uint8_t addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname = 0);
/**
* @brief This function checks whether the main checksum of bank 1 is valid
*/
bool isMainChecksumValid();
/**
* @brief Updates the main data checksum in bank 1
*/
void updateMainChecksum();
/**
* @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)
* And a checksum per box. (which will be dealt with in the Gen1Box class)
*
* So yeah, this function is about the whole memory bank checksums in memory bank 2 and 3
*/
bool isWholeBoxBankValid(uint8_t bankIndex);
/**
* @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_;
};
#ifndef GEN1GAMEREADER_H
#define GEN1GAMEREADER_H
#include "gen1/Gen1SpriteDecoder.h"
#include "gen1/Gen1IconDecoder.h"
#include "gen1/Gen1PlayerPokemonStorage.h"
#include "gen1/Gen1DistributionPokemon.h"
#include "gen1/Gen1Localization.h"
#include "gen1/Gen1Maps.h"
class IRomReader;
class ISaveManager;
class Gen1GameReader
{
public:
Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language = Gen1LocalizationLanguage::MAX);
/**
* @brief Retrieves the current game cartridges'/roms' language
*/
Gen1LocalizationLanguage getGameLanguage() const;
/**
* @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,
* so make sure to strdup() it if you need to store it for later
*
* @return const char*
*/
const char* getPokemonName(uint8_t index) const;
/**
* @brief Gets the pokedex number of a pokemon at the given internal index
*/
uint8_t getPokemonNumber(uint8_t index) const;
/**
* @brief This function returns the Gen1PokemonIconType for a pokemon at the given index
*/
Gen1PokemonIconType getPokemonIconType(uint8_t index) const;
/**
* @brief With this function, you can check if the index is valid and not referring to missingno
*/
bool isValidIndex(uint8_t index) const;
/**
* @brief This function reads the pokemon stats at the given index
*
* @return whether or not this operation was successful
*/
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
*/
uint8_t getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber);
/**
* @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
*
* Based on: https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I)
*
* @param outColorPalette
*/
void readColorPalette(uint8_t paletteId, uint16_t* outColorPalette);
/**
* @brief Get the trainer name from the save file
* 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.
*/
const char* getTrainerName() const;
/**
* @brief Get the rival name from the save file
* 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.
*/
const char* getRivalName() const;
/**
* @brief Get the player ID from the save file
*/
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.
*/
Gen1Maps getCurrentMap() const;
/**
* @brief Returns a Gen1Party instance, which can be used to retrieve the information about the pokemon currently in the trainers' party
*/
Gen1Party getParty();
/**
* @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
*/
Gen1Box getBox(uint8_t boxIndex);
/**
* @brief Returns the 0-based index of the currently selected PC box for pokemon
*/
uint8_t getCurrentBoxIndex();
/**
* @brief This function returns whether the pokemon with the given pokedexNumber has been SEEN/OWNED.
*/
bool getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
/**
* @brief This function sets the pokedex flag for the given pokedexNumber
*/
void setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const;
/**
* @brief This function returns the counter value of the given dex flag
*/
uint8_t getPokedexCounter(PokedexFlag dexFlag) 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.
* The size of the returned buffer is SPRITE_BITPLANE_BUFFER_SIZE_IN_BYTES * 2
*
* 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* decodeSprite(uint8_t bankIndex, uint16_t pointer);
/**
* @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.
* You need to feed it to an instance of SpriteRenderer to convert it to a useful format
*
* 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
*/
uint8_t* decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame = true);
/**
* @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
*
* @param originalTrainerID optional parameter. If not specified (=null), the original trainer id string will be set to the players'
* @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)
* 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);
/**
* @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
*
* @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)
* 0xFE - Added to players' party
* 0x0 - 0xB - Added to box at index <value>
*/
uint8_t addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname = 0);
/**
* @brief This function checks whether the main checksum of bank 1 is valid
*/
bool isMainChecksumValid();
/**
* @brief Updates the main data checksum in bank 1
*/
void updateMainChecksum();
/**
* @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)
* And a checksum per box. (which will be dealt with in the Gen1Box class)
*
* So yeah, this function is about the whole memory bank checksums in memory bank 2 and 3
*/
bool isWholeBoxBankValid(uint8_t bankIndex);
/**
* @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

View File

@ -1,90 +1,91 @@
#ifndef _GEN1LOCALIZATION_H
#define _GEN1LOCALIZATION_H
#include "gen1/Gen1Common.h"
/**
* @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.
* It can also be used as an invalid value to force Gen1GameReader to identify
* the localization language
*/
enum class Gen1LocalizationLanguage
{
ENGLISH,
FRENCH,
SPANISH,
GERMAN,
ITALIAN,
JAPANESE,
MAX
};
/**
* @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.
*
* 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.
*
*/
typedef struct Gen1LocalizationRomOffsets
{
/**
* @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)
*/
uint32_t stats;
/**
* @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
*/
uint32_t statsMew;
/**
* @brief This is the rom offset at which the pokedex numbers of pokémon are stored in pokemon index order
*/
uint32_t numbers;
/**
* @brief This is the rom offset at which we can find the encoded pokémon names.
* These can be decoded with gen1_decodePokeText()
*/
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.
*/
uint32_t iconTypes;
/**
* @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)
*
* see https://github.com/pret/pokered/blob/master/data/icon_pointers.asm
*/
uint32_t icons;
/**
* @brief This is the rom offset at which a palette index is stored for each pokémon in pokedex number order.
*/
uint32_t paletteIndices;
/**
* @brief This is the rom offset at which each pokémon sprite color palette is stored in palette index order
*/
uint32_t palettes;
} Gen1LocalizationRomOffsets;
typedef struct Gen1LocalizationSRAMOffsets
{
uint32_t trainerID;
uint32_t currentMap;
uint32_t dexSeen;
uint32_t dexOwned;
uint32_t rivalName;
uint32_t party;
uint32_t currentBoxIndex;
uint32_t checksum;
} Gen1LocalizationSRAMOffsets;
const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language);
const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language);
#ifndef _GEN1LOCALIZATION_H
#define _GEN1LOCALIZATION_H
#include "gen1/Gen1Common.h"
/**
* @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.
* It can also be used as an invalid value to force Gen1GameReader to identify
* the localization language
*/
enum class Gen1LocalizationLanguage
{
ENGLISH,
FRENCH,
SPANISH,
GERMAN,
ITALIAN,
JAPANESE,
MAX
};
/**
* @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.
*
* 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.
*
*/
typedef struct Gen1LocalizationRomOffsets
{
/**
* @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)
*/
uint32_t stats;
/**
* @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
*/
uint32_t statsMew;
/**
* @brief This is the rom offset at which the pokedex numbers of pokémon are stored in pokemon index order
*/
uint32_t numbers;
/**
* @brief This is the rom offset at which we can find the encoded pokémon names.
* These can be decoded with gen1_decodePokeText()
*/
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.
*/
uint32_t iconTypes;
/**
* @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)
*
* see https://github.com/pret/pokered/blob/master/data/icon_pointers.asm
*/
uint32_t icons;
/**
* @brief This is the rom offset at which a palette index is stored for each pokémon in pokedex number order.
*/
uint32_t paletteIndices;
/**
* @brief This is the rom offset at which each pokémon sprite color palette is stored in palette index order
*/
uint32_t palettes;
} Gen1LocalizationRomOffsets;
typedef struct Gen1LocalizationSRAMOffsets
{
uint32_t trainerID;
uint32_t trainerMoney;
uint32_t currentMap;
uint32_t dexSeen;
uint32_t dexOwned;
uint32_t rivalName;
uint32_t party;
uint32_t currentBoxIndex;
uint32_t checksum;
} Gen1LocalizationSRAMOffsets;
const Gen1LocalizationRomOffsets& gen1_getRomOffsets(Gen1GameType gameType, Gen1LocalizationLanguage language);
const Gen1LocalizationSRAMOffsets& gen1_getSRAMOffsets(Gen1LocalizationLanguage language);
#endif

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;

File diff suppressed because it is too large Load Diff

View File

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

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,