From a44b908587fedc2931323f074205e893d8f38ffb Mon Sep 17 00:00:00 2001 From: risingPhil Date: Wed, 25 Sep 2024 20:22:17 +0200 Subject: [PATCH] Implement gen 2 party icon decoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now you can also decode the party icons for the gen 2 pokémon games --- examples/decodePartyIcons/main.cpp | 47 +++++++++++++++++++++++++----- include/gen2/Gen2Common.h | 46 +++++++++++++++++++++++++++++ include/gen2/Gen2GameReader.h | 12 ++++++++ src/gen2/Gen2Common.cpp | 7 +++++ src/gen2/Gen2GameReader.cpp | 46 +++++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 8 deletions(-) diff --git a/examples/decodePartyIcons/main.cpp b/examples/decodePartyIcons/main.cpp index 7519b2d..b177dac 100644 --- a/examples/decodePartyIcons/main.cpp +++ b/examples/decodePartyIcons/main.cpp @@ -1,5 +1,5 @@ #include "gen1/Gen1GameReader.h" -#include "gen1/Gen1SpriteDecoder.h" +#include "gen2/Gen2GameReader.h" #include "SpriteRenderer.h" #include "RomReader.h" #include "SaveManager.h" @@ -12,7 +12,7 @@ using OutputFormat = SpriteRenderer::OutputFormat; using TileOrder = SpriteRenderer::TileOrder; -void decodeGen1Icon(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type, Gen1PokemonIconType iconType, bool firstFrame) +static void decodeGen1Icon(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type, Gen1PokemonIconType iconType, bool firstFrame) { Gen1GameReader gameReader(romReader, saveManager, gen1Type); SpriteRenderer renderer; @@ -29,8 +29,24 @@ void decodeGen1Icon(IRomReader& romReader, ISaveManager& saveManager, Gen1GameTy write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false); } +static void decodeGen2Icon(IRomReader& romReader, ISaveManager& saveManager, Gen2GameType gen2Type, Gen2PokemonIconType iconType, bool firstFrame) +{ + Gen2GameReader gameReader(romReader, saveManager, gen2Type); + SpriteRenderer renderer; + char fileNameBuf[100]; + uint8_t *outputBuffer; -void decodeGen1Icons(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type) + outputBuffer = gameReader.decodePokemonIcon(iconType, renderer, OutputFormat::RGB, firstFrame); + if(!outputBuffer) + { + fprintf(stderr, "ERROR: Could not decode icon for icon type %d and firstFrame %d!\n", (int)iconType, firstFrame); + return; + } + snprintf(fileNameBuf, sizeof(fileNameBuf), "%d_frame%u.png", (int)iconType, (firstFrame) ? 1 : 2); + write_png(fileNameBuf, outputBuffer, 2 * 8, 2 * 8, false); +} + +static void decodeGen1Icons(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type) { for(int i=0; i < GEN1_ICONTYPE_MAX; ++i) { @@ -39,6 +55,15 @@ void decodeGen1Icons(IRomReader& romReader, ISaveManager& saveManager, Gen1GameT } } +static void decodeGen2Icons(IRomReader& romReader, ISaveManager& saveManager, Gen2GameType gen2Type) +{ + for(int i=1; i < GEN2_ICONTYPE_MAX; ++i) + { + decodeGen2Icon(romReader, saveManager, gen2Type, (Gen2PokemonIconType)i, true); + decodeGen2Icon(romReader, saveManager, gen2Type, (Gen2PokemonIconType)i, false); + } +} + int main(int argc, char** argv) { if(argc != 2) @@ -65,13 +90,19 @@ int main(int argc, char** argv) //check if we're dealing with gen 1 const Gen1GameType gen1Type = gen1_determineGameType(cartridgeHeader); - if(gen1Type == Gen1GameType::INVALID) + const Gen2GameType gen2Type = gen2_determineGameType(cartridgeHeader); + if(gen1Type != Gen1GameType::INVALID) { - fprintf(stderr, "ERROR: sorry, this tool only supports gen 1!\n"); - return 1; + decodeGen1Icons(romReader, saveManager, gen1Type); + } + else if(gen2Type != Gen2GameType::INVALID) + { + decodeGen2Icons(romReader, saveManager, gen2Type); + } + else + { + fprintf(stderr, "ERROR: The specified rom is not a gen 1 or gen 2 pokémon game!\n"); } - - decodeGen1Icons(romReader, saveManager, gen1Type); free(romBuffer); romBuffer = 0; diff --git a/include/gen2/Gen2Common.h b/include/gen2/Gen2Common.h index 5b31323..b2c4414 100644 --- a/include/gen2/Gen2Common.h +++ b/include/gen2/Gen2Common.h @@ -94,6 +94,50 @@ enum Gen2ItemListType GEN2_ITEMLISTTYPE_MAX }; +enum Gen2PokemonIconType +{ + GEN2_ICONTYPE_NULL, + GEN2_ICONTYPE_POLIWAG, + GEN2_ICONTYPE_JIGGLYPUFF, + GEN2_ICONTYPE_DIGLETT, + GEN2_ICONTYPE_PIKACHU, + GEN2_ICONTYPE_STARYU, + GEN2_ICONTYPE_FISH, + GEN2_ICONTYPE_BIRD, + GEN2_ICONTYPE_MONSTER, + GEN2_ICONTYPE_CLEFAIRY, + GEN2_ICONTYPE_ODDISH, + GEN2_ICONTYPE_BUG, + GEN2_ICONTYPE_GHOST, + GEN2_ICONTYPE_LAPRAS, + GEN2_ICONTYPE_HUMANSHAPE, + GEN2_ICONTYPE_FOX, + GEN2_ICONTYPE_EQUINE, + GEN2_ICONTYPE_SHELL, + GEN2_ICONTYPE_BLOB, + GEN2_ICONTYPE_SERPENT, + GEN2_ICONTYPE_VOLTORB, + GEN2_ICONTYPE_SQUIRTLE, + GEN2_ICONTYPE_BULBASAUR, + GEN2_ICONTYPE_CHARMANDER, + GEN2_ICONTYPE_CATERPILLAR, + GEN2_ICONTYPE_UNOWN, + GEN2_ICONTYPE_GEODUDE, + GEN2_ICONTYPE_FIGHTER, + GEN2_ICONTYPE_EGG, + GEN2_ICONTYPE_JELLYFISH, + GEN2_ICONTYPE_MOTH, + GEN2_ICONTYPE_BAT, + GEN2_ICONTYPE_SNORLAX, + GEN2_ICONTYPE_HO_OH, + GEN2_ICONTYPE_LUGIA, + GEN2_ICONTYPE_GYARADOS, + GEN2_ICONTYPE_SLOWPOKE, + GEN2_ICONTYPE_SUDOWOODO, + GEN2_ICONTYPE_BIGMON, + GEN2_ICONTYPE_MAX +}; + typedef struct Gen2TrainerPokemon { uint8_t poke_index; @@ -129,6 +173,8 @@ typedef struct Gen2TrainerPokemon uint16_t special_def; } Gen2TrainerPokemon; +extern uint16_t gen2_iconColorPalette[4]; + class Gen2ItemList { public: diff --git a/include/gen2/Gen2GameReader.h b/include/gen2/Gen2GameReader.h index 9bce5a4..1287d8b 100644 --- a/include/gen2/Gen2GameReader.h +++ b/include/gen2/Gen2GameReader.h @@ -4,6 +4,7 @@ #include "gen2/Gen2SpriteDecoder.h" #include "gen2/Gen2PlayerPokemonStorage.h" #include "gen2/Gen2DistributionPokemon.h" +#include "SpriteRenderer.h" class IRomReader; class ISaveManager; @@ -28,6 +29,11 @@ public: */ const char *getPokemonName(uint8_t index) const; + /** + * Returns the specific icon type for a given pokémon + */ + Gen2PokemonIconType getPokemonIconType(uint8_t index) const; + /** * @brief With this function, you can check if the index is valid and not referring to garbage data */ @@ -68,6 +74,12 @@ public: */ uint8_t *decodeSprite(uint8_t bankIndex, uint16_t pointer); + /** + * @brief This function decodes the given pokemon icon and returns the internal buffer of the given SpriteRenderer instance + * The size of the returned buffer is 16x16 pixels + */ + uint8_t* decodePokemonIcon(Gen2PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, 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. diff --git a/src/gen2/Gen2Common.cpp b/src/gen2/Gen2Common.cpp index cdcd012..9a10267 100644 --- a/src/gen2/Gen2Common.cpp +++ b/src/gen2/Gen2Common.cpp @@ -11,6 +11,13 @@ #define POKEMON_SILVER_CARTRIDGE_TITLE "POKEMON_SLV" #define POKEMON_CRYSTAL_CARTRIDGE_TITLE "PM_CRYSTAL" +uint16_t gen2_iconColorPalette[4] = { + 0x7FFF, + 0x2A5E, + 0x10FE, + 0x0 +}; + static TextCodePair gen2TextCodes[] = { {0x56, "……"}, {0x5D, "TRAINER"}, diff --git a/src/gen2/Gen2GameReader.cpp b/src/gen2/Gen2GameReader.cpp index a56430e..7a5a854 100644 --- a/src/gen2/Gen2GameReader.cpp +++ b/src/gen2/Gen2GameReader.cpp @@ -304,6 +304,18 @@ const char *Gen2GameReader::getPokemonName(uint8_t index) const return result; } +Gen2PokemonIconType Gen2GameReader::getPokemonIconType(uint8_t index) const +{ + const uint32_t romOffset = (isGameCrystal()) ? 0x8EAC4 : 0x8E975; + uint8_t byteVal; + + romReader_.seek(romOffset); + romReader_.advance((index - 1)); + romReader_.readByte(byteVal); + + return (Gen2PokemonIconType)byteVal; +} + bool Gen2GameReader::isValidIndex(uint8_t index) const { return (index != 0) && (index < 252); @@ -450,6 +462,40 @@ uint8_t *Gen2GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer) return spriteDecoder_.decode(bankIndex, pointer); } +uint8_t *Gen2GameReader::decodePokemonIcon(Gen2PokemonIconType iconType, SpriteRenderer& renderer, SpriteRenderer::OutputFormat outputFormat, bool firstFrame) +{ + const uint32_t romOffset = (gameType_ == Gen2GameType::CRYSTAL) ? 0x8EBBF : 0x8EA70; + const uint8_t MAX_NUM_TILES = 4; + const uint8_t TILE_WIDTH = 8; + const uint8_t TILE_HEIGHT = 8; + const uint8_t BITS_PER_PIXEL = 2; + const uint8_t BYTES_PER_TILE = ((TILE_WIDTH * TILE_HEIGHT) / 8) * BITS_PER_PIXEL; + const uint8_t TILES_PER_ICON = 4; + const uint32_t ENTRY_SIZE = 2; + uint8_t iconSrcBuffer[MAX_NUM_TILES * BYTES_PER_TILE]; + const uint8_t bankIndex = 0x23; + uint16_t pointer; + + // read IconPointers table entry + romReader_.seek(romOffset); + romReader_.advance(((uint32_t)iconType) * ENTRY_SIZE); + romReader_.readUint16(pointer); + + // now jump to the pointer to find the sprite + romReader_.seekToRomPointer(pointer, bankIndex); + + // every sprite contains 2 frames. + // if we want the second one, we need to skip 1 16x16 sprite + if(!firstFrame) + { + romReader_.advance(TILES_PER_ICON * BYTES_PER_TILE); + } + + romReader_.read(iconSrcBuffer, TILES_PER_ICON * BYTES_PER_TILE); + + return renderer.draw(iconSrcBuffer, outputFormat, gen2_iconColorPalette, 2, 2, SpriteRenderer::TileOrder::HORIZONTAL); +} + uint8_t Gen2GameReader::addPokemon(Gen2TrainerPokemon &poke, bool isEgg, const char *originalTrainerID, const char *nickname) { Gen2Party party = getParty();