diff --git a/Makefile b/Makefile index aff3e0e..ac160e0 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/examples/checkSave/main.cpp b/examples/checkSave/main.cpp index 3d58ccb..3f32b90 100644 --- a/examples/checkSave/main.cpp +++ b/examples/checkSave/main.cpp @@ -1,73 +1,79 @@ -#include "gen1/Gen1GameReader.h" -#include "gen2/Gen2GameReader.h" -#include "RomReader.h" -#include "SaveManager.h" -#include "utils.h" - -#include -#include -#include - -static void print_usage() -{ - printf("Usage: checkSave \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 +#include +#include + +static void print_usage() +{ + printf("Usage: checkSave \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; } \ No newline at end of file diff --git a/include/gen1/Gen1GameReader.h b/include/gen1/Gen1GameReader.h index a6f66df..34a1684 100644 --- a/include/gen1/Gen1GameReader.h +++ b/include/gen1/Gen1GameReader.h @@ -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 - */ - 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 - */ - 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 + */ + 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 + */ + 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 \ No newline at end of file diff --git a/include/gen1/Gen1Localization.h b/include/gen1/Gen1Localization.h index 4810123..1ac7811 100644 --- a/include/gen1/Gen1Localization.h +++ b/include/gen1/Gen1Localization.h @@ -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 \ No newline at end of file diff --git a/include/gen2/Gen2GameReader.h b/include/gen2/Gen2GameReader.h index 6f7b813..0b06b08 100644 --- a/include/gen2/Gen2GameReader.h +++ b/include/gen2/Gen2GameReader.h @@ -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. diff --git a/include/gen2/Gen2Localization.h b/include/gen2/Gen2Localization.h index 95ea03f..ca535da 100644 --- a/include/gen2/Gen2Localization.h +++ b/include/gen2/Gen2Localization.h @@ -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; diff --git a/src/gen1/Gen1GameReader.cpp b/src/gen1/Gen1GameReader.cpp index 838491b..b82829a 100644 --- a/src/gen1/Gen1GameReader.cpp +++ b/src/gen1/Gen1GameReader.cpp @@ -1,575 +1,643 @@ -#include "gen1/Gen1GameReader.h" -#include "gen1/Gen1SpriteDecoder.h" -#include "RomReader.h" -#include "SaveManager.h" -#include "utils.h" - -#include -#include - -/** - * @brief This function calculates the main data checksum - */ -uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLanguage localization) -{ - Gen1Checksum checksum; - const uint32_t checksummedDataStart = 0x2598; - const uint32_t checksummedDataEnd = gen1_getSRAMOffsets(localization).checksum; - const uint32_t numBytes = checksummedDataEnd - checksummedDataStart; - uint32_t i; - uint8_t byte; - - saveManager.seek(checksummedDataStart); - - for(i=0; i < numBytes; ++i) - { - saveManager.readByte(byte); - checksum.addByte(byte); - } - - return checksum.get(); -} - -uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankIndex) -{ - Gen1Checksum checksum; - const uint16_t checksummedDataStart = 0x0; - const uint16_t checksummedDataEnd = 0x1A4C; - const uint16_t numBytes = checksummedDataEnd - checksummedDataStart; - uint16_t i; - uint8_t byte; - - saveManager.seekToBankOffset(bankIndex, checksummedDataStart); - for(i=0; i < numBytes; ++i) - { - saveManager.readByte(byte); - checksum.addByte(byte); - } - - return checksum.get(); -} - -Gen1GameReader::Gen1GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen1GameType gameType, Gen1LocalizationLanguage language) - : romReader_(romReader) - , saveManager_(saveManager) - , spriteDecoder_(romReader_) - , iconDecoder_(romReader, gameType) - , gameType_(gameType) - , localization_(language) -{ - if(language == Gen1LocalizationLanguage::MAX) - { - localization_ = gen1_determineGameLanguage(romReader, gameType); - } -} - -Gen1LocalizationLanguage Gen1GameReader::getGameLanguage() const -{ - return localization_; -} - -const char *Gen1GameReader::getPokemonName(uint8_t index) const -{ - // based on: https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L493 - static char result[20]; - uint8_t encodedText[0xA]; - uint32_t numRead; - const uint16_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0xA : 0x5; - const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).names; - - if(!romOffset) - { - snprintf(result, sizeof(result) - 1, "poke-%hhu", index); - return result; - } - - romReader_.seek(romOffset); - romReader_.advance((index - 1) * entrySize); - - // max 10 bytes - numRead = romReader_.readUntil(encodedText, 0x50, entrySize); - - gen1_decodePokeText(encodedText, numRead, result, sizeof(result), localization_); - return result; -} - -uint8_t Gen1GameReader::getPokemonNumber(uint8_t index) const -{ - // Based on https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L509 - uint8_t result = 0xFF; - const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).numbers; - romReader_.seek(romOffset + (index - 1)); - romReader_.readByte(result); - return result; -} - -Gen1PokemonIconType Gen1GameReader::getPokemonIconType(uint8_t index) const -{ - //MonPartyData in pret/pokered and pret/pokeyellow - // strangely, this array is in pokemon _number_ order, not index - uint8_t number = getPokemonNumber(index); - const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).iconTypes; - uint8_t byteVal; - Gen1PokemonIconType result; - - if(number == 0xFF) - { - // invalid number! - return Gen1PokemonIconType::GEN1_ICONTYPE_MAX; - } - // number is 1-based, but the array/offset is obviously 0-based. So subtract one - --number; - - romReader_.seek(romOffset); - - // MonPartyData is a nybble (4 bit) array. - romReader_.advance((number / 2)); - romReader_.readByte(byteVal); - - if((number & 0x1) == 0) - { - // if the number is even, the upper 4 bits need to be used - result = (Gen1PokemonIconType)(byteVal >> 4); - } - else - { - // if odd, the lower 4 bits need to be used - result = (Gen1PokemonIconType)(byteVal & 0x0F); - } - return result; -} - -bool Gen1GameReader::isValidIndex(uint8_t index) const -{ - switch(index) - { - case 0x0: - case 0x1F: - case 0x20: - case 0x32: - case 0x34: - case 0x38: - case 0x3D: - case 0x3E: - case 0x3F: - case 0x43: - case 0x44: - case 0x45: - case 0x4F: - case 0x50: - case 0x51: - case 0x56: - case 0x57: - case 0x5E: - case 0x5F: - case 0x73: - case 0x79: - case 0x7A: - case 0x7F: - case 0x86: - case 0x87: - case 0x89: - case 0x8C: - case 0x92: - case 0x9C: - case 0x9F: - case 0xA0: - case 0xA1: - case 0xA2: - case 0xAC: - case 0xAE: - case 0xAF: - case 0xB5: - case 0xB6: - case 0xB7: - case 0xB8: - // Invalid (MISSINGNO) entry - return false; - default: - break; - } - return (index < 192); -} - -bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outStats) const -{ - // Strangely enough, the pokemon stats data is not stored by index, but by pokedex number. - // So we need to do a conversion here - // Based on: https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_species_data_structure_(Generation_I)#:~:text=The%20Pok%C3%A9mon%20species%20data%20structure,order%20rather%20than%20index%20number. - // https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L516 - const uint8_t statsStructSize = 28; - const uint8_t pokeNumber = getPokemonNumber(index); - - uint8_t spriteBank; - - if (gameType_ != Gen1GameType::YELLOW && index == 0x15) - { - spriteBank = 0x1; - } - else if (index == 0xB6) - { - spriteBank = 0xB; - } - else if (index < 0x1F) - { - spriteBank = 0x9; - } - else if (index < 0x4A) - { - spriteBank = 0xA; - } - else if (index < 0x74) - { - spriteBank = 0xB; - } - else if (index < 0x99) - { - spriteBank = 0xC; - } - else - { - spriteBank = 0xD; - } - - if(gameType_ != Gen1GameType::YELLOW) - { - // Mew (of which the pokenumber is 151) stats are stored at a completely different location in the rom than the rest - if (pokeNumber != 151) - { - romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); - } - else - { - // mew stats - romReader_.seek(gen1_getRomOffsets(gameType_, localization_).statsMew); - } - - if (pokeNumber != 151) - { - // the number is 1-based. - romReader_.advance(statsStructSize * (pokeNumber - 1)); - } - } - else - { - // Dealing with Pokemon yellow - romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); - // the number is 1-based. - romReader_.advance(statsStructSize * (pokeNumber - 1)); - } - - romReader_.readByte(outStats.pokedex_number); - romReader_.readByte(outStats.base_hp); - romReader_.readByte(outStats.base_attack); - romReader_.readByte(outStats.base_defense); - romReader_.readByte(outStats.base_speed); - romReader_.readByte(outStats.base_special); - romReader_.readByte(outStats.type1); - romReader_.readByte(outStats.type2); - romReader_.readByte(outStats.catch_rate); - romReader_.readByte(outStats.base_exp_yield); - romReader_.readByte(outStats.front_sprite_dimensions); - outStats.sprite_bank = spriteBank; - romReader_.readUint16(outStats.pointer_to_frontsprite); - romReader_.readUint16(outStats.pointer_to_backsprite); - romReader_.read(outStats.lvl1_attacks, 4); - romReader_.readByte(outStats.growth_rate); - romReader_.read(outStats.tm_hm_flags, 7); - return true; -} - -uint8_t Gen1GameReader::getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber) -{ - uint8_t result; - // pokeyellow.map from https://github.com/pret/pokeyellow (after compilation) - const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).paletteIndices; - if(!romReader_.seek(romOffset + (pokeNumber - 1))) - { - return 0xFF; - } - - if(!romReader_.readByte(result)) - { - result = 0xFF; - } - - return result; -} - -void Gen1GameReader::readColorPalette(uint8_t paletteId, uint16_t* outColorPalette) -{ - uint16_t* cur = outColorPalette; - const uint16_t* end = cur + 4; - - // based on https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red_and_Blue/ROM_map - // and https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I) - const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).palettes; - romReader_.seek(romOffset + (paletteId * 8)); - while(cur < end) - { - romReader_.readUint16((*cur), Endianness::LITTLE); - ++cur; - } - -} - -const char *Gen1GameReader::getTrainerName() const -{ - static char result[20]; - uint8_t encodedPlayerName[0xB]; - saveManager_.seek(0x2598); - - saveManager_.readUntil(encodedPlayerName, 0x50, 0xB); - gen1_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result), localization_); - return result; -} - -const char *Gen1GameReader::getRivalName() const -{ - static char result[20]; - uint8_t encodedRivalName[0xB]; - const uint32_t savOffset = gen1_getSRAMOffsets(localization_).rivalName; - - saveManager_.seek(savOffset); - - saveManager_.readUntil(encodedRivalName, 0x50, sizeof(encodedRivalName)); - gen1_decodePokeText(encodedRivalName, sizeof(encodedRivalName), result, sizeof(result), localization_); - return result; -} - -uint16_t Gen1GameReader::getTrainerID() const -{ - uint16_t result; - const uint32_t savOffset = gen1_getSRAMOffsets(localization_).trainerID; - saveManager_.seek(savOffset); - saveManager_.readUint16(result); - - return result; -} - -Gen1Maps Gen1GameReader::getCurrentMap() const -{ - uint8_t result; - const uint32_t savOffset = gen1_getSRAMOffsets(localization_).currentMap; - saveManager_.seek(savOffset); - - saveManager_.readByte(result); - return static_cast(result); -} - -Gen1Party Gen1GameReader::getParty() -{ - return Gen1Party((*this), saveManager_, localization_); -} - -Gen1Box Gen1GameReader::getBox(uint8_t boxIndex) -{ - return Gen1Box((*this), saveManager_, boxIndex, localization_); -} - -uint8_t Gen1GameReader::getCurrentBoxIndex() -{ - uint8_t byte; - const uint32_t savOffset = gen1_getSRAMOffsets(localization_).currentBoxIndex; - - saveManager_.seek(savOffset); - saveManager_.readByte(byte); - - return byte & 0x3F; -} - -bool Gen1GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const -{ - const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); - const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; - uint8_t byte; - - if(pokedexNumber < 1 || pokedexNumber > 151) - { - return false; - } - // for the next operations, the pokedexNumber will be used as a zero-based value - --pokedexNumber; - - saveManager_.seek(saveOffset + (pokedexNumber / 8)); - saveManager_.readByte(byte); - - // in this case, bits are ordered within bytes from lowest to highest - // source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#bank1_main_pokedex - return (byte >> (pokedexNumber % 8)) & 0x1; -} - -void Gen1GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const -{ - const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); - const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; - uint8_t byte; - - if(pokedexNumber < 1 || pokedexNumber > 151) - { - return; - } - // for the next operations, the pokedexNumber will be used as a zero-based value - --pokedexNumber; - - saveManager_.seek(saveOffset + (pokedexNumber / 8)); - byte = saveManager_.peek(); - - // in this case, bits are ordered within bytes from lowest to highest - // source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#bank1_main_pokedex - byte |= (1 << (pokedexNumber % 8)); - saveManager_.writeByte(byte); -} - -uint8_t Gen1GameReader::getPokedexCounter(PokedexFlag dexFlag) const -{ - const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); - const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; - uint8_t bytes[19]; - uint8_t result = 0; - - saveManager_.seek(saveOffset); - saveManager_.read(bytes, sizeof(bytes)); - - const uint8_t *cur = bytes; - const uint8_t *const end = bytes + sizeof(bytes); - uint8_t bit = 0; - - while(cur < end) - { - bit = 0; - // while we're not at the last bit yet, walk through all the bits and add to counter if set - while(bit != 0x80) - { - if(bit == 0) - { - bit = 1; - } - else - { - bit <<= 1; - } - - if((*cur) & bit) - { - ++result; - } - } - - ++cur; - } - return result; -} - -uint8_t* Gen1GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer) -{ - return spriteDecoder_.decode(bankIndex, pointer); -} - -uint8_t* Gen1GameReader::decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame) -{ - return iconDecoder_.decode(localization_, iconType, firstFrame); -} - -uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname) -{ - Gen1Party party = getParty(); - uint8_t result = 0xFF; - const uint8_t pokedexNumber = getPokemonNumber(poke.poke_index); - - if(!originalTrainerID) - { - originalTrainerID = getTrainerName(); - } - - if(party.getNumberOfPokemon() < party.getMaxNumberOfPokemon()) - { - party.add(poke, originalTrainerID, nickname); - result = 0xFE; - } - else - { - const uint8_t currentBoxIndex = getCurrentBoxIndex(); - const uint8_t numBoxes = (localization_ != Gen1LocalizationLanguage::JAPANESE)? 12 : 8; - for(uint8_t i = 0; i < numBoxes; ++i) - { - Gen1Box box = getBox(i); - if(box.getNumberOfPokemon() == box.getMaxNumberOfPokemon()) - { - continue; - } - - box.add(poke, originalTrainerID, nickname); - result = i; - - updateWholeBoxBankChecksum(getGen1BoxBankIndex(i, currentBoxIndex, localization_)); - break; - } - } - setPokedexFlag(PokedexFlag::POKEDEX_SEEN, pokedexNumber); - setPokedexFlag(PokedexFlag::POKEDEX_OWNED, pokedexNumber); - updateMainChecksum(); - return result; -} - -uint8_t Gen1GameReader::addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname) -{ - Gen1TrainerPokemon poke = distributionPoke.poke; - const char* originalTrainerName; - - // apply the attributes of Gen1DistributionPokemon to the actual Gen2TrainerPokemon instance - gen1_prepareDistributionPokemon((*this), distributionPoke, poke, originalTrainerName); - - return addPokemon(poke, originalTrainerName, nickname); -} - -bool Gen1GameReader::isMainChecksumValid() -{ - const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; - uint8_t storedChecksum; - uint8_t calculatedChecksum; - - saveManager_.seek(mainDataChecksumOffset); - saveManager_.readByte(storedChecksum); - - calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); - - return (storedChecksum == calculatedChecksum); -} - -void Gen1GameReader::updateMainChecksum() -{ - const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; - const uint8_t calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); - - saveManager_.seek(mainDataChecksumOffset); - saveManager_.writeByte(calculatedChecksum); -} - -bool Gen1GameReader::isWholeBoxBankValid(uint8_t bankIndex) -{ - const uint16_t wholeBankChecksumOffset = 0x1A4C; - uint8_t storedChecksum; - uint8_t calculatedChecksum; - - saveManager_.seekToBankOffset(bankIndex, wholeBankChecksumOffset); - saveManager_.readByte(storedChecksum); - - calculatedChecksum = calculateWholeBoxBankChecksum(saveManager_, bankIndex); - - return (storedChecksum == calculatedChecksum); -} - -void Gen1GameReader::updateWholeBoxBankChecksum(uint8_t bankIndex) -{ - const uint16_t wholeBankChecksumOffset = 0x1A4C; - if(bankIndex < 2 || bankIndex > 3) - { - // only valid/needed for bank 2 and 3 - return; - } - - const uint8_t calculatedChecksum = calculateWholeBoxBankChecksum(saveManager_, bankIndex); - saveManager_.seekToBankOffset(bankIndex, wholeBankChecksumOffset); - saveManager_.writeByte(calculatedChecksum); +#include "gen1/Gen1GameReader.h" +#include "gen1/Gen1SpriteDecoder.h" +#include "RomReader.h" +#include "SaveManager.h" +#include "utils.h" + +#include +#include + +/** + * @brief This function calculates the main data checksum + */ +static uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLanguage localization) +{ + Gen1Checksum checksum; + const uint32_t checksummedDataStart = 0x2598; + const uint32_t checksummedDataEnd = gen1_getSRAMOffsets(localization).checksum; + const uint32_t numBytes = checksummedDataEnd - checksummedDataStart; + uint32_t i; + uint8_t byte; + + saveManager.seek(checksummedDataStart); + + for(i=0; i < numBytes; ++i) + { + saveManager.readByte(byte); + checksum.addByte(byte); + } + + return checksum.get(); +} + +static uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankIndex) +{ + Gen1Checksum checksum; + const uint16_t checksummedDataStart = 0x0; + const uint16_t checksummedDataEnd = 0x1A4C; + const uint16_t numBytes = checksummedDataEnd - checksummedDataStart; + uint16_t i; + uint8_t byte; + + saveManager.seekToBankOffset(bankIndex, checksummedDataStart); + for(i=0; i < numBytes; ++i) + { + saveManager.readByte(byte); + checksum.addByte(byte); + } + + 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) + , spriteDecoder_(romReader_) + , iconDecoder_(romReader, gameType) + , gameType_(gameType) + , localization_(language) +{ + if(language == Gen1LocalizationLanguage::MAX) + { + localization_ = gen1_determineGameLanguage(romReader, gameType); + } +} + +Gen1LocalizationLanguage Gen1GameReader::getGameLanguage() const +{ + return localization_; +} + +const char *Gen1GameReader::getPokemonName(uint8_t index) const +{ + // based on: https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L493 + static char result[20]; + uint8_t encodedText[0xA]; + uint32_t numRead; + const uint16_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0xA : 0x5; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).names; + + if(!romOffset) + { + snprintf(result, sizeof(result) - 1, "poke-%hhu", index); + return result; + } + + romReader_.seek(romOffset); + romReader_.advance((index - 1) * entrySize); + + // max 10 bytes + numRead = romReader_.readUntil(encodedText, 0x50, entrySize); + + gen1_decodePokeText(encodedText, numRead, result, sizeof(result), localization_); + return result; +} + +uint8_t Gen1GameReader::getPokemonNumber(uint8_t index) const +{ + // Based on https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L509 + uint8_t result = 0xFF; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).numbers; + romReader_.seek(romOffset + (index - 1)); + romReader_.readByte(result); + return result; +} + +Gen1PokemonIconType Gen1GameReader::getPokemonIconType(uint8_t index) const +{ + //MonPartyData in pret/pokered and pret/pokeyellow + // strangely, this array is in pokemon _number_ order, not index + uint8_t number = getPokemonNumber(index); + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).iconTypes; + uint8_t byteVal; + Gen1PokemonIconType result; + + if(number == 0xFF) + { + // invalid number! + return Gen1PokemonIconType::GEN1_ICONTYPE_MAX; + } + // number is 1-based, but the array/offset is obviously 0-based. So subtract one + --number; + + romReader_.seek(romOffset); + + // MonPartyData is a nybble (4 bit) array. + romReader_.advance((number / 2)); + romReader_.readByte(byteVal); + + if((number & 0x1) == 0) + { + // if the number is even, the upper 4 bits need to be used + result = (Gen1PokemonIconType)(byteVal >> 4); + } + else + { + // if odd, the lower 4 bits need to be used + result = (Gen1PokemonIconType)(byteVal & 0x0F); + } + return result; +} + +bool Gen1GameReader::isValidIndex(uint8_t index) const +{ + switch(index) + { + case 0x0: + case 0x1F: + case 0x20: + case 0x32: + case 0x34: + case 0x38: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x43: + case 0x44: + case 0x45: + case 0x4F: + case 0x50: + case 0x51: + case 0x56: + case 0x57: + case 0x5E: + case 0x5F: + case 0x73: + case 0x79: + case 0x7A: + case 0x7F: + case 0x86: + case 0x87: + case 0x89: + case 0x8C: + case 0x92: + case 0x9C: + case 0x9F: + case 0xA0: + case 0xA1: + case 0xA2: + case 0xAC: + case 0xAE: + case 0xAF: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + // Invalid (MISSINGNO) entry + return false; + default: + break; + } + return (index < 192); +} + +bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outStats) const +{ + // Strangely enough, the pokemon stats data is not stored by index, but by pokedex number. + // So we need to do a conversion here + // Based on: https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_species_data_structure_(Generation_I)#:~:text=The%20Pok%C3%A9mon%20species%20data%20structure,order%20rather%20than%20index%20number. + // https://github.com/seanmorris/pokemon-parser/blob/master/source/PokemonRom.js#L516 + const uint8_t statsStructSize = 28; + const uint8_t pokeNumber = getPokemonNumber(index); + + uint8_t spriteBank; + + if (gameType_ != Gen1GameType::YELLOW && index == 0x15) + { + spriteBank = 0x1; + } + else if (index == 0xB6) + { + spriteBank = 0xB; + } + else if (index < 0x1F) + { + spriteBank = 0x9; + } + else if (index < 0x4A) + { + spriteBank = 0xA; + } + else if (index < 0x74) + { + spriteBank = 0xB; + } + else if (index < 0x99) + { + spriteBank = 0xC; + } + else + { + spriteBank = 0xD; + } + + if(gameType_ != Gen1GameType::YELLOW) + { + // Mew (of which the pokenumber is 151) stats are stored at a completely different location in the rom than the rest + if (pokeNumber != 151) + { + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); + } + else + { + // mew stats + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).statsMew); + } + + if (pokeNumber != 151) + { + // the number is 1-based. + romReader_.advance(statsStructSize * (pokeNumber - 1)); + } + } + else + { + // Dealing with Pokemon yellow + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); + // the number is 1-based. + romReader_.advance(statsStructSize * (pokeNumber - 1)); + } + + romReader_.readByte(outStats.pokedex_number); + romReader_.readByte(outStats.base_hp); + romReader_.readByte(outStats.base_attack); + romReader_.readByte(outStats.base_defense); + romReader_.readByte(outStats.base_speed); + romReader_.readByte(outStats.base_special); + romReader_.readByte(outStats.type1); + romReader_.readByte(outStats.type2); + romReader_.readByte(outStats.catch_rate); + romReader_.readByte(outStats.base_exp_yield); + romReader_.readByte(outStats.front_sprite_dimensions); + outStats.sprite_bank = spriteBank; + romReader_.readUint16(outStats.pointer_to_frontsprite); + romReader_.readUint16(outStats.pointer_to_backsprite); + romReader_.read(outStats.lvl1_attacks, 4); + romReader_.readByte(outStats.growth_rate); + romReader_.read(outStats.tm_hm_flags, 7); + return true; +} + +uint8_t Gen1GameReader::getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber) +{ + uint8_t result; + // pokeyellow.map from https://github.com/pret/pokeyellow (after compilation) + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).paletteIndices; + if(!romReader_.seek(romOffset + (pokeNumber - 1))) + { + return 0xFF; + } + + if(!romReader_.readByte(result)) + { + result = 0xFF; + } + + return result; +} + +void Gen1GameReader::readColorPalette(uint8_t paletteId, uint16_t* outColorPalette) +{ + uint16_t* cur = outColorPalette; + const uint16_t* end = cur + 4; + + // based on https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red_and_Blue/ROM_map + // and https://bulbapedia.bulbagarden.net/wiki/List_of_color_palettes_by_index_number_(Generation_I) + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).palettes; + romReader_.seek(romOffset + (paletteId * 8)); + while(cur < end) + { + romReader_.readUint16((*cur), Endianness::LITTLE); + ++cur; + } + +} + +const char *Gen1GameReader::getTrainerName() const +{ + static char result[20]; + uint8_t encodedPlayerName[0xB]; + saveManager_.seek(0x2598); + + saveManager_.readUntil(encodedPlayerName, 0x50, 0xB); + gen1_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result), localization_); + return result; +} + +const char *Gen1GameReader::getRivalName() const +{ + static char result[20]; + uint8_t encodedRivalName[0xB]; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).rivalName; + + saveManager_.seek(savOffset); + + saveManager_.readUntil(encodedRivalName, 0x50, sizeof(encodedRivalName)); + gen1_decodePokeText(encodedRivalName, sizeof(encodedRivalName), result, sizeof(result), localization_); + return result; +} + +uint16_t Gen1GameReader::getTrainerID() const +{ + uint16_t result; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).trainerID; + saveManager_.seek(savOffset); + saveManager_.readUint16(result); + + 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; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).currentMap; + saveManager_.seek(savOffset); + + saveManager_.readByte(result); + return static_cast(result); +} + +Gen1Party Gen1GameReader::getParty() +{ + return Gen1Party((*this), saveManager_, localization_); +} + +Gen1Box Gen1GameReader::getBox(uint8_t boxIndex) +{ + return Gen1Box((*this), saveManager_, boxIndex, localization_); +} + +uint8_t Gen1GameReader::getCurrentBoxIndex() +{ + uint8_t byte; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).currentBoxIndex; + + saveManager_.seek(savOffset); + saveManager_.readByte(byte); + + return byte & 0x3F; +} + +bool Gen1GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const +{ + const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); + const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; + uint8_t byte; + + if(pokedexNumber < 1 || pokedexNumber > 151) + { + return false; + } + // for the next operations, the pokedexNumber will be used as a zero-based value + --pokedexNumber; + + saveManager_.seek(saveOffset + (pokedexNumber / 8)); + saveManager_.readByte(byte); + + // in this case, bits are ordered within bytes from lowest to highest + // source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#bank1_main_pokedex + return (byte >> (pokedexNumber % 8)) & 0x1; +} + +void Gen1GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const +{ + const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); + const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; + uint8_t byte; + + if(pokedexNumber < 1 || pokedexNumber > 151) + { + return; + } + // for the next operations, the pokedexNumber will be used as a zero-based value + --pokedexNumber; + + saveManager_.seek(saveOffset + (pokedexNumber / 8)); + byte = saveManager_.peek(); + + // in this case, bits are ordered within bytes from lowest to highest + // source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#bank1_main_pokedex + byte |= (1 << (pokedexNumber % 8)); + saveManager_.writeByte(byte); +} + +uint8_t Gen1GameReader::getPokedexCounter(PokedexFlag dexFlag) const +{ + const Gen1LocalizationSRAMOffsets& sramOffsets = gen1_getSRAMOffsets(localization_); + const uint32_t saveOffset = (dexFlag == POKEDEX_SEEN) ? sramOffsets.dexSeen : sramOffsets.dexOwned; + uint8_t bytes[19]; + uint8_t result = 0; + + saveManager_.seek(saveOffset); + saveManager_.read(bytes, sizeof(bytes)); + + const uint8_t *cur = bytes; + const uint8_t *const end = bytes + sizeof(bytes); + uint8_t bit = 0; + + while(cur < end) + { + bit = 0; + // while we're not at the last bit yet, walk through all the bits and add to counter if set + while(bit != 0x80) + { + if(bit == 0) + { + bit = 1; + } + else + { + bit <<= 1; + } + + if((*cur) & bit) + { + ++result; + } + } + + ++cur; + } + return result; +} + +uint8_t* Gen1GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer) +{ + return spriteDecoder_.decode(bankIndex, pointer); +} + +uint8_t* Gen1GameReader::decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame) +{ + return iconDecoder_.decode(localization_, iconType, firstFrame); +} + +uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname) +{ + Gen1Party party = getParty(); + uint8_t result = 0xFF; + const uint8_t pokedexNumber = getPokemonNumber(poke.poke_index); + + if(!originalTrainerID) + { + originalTrainerID = getTrainerName(); + } + + if(party.getNumberOfPokemon() < party.getMaxNumberOfPokemon()) + { + party.add(poke, originalTrainerID, nickname); + result = 0xFE; + } + else + { + const uint8_t currentBoxIndex = getCurrentBoxIndex(); + const uint8_t numBoxes = (localization_ != Gen1LocalizationLanguage::JAPANESE)? 12 : 8; + for(uint8_t i = 0; i < numBoxes; ++i) + { + Gen1Box box = getBox(i); + if(box.getNumberOfPokemon() == box.getMaxNumberOfPokemon()) + { + continue; + } + + box.add(poke, originalTrainerID, nickname); + result = i; + + updateWholeBoxBankChecksum(getGen1BoxBankIndex(i, currentBoxIndex, localization_)); + break; + } + } + setPokedexFlag(PokedexFlag::POKEDEX_SEEN, pokedexNumber); + setPokedexFlag(PokedexFlag::POKEDEX_OWNED, pokedexNumber); + updateMainChecksum(); + return result; +} + +uint8_t Gen1GameReader::addDistributionPokemon(const Gen1DistributionPokemon& distributionPoke, const char* nickname) +{ + Gen1TrainerPokemon poke = distributionPoke.poke; + const char* originalTrainerName; + + // apply the attributes of Gen1DistributionPokemon to the actual Gen2TrainerPokemon instance + gen1_prepareDistributionPokemon((*this), distributionPoke, poke, originalTrainerName); + + return addPokemon(poke, originalTrainerName, nickname); +} + +bool Gen1GameReader::isMainChecksumValid() +{ + const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; + uint8_t storedChecksum; + uint8_t calculatedChecksum; + + saveManager_.seek(mainDataChecksumOffset); + saveManager_.readByte(storedChecksum); + + calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); + + return (storedChecksum == calculatedChecksum); +} + +void Gen1GameReader::updateMainChecksum() +{ + const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; + const uint8_t calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); + + saveManager_.seek(mainDataChecksumOffset); + saveManager_.writeByte(calculatedChecksum); +} + +bool Gen1GameReader::isWholeBoxBankValid(uint8_t bankIndex) +{ + const uint16_t wholeBankChecksumOffset = 0x1A4C; + uint8_t storedChecksum; + uint8_t calculatedChecksum; + + saveManager_.seekToBankOffset(bankIndex, wholeBankChecksumOffset); + saveManager_.readByte(storedChecksum); + + calculatedChecksum = calculateWholeBoxBankChecksum(saveManager_, bankIndex); + + return (storedChecksum == calculatedChecksum); +} + +void Gen1GameReader::updateWholeBoxBankChecksum(uint8_t bankIndex) +{ + const uint16_t wholeBankChecksumOffset = 0x1A4C; + if(bankIndex < 2 || bankIndex > 3) + { + // only valid/needed for bank 2 and 3 + return; + } + + const uint8_t calculatedChecksum = calculateWholeBoxBankChecksum(saveManager_, bankIndex); + saveManager_.seekToBankOffset(bankIndex, wholeBankChecksumOffset); + saveManager_.writeByte(calculatedChecksum); } \ No newline at end of file diff --git a/src/gen1/Gen1Localization.cpp b/src/gen1/Gen1Localization.cpp index 557a4b8..d20e9da 100644 --- a/src/gen1/Gen1Localization.cpp +++ b/src/gen1/Gen1Localization.cpp @@ -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; } \ No newline at end of file diff --git a/src/gen2/Gen2GameReader.cpp b/src/gen2/Gen2GameReader.cpp index 6de4e57..93de621 100644 --- a/src/gen2/Gen2GameReader.cpp +++ b/src/gen2/Gen2GameReader.cpp @@ -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]; diff --git a/src/gen2/Gen2Localization.cpp b/src/gen2/Gen2Localization.cpp index 0cda8e4..f021a67 100644 --- a/src/gen2/Gen2Localization.cpp +++ b/src/gen2/Gen2Localization.cpp @@ -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,