From aedb58832c188d3c7ce28da6bc1c453c128ec1be Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 17 Feb 2025 12:38:21 +0100 Subject: [PATCH] V0.3 (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feature/add support for localized versions (#3) * Add support for gen I localizations There currently is no way to automatically detect the localization yet though. * Add gen1_determineGameLanguage() and use it automatically in Gen1GameReader if not specified in the constructor * Add support for the gen II localizations Note: for Korean games, specifically, I did not implement the Korean character set (nor do I plan to) The reason is that it is entirely different and more complex to implement than the other character sets. I'm not personally invested nor interested enough to go through the hassle. This affects the pokémon names that will be displayed when using libpokemegb. Instead as a fallback, it will return "poke-" for these roms. Feel free to contribute proper Korean character set (and pokémon names) support! * Streamline the Gen I localization code to be structured similarly to the gen II's * Fix bug When Gen1GameReader is triggering the language detection automatically, the Gen1IconDecoder instance is not updated with the detected language * Update README.md * Fix markdown special char escape problem in README.md * Fix name decoding for localizations * Fix detection of German Pokémon Yellow * Fix bug: Japanese saves were not detected correctly for gen I * Convert every character map entry into UTF-8. libdragon requires every string to be in UTF-8 encoding * Fix bug with Japanese saves. Wrong offset was used for the party storage in several functions * Fix OT names and nicknames for Japanese cartridges * Fix another issue with Gen I nicknames. Gen II is also still entirely broken in this regard * Also fix Japanese pokemon name retrieval in gen I * Fix OT and nicknames for Japanese gen I games * Add support for Japanese and Korean Gen II save offsets Japenese and Korean Gen II saves have a different save structure (data at different offsets) * Disable save corruption check for Japanese/Korean games. I don't have the right save offsets nor can find them. (I'm not adept at decompilation myself and pokegold/pokecrystal don't have the japanese offsets. Nor does PkHex) * Refactor the gen1 related code to store the different SRAM offsets for gen 1 saves similarly as we do for gen 2 * Remove unnecessary casts * Avoid trying to decode Korean trainername and rival name. We don't support the character set, neither in libpokemegb and PokeMe64. The Korean character set is a lot more complex than the other ones. But it would also require the inclusion of a lot of additional character glyphs in PokeMe64 in order to show the characters. Right now I don't feel like doing that for a localization that won't be used by many in combination with PokeMe64. So instead of showing the trainer name and rival name, I will just replace it with "player" and "rival". It's not an amazing solution, but it's the best I'm willing to do for now. * Add support for the Korean character sets. This was needed after all, because we don't want injected pokémon on a Korean cartridge to end up having a nickname like "poke-". So that implies the need for being able to decode and encode pokémon names in Korean. So yeah, I bit the bullet and added support for it. This was really annoying to do, but it has been done! * Feature/add japanese distribution pokemon (#4) * Add Gen I Japanese Distribution Event Pokémon * Remove external definitions of each individual distribution pokémon from the headers and make them static in the source file * Add isJapanese field to Gen2DistributionPokemon struct and adapt every existing definition * Fix mistake in last commit * Make sure the OT name is replaced when dealing with Japanese distribution pokémon on a different language cart and vice versa * Add Japanese Mystery Egg #3 gen II event distribution Pokémon * Add remaining Japanese Event Distribution Pokémon * Fix another issue with Gen I nicknames. Gen II is also still entirely broken in this regard * Fix OT and nicknames for Japanese gen I games * Refactor the gen1 related code to store the different SRAM offsets for gen 1 saves similarly as we do for gen 2 * Bugfix/fix distribution event pokemon inconsistencies (#5) * Add Gen I Japanese Distribution Event Pokémon * Remove external definitions of each individual distribution pokémon from the headers and make them static in the source file * Add isJapanese field to Gen2DistributionPokemon struct and adapt every existing definition * Fix mistake in last commit * Make sure the OT name is replaced when dealing with Japanese distribution pokémon on a different language cart and vice versa * Add Japanese Mystery Egg #3 gen II event distribution Pokémon * Add remaining Japanese Event Distribution Pokémon * Fix another issue with Gen I nicknames. Gen II is also still entirely broken in this regard * Fix OT and nicknames for Japanese gen I games * Refactor the gen1 related code to store the different SRAM offsets for gen 1 saves similarly as we do for gen 2 * Fix some inconsistencies with Gen II distribution event pokémon. * Make use of the Move enum instead of magic numbers for the pokémon moves (#6) * Add Gen1GameReader::getCurrentMap() function to retrieve the current map the player is on. I may be able to use this when adding a move deleter for gen1 games later, to make sure the player is in the pokemon center. (to make sure the player doesn't get trapped because he/she would delete an HM move) * Fix broken Japanese PC Box support + add support for Pokémon Green (JPN) + fix currentMap retrieval on japanese saves So, turns out that PC Box support was entirely broken for the Japanese games with PokeMe64. Apparently, the japanese games only have 8 boxes instead of 12, but each of them has a capacity of 30 pokémon instead of 20! So that changes things for the save offsets. Also: the currentMap offset was wrong for Japanese games. So I had to fix that too! * Updated README.md * Fix the same issue with PC Boxes for Japanese Gen II games. The international releases have 14 pc boxes with 20 pokemon each. But the japanese gen II games have 9 pc boxes with 30 entries each. * Rework rom offsets for pokémon blue to avoid duplicate rom offset definitions. The only one different from the red/green offsets is the japanese one anyway. * Replace "PokeMe64" OT with "PM64" when injecting japanese mons into international games. Turns out the official character limit was 7 * Fill the unused bytes of a nickname with the terminator byte. This is needed because according to Bulbapedia, a nickname is only considered not a nickname if it matches with the uppercase species name with all unused bytes set to the 0x50 terminator. * Add gen1_isAPokeCenter() function --- README.md | 7 +- examples/Makefile | 3 + examples/do_stuff_gen1/main.cpp | 32 +- examples/do_stuff_gen2/main.cpp | 27 +- examples/encodeText_gen1/main.cpp | 6 +- examples/encodeText_gen2/main.cpp | 2 +- examples/findLocalizationOffsets/Makefile | 48 + examples/findLocalizationOffsets/main.cpp | 224 ++ include/RomReader.h | 6 + include/gen1/Gen1Common.h | 15 +- include/gen1/Gen1DistributionPokemon.h | 37 +- include/gen1/Gen1GameReader.h | 15 +- include/gen1/Gen1IconDecoder.h | 3 +- include/gen1/Gen1Localization.h | 90 + include/gen1/Gen1Maps.h | 266 ++ include/gen1/Gen1PlayerPokemonStorage.h | 8 +- include/gen2/Gen2Common.h | 18 +- include/gen2/Gen2DistributionPokemon.h | 157 +- include/gen2/Gen2GameReader.h | 6 +- include/gen2/Gen2IconDecoder.h | 3 +- include/gen2/Gen2Localization.h | 91 + include/gen2/Gen2PlayerPokemonStorage.h | 10 +- include/utils.h | 17 +- src/RomReader.cpp | 13 +- src/gen1/Gen1Common.cpp | 543 +++- src/gen1/Gen1DistributionPokemon.cpp | 694 ++++- src/gen1/Gen1GameReader.cpp | 133 +- src/gen1/Gen1IconDecoder.cpp | 4 +- src/gen1/Gen1Localization.cpp | 197 ++ src/gen1/Gen1Maps.cpp | 527 ++++ src/gen1/Gen1PlayerPokemonStorage.cpp | 189 +- src/gen2/Gen2Common.cpp | 3386 ++++++++++++++++++++- src/gen2/Gen2DistributionPokemon.cpp | 2135 +++++++++---- src/gen2/Gen2GameReader.cpp | 356 ++- src/gen2/Gen2IconDecoder.cpp | 18 +- src/gen2/Gen2Localization.cpp | 411 +++ src/gen2/Gen2PlayerPokemonStorage.cpp | 74 +- src/utils.cpp | 17 +- 38 files changed, 8273 insertions(+), 1515 deletions(-) create mode 100644 examples/findLocalizationOffsets/Makefile create mode 100644 examples/findLocalizationOffsets/main.cpp create mode 100644 include/gen1/Gen1Localization.h create mode 100644 include/gen1/Gen1Maps.h create mode 100644 include/gen2/Gen2Localization.h create mode 100644 src/gen1/Gen1Localization.cpp create mode 100644 src/gen1/Gen1Maps.cpp create mode 100644 src/gen2/Gen2Localization.cpp diff --git a/README.md b/README.md index 934c3b1..e0bab48 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@ # Introduction -This project implements functionality to read stats, front sprites, Trainers' party/PC boxes and other various properties either from the ROM or from a save file of Gen 1 and Gen 2 Pokémon games. +This project implements functionality to read stats, front sprites, Trainers' party/PC boxes and other various properties either directly from the ROM/cartridge or from a save file of Gen 1 and Gen 2 Pokémon games. It also has support and data to inject the Distribution event pokémon into gen 1 and gen 2 pokémon save files. I'm happy to accept pull requests if the community wants to do them. # Features - Decode front sprites from Gen 1 and Gen 2 Pokémon games +- Decode party icons in Gen 1 and 2 games - Read Trainers' party and PC boxes from Gen 1 and 2 - Inject Pokémon from past distribution events into your Gen 1/Gen 2 game save - Inject other pokémon into your Gen 1/Gen 2 game save - -# Limitations -- Right now, this library only supports the international versions of the games. +- Supports all localizations # Dependencies This library only depends on libc. I specifically wanted to avoid the bloat of libstdc++ or any other library. This choice is to keep this library small and portable. diff --git a/examples/Makefile b/examples/Makefile index 5561100..1c50cd8 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -15,6 +15,7 @@ all: $(MAKE) -C gen2_getEventFlag $(MAKECMDGOALS) $(MAKEOVERRIDES) $(MAKE) -C gen2_setEventFlag $(MAKECMDGOALS) $(MAKEOVERRIDES) $(MAKE) -C decodePartyIcons $(MAKECMDGOALS) $(MAKEOVERRIDES) + $(MAKE) -C findLocalizationOffsets $(MAKECMDGOALS) $(MAKEOVERRIDES) clean: $(MAKE) -C do_stuff_gen1 clean @@ -30,3 +31,5 @@ clean: $(MAKE) -C gen2_getEventFlag clean $(MAKE) -C gen2_setEventFlag clean $(MAKE) -C decodePartyIcons clean + $(MAKE) -C findLocalizationOffsets clean + diff --git a/examples/do_stuff_gen1/main.cpp b/examples/do_stuff_gen1/main.cpp index 8ac5feb..a1a1e1c 100644 --- a/examples/do_stuff_gen1/main.cpp +++ b/examples/do_stuff_gen1/main.cpp @@ -100,13 +100,18 @@ static void printParty(const char* prefix, Gen1GameReader& reader) } } -static void printBox(const char* prefix, Gen1GameReader& reader, uint8_t boxIndex) +static void printBox(const char* prefix, Gen1GameReader& reader, uint8_t boxIndex, uint8_t currentBoxIndex) { Gen1Box box = reader.getBox(boxIndex); Gen1TrainerPokemon poke; const uint8_t numPokemon = box.getNumberOfPokemon(); + bool boxValid = box.isChecksumValid(currentBoxIndex); - printf("%sBox %hhu:\n", prefix, boxIndex + 1); + printf("%sBox %hhu: %s\n", prefix, boxIndex + 1, (boxValid) ? "valid" : "invalid"); + if(!boxValid) + { + return; + } printf("%sNumber: %hhu\n", prefix, numPokemon); for(uint8_t i=0; i < numPokemon; ++i) @@ -166,8 +171,8 @@ int main(int argc, char** argv) } Gen1GameReader gen1Reader(romReader, saveManager, gameType); - Gen1PokeStats stats; #ifdef PRINT_POKESTATS + Gen1PokeStats stats; printf("Pokémon names:\n"); // Note: indices are 1-based: https://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_index_number_(Generation_I) for(uint8_t i = 1; i < 191; ++i) @@ -179,23 +184,25 @@ int main(int argc, char** argv) printf("end\n"); #endif + uint8_t currentBoxIndex = gen1Reader.getCurrentBoxIndex(); printf("Save game:\n"); printf("\tTrainer: %s (0x%hx)\n", gen1Reader.getTrainerName(), gen1Reader.getTrainerID()); + printf("\tCurrent Map: %s\n", gen1Maps_toString(gen1Reader.getCurrentMap())); printf("\tRival: %s\n", gen1Reader.getRivalName()); printf("\tSeen: %hhu\n", gen1Reader.getPokedexCounter(POKEDEX_SEEN)); printf("\tOwned: %hhu\n", gen1Reader.getPokedexCounter(POKEDEX_OWNED)); printf("\tParty:\n"); printParty("\t\t", gen1Reader); - printBox("\t\t", gen1Reader, 0); - printBox("\t\t", gen1Reader, 1); + printBox("\t\t", gen1Reader, 0, currentBoxIndex); + printBox("\t\t", gen1Reader, 1, currentBoxIndex); #if 0 printf("Main checksum valid: %d\n", gen1Reader.isMainChecksumValid()); printf("Bank 2 valid: %d\n", gen1Reader.isWholeBoxBankValid(2)); printf("Bank 3 valid: %d\n", gen1Reader.isWholeBoxBankValid(3)); - uint8_t currentBoxIndex = gen1Reader.getCurrentBoxIndex(); - for(uint8_t i = 0; i < 13; ++i) + const uint8_t numBoxes = (gen1Reader.getGameLanguage() != Gen1LocalizationLanguage::JAPANESE) ? 12 : 8; + for(uint8_t i = 0; i < numBoxes; ++i) { Gen1Box box = gen1Reader.getBox(i); printf("Box %hhu valid: %d, currentBoxIndex=%hhu\n", (i + 1), box.isChecksumValid(currentBoxIndex), currentBoxIndex); @@ -244,8 +251,15 @@ int main(int argc, char** argv) gen1Reader.addPokemon(bulby, nullptr, "bulmama"); gen1Reader.addPokemon(bulby); #endif - gen1Reader.addDistributionPokemon(g1_coraChatelineauMew); - gen1Reader.addDistributionPokemon(g1_nintendoPowerPikachu); + + const Gen1DistributionPokemon** distributionEventPokemon; + uint32_t numPokemon; + gen1_getMainDistributionPokemonList(distributionEventPokemon, numPokemon); + const Gen1DistributionPokemon* g1_coraChatelineauMew = distributionEventPokemon[12]; + const Gen1DistributionPokemon* g1_nintendoPowerPikachu = distributionEventPokemon[24]; + + gen1Reader.addDistributionPokemon(*g1_coraChatelineauMew); + gen1Reader.addDistributionPokemon(*g1_nintendoPowerPikachu); FILE* f = fopen("out.sav", "w"); fwrite(savBuffer, 1, savFileSize, f); diff --git a/examples/do_stuff_gen2/main.cpp b/examples/do_stuff_gen2/main.cpp index 7138132..7905ecd 100644 --- a/examples/do_stuff_gen2/main.cpp +++ b/examples/do_stuff_gen2/main.cpp @@ -225,16 +225,27 @@ int main(int argc, char** argv) printf("Backup checksum valid\n"); } + const Gen2DistributionPokemon** distributionList; + uint32_t listSize; printf("Injecting Mew and Celebi\n"); - gameReader.addDistributionPokemon(g2_clubNintendoMexico_Mew); - gameReader.addDistributionPokemon(g2_nintendoPowerCelebi); + gen2_getMainDistributionPokemonList(distributionList, listSize); + gameReader.addDistributionPokemon(*distributionList[0]); + gameReader.addDistributionPokemon(*distributionList[6]); - gameReader.addDistributionPokemon(g2_pcny_mistRemoraid); - gameReader.addDistributionPokemon(g2_pcny_biteLapras); - gameReader.addDistributionPokemon(g2_pcny_shinyZapdos); - gameReader.addDistributionPokemon(g2_pcny_shinyMoltres); - gameReader.addDistributionPokemon(g2_pcny_shinyArticuno); - gameReader.addDistributionPokemon(g2_pcny_shinyEntei); + printf("Injecting a few PCNY pokémon\n"); + gen2_getPokemonCenterNewYorkDistributionPokemonList(distributionList, listSize); + // mist remoraid + gameReader.addDistributionPokemon(*distributionList[114]); + // bite lapras + gameReader.addDistributionPokemon(*distributionList[52]); + // Shiny Zapdos + gameReader.addDistributionPokemon(*distributionList[63]); + // Shiny Moltres + gameReader.addDistributionPokemon(*distributionList[64]); + // Shiny Articuno + gameReader.addDistributionPokemon(*distributionList[62]); + // Shiny Entei + gameReader.addDistributionPokemon(*distributionList[129]); gameReader.finishSave(); if(gameReader.isMainChecksumValid()) diff --git a/examples/encodeText_gen1/main.cpp b/examples/encodeText_gen1/main.cpp index 54242b3..ff4453a 100644 --- a/examples/encodeText_gen1/main.cpp +++ b/examples/encodeText_gen1/main.cpp @@ -1,8 +1,10 @@ -#include "gen1/Gen1Common.h" +#include "gen1/Gen1Localization.h" #include #include +static const Gen1LocalizationLanguage g1_localization = Gen1LocalizationLanguage::ENGLISH; + static void print_hex(const unsigned char* buffer, size_t length) { for (size_t i = 0; i < length; i++) @@ -22,6 +24,6 @@ int main(int argc, char** argv) uint8_t outputBuffer[4096]; - const uint16_t size = gen1_encodePokeText(argv[1], strlen(argv[1]), outputBuffer, sizeof(outputBuffer), 0x50); + const uint16_t size = gen1_encodePokeText(argv[1], strlen(argv[1]), outputBuffer, sizeof(outputBuffer), 0x50, g1_localization); print_hex(outputBuffer, size); } \ No newline at end of file diff --git a/examples/encodeText_gen2/main.cpp b/examples/encodeText_gen2/main.cpp index 2d69865..60533f0 100644 --- a/examples/encodeText_gen2/main.cpp +++ b/examples/encodeText_gen2/main.cpp @@ -22,6 +22,6 @@ int main(int argc, char** argv) uint8_t outputBuffer[4096]; - const uint16_t size = gen2_encodePokeText(argv[1], strlen(argv[1]), outputBuffer, sizeof(outputBuffer), 0x50); + const uint16_t size = gen2_encodePokeText(argv[1], strlen(argv[1]), outputBuffer, sizeof(outputBuffer), 0x50, Gen2LocalizationLanguage::ENGLISH); print_hex(outputBuffer, size); } \ No newline at end of file diff --git a/examples/findLocalizationOffsets/Makefile b/examples/findLocalizationOffsets/Makefile new file mode 100644 index 0000000..864187a --- /dev/null +++ b/examples/findLocalizationOffsets/Makefile @@ -0,0 +1,48 @@ +# # 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 := . +# 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)) + +LIBS := -lpokemegb +ifeq ($(PNG_SUPPORT),1) + LIBS += -lpng +endif + +# Ensure necessary directories exist +# This function ensures the directory for the target exists +define make_directory + @mkdir -p $(dir $@) +endef + +# Target executable +TARGET := ../../findLocalizationOffsets + +# Phony targets +.PHONY: all clean + +# Default target +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) $^ -o $@ $(LIBS) -L ../../ + +# Rule to compile source files +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp | $(BUILD_DIR) + $(make_directory) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Create the build directory if it doesn't exist +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Clean rule +clean: + rm -rf $(BUILD_DIR) $(TARGET) \ No newline at end of file diff --git a/examples/findLocalizationOffsets/main.cpp b/examples/findLocalizationOffsets/main.cpp new file mode 100644 index 0000000..ee2ddff --- /dev/null +++ b/examples/findLocalizationOffsets/main.cpp @@ -0,0 +1,224 @@ +#include "utils.h" +#include "RomReader.h" +#include "gen1/Gen1Common.h" +#include "gen2/Gen2Common.h" +#include "gen1/Gen1Localization.h" +#include "gen2/Gen2Localization.h" + +#include +#include + +/** + * This tool tries to find rom offsets of a game localization based on certain byte patterns in the international english games. + */ + +/* + * The next variables need to be modified in a hardcoded way right now. They affect the findNames() function by specifying the localization for the + * text encode/decode process. It therefore affects the character set table used to encode the pokémon names. + * Therefore set these accordingly for the localization you're trying to search a pokémon name for. + * + * Also note that the pokémon name is hardcoded below. To search for a region-specific name, you'll have to modify it in the code as well. + * + * Yes, I'm aware this is a bit unclean, but once the relevant rom offsets are found once, you don't typically need this tool anymore. + * Still, I'm leaving this application in the codebase in case I need more rom offsets later (or in case Korean localization support would get implemented at some point) +*/ +static const Gen1LocalizationLanguage g1_localization = Gen1LocalizationLanguage::JAPANESE; +static const Gen2LocalizationLanguage g2_localization = Gen2LocalizationLanguage::ENGLISH; + +static uint32_t findBinaryPattern(uint8_t* buffer, size_t bufferSize, uint8_t* pattern, size_t patternSize) +{ + uint32_t lastResult = 0; + uint8_t* cur = buffer; + while(cur) + { + cur = (uint8_t*)memmem(cur, bufferSize - (cur - buffer), pattern, patternSize); + if(cur) + { + lastResult = (uint32_t)(cur - buffer); + printf("%s: found pattern at offset 0x%08x\n", __FUNCTION__, lastResult); + cur += patternSize; + } + } + return lastResult; +} + +static uint32_t findNames(uint8_t* buffer, size_t bufferSize, const char** nameList, uint8_t nameListSize, uint8_t gen) +{ + uint8_t nameBuffer[0xB]; + uint8_t i; + uint16_t nameLength; + const uint8_t nameEntrySize = 0xA; + uint8_t* firstEntry; + uint8_t* cur = buffer; + bool match; + + if(gen == 1) + { + nameLength = gen1_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g1_localization); + } + else if(gen == 2) + { + nameLength = gen2_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g2_localization); + } + firstEntry = (uint8_t*)memmem(cur, bufferSize - (cur - buffer), nameBuffer, nameLength - 1); + + while(firstEntry) + { + cur = firstEntry + nameEntrySize; + match = true; + for(i = 1; i < nameListSize; ++i) + { + if(gen == 1) + { + nameLength = gen1_encodePokeText(nameList[i], strlen(nameList[i]), nameBuffer, sizeof(nameBuffer), 0x50, g1_localization); + } + else if(gen == 2) + { + nameLength = gen2_encodePokeText(nameList[i], strlen(nameList[i]), nameBuffer, sizeof(nameBuffer), 0x50, g2_localization); + } + + if(memcmp(cur, nameBuffer, nameLength)) + { + //not equal, so no match at this offset + match = false; + break; + } + cur += nameEntrySize; + } + + if(match) + { + return (uint32_t)(firstEntry - buffer); + } + // re-encode first name + if(gen == 1) + { + nameLength = gen1_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g1_localization); + } + else if(gen == 2) + { + nameLength = gen2_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g2_localization); + } + // continue searching for another occurrence of the first name starting from the bytes directly after + // the previously found location + cur = firstEntry + nameEntrySize; + firstEntry = (uint8_t*)memmem(cur, bufferSize - (cur - buffer), nameBuffer, nameLength); + } + + return 0xFFFFFFFF; +} + +#if 0 +static void dumpHex(const uint8_t* buffer, uint16_t numBytes, const char* prefix) +{ + uint16_t i = 0; + while(i < numBytes) + { + if(i % 8 == 0) + { + printf("\n%s", prefix); + } + printf("%02hhX ", buffer[i]); + ++i; + } + printf("\n"); +} +#endif + +static void processGen1(Gen1GameType gameType, uint8_t* romBuffer, uint8_t* localizedRomBuffer, size_t localizedRomBufferSize) +{ + // compare with the english/international release + const Gen1LocalizationRomOffsets& eng = gen1_getRomOffsets(gameType, Gen1LocalizationLanguage::ENGLISH); + + printf("Stats:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.stats, 11); + printf("Stats Mew:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.statsMew, 11); + printf("Numbers:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.numbers, 11); + + // WARNING: Modify this variable with the localized name of the pokémon we're trying to look up. (RHYDON, because it's index 1) + const char* pokeNames[] = {"サイドン"}; + printf("Names: 0x%08x\n", findNames(localizedRomBuffer, localizedRomBufferSize, pokeNames, 1, 1)); + printf("IconTypes:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.iconTypes, 11); + printf("Icons:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.icons, 6); + printf("PaletteIndices:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.paletteIndices, 11); + printf("Palettes:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.palettes, 11); +} + +static void processGen2(Gen2GameType gameType, uint8_t* romBuffer, uint8_t* localizedRomBuffer, size_t localizedRomBufferSize) +{ + // compare with the english/international release + const Gen2LocalizationRomOffsets& eng = gen2_getRomOffsets(gameType, Gen2LocalizationLanguage::ENGLISH); + + printf("Stats:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.stats, 11); + // WARNING: Modify this variable with the localized name of the pokémon we're trying to look up. (BULBASAUR, because it's index 1) + const char* pokeNames[] = {"BULBASAUR"}; + printf("Names: 0x%08x\n", findNames(localizedRomBuffer, localizedRomBufferSize, pokeNames, 1, 2)); + printf("IconTypes:\n"); + const uint32_t iconTypesOffset = findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.iconTypes, 44); + + // Because the data shifts around between localizations and because eng.icons is a pointer (+ bank) list, we can't do an exact binary pattern search to figure out the icons offset + // Luckily, the https://github.com/pret/pokegold project's pokegold.sym file tells us that the "Icons" offset is 78 bytes after the "IconPointers". + // Therefore if we find the iconTypes offset, we should in theory be able to calculate the Icons offset based on that + printf("Icons: 0x%08x\n", iconTypesOffset + 0xFB); + printf("SpritePointers:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.spritePointers, 11); + printf("SpritePalettes:\n"); + findBinaryPattern(localizedRomBuffer, localizedRomBufferSize, romBuffer + eng.spritePalettes, 11); +} + +int main(int argc, char** argv) +{ + if(argc != 3) + { + fprintf(stderr, "Usage: findLocalizationOffsets \n"); + return 1; + } + + uint8_t* romBuffer; + uint8_t* localizedRomBuffer; + uint32_t romFileSize; + uint32_t localizedRomBufferSize; + + romBuffer = readFileIntoBuffer(argv[1], romFileSize); + if(!romBuffer) + { + fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[1]); + return 1; + } + + localizedRomBuffer = readFileIntoBuffer(argv[2], localizedRomBufferSize); + if(!localizedRomBuffer) + { + fprintf(stderr, "ERROR: Couldn't read file %s\n", argv[2]); + return 1; + } + + GameboyCartridgeHeader cartridgeHeader; + BufferBasedRomReader romReader(romBuffer, romFileSize); + + 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) + { + processGen1(gen1Type, romBuffer, localizedRomBuffer, localizedRomBufferSize); + return 0; + } + else if(gen2Type != Gen2GameType::INVALID) + { + processGen2(gen2Type, romBuffer, localizedRomBuffer, localizedRomBufferSize); + return 0; + } + + fprintf(stderr, "ERROR: No valid gen I/II rom found!\n"); + return 1; +} \ No newline at end of file diff --git a/include/RomReader.h b/include/RomReader.h index 1af83a8..9a8ef14 100644 --- a/include/RomReader.h +++ b/include/RomReader.h @@ -29,6 +29,11 @@ public: */ virtual uint32_t readUntil(uint8_t* outBuffer, uint8_t terminator, uint32_t maxRead) = 0; + /** + * @brief Same as above but with multiple possible terminator characters + */ + virtual uint32_t readUntil(uint8_t* outBuffer, const uint8_t* terminatorList, uint8_t numTerminators, uint32_t maxRead) = 0; + /** * @brief Reads a 16 bit unsigned integer from the rom at the current position. * The Endianness enum indicates whether the field we're trying to read is actually stored in little endian or big endian form. @@ -112,6 +117,7 @@ public: bool seekToRomPointer(uint16_t pointer, uint8_t bankIndex = 0xFF) override; uint32_t readUntil(uint8_t* outBuffer, uint8_t terminator, uint32_t maxRead) override; + uint32_t readUntil(uint8_t* outBuffer, const uint8_t* terminatorList, uint8_t numTerminators, uint32_t maxRead) override; protected: private: const bool cpuLittleEndian_; diff --git a/include/gen1/Gen1Common.h b/include/gen1/Gen1Common.h index 619d56b..ec00eb9 100644 --- a/include/gen1/Gen1Common.h +++ b/include/gen1/Gen1Common.h @@ -4,14 +4,18 @@ #include "common.h" class Gen1GameReader; +class IRomReader; typedef struct Gen1DistributionPokemon Gen1DistributionPokemon; +enum class Gen1LocalizationLanguage; + enum class Gen1GameType { INVALID, BLUE, RED, - YELLOW + YELLOW, + GREEN }; typedef struct Gen1PokeStats @@ -123,17 +127,22 @@ private: */ Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeader); +/** + * @brief this function determines the games' language + */ +Gen1LocalizationLanguage gen1_determineGameLanguage(IRomReader& romReader, Gen1GameType gameType); + void gen1_recalculatePokeStats(Gen1GameReader& gameReader, Gen1TrainerPokemon& poke); /** * @brief This function decodes a text (This could be a name or something else) found in the rom. * @return the number of characters copied to the output buffer */ -uint16_t gen1_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength); +uint16_t gen1_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength, Gen1LocalizationLanguage language); /** * @brief The opposite of gen1_decodePokeText() */ -uint16_t gen1_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator); +uint16_t gen1_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator, Gen1LocalizationLanguage language); /** * Prepares the gen 1 distribution pokemon for injection with Gen1GameReader::addPokemon() diff --git a/include/gen1/Gen1DistributionPokemon.h b/include/gen1/Gen1DistributionPokemon.h index f462651..31a27e4 100644 --- a/include/gen1/Gen1DistributionPokemon.h +++ b/include/gen1/Gen1DistributionPokemon.h @@ -18,40 +18,17 @@ typedef struct Gen1DistributionPokemon bool setPlayerAsOriginalTrainer; // generate a random trainer ID for the pokemon. see originalTrainerID above bool regenerateTrainerID; + // indicates whether this is a Japanese distribution event pokémon. + // This is important because we can't support the Trainer OT characters of a Japanese one in other + // localizations. When injecting a Japanese pokémon into a different localization, we replace the OT with POKEME64. + // when injecting a US/EU pokémon into a japanese cartridge, we'll replace the OT with ポケ64 + bool isJapanese; + // overrides the held item field + uint8_t held_item_override; // definition of the pokemon Gen1TrainerPokemon poke; } Gen1DistributionPokemon; -// https://bulbapedia.bulbagarden.net/wiki/List_of_European_language_event_Pok%C3%A9mon_distributions_(Generation_I) - -extern const Gen1DistributionPokemon g1_nintendoUKPokemonFestivalMew2016; -extern const Gen1DistributionPokemon g1_clubNintendoMexicoGiveawayMew; -extern const Gen1DistributionPokemon g1_farMorBornMew; -extern const Gen1DistributionPokemon g1_christmasPresentMew; -extern const Gen1DistributionPokemon g1_swedenMewOnTour; -extern const Gen1DistributionPokemon g1_tennispalatsiMew; -extern const Gen1DistributionPokemon g1_norwayPokemonTourMew; -extern const Gen1DistributionPokemon g1_clubNintendoMew; -extern const Gen1DistributionPokemon g1_pokemon2000ChampionshipMew; -extern const Gen1DistributionPokemon g1_millenniumDomeMew; -extern const Gen1DistributionPokemon g1_austriaMew; -extern const Gen1DistributionPokemon g1_ukIrelandPokemonChampionship2000Mew; -extern const Gen1DistributionPokemon g1_coraChatelineauMew; -extern const Gen1DistributionPokemon g1_francePokemonTournamentMew; -extern const Gen1DistributionPokemon g1_spainPokemonTournamentMew; -extern const Gen1DistributionPokemon g1_mewsFlashMew; -extern const Gen1DistributionPokemon g1_pokemonPatrolMew; -extern const Gen1DistributionPokemon g1_nintendoOfficialMagazineTourMew; -extern const Gen1DistributionPokemon g1_canadianPokemonStadiumTour2000Mew; -extern const Gen1DistributionPokemon g1_pokemonStadiumTour2000Mew; -extern const Gen1DistributionPokemon g1_canadaToysRUsMew; -extern const Gen1DistributionPokemon g1_usToysRUsMew; -extern const Gen1DistributionPokemon g1_nintendoPowerMew; -extern const Gen1DistributionPokemon g1_pokemonLeagueNintendoTrainingTour99Mew; -extern const Gen1DistributionPokemon g1_nintendoPowerPikachu; -extern const Gen1DistributionPokemon g1_pokeTourMew; -extern const Gen1DistributionPokemon g1_pokemonPowerMew; - /** * @brief Gets a list of const Gen1DistributionPokemon pointers of the distribution pokemon that were available worldwide * and stores it in the specified outList. diff --git a/include/gen1/Gen1GameReader.h b/include/gen1/Gen1GameReader.h index 667f4fd..a6f66df 100644 --- a/include/gen1/Gen1GameReader.h +++ b/include/gen1/Gen1GameReader.h @@ -5,6 +5,8 @@ #include "gen1/Gen1IconDecoder.h" #include "gen1/Gen1PlayerPokemonStorage.h" #include "gen1/Gen1DistributionPokemon.h" +#include "gen1/Gen1Localization.h" +#include "gen1/Gen1Maps.h" class IRomReader; class ISaveManager; @@ -12,7 +14,12 @@ class ISaveManager; class Gen1GameReader { public: - Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType); + 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 @@ -79,6 +86,11 @@ public: */ 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 */ @@ -186,6 +198,7 @@ private: Gen1SpriteDecoder spriteDecoder_; Gen1IconDecoder iconDecoder_; Gen1GameType gameType_; + Gen1LocalizationLanguage localization_; }; #endif \ No newline at end of file diff --git a/include/gen1/Gen1IconDecoder.h b/include/gen1/Gen1IconDecoder.h index 3096e91..f9fa2eb 100644 --- a/include/gen1/Gen1IconDecoder.h +++ b/include/gen1/Gen1IconDecoder.h @@ -2,6 +2,7 @@ #define _GEN1POKEMONICONDECODER_H #include "gen1/Gen1Common.h" +#include "gen1/Gen1Localization.h" #define GEN1_TILE_BITS_PER_PIXEL 2 #define GEN1_BYTES_PER_TILE 16 @@ -27,7 +28,7 @@ public: * * @return Internal buffer containing the icon tiles in vertical tile order. */ - uint8_t* decode(Gen1PokemonIconType iconType, bool firstFrame); + uint8_t* decode(Gen1LocalizationLanguage language, Gen1PokemonIconType iconType, bool firstFrame); protected: private: uint8_t buffer_[GEN1_ICON_NUM_BYTES]; diff --git a/include/gen1/Gen1Localization.h b/include/gen1/Gen1Localization.h new file mode 100644 index 0000000..4810123 --- /dev/null +++ b/include/gen1/Gen1Localization.h @@ -0,0 +1,90 @@ +#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); +#endif \ No newline at end of file diff --git a/include/gen1/Gen1Maps.h b/include/gen1/Gen1Maps.h new file mode 100644 index 0000000..5e3b575 --- /dev/null +++ b/include/gen1/Gen1Maps.h @@ -0,0 +1,266 @@ +#ifndef _GEN1MAPS_H +#define _GEN1MAPS_H + +enum class Gen1Maps +{ + PALLET_TOWN = 0, + VIRIDIAN_CITY, + PEWTER_CITY, + CERULEAN_CITY, + LAVENDER_TOWN, + VERMILION_CITY, + CELADON_CITY, + FUCHSIA_CITY, + CINNABAR_ISLAND, + INDIGO_PLATEAU, + SAFFRON_CITY, + UNUSED_MAP_0B, + ROUTE_1, + ROUTE_2, + ROUTE_3, + ROUTE_4, + ROUTE_5, + ROUTE_6, + ROUTE_7, + ROUTE_8, + ROUTE_9, + ROUTE_10, + ROUTE_11, + ROUTE_12, + ROUTE_13, + ROUTE_14, + ROUTE_15, + ROUTE_16, + ROUTE_17, + ROUTE_18, + ROUTE_19, + ROUTE_20, + ROUTE_21, + ROUTE_22, + ROUTE_23, + ROUTE_24, + ROUTE_25, + REDS_HOUSE_1F, + REDS_HOUSE_2F, + BLUES_HOUSE, + OAKS_LAB, + VIRIDIAN_POKECENTER, + VIRIDIAN_MART, + VIRIDIAN_SCHOOL_HOUSE, + VIRIDIAN_NICKNAME_HOUSE, + VIRIDIAN_GYM, + DIGGLETTS_CAVE_ROUTE_2, + VIRIDIAN_FOREST_NORTH_GATE, + ROUTE_2_TRADE_HOUSE, + ROUTE_2_GATE, + VIRIDIAN_FOREST_SOUTH_GATE, + VIRIDIAN_FOREST, + MUSEUM_1F, + MUSEUM_2F, + PEWTER_GYM, + PEWTER_NIDORAN_HOUSE, + PEWTER_MART, + PEWTER_SPEECH_HOUSE, + PEWTER_POKECENTER, + MT_MOON_1F, + MT_MOON_B1F, + MT_MOON_B2F, + CERULEAN_THRASHED_HOUSE, + CERULEAN_TRADE_HOUSE, + CERULEAN_POKECENTER, + CERULEAN_GYM, + BIKE_SHOP, + CERULEAN_MART, + MT_MOON_POKECENTER, + CERULEAN_THRASHED_HOUSE_COPY, + ROUTE_5_GATE, + UNDERGROUND_PATH_ROUTE_5, + DAYCARE, + ROUTE_6_GATE, + UNDERGROUND_PATH_ROUTE_6, + UNDERGROUND_PATH_ROUTE_6_COPY, + ROUTE_7_GATE, + UNDERGROUND_PATH_ROUTE_7, + UNDERGROUND_PATH_ROUTE_7_COPY, + ROUTE_8_GATE, + UNDERGROUND_PATH_ROUTE_8, + ROCK_TUNNEL_POKECENTER, + ROCK_TUNNEL_1F, + POWER_PLANT, + ROUTE_11_GATE_1F, + DIGLETTS_CAVE_ROUTE_11, + ROUTE_11_GATE_2F, + ROUTE_12_GATE_1F, + BILLS_HOUSE, + VERMILION_POKECENTER, + POKEMON_FAN_CLUB, + VERMILION_MART, + VERMILION_GYM, + VERMILION_PIDGEY_HOUSE, + VERMILION_DOCK, + SS_ANNE_1F, + SS_ANNE_2F, + SS_ANNE_3F, + SS_ANNE_B1F, + SS_ANNE_BOW, + SS_ANNE_KITCHEN, + SS_ANNE_CAPTAINS_ROOM, + SS_ANNE_1F_ROOMS, + SS_ANNE_2F_ROOMS, + SS_ANNE_B1F_ROOMS, + UNUSED_MAP_69, + UNUSED_MAP_6A, + UNUSED_MAP_6B, + VICTORY_ROAD_1F, + UNUSED_MAP_6D, + UNUSED_MAP_6E, + UNUSED_MAP_6F, + UNUSED_MAP_70, + LANCES_ROOM, + UNUSED_MAP_72, + UNUSED_MAP_73, + UNUSED_MAP_74, + UNUSED_MAP_75, + HALL_OF_FAME, + UNDERGROUND_PATH_NORTH_SOUTH, + CHAMPIONS_ROOM, + UNDERGROUND_PATH_WEST_EAST, + CELEDON_MART_1F, + CELEDON_MART_2F, + CELEDON_MART_3F, + CELEDON_MART_4F, + CELEDON_MART_ROOF, + CELEDON_MART_ELEVATOR, + CELEDON_MANSION_1F, + CELEDON_MANSION_2F, + CELEDON_MANSION_3F, + CELEDON_MANSION_ROOF, + CELEDON_MANSION_ROOF_HOUSE, + CELEDON_POKECENTER, + CELEDON_GYM, + GAME_CORNER, + CELEDON_MART_5F, + GAME_CORNER_PRIZE_ROOM, + CELEDON_DINER, + CELEDON_CHIEF_HOUSE, + CELEDON_HOTEL, + LAVENDER_POKECENTER, + POKEMON_TOWER_1F, + POKEMON_TOWER_2F, + POKEMON_TOWER_3F, + POKEMON_TOWER_4F, + POKEMON_TOWER_5F, + POKEMON_TOWER_6F, + POKEMON_TOWER_7F, + MR_FUJIS_HOUSE, + LAVENDER_MART, + LAVENDER_CUBONE_HOUSE, + FUCHSIA_MART, + FUCHSIA_BILLS_GRANDPAS_HOUSE, + FUCHSIA_POKECENTER, + WARDENS_HOUSE, + SAFARI_ZONE_GATE, + FUCHSIA_GYM, + FUCHSIA_MEETING_ROOM, + SEAFOAM_ISLANDS_B1F, + SEAFOAM_ISLANDS_B2F, + SEAFOAM_ISLANDS_B3F, + SEAFOAM_ISLANDS_B4F, + VERMILION_OLD_ROD_HOUSE, + FUCHSIA_GOOD_ROD_HOUSE, + POKEMON_MANSION_1F, + CINNABAR_GYM, + CINNABAR_LAB, + CINNABAR_LAB_TRADE_ROOM, + CINNABAR_LAB_METRONOME_ROOM, + CINNABAR_LAB_FOSSIL_ROOM, + CINNABAR_POKECENTER, + CINNABAR_MART, + CINNABAR_MART_COPY, + INDIGO_PLATEAU_LOBBY, + COPYCATS_HOUSE_1F, + COPYCATS_HOUSE_2F, + FIGHTING_DOJO, + SAFFRON_GYM, + SAFFRON_PIDGEY_HOUSE, + SAFFRON_MART, + SILPH_CO_1F, + SAFFRON_POKECENTER, + MR_PSYCHICS_HOUSE, + ROUTE_15_GATE_1F, + ROUTE_15_GATE_2F, + ROUTE_16_GATE_1F, + ROUTE_16_GATE_2F, + ROUTE_16_FLY_HOUSE, + ROUTE_12_SUPER_ROD_HOUSE, + ROUTE_18_GATE_1F, + ROUTE_18_GATE_2F, + SEAFOAM_ISLANDS_1F, + ROUTE_22_GATE, + VICTORY_ROAD_2F, + ROUTE_12_GATE_2F, + VERMILION_TRADE_HOUSE, + DIGLETTS_CAVE, + VICTORY_ROAD_3F, + ROCKET_HIDEOUT_B1F, + ROCKET_HIDEOUT_B2F, + ROCKET_HIDEOUT_B3F, + ROCKET_HIDEOUT_B4F, + ROCKET_HIDEOUT_ELEVATOR, + UNUSED_MAP_CC, + UNUSED_MAP_CD, + UNUSED_MAP_CE, + SILPH_CO_2F, + SILPH_CO_3F, + SILPH_CO_4F, + SILPH_CO_5F, + SILPH_CO_6F, + SILPH_CO_7F, + SILPH_CO_8F, + POKEMON_MANSION_2F, + POKEMON_MANSION_3F, + POKEMON_MANSION_B1F, + SAFARI_ZONE_EAST, + SAFARI_ZONE_NORTH, + SAFARI_ZONE_WEST, + SAFARI_ZONE_CENTER, + SAFARI_ZONE_CENTER_REST_HOUSE, + SAFARI_ZONE_SECRET_HOUSE, + SAFARI_ZONE_WEST_REST_HOUSE, + SAFARI_ZONE_EAST_REST_HOUSE, + SAFARI_ZONE_NORTH_REST_HOUSE, + CERULEAN_CAVE_2F, + CERULEAN_CAVE_B1F, + CERULEAN_CAVE_1F, + NAME_RATERS_HOUSE, + CERULEAN_BADGE_HOUSE, + UNUSED_MAP_E7, + ROCK_TUNNEL_B1F, + SILPH_CO_9F, + SILPH_CO_10F, + SILPH_CO_11F, + SILPH_CO_ELEVATOR, + UNUSED_MAP_ED, + UNUSED_MAP_EE, + TRADE_CENTER, + COLOSSEUM, + UNUSED_MAP_F1, + UNUSED_MAP_F2, + UNUSED_MAP_F3, + UNUSED_MAP_F4, + LORELEIS_ROOM, + BRUNOS_ROOM, + AGATHAS_ROOM +}; + +/** + * stringifies a Gen1Maps enum value + */ +const char* gen1Maps_toString(Gen1Maps map); + +/** + * @brief Returns a boolean indicating whether the current map is a pokémon center + */ +bool gen1_isAPokeCenter(Gen1Maps map); + +#endif \ No newline at end of file diff --git a/include/gen1/Gen1PlayerPokemonStorage.h b/include/gen1/Gen1PlayerPokemonStorage.h index fb81990..ad47292 100644 --- a/include/gen1/Gen1PlayerPokemonStorage.h +++ b/include/gen1/Gen1PlayerPokemonStorage.h @@ -6,7 +6,7 @@ class Gen1GameReader; class ISaveManager; -uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex); +uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language); /** * @brief This class represents the trainer party in the GEN1 games @@ -14,7 +14,7 @@ uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex); class Gen1Party { public: - Gen1Party(Gen1GameReader& gameReader, ISaveManager& saveManager); + Gen1Party(Gen1GameReader& gameReader, ISaveManager& saveManager, Gen1LocalizationLanguage language); ~Gen1Party(); /** @@ -41,6 +41,7 @@ protected: private: Gen1GameReader& gameReader_; ISaveManager& saveManager_; + Gen1LocalizationLanguage localization_; }; /** @@ -49,7 +50,7 @@ private: class Gen1Box { public: - Gen1Box(Gen1GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex); + Gen1Box(Gen1GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex, Gen1LocalizationLanguage language); ~Gen1Box(); /** @@ -73,6 +74,7 @@ private: Gen1GameReader& gameReader_; ISaveManager& saveManager_; uint8_t boxIndex_; + Gen1LocalizationLanguage localization_; }; #endif \ No newline at end of file diff --git a/include/gen2/Gen2Common.h b/include/gen2/Gen2Common.h index b2c4414..bf898dd 100644 --- a/include/gen2/Gen2Common.h +++ b/include/gen2/Gen2Common.h @@ -2,9 +2,11 @@ #define _GEN2COMMON_H #include "common.h" +#include "Gen2Localization.h" class Gen2GameReader; class ISaveManager; +class IRomReader; typedef struct Gen2DistributionPokemon Gen2DistributionPokemon; @@ -17,6 +19,7 @@ typedef struct Gen2DistributionPokemon Gen2DistributionPokemon; #define CRYSTAL_EVENTFLAG_KURT_READY_TO_RETURN_GSBALL 191 #define CRYSTAL_EVENTFLAG_GSBALL_USABLE_IN_ILEX_FOREST_SHRINE 192 #define CRYSTAL_EVENTFLAG_RECEIVED_GSBALL 832 +#define CRYSTAL_GS_BALL_ENABLE_VALUE 0xB enum class Gen2GameType { @@ -87,6 +90,7 @@ enum Gen2GrowthRate enum Gen2ItemListType { GEN2_ITEMLISTTYPE_INVALID, + GEN2_ITEMLISTTYPE_TMHM, GEN2_ITEMLISTTYPE_ITEMPOCKET, GEN2_ITEMLISTTYPE_KEYITEMPOCKET, GEN2_ITEMLISTTYPE_BALLPOCKET, @@ -178,7 +182,7 @@ extern uint16_t gen2_iconColorPalette[4]; class Gen2ItemList { public: - Gen2ItemList(ISaveManager& saveManager, Gen2ItemListType type, bool isCrystal); + Gen2ItemList(ISaveManager& saveManager, Gen2ItemListType type, Gen2GameType gameType, Gen2LocalizationLanguage localization); uint8_t getCount(); uint8_t getCapacity(); @@ -210,7 +214,8 @@ private: ISaveManager& saveManager_; const Gen2ItemListType type_; - const bool isCrystal_; + const Gen2GameType gameType_; + const Gen2LocalizationLanguage localization_; }; class Gen2Checksum @@ -230,17 +235,22 @@ private: */ Gen2GameType gen2_determineGameType(const GameboyCartridgeHeader& cartridgeHeader); +/** + * @brief this function determines the games' language + */ +Gen2LocalizationLanguage gen2_determineGameLanguage(IRomReader& romReader, Gen2GameType gameType); + void gen2_recalculatePokeStats(Gen2GameReader& gameReader, Gen2TrainerPokemon& poke); /** * @brief This function decodes a text (This could be a name or something else) found in the rom. * @return the number of characters copied to the output buffer */ -uint16_t gen2_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength); +uint16_t gen2_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength, Gen2LocalizationLanguage language = Gen2LocalizationLanguage::MAX); /** * @brief The opposite of gen1_decodePokeText() */ -uint16_t gen2_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator); +uint16_t gen2_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator, Gen2LocalizationLanguage language = Gen2LocalizationLanguage::MAX); /** * @brief This function determines whether the given trainer pokemon is shiny diff --git a/include/gen2/Gen2DistributionPokemon.h b/include/gen2/Gen2DistributionPokemon.h index f1ec7df..c9fb9bd 100644 --- a/include/gen2/Gen2DistributionPokemon.h +++ b/include/gen2/Gen2DistributionPokemon.h @@ -36,164 +36,17 @@ typedef struct Gen2DistributionPokemon // or whether the IVs need to be randomized instead // shinyChance still applies here if set to a value != 0xFF bool randomizeIVs; + // indicates whether this is a Japanese distribution event pokémon. + // This is important because we can't support the Trainer OT characters of a Japanese one in other + // localizations. When injecting a Japanese pokémon into a different localization, we replace the OT with POKEME64. + // when injecting a US/EU pokémon into a japanese cartridge, we'll replace the OT with ポケ64 + bool isJapanese; // definition of the pokemon Gen2TrainerPokemon poke; } Gen2DistributionPokemon; // Note: see gen2_getMainDistributionPokemonList() and gen2_getPokemonCenterNewYorkDistributionPokemonList() below -extern const Gen2DistributionPokemon g2_clubNintendoMexico_Mew; -extern const Gen2DistributionPokemon g2_clubNintendoMexico_Celebi; - -extern const Gen2DistributionPokemon g2_swedenCelebi; -extern const Gen2DistributionPokemon g2_westfieldShopping; -extern const Gen2DistributionPokemon g2_celebiTour; -extern const Gen2DistributionPokemon g2_pokemonFunFestCelebi; -extern const Gen2DistributionPokemon g2_nintendoPowerCelebi; -extern const Gen2DistributionPokemon g2_celebiSweepstakes; - -// PCNY (Pokemon Center New York) Gotta Catch 'em all! station distribution pokémon below -extern const Gen2DistributionPokemon g2_pcny_ancientpowerBulbasaur; -extern const Gen2DistributionPokemon g2_pcny_shinyVenusaur; -extern const Gen2DistributionPokemon g2_pcny_crunchCharmander; -extern const Gen2DistributionPokemon g2_pcny_shinyCharizard; -extern const Gen2DistributionPokemon g2_pcny_zapCannonSquirtle; -extern const Gen2DistributionPokemon g2_pcny_shinyBlastoise; -extern const Gen2DistributionPokemon g2_pcny_sonicboomSpearow; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranFem; -extern const Gen2DistributionPokemon g2_pcny_moonlightNidoranFem; -extern const Gen2DistributionPokemon g2_pcny_sweetKissNidoranFem; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranMale; -extern const Gen2DistributionPokemon g2_pcny_morningSunNidoranMale; -extern const Gen2DistributionPokemon g2_pcny_sweetKissNidoranMale; -extern const Gen2DistributionPokemon g2_pcny_flailZubat; -extern const Gen2DistributionPokemon g2_pcny_leechSeedOddish; -extern const Gen2DistributionPokemon g2_pcny_synthesisParas; -extern const Gen2DistributionPokemon g2_pcny_petalDancePsyduck; -extern const Gen2DistributionPokemon g2_pcny_triAttackPsyduck; -extern const Gen2DistributionPokemon g2_pcny_growthPoliwag; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissPoliwag; -extern const Gen2DistributionPokemon g2_pcny_sweetKissPoliwag; -extern const Gen2DistributionPokemon g2_pcny_foresightAbra; -extern const Gen2DistributionPokemon g2_pcny_falseSwipeMachop; -extern const Gen2DistributionPokemon g2_pcny_thrashMachop; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissBellsprout; -extern const Gen2DistributionPokemon g2_pcny_sweetKissBellsprout; -extern const Gen2DistributionPokemon g2_pcny_confuseRayTentacool; -extern const Gen2DistributionPokemon g2_pcny_rapidSpinGeodude; -extern const Gen2DistributionPokemon g2_pcny_lowKickPonyta; -extern const Gen2DistributionPokemon g2_pcny_agilityMagnemite; -extern const Gen2DistributionPokemon g2_pcny_furyCutterFarfetchd; -extern const Gen2DistributionPokemon g2_pcny_lowKickDoduo; -extern const Gen2DistributionPokemon g2_pcny_flailSeel; -extern const Gen2DistributionPokemon g2_pcny_sharpenOnix; -extern const Gen2DistributionPokemon g2_pcny_amnesiaDrowsee; -extern const Gen2DistributionPokemon g2_pcny_metalClawKrabby; -extern const Gen2DistributionPokemon g2_pcny_agilityVoltorb; -extern const Gen2DistributionPokemon g2_pcny_sweetScentExeggcute; -extern const Gen2DistributionPokemon g2_pcny_furyAttackCubone; -extern const Gen2DistributionPokemon g2_pcny_doubleSlapLickitung; -extern const Gen2DistributionPokemon g2_pcny_sweetScentChansey; -extern const Gen2DistributionPokemon g2_pcny_synthesisTangela; -extern const Gen2DistributionPokemon g2_pcny_faintAttackKangaskhan; -extern const Gen2DistributionPokemon g2_pcny_hazeHorsea; -extern const Gen2DistributionPokemon g2_pcny_swordsDanceGoldeen; -extern const Gen2DistributionPokemon g2_pcny_twisterStaryu; -extern const Gen2DistributionPokemon g2_pcny_mindReaderMrMime; -extern const Gen2DistributionPokemon g2_pcny_sonicBoomScyther; -extern const Gen2DistributionPokemon g2_pcny_rockThrowPinsir; -extern const Gen2DistributionPokemon g2_pcny_quickAttackTauros; -extern const Gen2DistributionPokemon g2_pcny_bubbleMagikarp; -extern const Gen2DistributionPokemon g2_pcny_reversalMagikarp; -extern const Gen2DistributionPokemon g2_pcny_biteLapras; -extern const Gen2DistributionPokemon g2_pcny_futureSightLapras; -extern const Gen2DistributionPokemon g2_pcny_growthEevee; -extern const Gen2DistributionPokemon g2_pcny_barrierPorygon; -extern const Gen2DistributionPokemon g2_pcny_rockThrowOmanyte; -extern const Gen2DistributionPokemon g2_pcny_rockThrowKabuto; -extern const Gen2DistributionPokemon g2_pcny_rockThrowAerodactyl; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissSnorlax; -extern const Gen2DistributionPokemon g2_pcny_splashSnorlax; -extern const Gen2DistributionPokemon g2_pcny_sweetKissSnorlax; -extern const Gen2DistributionPokemon g2_pcny_shinyArticuno; -extern const Gen2DistributionPokemon g2_pcny_shinyZapdos; -extern const Gen2DistributionPokemon g2_pcny_shinyMoltres; -extern const Gen2DistributionPokemon g2_pcny_hydroPumpDratini; -extern const Gen2DistributionPokemon g2_pcny_shinyMewtwo; -extern const Gen2DistributionPokemon g2_pcny_shinyMew; -extern const Gen2DistributionPokemon g2_pcny_petalDanceChikorita; -extern const Gen2DistributionPokemon g2_pcny_shinyMeganium; -extern const Gen2DistributionPokemon g2_pcny_doubleEdgeCyndaquil; -extern const Gen2DistributionPokemon g2_pcny_shinyTyphlosion; -extern const Gen2DistributionPokemon g2_pcny_submissionTotodile; -extern const Gen2DistributionPokemon g2_pcny_shinyFeraligatr; -extern const Gen2DistributionPokemon g2_pcny_dizzyPunchSentret; -extern const Gen2DistributionPokemon g2_pcny_nightShadeHoothoot; -extern const Gen2DistributionPokemon g2_pcny_barrierLedyba; -extern const Gen2DistributionPokemon g2_pcny_growthSpinarak; -extern const Gen2DistributionPokemon g2_pcny_lightScreenChinchou; -extern const Gen2DistributionPokemon g2_pcny_dizzyPunchPichu; -extern const Gen2DistributionPokemon g2_pcny_petalDancePichu; -extern const Gen2DistributionPokemon g2_pcny_scaryFacePichu; -extern const Gen2DistributionPokemon g2_pcny_singPichu; -extern const Gen2DistributionPokemon g2_pcny_petalDanceCleffa; -extern const Gen2DistributionPokemon g2_pcny_scaryFaceCleffa; -extern const Gen2DistributionPokemon g2_pcny_swiftCleffa; -extern const Gen2DistributionPokemon g2_pcny_mimicIgglybuff; -extern const Gen2DistributionPokemon g2_pcny_petalDanceIgglybuff; -extern const Gen2DistributionPokemon g2_pcny_scaryFaceIgglybuff; -extern const Gen2DistributionPokemon g2_pcny_safeguardNatu; -extern const Gen2DistributionPokemon g2_pcny_dizzyPunchMarill; -extern const Gen2DistributionPokemon g2_pcny_hydroPumpMarill; -extern const Gen2DistributionPokemon g2_pcny_scaryFaceMarill; -extern const Gen2DistributionPokemon g2_pcny_substituteSudowoodo; -extern const Gen2DistributionPokemon g2_pcny_agilityHoppip; -extern const Gen2DistributionPokemon g2_pcny_mimicAipom; -extern const Gen2DistributionPokemon g2_pcny_splashSunkern; -extern const Gen2DistributionPokemon g2_pcny_steelWingYanma; -extern const Gen2DistributionPokemon g2_pcny_sweetKissYanma; -extern const Gen2DistributionPokemon g2_pcny_bellyDrumWooper; -extern const Gen2DistributionPokemon g2_pcny_scaryFaceWooper; -extern const Gen2DistributionPokemon g2_pcny_beatUpMurkrow; -extern const Gen2DistributionPokemon g2_pcny_hypnosisMisdreavus; -extern const Gen2DistributionPokemon g2_pcny_mimicWobbuffet; -extern const Gen2DistributionPokemon g2_pcny_substitutePineco; -extern const Gen2DistributionPokemon g2_pcny_furyAttackDunsparce; -extern const Gen2DistributionPokemon g2_pcny_hornDrillDunsparce; -extern const Gen2DistributionPokemon g2_pcny_lovelyKissSnubbull; -extern const Gen2DistributionPokemon g2_pcny_doubleEdgeQwilfish; -extern const Gen2DistributionPokemon g2_pcny_seismicTossHeracross; -extern const Gen2DistributionPokemon g2_pcny_moonlightSneasel; -extern const Gen2DistributionPokemon g2_pcny_sweetScentTeddiursa; -extern const Gen2DistributionPokemon g2_pcny_whirlwindSwinub; -extern const Gen2DistributionPokemon g2_pcny_amnesiaRemoraid; -extern const Gen2DistributionPokemon g2_pcny_mistRemoraid; -extern const Gen2DistributionPokemon g2_pcny_payDayDelibird; -extern const Gen2DistributionPokemon g2_pcny_spikesDelibird; -extern const Gen2DistributionPokemon g2_pcny_gustMantine; -extern const Gen2DistributionPokemon g2_pcny_furyCutterSkarmory; -extern const Gen2DistributionPokemon g2_pcny_absorbPhanpy; -extern const Gen2DistributionPokemon g2_pcny_safeguardStantler; -extern const Gen2DistributionPokemon g2_pcny_rageTyrogue; -extern const Gen2DistributionPokemon g2_pcny_metronomeSmoochum; -extern const Gen2DistributionPokemon g2_pcny_petalDanceSmoochum; -extern const Gen2DistributionPokemon g2_pcny_dizzyPunchElekid; -extern const Gen2DistributionPokemon g2_pcny_pursuitElekid; -extern const Gen2DistributionPokemon g2_pcny_faintAttackMagby; -extern const Gen2DistributionPokemon g2_pcny_megaKickMiltank; -extern const Gen2DistributionPokemon g2_pcny_shinyRaikou; -extern const Gen2DistributionPokemon g2_pcny_shinyEntei; -extern const Gen2DistributionPokemon g2_pcny_shinySuicune; - -// The regular (non-shiny) suicune on https://bulbapedia.bulbagarden.net/wiki/List_of_PCNY_event_Pok%C3%A9mon_distributions_(Generation_II)#Suicune -// has too many question marks and not even any moves. So I'm ignoring that one. -//extern const Gen2DistributionPokemon g2_pcny_suicune; - -extern const Gen2DistributionPokemon g2_pcny_rageLarvitar; -extern const Gen2DistributionPokemon g2_pcny_shinyLugia; -extern const Gen2DistributionPokemon g2_pcny_shinyHoOh; -extern const Gen2DistributionPokemon g2_pcny_Celebi; - /** * @brief Gets a list of const Gen2DistributionPokemon pointers of the distribution pokemon that were available worldwide * and stores it in the specified outList. diff --git a/include/gen2/Gen2GameReader.h b/include/gen2/Gen2GameReader.h index ddee6f5..6f7b813 100644 --- a/include/gen2/Gen2GameReader.h +++ b/include/gen2/Gen2GameReader.h @@ -5,6 +5,7 @@ #include "gen2/Gen2IconDecoder.h" #include "gen2/Gen2PlayerPokemonStorage.h" #include "gen2/Gen2DistributionPokemon.h" +#include "gen2/Gen2Localization.h" class IRomReader; class ISaveManager; @@ -15,11 +16,13 @@ class ISaveManager; class Gen2GameReader { public: - Gen2GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen2GameType gameType); + Gen2GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage language = Gen2LocalizationLanguage::MAX); ~Gen2GameReader(); Gen2GameType getGameType() const; + Gen2LocalizationLanguage 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, @@ -239,6 +242,7 @@ private: Gen2SpriteDecoder spriteDecoder_; Gen2IconDecoder iconDecoder_; Gen2GameType gameType_; + Gen2LocalizationLanguage localization_; }; #endif \ No newline at end of file diff --git a/include/gen2/Gen2IconDecoder.h b/include/gen2/Gen2IconDecoder.h index 8e15f8e..12d2811 100644 --- a/include/gen2/Gen2IconDecoder.h +++ b/include/gen2/Gen2IconDecoder.h @@ -2,6 +2,7 @@ #define _GEN2ICONDECODER_H #include "gen2/Gen2Common.h" +#include "gen2/Gen2Localization.h" #define GEN2_TILE_BITS_PER_PIXEL 2 #define GEN2_BYTES_PER_TILE 16 @@ -27,7 +28,7 @@ public: * * @return Internal buffer containing the icon tiles in vertical tile order. */ - uint8_t* decode(Gen2PokemonIconType iconType, bool firstFrame); + uint8_t* decode(Gen2LocalizationLanguage language, Gen2PokemonIconType iconType, bool firstFrame); protected: private: uint8_t buffer_[GEN2_ICON_NUM_BYTES]; diff --git a/include/gen2/Gen2Localization.h b/include/gen2/Gen2Localization.h new file mode 100644 index 0000000..95ea03f --- /dev/null +++ b/include/gen2/Gen2Localization.h @@ -0,0 +1,91 @@ +#ifndef _GEN2LOCALIZATION_H +#define _GEN2LOCALIZATION_H + +#include + +enum class Gen2GameType; + +enum class Gen2LocalizationLanguage +{ + ENGLISH, + FRENCH, + ITALIAN, + SPANISH, + GERMAN, + /** + * Warning: pokémon name decoding is not supported for Korean games! + * It has a much more difficult character encoding scheme than the other languages (including Japanese) + * https://bulbapedia.bulbagarden.net/wiki/Korean_character_encoding_(Generation_II) + * + * I'm personally not that interested in supporting that. Sorry. I'd welcome contributions for it though! + */ + KOREAN, + JAPANESE, + /** + * This value indicates that we haven't found a supported localization. It can also be used as a marker to + * indicate that the game has not been identified yet. + */ + MAX +}; + +/** + * This struct contains the relevant rom offsets for a specific Gen II game localization. + * Because of the localization process, data has shifted to different positions inside the rom + * So we need separate rom offsets for each of these localization+game type variation. + */ +typedef struct Gen2LocalizationRomOffsets +{ + /** + * Rom offset of the species structs which define the base stats of a pokémon in the pokedex number order + */ + uint32_t stats; + /** + * Rom offset of the pokémon name definitions in pokedex number order + */ + uint32_t names; + /** + * Rom offset of the list of icon types per pokémon in the pokedex number order + */ + uint32_t iconTypes; + /** + * Rom offset for the icon pointer table for the party mini-sprite icons + */ + uint32_t icons; + /** + * Rom offset for the list of pokémon sprite pointers. (front + back) + */ + uint32_t spritePointers; + /** + * Rom offset for the list of pokémon sprite palettes. Each pokémon has 2 palettes: non-shiny and shiny. + * For each of the palettes, only 2 16-bit colors are defined. The other two of the 4-color gameboy colorpalette + * are always black and white, so they don't need to be stored in this list + */ + uint32_t spritePalettes; +} Gen2LocalizationRomOffsets; + +typedef struct Gen2LocalizationSRAMOffsets +{ + uint32_t currentBoxIndex; + uint32_t currentBox; + uint32_t dexSeen; + uint32_t dexOwned; + uint32_t party; + uint32_t eventFlags; + uint32_t mainChecksum; + uint32_t mainChecksumDataEndPos; + uint32_t backupChecksum; + uint32_t rivalName; + uint32_t gsBallMain; + uint32_t gsBallBackup; + uint32_t itemPocketTMHM; + uint32_t itemPocketItem; + uint32_t itemPocketKey; + uint32_t itemPocketBall; + uint32_t itemPocketPC; + uint32_t rtcFlags; +} Gen2LocalizationSRAMOffsets; + +const Gen2LocalizationRomOffsets& gen2_getRomOffsets(Gen2GameType gameType, Gen2LocalizationLanguage language); +const Gen2LocalizationSRAMOffsets& gen2_getSRAMOffsets(Gen2GameType gameType, Gen2LocalizationLanguage language); + +#endif \ No newline at end of file diff --git a/include/gen2/Gen2PlayerPokemonStorage.h b/include/gen2/Gen2PlayerPokemonStorage.h index 19ba560..251e3f3 100644 --- a/include/gen2/Gen2PlayerPokemonStorage.h +++ b/include/gen2/Gen2PlayerPokemonStorage.h @@ -1,7 +1,8 @@ #ifndef _GEN2PLAYERPOKEMONSTORAGE_H #define _GEN2PLAYERPOKEMONSTORAGE_H -#include "Gen2Common.h" +#include "gen2/Gen2Common.h" +#include "gen2/Gen2Localization.h" class ISaveManager; class Gen2GameReader; @@ -20,7 +21,7 @@ const int GEN2_PC_BOX_SIZE_IN_BYTES = 20 * (32 + 23) + 2; class Gen2PokemonList { public: - Gen2PokemonList(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t listCapacity, uint8_t entrySize); + Gen2PokemonList(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t listCapacity, uint8_t entrySize, Gen2LocalizationLanguage language); virtual ~Gen2PokemonList(); virtual uint32_t getSaveOffset() = 0; @@ -79,6 +80,7 @@ public: protected: Gen2GameReader& gameReader_; ISaveManager& saveManager_; + Gen2LocalizationLanguage localization_; private: const uint8_t listCapacity_; const uint8_t entrySize_; @@ -87,7 +89,7 @@ private: class Gen2Party : public Gen2PokemonList { public: - Gen2Party(Gen2GameReader& gameReader, ISaveManager& saveManager); + Gen2Party(Gen2GameReader& gameReader, ISaveManager& saveManager, Gen2LocalizationLanguage language); virtual ~Gen2Party(); uint32_t getSaveOffset() override; @@ -98,7 +100,7 @@ private: class Gen2Box : public Gen2PokemonList { public: - Gen2Box(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex); + Gen2Box(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex, Gen2LocalizationLanguage language); virtual ~Gen2Box(); uint32_t getSaveOffset() override; diff --git a/include/utils.h b/include/utils.h index 03dfb0a..eabb916 100644 --- a/include/utils.h +++ b/include/utils.h @@ -11,16 +11,29 @@ struct TextCodePair; */ const uint8_t* memSearch(const uint8_t* haystack, uint32_t hayStackLength, const uint8_t* needle, uint32_t needleLength); +/** + * @brief This function searches the specified textCodes character map for the specific code and returns the TextCodePair + * or null when not found + */ +const char* findCharsByTextCode(const TextCodePair* textCodes, uint16_t numEntries, uint8_t code); + +/** + * @brief This function search the specified textCodes character map for the specific input and stores the associated code into outCode. + * + * returns false if not found + */ +bool findTextcodeByString(const TextCodePair* textCodes, uint16_t numEntries, const char* input, uint16_t inputLength, uint8_t& outCode, uint16_t& needleLength); + /** * @brief This function decodes a text (This could be a name or something else) found in the rom based on the textCodes list you pass in. * @return the number of characters copied to the output buffer */ -uint16_t decodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength); +uint16_t decodeText(const struct TextCodePair* textCodes, uint16_t numTextCodes, const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength); /** * @brief The opposite of decodeText() */ -uint16_t encodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator); +uint16_t encodeText(const struct TextCodePair* textCodes, uint16_t numTextCodes, const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator); bool isCurrentCPULittleEndian(); diff --git a/src/RomReader.cpp b/src/RomReader.cpp index db29820..c4cdc44 100644 --- a/src/RomReader.cpp +++ b/src/RomReader.cpp @@ -114,13 +114,22 @@ bool BaseRomReader::seekToRomPointer(uint16_t pointer, uint8_t bankIndex) } uint32_t BaseRomReader::readUntil(uint8_t* outBuffer, uint8_t terminator, uint32_t maxRead) +{ + return readUntil(outBuffer, &terminator, 1, maxRead); +} + +uint32_t BaseRomReader::readUntil(uint8_t* outBuffer, const uint8_t* terminatorList, uint8_t numTerminators, uint32_t maxRead) { uint8_t* cur = outBuffer; + uint8_t i; while(static_cast(cur - outBuffer) < maxRead && read(cur, 1)) { - if((*cur) == terminator) + for(i=0; i < numTerminators; ++i) { - break; + if((*cur) == terminatorList[i]) + { + return (cur - outBuffer); + } } ++cur; } diff --git a/src/gen1/Gen1Common.cpp b/src/gen1/Gen1Common.cpp index ffff65a..59abc9d 100644 --- a/src/gen1/Gen1Common.cpp +++ b/src/gen1/Gen1Common.cpp @@ -1,5 +1,6 @@ #include "gen1/Gen1Common.h" #include "gen1/Gen1GameReader.h" +#include "RomReader.h" #include "utils.h" #include "common.h" @@ -8,112 +9,358 @@ #define POKEMON_BLUE_CARTRIDGE_TITLE "POKEMON BLUE" #define POKEMON_RED_CARTRIDGE_TITLE "POKEMON RED" -#define POKEMON_YELLOW_CARTRIDGE_TITLE "POKEMON YELLOW" +#define POKEMON_GREEN_CARTRIDGE_TITLE "POKEMON GREEN" +#define POKEMON_YELLOW_CARTRIDGE_TITLE "POKEMON YEL" +static const uint8_t g1_indexNumberMapFingerprint[] = { + 0x70, 0x73, 0x20, 0x23, 0x15, 0x64, 0x22, 0x50, + 0x02, 0x67, 0x6C, 0x66, 0x58, 0x5E, 0x1D, 0x1F, +}; -static TextCodePair gen1TextCodes[] = { - {0x4F, " "}, - {0x57, "#"}, - {0x51, "*"}, - {0x52, "A1"}, - {0x53, "A2"}, - {0x54, "POKé"}, - {0x55, "+"}, - {0x58, "$"}, - {0x5D, "TRAINER"}, - {0x75, "…"}, - {0x7F, " "}, - {0x80, "A"}, - {0x81, "B"}, - {0x82, "C"}, - {0x83, "D"}, - {0x84, "E"}, - {0x85, "F"}, - {0x86, "G"}, - {0x87, "H"}, - {0x88, "I"}, - {0x89, "J"}, - {0x8A, "K"}, - {0x8B, "L"}, - {0x8C, "M"}, - {0x8D, "N"}, - {0x8E, "O"}, - {0x8F, "P"}, - {0x90, "Q"}, - {0x91, "R"}, - {0x92, "S"}, - {0x93, "T"}, - {0x94, "U"}, - {0x95, "V"}, - {0x96, "W"}, - {0x97, "X"}, - {0x98, "Y"}, - {0x99, "Z"}, - {0x9A, "("}, - {0x9B, ")"}, - {0x9C, ","}, - {0x9D, ";"}, - {0x9E, "["}, - {0x9F, "]"}, - {0xA0, "a"}, - {0xA1, "b"}, - {0xA2, "c"}, - {0xA3, "d"}, - {0xA4, "e"}, - {0xA5, "f"}, - {0xA6, "g"}, - {0xA7, "h"}, - {0xA8, "i"}, - {0xA9, "j"}, - {0xAA, "k"}, - {0xAB, "l"}, - {0xAC, "m"}, - {0xAD, "n"}, - {0xAE, "o"}, - {0xAF, "p"}, - {0xB0, "q"}, - {0xB1, "r"}, - {0xB2, "s"}, - {0xB3, "t"}, - {0xB4, "u"}, - {0xB5, "v"}, - {0xB6, "w"}, - {0xB7, "x"}, - {0xB8, "y"}, - {0xB9, "z"}, - {0xBA, "é"}, - {0xBB, "\'d"}, - {0xBC, "\'l"}, - {0xBD, "\'s"}, - {0xBE, "\'t"}, - {0xBF, "\'v"}, - {0xE0, "\'"}, - {0xE1, "PK"}, - {0xE2, "MN"}, - {0xE3, "-"}, - {0xE4, "\'r"}, - {0xE5, "\'m"}, - {0xE6, "?"}, - {0xE7, "!"}, - {0xE8, "."}, - {0xED, "→"}, - {0xEE, "↓"}, - {0xEF, "♂"}, - {0xF0, "¥"}, - {0xF1, "×"}, - {0xF3, "/"}, - {0xF4, ","}, - {0xF5, "♀"}, - {0xF6, "0"}, - {0xF7, "1"}, - {0xF8, "2"}, - {0xF9, "3"}, - {0xFA, "4"}, - {0xFB, "5"}, - {0xFC, "6"}, - {0xFD, "7"}, - {0xFE, "8"}, - {0xFF, "9"} +static const TextCodePair gen1TextCodesMain[] = { + {0x4F, u8" "}, + {0x57, u8"#"}, + {0x51, u8"*"}, + {0x52, u8"A1"}, + {0x53, u8"A2"}, + {0x54, u8"POKé"}, + {0x55, u8"+"}, + {0x58, u8"$"}, + {0x5D, u8"TRAINER"}, + {0x75, u8"…"}, + {0x7F, u8" "}, + {0x80, u8"A"}, + {0x81, u8"B"}, + {0x82, u8"C"}, + {0x83, u8"D"}, + {0x84, u8"E"}, + {0x85, u8"F"}, + {0x86, u8"G"}, + {0x87, u8"H"}, + {0x88, u8"I"}, + {0x89, u8"J"}, + {0x8A, u8"K"}, + {0x8B, u8"L"}, + {0x8C, u8"M"}, + {0x8D, u8"N"}, + {0x8E, u8"O"}, + {0x8F, u8"P"}, + {0x90, u8"Q"}, + {0x91, u8"R"}, + {0x92, u8"S"}, + {0x93, u8"T"}, + {0x94, u8"U"}, + {0x95, u8"V"}, + {0x96, u8"W"}, + {0x97, u8"X"}, + {0x98, u8"Y"}, + {0x99, u8"Z"}, + {0x9A, u8"("}, + {0x9B, u8")"}, + {0x9C, u8","}, + {0x9D, u8";"}, + {0x9E, u8"["}, + {0x9F, u8"]"}, + {0xA0, u8"a"}, + {0xA1, u8"b"}, + {0xA2, u8"c"}, + {0xA3, u8"d"}, + {0xA4, u8"e"}, + {0xA5, u8"f"}, + {0xA6, u8"g"}, + {0xA7, u8"h"}, + {0xA8, u8"i"}, + {0xA9, u8"j"}, + {0xAA, u8"k"}, + {0xAB, u8"l"}, + {0xAC, u8"m"}, + {0xAD, u8"n"}, + {0xAE, u8"o"}, + {0xAF, u8"p"}, + {0xB0, u8"q"}, + {0xB1, u8"r"}, + {0xB2, u8"s"}, + {0xB3, u8"t"}, + {0xB4, u8"u"}, + {0xB5, u8"v"}, + {0xB6, u8"w"}, + {0xB7, u8"x"}, + {0xB8, u8"y"}, + {0xB9, u8"z"}, + {0xBA, u8"é"}, + {0xBB, u8"\'d"}, + {0xBC, u8"\'l"}, + {0xBD, u8"\'s"}, + {0xBE, u8"\'t"}, + {0xBF, u8"\'v"}, + {0xE0, u8"\'"}, + {0xE1, u8"PK"}, + {0xE2, u8"MN"}, + {0xE3, u8"-"}, + {0xE4, u8"\'r"}, + {0xE5, u8"\'m"}, + {0xE6, u8"?"}, + {0xE7, u8"!"}, + {0xE8, u8"."}, + {0xED, u8"→"}, + {0xEE, u8"↓"}, + {0xEF, u8"♂"}, + {0xF0, u8"¥"}, + {0xF1, u8"×"}, + {0xF3, u8"/"}, + {0xF4, u8","}, + {0xF5, u8"♀"}, + {0xF6, u8"0"}, + {0xF7, u8"1"}, + {0xF8, u8"2"}, + {0xF9, u8"3"}, + {0xFA, u8"4"}, + {0xFB, u8"5"}, + {0xFC, u8"6"}, + {0xFD, u8"7"}, + {0xFE, u8"8"}, + {0xFF, u8"9"} +}; + +static const TextCodePair gen1TextCodesJpn[] = { + {0x01, u8"イ゙"}, + {0x02, u8"ヴ"}, + {0x03, u8"エ゙"}, + {0x04, u8"オ゙"}, + {0x05, u8"ガ"}, + {0x06, u8"ギ"}, + {0x07, u8"グ"}, + {0x08, u8"ゲ"}, + {0x09, u8"ゴ"}, + {0x0A, u8"ザ"}, + {0x0B, u8"ジ"}, + {0x0C, u8"ズ"}, + {0x0D, u8"ゼ"}, + {0x0E, u8"ゾ"}, + {0x0F, u8"ダ"}, + {0x10, u8"ヂ"}, + {0x11, u8"ヅ"}, + {0x12, u8"デ"}, + {0x13, u8"ド"}, + {0x14, u8"ナ゙"}, + {0x15, u8"ニ゙"}, + {0x16, u8"ヌ゙"}, + {0x17, u8"ネ゙"}, + {0x18, u8"ノ゙"}, + {0x19, u8"バ"}, + {0x1A, u8"ビ"}, + {0x1B, u8"ブ"}, + {0x1C, u8"ボ"}, + {0x1D, u8"マ゙"}, + {0x1E, u8"ミ゙"}, + {0x1F, u8"ム゙"}, + {0x20, u8"ィ゙"}, + {0x21, u8"あ゙"}, + {0x22, u8"い゙"}, + {0x23, u8"ゔ"}, + {0x24, u8"え゙"}, + {0x25, u8"お゙"}, + {0x26, u8"が"}, + {0x27, u8"ぎ"}, + {0x28, u8"ぐ"}, + {0x29, u8"げ"}, + {0x2A, u8"ご"}, + {0x2B, u8"ざ"}, + {0x2C, u8"じ"}, + {0x2D, u8"ず"}, + {0x2E, u8"ぜ"}, + {0x2F, u8"ぞ"}, + {0x30, u8"だ"}, + {0x31, u8"ぢ"}, + {0x32, u8"づ"}, + {0x33, u8"で"}, + {0x34, u8"ど"}, + {0x35, u8"な゙"}, + {0x36, u8"に゙"}, + {0x37, u8"ぬ゙"}, + {0x38, u8"ね゙"}, + {0x39, u8"の゙"}, + {0x3A, u8"ば"}, + {0x3B, u8"び"}, + {0x3C, u8"ぶ"}, + {0x3D, u8"べ"}, + {0x3E, u8"ぼ"}, + {0x3F, u8"ま゙"}, + {0x40, u8"パ"}, + {0x41, u8"ピ"}, + {0x42, u8"プ"}, + {0x43, u8"ポ"}, + {0x44, u8"ぱ"}, + {0x45, u8"ぴ"}, + {0x46, u8"ぷ"}, + {0x47, u8"ぺ"}, + {0x48, u8"ぽ"}, + {0x49, u8"ま゚"}, + {0x4D, u8"も゚"}, + {0x4F, u8" "}, + {0x57, u8"#"}, + {0x51, u8"*"}, + {0x52, u8"A1"}, + {0x53, u8"A2"}, + {0x54, u8"POKé"}, + {0x55, u8"+"}, + {0x58, u8"$"}, + {0x5D, u8"TRAINER"}, + {0x75, u8"…"}, + {0x7F, u8" "}, + {0x60, u8"A"}, + {0x61, u8"B"}, + {0x62, u8"C"}, + {0x63, u8"D"}, + {0x64, u8"E"}, + {0x65, u8"F"}, + {0x66, u8"G"}, + {0x67, u8"H"}, + {0x68, u8"I"}, + {0x69, u8"V"}, + {0x6A, u8"S"}, + {0x6B, u8"L"}, + {0x6C, u8"M"}, + {0x6D, u8":"}, + {0x6E, u8"ぃ"}, + {0x6F, u8"ぅ"}, + {0x70, u8"「"}, + {0x71, u8"」"}, + {0x72, u8"『"}, + {0x73, u8"』"}, + {0x74, u8"・"}, + {0x75, u8"…"}, + {0x76, u8"ぁ"}, + {0x77, u8"ぇ"}, + {0x78, u8"ぉ"}, + {0x80, u8"ア"}, + {0x81, u8"イ"}, + {0x82, u8"ウ"}, + {0x83, u8"エ"}, + {0x84, u8"オ"}, + {0x85, u8"カ"}, + {0x86, u8"キ"}, + {0x87, u8"ク"}, + {0x88, u8"ケ"}, + {0x89, u8"コ"}, + {0x8A, u8"サ"}, + {0x8B, u8"シ"}, + {0x8C, u8"ス"}, + {0x8D, u8"セ"}, + {0x8E, u8"ソ"}, + {0x8F, u8"タ"}, + {0x90, u8"チ"}, + {0x91, u8"ツ"}, + {0x92, u8"テ"}, + {0x93, u8"ト"}, + {0x94, u8"ナ"}, + {0x95, u8"ニ"}, + {0x96, u8"ヌ"}, + {0x97, u8"ネ"}, + {0x98, u8"ノ"}, + {0x99, u8"ハ"}, + {0x9A, u8"ヒ"}, + {0x9B, u8"フ"}, + {0x9C, u8"ホ"}, + {0x9D, u8"マ"}, + {0x9E, u8"ミ"}, + {0x9F, u8"ム"}, + {0xA0, u8"メ"}, + {0xA1, u8"モ"}, + {0xA2, u8"ヤ"}, + {0xA3, u8"ユ"}, + {0xA4, u8"ヨ"}, + {0xA5, u8"ラ"}, + {0xA6, u8"ル"}, + {0xA7, u8"レ"}, + {0xA8, u8"ロ"}, + {0xA9, u8"ワ"}, + {0xAA, u8"ヲ"}, + {0xAB, u8"ン"}, + {0xAC, u8"ッ"}, + {0xAD, u8"ャ"}, + {0xAE, u8"ュ"}, + {0xAF, u8"ョ"}, + {0xB0, u8"ィ"}, + {0xB1, u8"あ"}, + {0xB2, u8"い"}, + {0xB3, u8"う"}, + {0xB4, u8"え"}, + {0xB5, u8"お"}, + {0xB6, u8"か"}, + {0xB7, u8"き"}, + {0xB8, u8"く"}, + {0xB9, u8"け"}, + {0xBA, u8"こ"}, + {0xBB, u8"さ"}, + {0xBC, u8"し"}, + {0xBD, u8"す"}, + {0xBE, u8"せ"}, + {0xBF, u8"そ"}, + {0xC0, u8"た"}, + {0xC1, u8"ち"}, + {0xC2, u8"つ"}, + {0xC3, u8"て"}, + {0xC4, u8"と"}, + {0xC5, u8"な"}, + {0xC6, u8"に"}, + {0xC7, u8"ぬ"}, + {0xC8, u8"ね"}, + {0xC9, u8"の"}, + {0xCA, u8"は"}, + {0xCB, u8"ひ"}, + {0xCC, u8"ふ"}, + {0xCD, u8"へ"}, + {0xCE, u8"ほ"}, + {0xCF, u8"ま"}, + {0xD0, u8"み"}, + {0xD1, u8"む"}, + {0xD2, u8"め"}, + {0xD3, u8"も"}, + {0xD4, u8"や"}, + {0xD5, u8"ゆ"}, + {0xD6, u8"よ"}, + {0xD7, u8"ら"}, + {0xD8, u8"リ"}, + {0xD9, u8"る"}, + {0xDA, u8"れ"}, + {0xDB, u8"ろ"}, + {0xDC, u8"わ"}, + {0xDD, u8"を"}, + {0xDE, u8"ん"}, + {0xDF, u8"っ"}, + {0xE0, u8"ゃ"}, + {0xE1, u8"ゅ"}, + {0xE2, u8"ょ"}, + {0xE3, u8"ー"}, + {0xE4, u8"゜"}, + {0xE5, u8"゛"}, + {0xE6, u8"?"}, + {0xE7, u8"!"}, + {0xE8, u8"。"}, + {0xE9, u8"ァ"}, + {0xEA, u8"ゥ"}, + {0xEB, u8"ェ"}, + {0xEC, u8"▷"}, + {0xED, u8"▶"}, + {0xEE, u8"▼"}, + {0xEF, u8"♂"}, + {0xF0, u8"円"}, + {0xF1, u8"×"}, + {0xF2, u8"."}, + {0xF3, u8"/"}, + {0xF4, u8"ォ"}, + {0xF5, u8"♀"}, + {0xF6, u8"0"}, + {0xF7, u8"1"}, + {0xF8, u8"2"}, + {0xF9, u8"3"}, + {0xFA, u8"4"}, + {0xFB, u8"5"}, + {0xFC, u8"6"}, + {0xFD, u8"7"}, + {0xFE, u8"8"}, + {0xFF, u8"9"} }; Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeader) @@ -131,6 +378,10 @@ Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeade { result = Gen1GameType::YELLOW; } + else if (strncmp(cartridgeHeader.title, POKEMON_GREEN_CARTRIDGE_TITLE, sizeof(POKEMON_GREEN_CARTRIDGE_TITLE) - 1) == 0) + { + result = Gen1GameType::GREEN; + } else { result = Gen1GameType::INVALID; @@ -138,6 +389,29 @@ Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeade return result; } +Gen1LocalizationLanguage gen1_determineGameLanguage(IRomReader& romReader, Gen1GameType gameType) +{ + // The pokemon index-to-pokedex-number map has a unique rom offset in each of the game localizations. + // It also should have the exact same data in all gen 1 games and all of their localizations. + // Therefore we can use a fingerprint byte pattern to check these locations to figure out which localization we have. + uint8_t buffer[sizeof(g1_indexNumberMapFingerprint)]; + + for(uint8_t i=0; i < static_cast(Gen1LocalizationLanguage::MAX); ++i) + { + const Gen1LocalizationRomOffsets& romOffsets = gen1_getRomOffsets(gameType, (Gen1LocalizationLanguage)i); + romReader.seek(romOffsets.numbers); + romReader.read(buffer, sizeof(g1_indexNumberMapFingerprint)); + if(memcmp(buffer, g1_indexNumberMapFingerprint, sizeof(g1_indexNumberMapFingerprint)) == 0) + { + // we found the fingerprint at the "numbers" offset of the current localization! + // Therefore we know which game language we're dealing with! + return (Gen1LocalizationLanguage)i; + } + } + + return Gen1LocalizationLanguage::MAX; +} + void gen1_recalculatePokeStats(Gen1GameReader& reader, Gen1TrainerPokemon& poke) { Gen1PokeStats stats; @@ -151,16 +425,42 @@ void gen1_recalculatePokeStats(Gen1GameReader& reader, Gen1TrainerPokemon& poke) poke.special = calculatePokeStat(PokeStat::SPECIAL, stats.base_special, getStatIV(PokeStat::SPECIAL, poke.iv_data), poke.special_effort_value, poke.level); } -uint16_t gen1_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength) +uint16_t gen1_decodePokeText(const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength, Gen1LocalizationLanguage language) { - const uint16_t numEntries = sizeof(gen1TextCodes) / sizeof(struct TextCodePair); - return decodeText(gen1TextCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength); + const TextCodePair* textCodes; + uint16_t numEntries; + + if(language != Gen1LocalizationLanguage::JAPANESE) + { + textCodes = gen1TextCodesMain; + numEntries = sizeof(gen1TextCodesMain) / sizeof(struct TextCodePair); + } + else + { + textCodes = gen1TextCodesJpn; + numEntries = sizeof(gen1TextCodesJpn) / sizeof(struct TextCodePair); + } + + return decodeText(textCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength); } -uint16_t gen1_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator) +uint16_t gen1_encodePokeText(const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator, Gen1LocalizationLanguage language) { - const uint16_t numEntries = sizeof(gen1TextCodes) / sizeof(struct TextCodePair); - return encodeText(gen1TextCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, terminator); + const TextCodePair* textCodes; + uint16_t numEntries; + + if(language != Gen1LocalizationLanguage::JAPANESE) + { + textCodes = gen1TextCodesMain; + numEntries = sizeof(gen1TextCodesMain) / sizeof(struct TextCodePair); + } + else + { + textCodes = gen1TextCodesJpn; + numEntries = sizeof(gen1TextCodesJpn) / sizeof(struct TextCodePair); + } + + return encodeText(textCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, terminator); } void gen1_prepareDistributionPokemon(Gen1GameReader& gameReader, const Gen1DistributionPokemon& distributionPoke, Gen1TrainerPokemon& poke, const char*& originalTrainerName) @@ -172,7 +472,28 @@ void gen1_prepareDistributionPokemon(Gen1GameReader& gameReader, const Gen1Distr } else { - originalTrainerName = distributionPoke.originalTrainer; + const Gen1LocalizationLanguage gameLang = gameReader.getGameLanguage(); + + if(gameLang == Gen1LocalizationLanguage::JAPANESE && !distributionPoke.isJapanese) + { + // The Japanese games don't have all the latin characters in their character set. + // So if transferring a non-japanese distribution event pokémon to a japanese cartridge, + // we need to replace the OT. + // If Google Translate is any reliable, ポケメ64 should translate to "Pokeme 64". + originalTrainerName = "ポケメ64"; + } + else if(gameLang != Gen1LocalizationLanguage::JAPANESE && distributionPoke.isJapanese) + { + // Obviously non-japanese cartridges/roms don't have the japanese characters in their character set. + // So if transferring a japanese distribution event pokémon to a non-japanese cartridge, + // we need to replace the OT. + // We'll replace it with PM64 + originalTrainerName = "PM64"; + } + else + { + originalTrainerName = distributionPoke.originalTrainer; + } if(distributionPoke.regenerateTrainerID) { diff --git a/src/gen1/Gen1DistributionPokemon.cpp b/src/gen1/Gen1DistributionPokemon.cpp index f6ea45d..9a6b053 100644 --- a/src/gen1/Gen1DistributionPokemon.cpp +++ b/src/gen1/Gen1DistributionPokemon.cpp @@ -1,309 +1,359 @@ #include "gen1/Gen1DistributionPokemon.h" #include "common.h" +#include "Moves.h" static const Gen1TrainerPokemon commonMewDefinition = { - 0x15, - 1, // I don't want to bother with figuring out the max hp here, so I set current hp to 1 - 5, - 0, - 0x18, - 0x18, - 45, // based on Gen1PokeStats - 0x1, // pound - 0x0, - 0x0, - 0x0, - 0, - getExpNeededForLevel(5, 3), - 0, - 0, - 0, - 0, - 0, - {0xA1, 0xC5}, - 35, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 + .poke_index = 0x15, + .current_hp = 1, // I don't want to bother with figuring out the max hp here, so I set current hp to 1 + .level = 5, + .status_condition = 0, + .type1 = 0x18, + .type2 = 0x18, + .catch_rate_or_held_item = 45, // based on Gen1PokeStats + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = 0x0, + .index_move3 = 0x0, + .index_move4 = 0x0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0xA1, 0xC5}, + .pp_move1 = 35, + .pp_move2 = 0, + .pp_move3 = 0, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 }; static const Gen1TrainerPokemon surfingPikachu = { - 0x54, - 1, - 5, - 0, - 0x17, - 0x17, - 190, - 84, - 45, - 57, - 0, - 0, - getExpNeededForLevel(5, 0), - 0, - 0, - 0, - 0, - 0, - {0, 0}, - 30, - 40, - 15, - 0, - 0, - 0, - 0, - 0, - 0 + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 0x17, + .type2 = 0x17, + .catch_rate_or_held_item = 190, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SURF, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 }; -const Gen1DistributionPokemon g1_nintendoUKPokemonFestivalMew2016 = { +// https://bulbapedia.bulbagarden.net/wiki/List_of_European_language_event_Pok%C3%A9mon_distributions_(Generation_I) +static const Gen1DistributionPokemon g1_nintendoUKPokemonFestivalMew2016 = { "Nintendo UK Pokemon Festival Mew 2016", "GF", 22796, {0xFF, 0xFF}, false, false, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_clubNintendoMexicoGiveawayMew = { +static const Gen1DistributionPokemon g1_clubNintendoMexicoGiveawayMew = { "Club Nintendo Mexico Giveaway Mew", "ASH", 45515, {0xA1, 0xC5}, false, false, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_farMorBornMew = { +static const Gen1DistributionPokemon g1_farMorBornMew = { "Far, Mor & Born Mew", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_christmasPresentMew = { +static const Gen1DistributionPokemon g1_christmasPresentMew = { "Christmas Present Mew", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_swedenMewOnTour = { +static const Gen1DistributionPokemon g1_swedenMewOnTour = { "Sweden Mew On Tour", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_tennispalatsiMew = { +static const Gen1DistributionPokemon g1_tennispalatsiMew = { "Tennispalatsi Mew", "FINLAND", 1400, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_norwayPokemonTourMew = { +static const Gen1DistributionPokemon g1_norwayPokemonTourMew = { "Norway Pokémon Tour Mew", "EUROPE", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_clubNintendoMew = { +static const Gen1DistributionPokemon g1_clubNintendoMew = { "Club Nintendo Mew", "EUROPE", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_pokemon2000ChampionshipMew = { +static const Gen1DistributionPokemon g1_pokemon2000ChampionshipMew = { "Pokémon 2000 World Championship Mew", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_millenniumDomeMew = { +static const Gen1DistributionPokemon g1_millenniumDomeMew = { "Millennium Dome Mew", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_austriaMew = { +static const Gen1DistributionPokemon g1_austriaMew = { "Austria Mew", "AUSTRIA", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_ukIrelandPokemonChampionship2000Mew = { +static const Gen1DistributionPokemon g1_ukIrelandPokemonChampionship2000Mew = { "UK and Ireland Pokémon Championship 2000 Mew", "UK", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_coraChatelineauMew = { +static const Gen1DistributionPokemon g1_coraChatelineauMew = { "Cora Châtelineau Mew", "BENELUX", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_francePokemonTournamentMew = { +static const Gen1DistributionPokemon g1_francePokemonTournamentMew = { "France Pokémon Tournament Mew", "", 0, {0xA1, 0xC5}, true, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_spainPokemonTournamentMew = { +static const Gen1DistributionPokemon g1_spainPokemonTournamentMew = { "Spain Pokémon Tournament Mew", "EUROPE", 0, {0x64, 0x38}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_mewsFlashMew = { +static const Gen1DistributionPokemon g1_mewsFlashMew = { "Mews Flash Mew", "UK", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_pokemonPatrolMew = { +static const Gen1DistributionPokemon g1_pokemonPatrolMew = { "Pokémon Patrol Mew", "YOSHIBB", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_nintendoOfficialMagazineTourMew = { +static const Gen1DistributionPokemon g1_nintendoOfficialMagazineTourMew = { "Nintendo Official Magazine Tour Mew", "UK", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_canadianPokemonStadiumTour2000Mew = { +static const Gen1DistributionPokemon g1_canadianPokemonStadiumTour2000Mew = { "Canadian Pokémon Stadium Tour 2000 Mew", "LUIGIC", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_pokemonStadiumTour2000Mew = { +static const Gen1DistributionPokemon g1_pokemonStadiumTour2000Mew = { "Pokémon 2000 Stadium Tour Mew", "LUIGE", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_canadaToysRUsMew = { +static const Gen1DistributionPokemon g1_canadaToysRUsMew = { "Canada Toys \"R\" Us Mew", "YOSHIRA", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_usToysRUsMew = { +static const Gen1DistributionPokemon g1_usToysRUsMew = { "U.S. Toys \"R\" Us Mew", "YOSHIBA", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_nintendoPowerMew = { +static const Gen1DistributionPokemon g1_nintendoPowerMew = { "Nintendo Power Mew", "YOSHIRB", 1000, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_pokemonLeagueNintendoTrainingTour99Mew = { +static const Gen1DistributionPokemon g1_pokemonLeagueNintendoTrainingTour99Mew = { "Pokémon League Nintendo Training Tour '99 Mew", "YOSHIRA", 0, {0xA1, 0xC5}, false, true, + false, + 0, commonMewDefinition }; -const Gen1DistributionPokemon g1_nintendoPowerPikachu = { +static const Gen1DistributionPokemon g1_nintendoPowerPikachu = { "Nintendo Power Pikachu", "", 1000, @@ -311,29 +361,461 @@ const Gen1DistributionPokemon g1_nintendoPowerPikachu = { // https://projectpokemon.org/home/forums/topic/37431-gen-i-v-event-contributions-thread/page/5/ true, true, + false, + 0, surfingPikachu }; -const Gen1DistributionPokemon g1_pokeTourMew = { - "Poké Tour Mew", - "AUS", - 0, - {0xA1, 0xC5}, - false, - true, - commonMewDefinition +static const Gen1DistributionPokemon g1_pokeTourMew = { + .name = "Poké Tour Mew", + .originalTrainer = "AUS", + .originalTrainerID = 0, + .iv_data = {0xA1, 0xC5}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = false, + .held_item_override = 0, + .poke = commonMewDefinition }; -const Gen1DistributionPokemon g1_pokemonPowerMew = { - "Pokémon Power Mew", - "MARIO", - 151, - {0xA1, 0xC5}, - false, - true, - commonMewDefinition +static const Gen1DistributionPokemon g1_pokemonPowerMew = { + .name = "Pokémon Power Mew", + .originalTrainer = "MARIO", + .originalTrainerID = 151, + .iv_data = {0xA1, 0xC5}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = false, + .held_item_override = 0, + .poke = commonMewDefinition }; +static const Gen1DistributionPokemon g1_jpn_gamefreakMew = { + .name = "Game Freak Mew 2016 (JPN)", + .originalTrainer = "ゲーフリ", + .originalTrainerID = 22796, + .iv_data = {0xFF, 0xFF}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = false, + .isJapanese = true, + .held_item_override = 0, + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_spaceworld99Mew = { + .name = "Nintendo Space World '99 Mew (JPN)", + .originalTrainer = "マクハリ", + .originalTrainerID = 0, + .iv_data = {0xFF, 0xFF}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_pokemonStampFearow = { + .name = "Pokémon Stamp Fearow (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0, + .poke = { + .poke_index = 0x23, + .current_hp = 1, + .level = 25, + .status_condition = 0, + .type1 = 0, // NORMAL + .type2 = 2, // FLYING + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::FURY_ATTACK, + .index_move4 = (uint8_t)Move::PAY_DAY, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(25, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 40, + .pp_move2 = 30, + .pp_move3 = 20, + .pp_move4 = 20, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_pokemonStampRapidash = { + .name = "Pokémon Stamp Rapidash (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0, + .poke = { + .poke_index = 0xA4, + .current_hp = 1, + .level = 40, + .status_condition = 0, + .type1 = 20, // FIRE + .type2 = 20, // FIRE + .catch_rate_or_held_item = 0x3C, + .index_move1 = (uint8_t)Move::EMBER, + .index_move2 = (uint8_t)Move::FIRE_SPIN, + .index_move3 = (uint8_t)Move::STOMP, + .index_move4 = (uint8_t)Move::PAY_DAY, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(40, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 25, + .pp_move2 = 15, + .pp_move3 = 20, + .pp_move4 = 20, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_summer98PokemonBattleTourPikachu = { + .name = "Summer '98 Pokémon Battle Tour Pikachu (JPN)", + .originalTrainer = "イマクニ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0, + .poke = { + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 23, // ELECTRIC + .type2 = 23, // ELECTRIC + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SURF, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_universityMagikarp = { + .name = "University Magikarp (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0, + .poke = { + .poke_index = 0x85, + .current_hp = 1, + .level = 15, + .status_condition = 0, + .type1 = 21, // WATER + .type2 = 21, // WATER + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::SPLASH, + .index_move2 = (uint8_t)Move::DRAGON_RAGE, + .index_move3 = 0, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(15, GRW_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 40, + .pp_move2 = 10, + .pp_move3 = 0, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_ng_worldHobbyFairMew = { + .name = "NG World Hobby Fair Mew (JPN)", + .originalTrainer = "トウキョー", + .originalTrainerID = 0, + .iv_data = {0xA1, 0xC5}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_nintendoSpaceWorld97Mew = { + .name = "Nintendo Space World '97 Mew (JPN)", + .originalTrainer = "クッパ", + .originalTrainerID = 0, + .iv_data = {0xA1, 0xC5}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_nintendo64SurfingPikachu = { + .name = "Nintendo 64 Surfing Pikachu (JPN)", + .originalTrainer = "ニンテン", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0xAD, // BERRY + .poke = { + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 23, // ELECTRIC + .type2 = 23, // ELECTRIC + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SURF, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_flyingPikachu = { + .name = "Flying Pikachu (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0xAD, // BERRY + .poke = { + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 23, // ELECTRIC + .type2 = 23, // ELECTRIC + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::FLY, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_surfingPikachu = { + .name = "Surfing Pikachu (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0xAD, // BERRY + .poke = { + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 23, // ELECTRIC + .type2 = 23, // ELECTRIC + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SURF, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_pkmn2IdeaContestSurfingPikachu = { + .name = "PKMN 2 Idea Contest Surfing Pikachu (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0xAD, // BERRY + .poke = { + .poke_index = 0x54, + .current_hp = 1, + .level = 5, + .status_condition = 0, + .type1 = 23, // ELECTRIC + .type2 = 23, // ELECTRIC + .catch_rate_or_held_item = 0xAD, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SURF, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special = 0 + } +}; + +static const Gen1DistributionPokemon g1_jpn_corocoro20thAnniversaryMew = { + .name = "CoroCoro 20th Anniversary Mew (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, // Bitter Berry + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_4thNGWorldHobbyFairMew = { + .name = "4th NG World Hobby Fair Mew (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, // Bitter Berry + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_pkmn2ProdStartMew = { + .name = "PKMN2 Production Start Mew (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 0, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, // Bitter Berry + .poke = commonMewDefinition +}; + +static const Gen1DistributionPokemon g1_jpn_legendaryPokemonOfferMew = { + .name = "Legendary Pokémon Offer Mew (JPN)", + .originalTrainer = "コロコロ", + .originalTrainerID = 21, + .iv_data = {0, 0}, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .isJapanese = true, + .held_item_override = 0x53, // Bitter Berry + .poke = commonMewDefinition +}; + +// TODO: add more japanese distro pkmn + static const Gen1DistributionPokemon* mainList[] = { &g1_nintendoUKPokemonFestivalMew2016, &g1_clubNintendoMexicoGiveawayMew, @@ -361,7 +843,23 @@ static const Gen1DistributionPokemon* mainList[] = { &g1_pokemonLeagueNintendoTrainingTour99Mew, &g1_nintendoPowerPikachu, &g1_pokeTourMew, - &g1_pokemonPowerMew + &g1_pokemonPowerMew, + &g1_jpn_gamefreakMew, + &g1_jpn_spaceworld99Mew, + &g1_jpn_pokemonStampFearow, + &g1_jpn_pokemonStampRapidash, + &g1_jpn_summer98PokemonBattleTourPikachu, + &g1_jpn_universityMagikarp, + &g1_jpn_ng_worldHobbyFairMew, + &g1_jpn_nintendoSpaceWorld97Mew, + &g1_jpn_nintendo64SurfingPikachu, + &g1_jpn_flyingPikachu, + &g1_jpn_surfingPikachu, + &g1_jpn_pkmn2IdeaContestSurfingPikachu, + &g1_jpn_corocoro20thAnniversaryMew, + &g1_jpn_4thNGWorldHobbyFairMew, + &g1_jpn_pkmn2ProdStartMew, + &g1_jpn_legendaryPokemonOfferMew }; void gen1_getMainDistributionPokemonList(const Gen1DistributionPokemon**& outList, uint32_t& outSize) diff --git a/src/gen1/Gen1GameReader.cpp b/src/gen1/Gen1GameReader.cpp index 9e0239f..838491b 100644 --- a/src/gen1/Gen1GameReader.cpp +++ b/src/gen1/Gen1GameReader.cpp @@ -5,19 +5,21 @@ #include "utils.h" #include +#include + /** * @brief This function calculates the main data checksum */ -uint8_t calculateMainDataChecksum(ISaveManager& saveManager) +uint8_t calculateMainDataChecksum(ISaveManager& saveManager, Gen1LocalizationLanguage localization) { Gen1Checksum checksum; - const uint16_t checksummedDataStart = 0x598; - const uint16_t checksummedDataEnd = 0x1523; - const uint16_t numBytes = checksummedDataEnd - checksummedDataStart; - uint16_t i; + 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.seekToBankOffset(1, checksummedDataStart); + saveManager.seek(checksummedDataStart); for(i=0; i < numBytes; ++i) { @@ -47,13 +49,23 @@ uint8_t calculateWholeBoxBankChecksum(ISaveManager& saveManager, uint8_t bankInd return checksum.get(); } -Gen1GameReader::Gen1GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen1GameType gameType) +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 @@ -62,31 +74,22 @@ const char *Gen1GameReader::getPokemonName(uint8_t index) const static char result[20]; uint8_t encodedText[0xA]; uint32_t numRead; - uint16_t pointer; + const uint16_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0xA : 0x5; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).names; - if(gameType_ == Gen1GameType::BLUE || gameType_ == Gen1GameType::RED) + if(!romOffset) { - romReader_.seek(0x2FA3); - const uint8_t bankByte = romReader_.peek(); - - romReader_.seek(0x2FAE); - romReader_.readUint16(pointer); - - // seek to the right location - romReader_.seekToRomPointer(pointer, bankByte); - } - else - { - // Pkmn Yellow - romReader_.seek(0xE8000); + snprintf(result, sizeof(result) - 1, "poke-%hhu", index); + return result; } - romReader_.advance((index - 1) * 0xA); + romReader_.seek(romOffset); + romReader_.advance((index - 1) * entrySize); // max 10 bytes - numRead = romReader_.readUntil(encodedText, 0x50, 0xA); + numRead = romReader_.readUntil(encodedText, 0x50, entrySize); - gen1_decodePokeText(encodedText, numRead, result, sizeof(result)); + gen1_decodePokeText(encodedText, numRead, result, sizeof(result), localization_); return result; } @@ -94,7 +97,7 @@ 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 = (gameType_ == Gen1GameType::YELLOW) ? 0x410B1 : 0x41024; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).numbers; romReader_.seek(romOffset + (index - 1)); romReader_.readByte(result); return result; @@ -105,7 +108,7 @@ 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 = (gameType_ == Gen1GameType::YELLOW) ? 0x719BA : 0x7190D; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).iconTypes; uint8_t byteVal; Gen1PokemonIconType result; @@ -198,8 +201,6 @@ bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outS const uint8_t pokeNumber = getPokemonNumber(index); uint8_t spriteBank; - uint8_t statsBank; - uint16_t statsPointer; if (gameType_ != Gen1GameType::YELLOW && index == 0x15) { @@ -235,23 +236,14 @@ bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outS // 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(0x153B); - romReader_.readByte(statsBank); - - romReader_.seek(0x1578); - romReader_.readUint16(statsPointer); + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); } else { // mew stats - romReader_.seek(0x159C); - romReader_.readByte(statsBank); - - romReader_.seek(0x1593); - romReader_.readUint16(statsPointer); + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).statsMew); } - romReader_.seekToRomPointer(statsPointer, statsBank); if (pokeNumber != 151) { // the number is 1-based. @@ -261,7 +253,7 @@ bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outS else { // Dealing with Pokemon yellow - romReader_.seek(0x383DE); + romReader_.seek(gen1_getRomOffsets(gameType_, localization_).stats); // the number is 1-based. romReader_.advance(statsStructSize * (pokeNumber - 1)); } @@ -290,7 +282,7 @@ uint8_t Gen1GameReader::getColorPaletteIndexByPokemonNumber(uint8_t pokeNumber) { uint8_t result; // pokeyellow.map from https://github.com/pret/pokeyellow (after compilation) - const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? 0x72922 : 0x725C9; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).paletteIndices; if(!romReader_.seek(romOffset + (pokeNumber - 1))) { return 0xFF; @@ -311,7 +303,7 @@ void Gen1GameReader::readColorPalette(uint8_t paletteId, uint16_t* outColorPalet // 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 = (gameType_ == Gen1GameType::YELLOW) ? 0x72AF9 : 0x72660; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, localization_).palettes; romReader_.seek(romOffset + (paletteId * 8)); while(cur < end) { @@ -328,7 +320,7 @@ const char *Gen1GameReader::getTrainerName() const saveManager_.seek(0x2598); saveManager_.readUntil(encodedPlayerName, 0x50, 0xB); - gen1_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result)); + gen1_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result), localization_); return result; } @@ -336,38 +328,51 @@ const char *Gen1GameReader::getRivalName() const { static char result[20]; uint8_t encodedRivalName[0xB]; - saveManager_.seek(0x25F6); + 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)); + gen1_decodePokeText(encodedRivalName, sizeof(encodedRivalName), result, sizeof(result), localization_); return result; } uint16_t Gen1GameReader::getTrainerID() const { uint16_t result; - - saveManager_.seek(0x2605); + 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_); + return Gen1Party((*this), saveManager_, localization_); } Gen1Box Gen1GameReader::getBox(uint8_t boxIndex) { - return Gen1Box((*this), saveManager_, boxIndex); + return Gen1Box((*this), saveManager_, boxIndex, localization_); } uint8_t Gen1GameReader::getCurrentBoxIndex() { uint8_t byte; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).currentBoxIndex; - saveManager_.seek(0x284C); + saveManager_.seek(savOffset); saveManager_.readByte(byte); return byte & 0x3F; @@ -375,8 +380,10 @@ uint8_t Gen1GameReader::getCurrentBoxIndex() bool Gen1GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const { - const uint16_t saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x25B6 : 0x25A3; + 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; @@ -394,8 +401,10 @@ bool Gen1GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) void Gen1GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) const { - const uint16_t saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x25B6 : 0x25A3; + 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; @@ -414,7 +423,8 @@ void Gen1GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) uint8_t Gen1GameReader::getPokedexCounter(PokedexFlag dexFlag) const { - const uint16_t saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x25B6 : 0x25A3; + 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; @@ -458,7 +468,7 @@ uint8_t* Gen1GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer) uint8_t* Gen1GameReader::decodePokemonIcon(Gen1PokemonIconType iconType, bool firstFrame) { - return iconDecoder_.decode(iconType, firstFrame); + return iconDecoder_.decode(localization_, iconType, firstFrame); } uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname) @@ -480,7 +490,8 @@ uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* origina else { const uint8_t currentBoxIndex = getCurrentBoxIndex(); - for(uint8_t i = 0; i < 12; ++i) + 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()) @@ -491,7 +502,7 @@ uint8_t Gen1GameReader::addPokemon(Gen1TrainerPokemon& poke, const char* origina box.add(poke, originalTrainerID, nickname); result = i; - updateWholeBoxBankChecksum(getGen1BoxBankIndex(i, currentBoxIndex)); + updateWholeBoxBankChecksum(getGen1BoxBankIndex(i, currentBoxIndex, localization_)); break; } } @@ -514,22 +525,22 @@ uint8_t Gen1GameReader::addDistributionPokemon(const Gen1DistributionPokemon& di bool Gen1GameReader::isMainChecksumValid() { - const uint16_t mainDataChecksumOffset = 0x3523; + const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; uint8_t storedChecksum; uint8_t calculatedChecksum; saveManager_.seek(mainDataChecksumOffset); saveManager_.readByte(storedChecksum); - calculatedChecksum = calculateMainDataChecksum(saveManager_); + calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); return (storedChecksum == calculatedChecksum); } void Gen1GameReader::updateMainChecksum() { - const uint16_t mainDataChecksumOffset = 0x3523; - const uint8_t calculatedChecksum = calculateMainDataChecksum(saveManager_); + const uint32_t mainDataChecksumOffset = gen1_getSRAMOffsets(localization_).checksum; + const uint8_t calculatedChecksum = calculateMainDataChecksum(saveManager_, localization_); saveManager_.seek(mainDataChecksumOffset); saveManager_.writeByte(calculatedChecksum); diff --git a/src/gen1/Gen1IconDecoder.cpp b/src/gen1/Gen1IconDecoder.cpp index 3a79886..ba7ad67 100644 --- a/src/gen1/Gen1IconDecoder.cpp +++ b/src/gen1/Gen1IconDecoder.cpp @@ -41,9 +41,9 @@ Gen1IconDecoder::Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType) * * Anyway, we need to deal with this mess and that makes our code below a bit messy as well. */ -uint8_t* Gen1IconDecoder::decode(Gen1PokemonIconType iconType, bool firstFrame) +uint8_t* Gen1IconDecoder::decode(Gen1LocalizationLanguage language, Gen1PokemonIconType iconType, bool firstFrame) { - const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? 0x7184D : 0x717C0; + const uint32_t romOffset = gen1_getRomOffsets(gameType_, language).icons; const uint32_t ENTRY_SIZE = 6; const uint8_t MAX_NUM_TILES = 8; diff --git a/src/gen1/Gen1Localization.cpp b/src/gen1/Gen1Localization.cpp new file mode 100644 index 0000000..557a4b8 --- /dev/null +++ b/src/gen1/Gen1Localization.cpp @@ -0,0 +1,197 @@ +#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; +} \ No newline at end of file diff --git a/src/gen1/Gen1Maps.cpp b/src/gen1/Gen1Maps.cpp new file mode 100644 index 0000000..8716711 --- /dev/null +++ b/src/gen1/Gen1Maps.cpp @@ -0,0 +1,527 @@ +#include "gen1/Gen1Maps.h" + +const char *gen1Maps_toString(Gen1Maps map) +{ + switch (map) + { + case Gen1Maps::PALLET_TOWN: + return "Pallet Town"; + case Gen1Maps::VIRIDIAN_CITY: + return "Viridian City"; + case Gen1Maps::PEWTER_CITY: + return "Peach City"; + case Gen1Maps::CERULEAN_CITY: + return "Cerulean City"; + case Gen1Maps::LAVENDER_TOWN: + return "Lavender Town"; + case Gen1Maps::VERMILION_CITY: + return "Vermilion City"; + case Gen1Maps::CELADON_CITY: + return "Celadon City"; + case Gen1Maps::FUCHSIA_CITY: + return "Fuchsia City"; + case Gen1Maps::CINNABAR_ISLAND: + return "Cinnabar Island"; + case Gen1Maps::INDIGO_PLATEAU: + return "Indigo Plateau"; + case Gen1Maps::SAFFRON_CITY: + return "Saffron City"; + case Gen1Maps::UNUSED_MAP_0B: + return "Unused Map 0B"; + case Gen1Maps::ROUTE_1: + return "Route 1"; + case Gen1Maps::ROUTE_2: + return "Route 2"; + case Gen1Maps::ROUTE_3: + return "Route 3"; + case Gen1Maps::ROUTE_4: + return "Route 4"; + case Gen1Maps::ROUTE_5: + return "Route 5"; + case Gen1Maps::ROUTE_6: + return "Route 6"; + case Gen1Maps::ROUTE_7: + return "Route 7"; + case Gen1Maps::ROUTE_8: + return "Route 8"; + case Gen1Maps::ROUTE_9: + return "Route 9"; + case Gen1Maps::ROUTE_10: + return "Route 10"; + case Gen1Maps::ROUTE_11: + return "Route 11"; + case Gen1Maps::ROUTE_12: + return "Route 12"; + case Gen1Maps::ROUTE_13: + return "Route 13"; + case Gen1Maps::ROUTE_14: + return "Route 14"; + case Gen1Maps::ROUTE_15: + return "Route 15"; + case Gen1Maps::ROUTE_16: + return "Route 16"; + case Gen1Maps::ROUTE_17: + return "Route 17"; + case Gen1Maps::ROUTE_18: + return "Route 18"; + case Gen1Maps::ROUTE_19: + return "Route 19"; + case Gen1Maps::ROUTE_20: + return "Route 20"; + case Gen1Maps::ROUTE_21: + return "Route 21"; + case Gen1Maps::ROUTE_22: + return "Route 22"; + case Gen1Maps::ROUTE_23: + return "Route 23"; + case Gen1Maps::ROUTE_24: + return "Route 24"; + case Gen1Maps::ROUTE_25: + return "Route 25"; + case Gen1Maps::REDS_HOUSE_1F: + return "Red's House 1F"; + case Gen1Maps::REDS_HOUSE_2F: + return "Red's House 2F"; + case Gen1Maps::BLUES_HOUSE: + return "Blue's House"; + case Gen1Maps::OAKS_LAB: + return "Oak's Lab"; + case Gen1Maps::VIRIDIAN_POKECENTER: + return "Viridian Pokecenter"; + case Gen1Maps::VIRIDIAN_MART: + return "Viridian Mart"; + case Gen1Maps::VIRIDIAN_SCHOOL_HOUSE: + return "Viridian School House"; + case Gen1Maps::VIRIDIAN_NICKNAME_HOUSE: + return "Viridian Nickname House"; + case Gen1Maps::VIRIDIAN_GYM: + return "Viridian Gym"; + case Gen1Maps::DIGGLETTS_CAVE_ROUTE_2: + return "Diglett's Cave Route 2"; + case Gen1Maps::VIRIDIAN_FOREST_NORTH_GATE: + return "Viridian Forest North Gate"; + case Gen1Maps::ROUTE_2_TRADE_HOUSE: + return "Route 2 Trade House"; + case Gen1Maps::ROUTE_2_GATE: + return "Route 2 Gate"; + case Gen1Maps::VIRIDIAN_FOREST_SOUTH_GATE: + return "Viridian Forest South Gate"; + case Gen1Maps::VIRIDIAN_FOREST: + return "Viridian Forest"; + case Gen1Maps::MUSEUM_1F: + return "Museum 1F"; + case Gen1Maps::MUSEUM_2F: + return "Museum 2F"; + case Gen1Maps::PEWTER_GYM: + return "Peach Gym"; + case Gen1Maps::PEWTER_NIDORAN_HOUSE: + return "Peach Nidoran House"; + case Gen1Maps::PEWTER_MART: + return "Peach Mart"; + case Gen1Maps::PEWTER_SPEECH_HOUSE: + return "Peach Speech House"; + case Gen1Maps::PEWTER_POKECENTER: + return "Peach Pokecenter"; + case Gen1Maps::MT_MOON_1F: + return "Mt Moon 1F"; + case Gen1Maps::MT_MOON_B1F: + return "Mt Moon B1F"; + case Gen1Maps::MT_MOON_B2F: + return "Mt Moon B2F"; + case Gen1Maps::CERULEAN_THRASHED_HOUSE: + return "Cerulean Thrashed House"; + case Gen1Maps::CERULEAN_TRADE_HOUSE: + return "Cerulean Trade House"; + case Gen1Maps::CERULEAN_POKECENTER: + return "Cerulean Pokecenter"; + case Gen1Maps::CERULEAN_GYM: + return "Cerulean Gym"; + case Gen1Maps::BIKE_SHOP: + return "Bike Shop"; + case Gen1Maps::CERULEAN_MART: + return "Cerulean Mart"; + case Gen1Maps::MT_MOON_POKECENTER: + return "Mt Moon Pokecenter"; + case Gen1Maps::CERULEAN_THRASHED_HOUSE_COPY: + return "Cerulean Thrashed House Copy"; + case Gen1Maps::ROUTE_5_GATE: + return "Route 5 Gate"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_5: + return "Underground Path Route 5"; + case Gen1Maps::DAYCARE: + return "Daycare"; + case Gen1Maps::ROUTE_6_GATE: + return "Route 6 Gate"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_6: + return "Underground Path Route 6"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_6_COPY: + return "Underground Path Route 6 Copy"; + case Gen1Maps::ROUTE_7_GATE: + return "Route 7 Gate"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_7: + return "Underground Path Route 7"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_7_COPY: + return "Underground Path Route 7 Copy"; + case Gen1Maps::ROUTE_8_GATE: + return "Route 8 Gate"; + case Gen1Maps::UNDERGROUND_PATH_ROUTE_8: + return "Underground Path Route 8"; + case Gen1Maps::ROCK_TUNNEL_POKECENTER: + return "Rock Tunnel Pokecenter"; + case Gen1Maps::ROCK_TUNNEL_1F: + return "Rock Tunnel 1F"; + case Gen1Maps::POWER_PLANT: + return "Power Plant"; + case Gen1Maps::ROUTE_11_GATE_1F: + return "Route 11 Gate 1F"; + case Gen1Maps::DIGLETTS_CAVE_ROUTE_11: + return "Diglett's Cave Route 11"; + case Gen1Maps::ROUTE_11_GATE_2F: + return "Route 11 Gate 2F"; + case Gen1Maps::ROUTE_12_GATE_1F: + return "Route 12 Gate 1F"; + case Gen1Maps::BILLS_HOUSE: + return "Bill's House"; + case Gen1Maps::VERMILION_POKECENTER: + return "Vermilion Pokecenter"; + case Gen1Maps::POKEMON_FAN_CLUB: + return "Pokemon Fan Club"; + case Gen1Maps::VERMILION_MART: + return "Vermilion Mart"; + case Gen1Maps::VERMILION_GYM: + return "Vermilion Gym"; + case Gen1Maps::VERMILION_PIDGEY_HOUSE: + return "Vermilion Pidgey House"; + case Gen1Maps::VERMILION_DOCK: + return "Vermilion Dock"; + case Gen1Maps::SS_ANNE_1F: + return "SS Anne 1F"; + case Gen1Maps::SS_ANNE_2F: + return "SS Anne 2F"; + case Gen1Maps::SS_ANNE_3F: + return "SS Anne 3F"; + case Gen1Maps::SS_ANNE_B1F: + return "SS Anne B1F"; + case Gen1Maps::SS_ANNE_BOW: + return "SS Anne Bow"; + case Gen1Maps::SS_ANNE_KITCHEN: + return "SS Anne Kitchen"; + case Gen1Maps::SS_ANNE_CAPTAINS_ROOM: + return "SS Anne Captain's Room"; + case Gen1Maps::SS_ANNE_1F_ROOMS: + return "SS Anne 1F Rooms"; + case Gen1Maps::SS_ANNE_2F_ROOMS: + return "SS Anne 2F Rooms"; + case Gen1Maps::SS_ANNE_B1F_ROOMS: + return "SS Anne B1F Rooms"; + case Gen1Maps::UNUSED_MAP_69: + return "Unused Map 69"; + case Gen1Maps::UNUSED_MAP_6A: + return "Unused Map 6A"; + case Gen1Maps::UNUSED_MAP_6B: + return "Unused Map 6B"; + case Gen1Maps::VICTORY_ROAD_1F: + return "Victory Road 1F"; + case Gen1Maps::UNUSED_MAP_6D: + return "Unused Map 6D"; + case Gen1Maps::UNUSED_MAP_6E: + return "Unused Map 6E"; + case Gen1Maps::UNUSED_MAP_6F: + return "Unused Map 6F"; + case Gen1Maps::UNUSED_MAP_70: + return "Unused Map 70"; + case Gen1Maps::LANCES_ROOM: + return "Lance's Room"; + case Gen1Maps::UNUSED_MAP_72: + return "Unused Map 72"; + case Gen1Maps::UNUSED_MAP_73: + return "Unused Map 73"; + case Gen1Maps::UNUSED_MAP_74: + return "Unused Map 74"; + case Gen1Maps::UNUSED_MAP_75: + return "Unused Map 75"; + case Gen1Maps::HALL_OF_FAME: + return "Hall of Fame"; + case Gen1Maps::UNDERGROUND_PATH_NORTH_SOUTH: + return "Underground Path North South"; + case Gen1Maps::CHAMPIONS_ROOM: + return "Champion's Room"; + case Gen1Maps::UNDERGROUND_PATH_WEST_EAST: + return "Underground Path West East"; + case Gen1Maps::CELEDON_MART_1F: + return "Celadon Mart 1F"; + case Gen1Maps::CELEDON_MART_2F: + return "Celadon Mart 2F"; + case Gen1Maps::CELEDON_MART_3F: + return "Celadon Mart 3F"; + case Gen1Maps::CELEDON_MART_4F: + return "Celadon Mart 4F"; + case Gen1Maps::CELEDON_MART_ROOF: + return "Celadon Mart Roof"; + case Gen1Maps::CELEDON_MART_ELEVATOR: + return "Celadon Mart Elevator"; + case Gen1Maps::CELEDON_MANSION_1F: + return "Celadon Mansion 1F"; + case Gen1Maps::CELEDON_MANSION_2F: + return "Celadon Mansion 2F"; + case Gen1Maps::CELEDON_MANSION_3F: + return "Celadon Mansion 3F"; + case Gen1Maps::CELEDON_MANSION_ROOF: + return "Celadon Mansion Roof"; + case Gen1Maps::CELEDON_MANSION_ROOF_HOUSE: + return "Celadon Mansion Roof House"; + case Gen1Maps::CELEDON_POKECENTER: + return "Celadon Pokecenter"; + case Gen1Maps::CELEDON_GYM: + return "Celadon Gym"; + case Gen1Maps::GAME_CORNER: + return "Game Corner"; + case Gen1Maps::CELEDON_MART_5F: + return "Celadon Mart 5F"; + case Gen1Maps::GAME_CORNER_PRIZE_ROOM: + return "Game Corner Prize Room"; + case Gen1Maps::CELEDON_DINER: + return "Celadon Diner"; + case Gen1Maps::CELEDON_CHIEF_HOUSE: + return "Celadon Chief House"; + case Gen1Maps::CELEDON_HOTEL: + return "Celadon Hotel"; + case Gen1Maps::LAVENDER_POKECENTER: + return "Lavender Pokecenter"; + case Gen1Maps::POKEMON_TOWER_1F: + return "Pokemon Tower 1F"; + case Gen1Maps::POKEMON_TOWER_2F: + return "Pokemon Tower 2F"; + case Gen1Maps::POKEMON_TOWER_3F: + return "Pokemon Tower 3F"; + case Gen1Maps::POKEMON_TOWER_4F: + return "Pokemon Tower 4F"; + case Gen1Maps::POKEMON_TOWER_5F: + return "Pokemon Tower 5F"; + case Gen1Maps::POKEMON_TOWER_6F: + return "Pokemon Tower 6F"; + case Gen1Maps::POKEMON_TOWER_7F: + return "Pokemon Tower 7F"; + case Gen1Maps::MR_FUJIS_HOUSE: + return "Mr Fuji's House"; + case Gen1Maps::LAVENDER_MART: + return "Lavender Mart"; + case Gen1Maps::LAVENDER_CUBONE_HOUSE: + return "Lavender Cubone House"; + case Gen1Maps::FUCHSIA_MART: + return "Fuchsia Mart"; + case Gen1Maps::FUCHSIA_BILLS_GRANDPAS_HOUSE: + return "Fuchsia Bill's Grandpa's House"; + case Gen1Maps::FUCHSIA_POKECENTER: + return "Fuchsia Pokecenter"; + case Gen1Maps::WARDENS_HOUSE: + return "Warden's House"; + case Gen1Maps::SAFARI_ZONE_GATE: + return "Safari Zone Gate"; + case Gen1Maps::FUCHSIA_GYM: + return "Fuchsia Gym"; + case Gen1Maps::FUCHSIA_MEETING_ROOM: + return "Fuchsia Meeting Room"; + case Gen1Maps::SEAFOAM_ISLANDS_B1F: + return "Seafoam Islands B1F"; + case Gen1Maps::SEAFOAM_ISLANDS_B2F: + return "Seafoam Islands B2F"; + case Gen1Maps::SEAFOAM_ISLANDS_B3F: + return "Seafoam Islands B3F"; + case Gen1Maps::SEAFOAM_ISLANDS_B4F: + return "Seafoam Islands B4F"; + case Gen1Maps::VERMILION_OLD_ROD_HOUSE: + return "Vermilion Old Rod House"; + case Gen1Maps::FUCHSIA_GOOD_ROD_HOUSE: + return "Fuchsia Good Rod House"; + case Gen1Maps::POKEMON_MANSION_1F: + return "Pokemon Mansion 1F"; + case Gen1Maps::CINNABAR_GYM: + return "Cinnabar Gym"; + case Gen1Maps::CINNABAR_LAB: + return "Cinnabar Lab"; + case Gen1Maps::CINNABAR_LAB_TRADE_ROOM: + return "Cinnabar Lab Trade Room"; + case Gen1Maps::CINNABAR_LAB_METRONOME_ROOM: + return "Cinnabar Lab Metronome Room"; + case Gen1Maps::CINNABAR_LAB_FOSSIL_ROOM: + return "Cinnabar Lab Fossil Room"; + case Gen1Maps::CINNABAR_POKECENTER: + return "Cinnabar Pokecenter"; + case Gen1Maps::CINNABAR_MART: + return "Cinnabar Mart"; + case Gen1Maps::CINNABAR_MART_COPY: + return "Cinnabar Mart Copy"; + case Gen1Maps::INDIGO_PLATEAU_LOBBY: + return "Indigo Plateau Lobby"; + case Gen1Maps::COPYCATS_HOUSE_1F: + return "Copycats House 1F"; + case Gen1Maps::COPYCATS_HOUSE_2F: + return "Copycats House 2F"; + case Gen1Maps::FIGHTING_DOJO: + return "Fighting Dojo"; + case Gen1Maps::SAFFRON_GYM: + return "Saffron Gym"; + case Gen1Maps::SAFFRON_PIDGEY_HOUSE: + return "Saffron Pidgey House"; + case Gen1Maps::SAFFRON_MART: + return "Saffron Mart"; + case Gen1Maps::SILPH_CO_1F: + return "Silph Co 1F"; + case Gen1Maps::SAFFRON_POKECENTER: + return "Saffron Pokecenter"; + case Gen1Maps::MR_PSYCHICS_HOUSE: + return "Mr Psychics House"; + case Gen1Maps::ROUTE_15_GATE_1F: + return "Route 15 Gate 1F"; + case Gen1Maps::ROUTE_15_GATE_2F: + return "Route 15 Gate 2F"; + case Gen1Maps::ROUTE_16_GATE_1F: + return "Route 16 Gate 1F"; + case Gen1Maps::ROUTE_16_GATE_2F: + return "Route 16 Gate 2F"; + case Gen1Maps::ROUTE_16_FLY_HOUSE: + return "Route 16 Fly House"; + case Gen1Maps::ROUTE_12_SUPER_ROD_HOUSE: + return "Route 12 Super Rod House"; + case Gen1Maps::ROUTE_18_GATE_1F: + return "Route 18 Gate 1F"; + case Gen1Maps::ROUTE_18_GATE_2F: + return "Route 18 Gate 2F"; + case Gen1Maps::SEAFOAM_ISLANDS_1F: + return "Seafoam Islands 1F"; + case Gen1Maps::ROUTE_22_GATE: + return "Route 22 Gate"; + case Gen1Maps::VICTORY_ROAD_2F: + return "Victory Road 2F"; + case Gen1Maps::ROUTE_12_GATE_2F: + return "Route 12 Gate 2F"; + case Gen1Maps::VERMILION_TRADE_HOUSE: + return "Vermilion Trade House"; + case Gen1Maps::DIGLETTS_CAVE: + return "Digletts Cave"; + case Gen1Maps::VICTORY_ROAD_3F: + return "Victory Road 3F"; + case Gen1Maps::ROCKET_HIDEOUT_B1F: + return "Rocket Hideout B1F"; + case Gen1Maps::ROCKET_HIDEOUT_B2F: + return "Rocket Hideout B2F"; + case Gen1Maps::ROCKET_HIDEOUT_B3F: + return "Rocket Hideout B3F"; + case Gen1Maps::ROCKET_HIDEOUT_B4F: + return "Rocket Hideout B4F"; + case Gen1Maps::ROCKET_HIDEOUT_ELEVATOR: + return "Rocket Hideout Elevator"; + case Gen1Maps::UNUSED_MAP_CC: + return "Unused Map CC"; + case Gen1Maps::UNUSED_MAP_CD: + return "Unused Map CD"; + case Gen1Maps::UNUSED_MAP_CE: + return "Unused Map CE"; + case Gen1Maps::SILPH_CO_2F: + return "Silph Co 2F"; + case Gen1Maps::SILPH_CO_3F: + return "Silph Co 3F"; + case Gen1Maps::SILPH_CO_4F: + return "Silph Co 4F"; + case Gen1Maps::SILPH_CO_5F: + return "Silph Co 5F"; + case Gen1Maps::SILPH_CO_6F: + return "Silph Co 6F"; + case Gen1Maps::SILPH_CO_7F: + return "Silph Co 7F"; + case Gen1Maps::SILPH_CO_8F: + return "Silph Co 8F"; + case Gen1Maps::POKEMON_MANSION_2F: + return "Pokemon Mansion 2F"; + case Gen1Maps::POKEMON_MANSION_3F: + return "Pokemon Mansion 3F"; + case Gen1Maps::POKEMON_MANSION_B1F: + return "Pokemon Mansion B1F"; + case Gen1Maps::SAFARI_ZONE_EAST: + return "Safari Zone East"; + case Gen1Maps::SAFARI_ZONE_NORTH: + return "Safari Zone North"; + case Gen1Maps::SAFARI_ZONE_WEST: + return "Safari Zone West"; + case Gen1Maps::SAFARI_ZONE_CENTER: + return "Safari Zone Center"; + case Gen1Maps::SAFARI_ZONE_CENTER_REST_HOUSE: + return "Safari Zone Center Rest House"; + case Gen1Maps::SAFARI_ZONE_SECRET_HOUSE: + return "Safari Zone Secret House"; + case Gen1Maps::SAFARI_ZONE_WEST_REST_HOUSE: + return "Safari Zone West Rest House"; + case Gen1Maps::SAFARI_ZONE_EAST_REST_HOUSE: + return "Safari Zone East Rest House"; + case Gen1Maps::SAFARI_ZONE_NORTH_REST_HOUSE: + return "Safari Zone North Rest House"; + case Gen1Maps::CERULEAN_CAVE_2F: + return "Cerulean Cave 2F"; + case Gen1Maps::CERULEAN_CAVE_B1F: + return "Cerulean Cave B1F"; + case Gen1Maps::CERULEAN_CAVE_1F: + return "Cerulean Cave 1F"; + case Gen1Maps::NAME_RATERS_HOUSE: + return "Name Raters House"; + case Gen1Maps::CERULEAN_BADGE_HOUSE: + return "Cerulean Badge House"; + case Gen1Maps::UNUSED_MAP_E7: + return "Unused Map E7"; + case Gen1Maps::ROCK_TUNNEL_B1F: + return "Rock Tunnel B1F"; + case Gen1Maps::SILPH_CO_9F: + return "Silph Co 9F"; + case Gen1Maps::SILPH_CO_10F: + return "Silph Co 10F"; + case Gen1Maps::SILPH_CO_11F: + return "Silph Co 11F"; + case Gen1Maps::SILPH_CO_ELEVATOR: + return "Silph Co Elevator"; + case Gen1Maps::UNUSED_MAP_ED: + return "Unused Map ED"; + case Gen1Maps::UNUSED_MAP_EE: + return "Unused Map EE"; + case Gen1Maps::TRADE_CENTER: + return "Trade Center"; + case Gen1Maps::COLOSSEUM: + return "Colosseum"; + case Gen1Maps::UNUSED_MAP_F1: + return "Unused Map F1"; + case Gen1Maps::UNUSED_MAP_F2: + return "Unused Map F2"; + case Gen1Maps::UNUSED_MAP_F3: + return "Unused Map F3"; + case Gen1Maps::UNUSED_MAP_F4: + return "Unused Map F4"; + case Gen1Maps::LORELEIS_ROOM: + return "Loreleis Room"; + case Gen1Maps::BRUNOS_ROOM: + return "Brunos Room"; + case Gen1Maps::AGATHAS_ROOM: + return "Agathas Room"; + default: + return "Unknown Map"; + } +} + +bool gen1_isAPokeCenter(Gen1Maps map) +{ + switch(map) + { + case Gen1Maps::VIRIDIAN_POKECENTER: + case Gen1Maps::PEWTER_POKECENTER: + case Gen1Maps::CERULEAN_POKECENTER: + case Gen1Maps::MT_MOON_POKECENTER: + case Gen1Maps::ROCK_TUNNEL_POKECENTER: + case Gen1Maps::VERMILION_POKECENTER: + case Gen1Maps::CELEDON_POKECENTER: + case Gen1Maps::LAVENDER_POKECENTER: + case Gen1Maps::FUCHSIA_POKECENTER: + case Gen1Maps::CINNABAR_POKECENTER: + case Gen1Maps::SAFFRON_POKECENTER: + return true; + default: + return false; + } +} \ No newline at end of file diff --git a/src/gen1/Gen1PlayerPokemonStorage.cpp b/src/gen1/Gen1PlayerPokemonStorage.cpp index 37de44b..86fbb9b 100644 --- a/src/gen1/Gen1PlayerPokemonStorage.cpp +++ b/src/gen1/Gen1PlayerPokemonStorage.cpp @@ -15,34 +15,38 @@ typedef struct Gen1TrainerPartyMeta typedef struct Gen1TrainerBoxMeta { uint8_t number_of_pokemon; - uint8_t species_index_list[21]; + uint8_t species_index_list[31]; } Gen1TrainerBoxMeta; static const uint8_t NICKNAME_SIZE = 0xB; +static const uint8_t NICKNAME_SIZE_JPN = 0x6; static const uint8_t OT_NAME_SIZE = 0xB; +static const uint8_t OT_NAME_SIZE_JPN = 0x6; /** * @brief This function will load the metadata of the trainer party into the specified outPartyMeta variable. * Note that it won't contain the detailed data about the pokemon in the party. */ -static bool getPartyMetadata(ISaveManager& saveManager, Gen1TrainerPartyMeta &outPartyMeta) +static bool getPartyMetadata(ISaveManager& saveManager, Gen1TrainerPartyMeta &outPartyMeta, Gen1LocalizationLanguage localization) { - saveManager.seek(0x2F2C); + const uint32_t savOffset = gen1_getSRAMOffsets(localization).party; + saveManager.seek(savOffset); saveManager.readByte(outPartyMeta.number_of_pokemon); saveManager.read(outPartyMeta.species_index_list, 6); outPartyMeta.species_index_list[6] = 0xFF; return true; } -static void writePartyMetadata(ISaveManager& saveManager, Gen1TrainerPartyMeta& partyMeta) +static void writePartyMetadata(ISaveManager& saveManager, Gen1TrainerPartyMeta& partyMeta, Gen1LocalizationLanguage localization) { - saveManager.seek(0x2F2C); + const uint32_t savOffset = gen1_getSRAMOffsets(localization).party; + saveManager.seek(savOffset); saveManager.writeByte(partyMeta.number_of_pokemon); saveManager.write(partyMeta.species_index_list, 6); saveManager.writeByte(0xFF); } -uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex) +uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language) { // NOTE: important: current box data to the "longterm" bank (in bank 2 or 3) until you switch the current box in the pc // found this while experimenting with a Pkmn Blue save saved close to the start of the game by transferring a ratata to box 1 @@ -51,22 +55,27 @@ uint8_t getGen1BoxBankIndex(uint8_t boxIndex, uint8_t currentBoxIndex) { return 1; } - return (boxIndex < 6) ? 2 : 3; + + // non-japanese versions have 12 boxes of 20 items. Japanese ones have 8 boxes of 30 items. + const uint8_t boxesPerBank = (language != Gen1LocalizationLanguage::JAPANESE) ? 6 : 4; + return (boxIndex < boxesPerBank) ? 2 : 3; } -static uint16_t getBoxBankOffset(uint8_t boxIndex, uint8_t currentBoxIndex) +static uint16_t getBoxBankOffset(uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language) { - const uint16_t boxSize = 0x462; + const uint16_t boxSize = (language != Gen1LocalizationLanguage::JAPANESE) ? 0x462 : 0x566; // NOTE: important: current box data to the "longterm" bank (in bank 2 or 3) until you switch the current box in the pc // found this while experimenting with a Pkmn Blue save saved close to the start of the game by transferring a ratata to box 1 // the current box is stored at this offset if(boxIndex == currentBoxIndex) { - return 0x10C0; + return (language != Gen1LocalizationLanguage::JAPANESE) ? 0x10C0 : 0x102D; } - return (boxIndex % 6) * boxSize; + // non-japanese versions have 12 boxes of 20 items. Japanese ones have 8 boxes of 30 items. + const uint8_t boxesPerBank = (language != Gen1LocalizationLanguage::JAPANESE) ? 6 : 4; + return (boxIndex % boxesPerBank) * boxSize; } static void readCommonPokeData(ISaveManager& saveManager, Gen1TrainerPokemon& outTrainerPokemon) @@ -134,12 +143,13 @@ static void writeCommonPokeData(ISaveManager& saveManager, const Gen1TrainerPoke * @brief This function will load the metadata of the trainer box into the specified outBoxMeta variable. * Note that it won't contain the detailed data about the pokemon in the party. */ -static bool getBoxMetadata(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex, Gen1TrainerBoxMeta& outBoxMeta) +static bool getBoxMetadata(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language, Gen1TrainerBoxMeta& outBoxMeta) { - saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex), getBoxBankOffset(boxIndex, currentBoxIndex)); + const uint8_t boxCapacity = (language != Gen1LocalizationLanguage::JAPANESE) ? 20 : 30; + saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex, language), getBoxBankOffset(boxIndex, currentBoxIndex, language)); saveManager.readByte(outBoxMeta.number_of_pokemon); - saveManager.read(outBoxMeta.species_index_list, 20); - outBoxMeta.species_index_list[20] = 0xFF; + saveManager.read(outBoxMeta.species_index_list, boxCapacity); + outBoxMeta.species_index_list[boxCapacity] = 0xFF; // if seems the number_of_pokemon field read is unreliable if the box hasn't been used before. // fortunately we can also use the species index list to determine the number @@ -163,22 +173,23 @@ static bool getBoxMetadata(ISaveManager& saveManager, uint8_t boxIndex, uint8_t return true; } -static void writeBoxMetadata(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex, const Gen1TrainerBoxMeta& boxMeta) +static void writeBoxMetadata(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language, const Gen1TrainerBoxMeta& boxMeta) { - saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex), getBoxBankOffset(boxIndex, currentBoxIndex)); + const uint8_t boxCapacity = (language != Gen1LocalizationLanguage::JAPANESE) ? 20 : 30; + saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex, language), getBoxBankOffset(boxIndex, currentBoxIndex, language)); saveManager.writeByte(boxMeta.number_of_pokemon); - saveManager.write(boxMeta.species_index_list, 20); + saveManager.write(boxMeta.species_index_list, boxCapacity); saveManager.writeByte(0xFF); } -static uint8_t calculateBoxChecksum(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex) +static uint8_t calculateBoxChecksum(ISaveManager& saveManager, uint8_t boxIndex, uint8_t currentBoxIndex, Gen1LocalizationLanguage language) { Gen1Checksum checksum; - const uint16_t boxSize = 0x462; + const uint16_t boxSize = (language != Gen1LocalizationLanguage::JAPANESE) ? 0x462 : 0x566; uint16_t i; uint8_t byte; - saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex), getBoxBankOffset(boxIndex, currentBoxIndex)); + saveManager.seekToBankOffset(getGen1BoxBankIndex(boxIndex, currentBoxIndex, language), getBoxBankOffset(boxIndex, currentBoxIndex, language)); for(i = 0; i < boxSize; ++i) { @@ -188,9 +199,10 @@ static uint8_t calculateBoxChecksum(ISaveManager& saveManager, uint8_t boxIndex, return checksum.get(); } -Gen1Party::Gen1Party(Gen1GameReader& gameReader, ISaveManager& savManager) +Gen1Party::Gen1Party(Gen1GameReader& gameReader, ISaveManager& savManager, Gen1LocalizationLanguage language) : gameReader_(gameReader) , saveManager_(savManager) + , localization_(language) { } @@ -201,7 +213,7 @@ Gen1Party::~Gen1Party() uint8_t Gen1Party::getSpeciesAtIndex(uint8_t partyIndex) { Gen1TrainerPartyMeta partyMeta; - getPartyMetadata(saveManager_, partyMeta); + getPartyMetadata(saveManager_, partyMeta, localization_); if(partyIndex >= partyMeta.number_of_pokemon) { @@ -215,8 +227,9 @@ bool Gen1Party::getPokemon(uint8_t partyIndex, Gen1TrainerPokemon& outTrainerPok Gen1PokeStats stats; const uint8_t PARTY_POKEMON_NUM_BYTES = 44; const uint8_t FIRST_POKE_STRUCT_OFFSET = 8; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; - saveManager_.seek(0x2F2C + FIRST_POKE_STRUCT_OFFSET + ((partyIndex)*PARTY_POKEMON_NUM_BYTES)); + saveManager_.seek(savOffset + FIRST_POKE_STRUCT_OFFSET + ((partyIndex)*PARTY_POKEMON_NUM_BYTES)); readCommonPokeData(saveManager_, outTrainerPokemon); @@ -245,8 +258,9 @@ bool Gen1Party::setPokemon(uint8_t partyIndex, Gen1TrainerPokemon& poke) { const uint8_t PARTY_POKEMON_NUM_BYTES = 44; const uint8_t FIRST_POKE_STRUCT_OFFSET = 8; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; Gen1TrainerPartyMeta partyMeta; - getPartyMetadata(saveManager_, partyMeta); + getPartyMetadata(saveManager_, partyMeta, localization_); if(partyIndex >= partyMeta.number_of_pokemon) { @@ -262,14 +276,14 @@ bool Gen1Party::setPokemon(uint8_t partyIndex, Gen1TrainerPokemon& poke) partyMeta.species_index_list[partyMeta.number_of_pokemon] = 0xFF; } - writePartyMetadata(saveManager_, partyMeta); + writePartyMetadata(saveManager_, partyMeta, localization_); // make sure the stat fields are filled in by recalculating them. // this is the same as what happens when withdrawing them from an ingame PC box gen1_recalculatePokeStats(gameReader_, poke); poke.current_hp = poke.max_hp; - saveManager_.seek(0x2F2C + FIRST_POKE_STRUCT_OFFSET + ((partyIndex)*PARTY_POKEMON_NUM_BYTES)); + saveManager_.seek(savOffset + FIRST_POKE_STRUCT_OFFSET + ((partyIndex)*PARTY_POKEMON_NUM_BYTES)); writeCommonPokeData(saveManager_, poke); // according to https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_data_structure_(Generation_I) @@ -288,7 +302,7 @@ bool Gen1Party::setPokemon(uint8_t partyIndex, Gen1TrainerPokemon& poke) uint8_t Gen1Party::getNumberOfPokemon() { Gen1TrainerPartyMeta partyMeta; - if(!getPartyMetadata(saveManager_, partyMeta)) + if(!getPartyMetadata(saveManager_, partyMeta, localization_)) { return 0; } @@ -305,20 +319,24 @@ const char* Gen1Party::getPokemonNickname(uint8_t partyIndex) static char result[20]; uint8_t encodedNickName[NICKNAME_SIZE]; - const uint16_t FIRST_NICKNAME_NAME_OFFSET = 0x152; + const uint16_t FIRST_NICKNAME_NAME_OFFSET = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x152 : 0x134; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; - saveManager_.seek(0x2F2C + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * NICKNAME_SIZE)); - saveManager_.readUntil(encodedNickName, 0x50, NICKNAME_SIZE); + saveManager_.seek(savOffset + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * entrySize)); + saveManager_.readUntil(encodedNickName, 0x50, entrySize); - gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result)); + gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result), (Gen1LocalizationLanguage)localization_); return result; } void Gen1Party::setPokemonNickname(uint8_t partyIndex, const char* name) { - const uint16_t FIRST_NICKNAME_NAME_OFFSET = 0x152; uint8_t encodedNickName[NICKNAME_SIZE]; + const uint16_t FIRST_NICKNAME_NAME_OFFSET = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x152 : 0x134; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; if(!name) { Gen1TrainerPokemon poke; @@ -326,8 +344,8 @@ void Gen1Party::setPokemonNickname(uint8_t partyIndex, const char* name) name = gameReader_.getPokemonName(poke.poke_index); } - const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, NICKNAME_SIZE, 0x50); - saveManager_.seek(0x2F2C + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * NICKNAME_SIZE)); + const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, entrySize, 0x50, (Gen1LocalizationLanguage)localization_); + saveManager_.seek(savOffset + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * entrySize)); saveManager_.write(encodedNickName, encodedLength); } @@ -336,11 +354,13 @@ const char* Gen1Party::getOriginalTrainerOfPokemon(uint8_t partyIndex) static char result[20]; uint8_t encodedOTName[OT_NAME_SIZE]; const uint16_t FIRST_OT_NAME_OFFSET = 0x110; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? OT_NAME_SIZE : OT_NAME_SIZE_JPN; - saveManager_.seek(0x2F2C + FIRST_OT_NAME_OFFSET + (partyIndex * OT_NAME_SIZE)); - saveManager_.readUntil(encodedOTName, 0x50, OT_NAME_SIZE); + saveManager_.seek(savOffset + FIRST_OT_NAME_OFFSET + (partyIndex * entrySize)); + saveManager_.readUntil(encodedOTName, 0x50, entrySize); - gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result)); + gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result), (Gen1LocalizationLanguage)localization_); return result; } @@ -349,17 +369,19 @@ void Gen1Party::setOriginalTrainerOfPokemon(uint8_t partyIndex, const char* orig { uint8_t encodedOTName[OT_NAME_SIZE]; const uint16_t FIRST_OT_NAME_OFFSET = 0x110; + const uint32_t savOffset = gen1_getSRAMOffsets(localization_).party; + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? OT_NAME_SIZE : OT_NAME_SIZE_JPN; - const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50); + const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, entrySize, 0x50, (Gen1LocalizationLanguage)localization_); - saveManager_.seek(0x2F2C + FIRST_OT_NAME_OFFSET + (partyIndex * OT_NAME_SIZE)); + saveManager_.seek(savOffset + FIRST_OT_NAME_OFFSET + (partyIndex * entrySize)); saveManager_.write(encodedOTName, encodedLength); } bool Gen1Party::add(Gen1TrainerPokemon& poke, const char* originalTrainerID, const char* nickname) { Gen1TrainerPartyMeta partyMeta; - getPartyMetadata(saveManager_, partyMeta); + getPartyMetadata(saveManager_, partyMeta, localization_); const uint8_t partyIndex = partyMeta.number_of_pokemon; if(partyIndex >= getMaxNumberOfPokemon()) @@ -375,7 +397,7 @@ bool Gen1Party::add(Gen1TrainerPokemon& poke, const char* originalTrainerID, con partyMeta.species_index_list[partyMeta.number_of_pokemon] = 0xFF; } - writePartyMetadata(saveManager_, partyMeta); + writePartyMetadata(saveManager_, partyMeta, localization_); if(!setPokemon(partyIndex, poke)) { return false; @@ -388,10 +410,11 @@ bool Gen1Party::add(Gen1TrainerPokemon& poke, const char* originalTrainerID, con return true; } -Gen1Box::Gen1Box(Gen1GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex) +Gen1Box::Gen1Box(Gen1GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex, Gen1LocalizationLanguage language) : gameReader_(gameReader) , saveManager_(saveManager) , boxIndex_(boxIndex) + , localization_(language) { } @@ -403,7 +426,7 @@ uint8_t Gen1Box::getSpeciesAtIndex(uint8_t index) { Gen1TrainerBoxMeta boxMeta; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta); + getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta); if(index >= boxMeta.number_of_pokemon) { @@ -420,8 +443,8 @@ bool Gen1Box::getPokemon(uint8_t index, Gen1TrainerPokemon& outTrainerPokemon, b const uint8_t FIRST_POKE_STRUCT_OFFSET = 22; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); saveManager_.seekToBankOffset(bankIndex, boxOffset + FIRST_POKE_STRUCT_OFFSET + (index * BOX_POKEMON_NUM_BYTES)); @@ -445,7 +468,7 @@ bool Gen1Box::setPokemon(uint8_t index, Gen1TrainerPokemon& poke) const uint8_t FIRST_POKE_STRUCT_OFFSET = 22; Gen1TrainerBoxMeta boxMeta; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta); + getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta); if(index >= boxMeta.number_of_pokemon) { @@ -455,15 +478,15 @@ bool Gen1Box::setPokemon(uint8_t index, Gen1TrainerPokemon& poke) boxMeta.species_index_list[index] = poke.poke_index; - writeBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta); + writeBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta); // make sure the stat fields are filled in by recalculating them. // this is the same as what happens when withdrawing them from an ingame PC box gen1_recalculatePokeStats(gameReader_, poke); poke.current_hp = poke.max_hp; - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); saveManager_.seekToBankOffset(bankIndex, boxOffset + FIRST_POKE_STRUCT_OFFSET + (index * BOX_POKEMON_NUM_BYTES)); writeCommonPokeData(saveManager_, poke); @@ -480,7 +503,7 @@ uint8_t Gen1Box::getNumberOfPokemon() Gen1TrainerBoxMeta boxMeta; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - if(!getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta)) + if(!getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta)) { return 0; } @@ -489,7 +512,7 @@ uint8_t Gen1Box::getNumberOfPokemon() uint8_t Gen1Box::getMaxNumberOfPokemon() { - return 20; + return (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 20 : 30; } const char* Gen1Box::getPokemonNickname(uint8_t index) @@ -497,15 +520,16 @@ const char* Gen1Box::getPokemonNickname(uint8_t index) static char result[20]; uint8_t encodedNickName[NICKNAME_SIZE]; - const uint16_t nicknameOffset = 0x386; + const uint16_t nicknameOffset = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x386 : 0x4B2; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; - saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * NICKNAME_SIZE)); - saveManager_.readUntil(encodedNickName, 0x50, NICKNAME_SIZE); + saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * entrySize)); + saveManager_.readUntil(encodedNickName, 0x50, entrySize); - gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result)); + gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result), (Gen1LocalizationLanguage)localization_); return result; } @@ -513,10 +537,12 @@ const char* Gen1Box::getPokemonNickname(uint8_t index) void Gen1Box::setPokemonNickname(uint8_t index, const char* name) { uint8_t encodedNickName[NICKNAME_SIZE]; - const uint16_t nicknameOffset = 0x386; + const uint16_t nicknameOffset = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x386 : 0x4B2; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; + if(!name) { Gen1TrainerPokemon poke; @@ -524,8 +550,8 @@ void Gen1Box::setPokemonNickname(uint8_t index, const char* name) name = gameReader_.getPokemonName(poke.poke_index); } - const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, NICKNAME_SIZE, 0x50); - saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * NICKNAME_SIZE)); + const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, entrySize, 0x50, (Gen1LocalizationLanguage)localization_); + saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * entrySize)); saveManager_.write(encodedNickName, encodedLength); } @@ -534,15 +560,16 @@ const char* Gen1Box::getOriginalTrainerOfPokemon(uint8_t index) static char result[20]; uint8_t encodedOTName[OT_NAME_SIZE]; - const uint16_t otOffset = 0x2AA; + const uint16_t otOffset = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x2AA : 0x3FE; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? OT_NAME_SIZE : OT_NAME_SIZE_JPN; - saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * OT_NAME_SIZE)); - saveManager_.readUntil(encodedOTName, 0x50, OT_NAME_SIZE); + saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * entrySize)); + saveManager_.readUntil(encodedOTName, 0x50, entrySize); - gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result)); + gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result), (Gen1LocalizationLanguage)localization_); return result; } @@ -550,15 +577,15 @@ const char* Gen1Box::getOriginalTrainerOfPokemon(uint8_t index) void Gen1Box::setOriginalTrainerOfPokemon(uint8_t index, const char* originalTrainer) { uint8_t encodedOTName[OT_NAME_SIZE]; - const uint16_t otOffset = 0x2AA; + const uint16_t otOffset = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? 0x2AA : 0x3FE; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); - const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); + const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex, localization_); + const uint8_t entrySize = (localization_ != Gen1LocalizationLanguage::JAPANESE) ? OT_NAME_SIZE : OT_NAME_SIZE_JPN; - - const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50); + const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, entrySize, 0x50, (Gen1LocalizationLanguage)localization_); - saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * OT_NAME_SIZE)); + saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * entrySize)); saveManager_.write(encodedOTName, encodedLength); } @@ -566,7 +593,7 @@ bool Gen1Box::add(Gen1TrainerPokemon& poke, const char* originalTrainerID, const { Gen1TrainerBoxMeta boxMeta; const uint8_t currentBoxIndex = gameReader_.getCurrentBoxIndex(); - getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta); + getBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta); const uint8_t index = boxMeta.number_of_pokemon; if(index >= getMaxNumberOfPokemon()) @@ -584,7 +611,7 @@ bool Gen1Box::add(Gen1TrainerPokemon& poke, const char* originalTrainerID, const boxMeta.species_index_list[boxMeta.number_of_pokemon] = 0xFF; } - writeBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, boxMeta); + writeBoxMetadata(saveManager_, boxIndex_, currentBoxIndex, localization_, boxMeta); if(!setPokemon(index, poke)) { return false; @@ -610,13 +637,13 @@ bool Gen1Box::isChecksumValid(uint8_t currentBoxIndex) return true; } - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); const uint16_t offset = 0x1A4D + (boxIndex_ % 6); saveManager_.seekToBankOffset(bankIndex, offset); saveManager_.readByte(storedChecksum); - calculatedBoxChecksum = calculateBoxChecksum(saveManager_, boxIndex_, currentBoxIndex); + calculatedBoxChecksum = calculateBoxChecksum(saveManager_, boxIndex_, currentBoxIndex, localization_); return (storedChecksum == calculatedBoxChecksum); } @@ -627,9 +654,9 @@ void Gen1Box::updateChecksum(uint8_t currentBoxIndex) { return; } - const uint8_t calculatedBoxChecksum = calculateBoxChecksum(saveManager_, boxIndex_, currentBoxIndex); + const uint8_t calculatedBoxChecksum = calculateBoxChecksum(saveManager_, boxIndex_, currentBoxIndex, localization_); - const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex); + const uint8_t bankIndex = getGen1BoxBankIndex(boxIndex_, currentBoxIndex, localization_); const uint16_t offset = 0x1A4D + (boxIndex_ % 6); saveManager_.seekToBankOffset(bankIndex, offset); diff --git a/src/gen2/Gen2Common.cpp b/src/gen2/Gen2Common.cpp index 9a10267..ee2ea60 100644 --- a/src/gen2/Gen2Common.cpp +++ b/src/gen2/Gen2Common.cpp @@ -1,6 +1,7 @@ #include "gen2/Gen2Common.h" #include "gen2/Gen2GameReader.h" #include "SaveManager.h" +#include "RomReader.h" #include "utils.h" #include "common.h" @@ -18,130 +19,3149 @@ uint16_t gen2_iconColorPalette[4] = { 0x0 }; -static TextCodePair gen2TextCodes[] = { - {0x56, "……"}, - {0x5D, "TRAINER"}, - {0x60, "█"}, - {0x61, "▲"}, - {0x7F, " "}, - {0x80, "A"}, - {0x81, "B"}, - {0x82, "C"}, - {0x83, "D"}, - {0x84, "E"}, - {0x85, "F"}, - {0x86, "G"}, - {0x87, "H"}, - {0x88, "I"}, - {0x89, "J"}, - {0x8A, "K"}, - {0x8B, "L"}, - {0x8C, "M"}, - {0x8D, "N"}, - {0x8E, "O"}, - {0x8F, "P"}, - {0x90, "Q"}, - {0x91, "R"}, - {0x92, "S"}, - {0x93, "T"}, - {0x94, "U"}, - {0x95, "V"}, - {0x96, "W"}, - {0x97, "X"}, - {0x98, "Y"}, - {0x99, "Z"}, - {0x9A, "("}, - {0x9B, ")"}, - {0x9C, ":"}, - {0x9D, ";"}, - {0x9E, "["}, - {0x9F, "]"}, - {0xA0, "a"}, - {0xA1, "b"}, - {0xA2, "c"}, - {0xA3, "d"}, - {0xA4, "e"}, - {0xA5, "f"}, - {0xA6, "g"}, - {0xA7, "h"}, - {0xA8, "i"}, - {0xA9, "j"}, - {0xAA, "k"}, - {0xAB, "l"}, - {0xAC, "m"}, - {0xAD, "n"}, - {0xAE, "o"}, - {0xAF, "p"}, - {0xB0, "q"}, - {0xB1, "r"}, - {0xB2, "s"}, - {0xB3, "t"}, - {0xB4, "u"}, - {0xB5, "v"}, - {0xB6, "w"}, - {0xB7, "x"}, - {0xB8, "y"}, - {0xB9, "z"}, - {0xBA, "à"}, - {0xBB, "è"}, - {0xBD, "ù"}, - {0xBE, "ß"}, - {0xBF, "ç"}, - {0xC0, "Ä"}, - {0xC1, "Ö"}, - {0xC2, "Ü"}, - {0xC3, "ä"}, - {0xC4, "ö"}, - {0xC5, "ü"}, - {0xC6, "ë"}, - {0xC7, "ï"}, - {0xC8, "â"}, - {0xC9, "ô"}, - {0xCA, "û"}, - {0xCB, "ê"}, - {0xCC, "î"}, - {0xD0, "'d"}, - {0xD1, "'l"}, - {0xD2, "'m"}, - {0xD3, "'r"}, - {0xD4, "'s"}, - {0xD5, "'t"}, - {0xD6, "'v"}, - {0xE0, "\'"}, - {0xE1, "PK"}, - {0xE2, "MN"}, - {0xE3, "-"}, - {0xE6, "?"}, - {0xE7, "!"}, - {0xE8, "."}, - {0xE9, "&"}, - {0xEA, "é"}, - {0xEB, "🡆"}, - {0xEC, "▷"}, - {0xED, "▶"}, - {0xEE, "▼"}, - {0xEF, "♂"}, - {0xF0, "¥"}, - {0xF1, "×"}, - {0xF3, "/"}, - {0xF4, ","}, - {0xF5, "♀"}, - {0xF6, "0"}, - {0xF7, "1"}, - {0xF8, "2"}, - {0xF9, "3"}, - {0xFA, "4"}, - {0xFB, "5"}, - {0xFC, "6"}, - {0xFD, "7"}, - {0xFE, "8"}, - {0xFF, "9"} +static uint8_t gen2_statsFingerPrint[] = { + 0x01, 0x2D, 0x31, 0x31, 0x2D, 0x41, 0x41, 0x16, + 0x03, 0x2D, 0x40, 0x00, 0x00, 0x1F, 0x64, 0x14 }; -Gen2ItemList::Gen2ItemList(ISaveManager &saveManager, Gen2ItemListType type, bool isCrystal) +static TextCodePair gen2TextCodesMain[] = { + {0x56, u8"……"}, + {0x5D, u8"TRAINER"}, + {0x60, u8"█"}, + {0x61, u8"▲"}, + {0x74, u8"・"}, + {0x75, u8"…"}, + {0x7F, u8" "}, + {0x80, u8"A"}, + {0x81, u8"B"}, + {0x82, u8"C"}, + {0x83, u8"D"}, + {0x84, u8"E"}, + {0x85, u8"F"}, + {0x86, u8"G"}, + {0x87, u8"H"}, + {0x88, u8"I"}, + {0x89, u8"J"}, + {0x8A, u8"K"}, + {0x8B, u8"L"}, + {0x8C, u8"M"}, + {0x8D, u8"N"}, + {0x8E, u8"O"}, + {0x8F, u8"P"}, + {0x90, u8"Q"}, + {0x91, u8"R"}, + {0x92, u8"S"}, + {0x93, u8"T"}, + {0x94, u8"U"}, + {0x95, u8"V"}, + {0x96, u8"W"}, + {0x97, u8"X"}, + {0x98, u8"Y"}, + {0x99, u8"Z"}, + {0x9A, u8"("}, + {0x9B, u8")"}, + {0x9C, u8":"}, + {0x9D, u8";"}, + {0x9E, u8"["}, + {0x9F, u8"]"}, + {0xA0, u8"a"}, + {0xA1, u8"b"}, + {0xA2, u8"c"}, + {0xA3, u8"d"}, + {0xA4, u8"e"}, + {0xA5, u8"f"}, + {0xA6, u8"g"}, + {0xA7, u8"h"}, + {0xA8, u8"i"}, + {0xA9, u8"j"}, + {0xAA, u8"k"}, + {0xAB, u8"l"}, + {0xAC, u8"m"}, + {0xAD, u8"n"}, + {0xAE, u8"o"}, + {0xAF, u8"p"}, + {0xB0, u8"q"}, + {0xB1, u8"r"}, + {0xB2, u8"s"}, + {0xB3, u8"t"}, + {0xB4, u8"u"}, + {0xB5, u8"v"}, + {0xB6, u8"w"}, + {0xB7, u8"x"}, + {0xB8, u8"y"}, + {0xB9, u8"z"}, + {0xBA, u8"à"}, + {0xBB, u8"è"}, + {0xBD, u8"ù"}, + {0xBE, u8"ß"}, + {0xBF, u8"ç"}, + {0xC0, u8"Ä"}, + {0xC1, u8"Ö"}, + {0xC2, u8"Ü"}, + {0xC3, u8"ä"}, + {0xC4, u8"ö"}, + {0xC5, u8"ü"}, + {0xC6, u8"ë"}, + {0xC7, u8"ï"}, + {0xC8, u8"â"}, + {0xC9, u8"ô"}, + {0xCA, u8"û"}, + {0xCB, u8"ê"}, + {0xCC, u8"î"}, + {0xD0, u8"'d"}, + {0xD1, u8"'l"}, + {0xD2, u8"'m"}, + {0xD3, u8"'r"}, + {0xD4, u8"'s"}, + {0xD5, u8"'t"}, + {0xD6, u8"'v"}, + {0xE0, u8"\'"}, + {0xE1, u8"PK"}, + {0xE2, u8"MN"}, + {0xE3, u8"-"}, + {0xE6, u8"?"}, + {0xE7, u8"!"}, + {0xE8, u8"."}, + {0xE9, u8"&"}, + {0xEA, u8"é"}, + {0xEB, u8"🡆"}, + {0xEC, u8"▷"}, + {0xED, u8"▶"}, + {0xEE, u8"▼"}, + {0xEF, u8"♂"}, + {0xF0, u8"¥"}, + {0xF1, u8"×"}, + {0xF3, u8"/"}, + {0xF4, u8","}, + {0xF5, u8"♀"}, + {0xF6, u8"0"}, + {0xF7, u8"1"}, + {0xF8, u8"2"}, + {0xF9, u8"3"}, + {0xFA, u8"4"}, + {0xFB, u8"5"}, + {0xFC, u8"6"}, + {0xFD, u8"7"}, + {0xFE, u8"8"}, + {0xFF, u8"9"}, + {0x51, u8" "} +}; + +static const TextCodePair gen2TextCodesJpn[] = { + {0x01, u8"イ゙"}, + {0x02, u8"ヴ"}, + {0x03, u8"エ゙"}, + {0x04, u8"オ゙"}, + {0x05, u8"ガ"}, + {0x06, u8"ギ"}, + {0x07, u8"グ"}, + {0x08, u8"ゲ"}, + {0x09, u8"ゴ"}, + {0x0A, u8"ザ"}, + {0x0B, u8"ジ"}, + {0x0C, u8"ズ"}, + {0x0D, u8"ゼ"}, + {0x0E, u8"ゾ"}, + {0x0F, u8"ダ"}, + {0x10, u8"ヂ"}, + {0x11, u8"ヅ"}, + {0x12, u8"デ"}, + {0x13, u8"ド"}, + {0x14, u8"ナ゙"}, + {0x15, u8"ニ゙"}, + {0x16, u8"ヌ゙"}, + {0x17, u8"ネ゙"}, + {0x18, u8"ノ゙"}, + {0x19, u8"バ"}, + {0x1A, u8"ビ"}, + {0x1B, u8"ブ"}, + {0x1C, u8"ボ"}, + {0x1D, u8"マ゙"}, + {0x1E, u8"ミ゙"}, + {0x1F, u8"ム゙"}, + {0x20, u8"ィ゙"}, + {0x21, u8"あ゙"}, + {0x22, u8"い゙"}, + {0x23, u8"ゔ"}, + {0x24, u8"え゙"}, + {0x25, u8"お゙"}, + {0x26, u8"が"}, + {0x27, u8"ぎ"}, + {0x28, u8"ぐ"}, + {0x29, u8"げ"}, + {0x2A, u8"ご"}, + {0x2B, u8"ざ"}, + {0x2C, u8"じ"}, + {0x2D, u8"ず"}, + {0x2E, u8"ぜ"}, + {0x2F, u8"ぞ"}, + {0x30, u8"だ"}, + {0x31, u8"ぢ"}, + {0x32, u8"づ"}, + {0x33, u8"で"}, + {0x34, u8"ど"}, + {0x35, u8"な゙"}, + {0x36, u8"に゙"}, + {0x37, u8"ぬ゙"}, + {0x38, u8"ね゙"}, + {0x39, u8"の゙"}, + {0x3A, u8"ば"}, + {0x3B, u8"び"}, + {0x3C, u8"ぶ"}, + {0x3D, u8"べ"}, + {0x3E, u8"ぼ"}, + {0x3F, u8"ま゙"}, + {0x40, u8"パ"}, + {0x41, u8"ピ"}, + {0x42, u8"プ"}, + {0x43, u8"ポ"}, + {0x44, u8"ぱ"}, + {0x45, u8"ぴ"}, + {0x46, u8"ぷ"}, + {0x47, u8"ぺ"}, + {0x48, u8"ぽ"}, + {0x49, u8"ま゚"}, + {0x4D, u8"も゚"}, + {0x4F, u8" "}, + {0x57, u8"#"}, + {0x51, u8"*"}, + {0x52, u8"A1"}, + {0x53, u8"A2"}, + {0x54, u8"POKé"}, + {0x55, u8"+"}, + {0x58, u8"$"}, + {0x5D, u8"TRAINER"}, + {0x75, u8"…"}, + {0x7F, u8" "}, + {0x60, u8"A"}, + {0x61, u8"▲"}, + {0x62, u8"?"}, + {0x63, u8"D"}, + {0x64, u8"E"}, + {0x65, u8"F"}, + {0x66, u8"G"}, + {0x67, u8"H"}, + {0x68, u8"I"}, + {0x69, u8"V"}, + {0x6A, u8"S"}, + {0x6B, u8"L"}, + {0x6C, u8"M"}, + {0x6D, u8":"}, + {0x6E, u8"ぃ"}, + {0x6F, u8"ぅ"}, + {0x70, u8"「"}, + {0x71, u8"」"}, + {0x72, u8"『"}, + {0x73, u8"』"}, + {0x74, u8"・"}, + {0x75, u8"…"}, + {0x76, u8"ぁ"}, + {0x77, u8"ぇ"}, + {0x78, u8"ぉ"}, + {0x80, u8"ア"}, + {0x81, u8"イ"}, + {0x82, u8"ウ"}, + {0x83, u8"エ"}, + {0x84, u8"オ"}, + {0x85, u8"カ"}, + {0x86, u8"キ"}, + {0x87, u8"ク"}, + {0x88, u8"ケ"}, + {0x89, u8"コ"}, + {0x8A, u8"サ"}, + {0x8B, u8"シ"}, + {0x8C, u8"ス"}, + {0x8D, u8"セ"}, + {0x8E, u8"ソ"}, + {0x8F, u8"タ"}, + {0x90, u8"チ"}, + {0x91, u8"ツ"}, + {0x92, u8"テ"}, + {0x93, u8"ト"}, + {0x94, u8"ナ"}, + {0x95, u8"ニ"}, + {0x96, u8"ヌ"}, + {0x97, u8"ネ"}, + {0x98, u8"ノ"}, + {0x99, u8"ハ"}, + {0x9A, u8"ヒ"}, + {0x9B, u8"フ"}, + {0x9C, u8"ホ"}, + {0x9D, u8"マ"}, + {0x9E, u8"ミ"}, + {0x9F, u8"ム"}, + {0xA0, u8"メ"}, + {0xA1, u8"モ"}, + {0xA2, u8"ヤ"}, + {0xA3, u8"ユ"}, + {0xA4, u8"ヨ"}, + {0xA5, u8"ラ"}, + {0xA6, u8"ル"}, + {0xA7, u8"レ"}, + {0xA8, u8"ロ"}, + {0xA9, u8"ワ"}, + {0xAA, u8"ヲ"}, + {0xAB, u8"ン"}, + {0xAC, u8"ッ"}, + {0xAD, u8"ャ"}, + {0xAE, u8"ュ"}, + {0xAF, u8"ョ"}, + {0xB0, u8"ィ"}, + {0xB1, u8"あ"}, + {0xB2, u8"い"}, + {0xB3, u8"う"}, + {0xB4, u8"え"}, + {0xB5, u8"お"}, + {0xB6, u8"か"}, + {0xB7, u8"き"}, + {0xB8, u8"く"}, + {0xB9, u8"け"}, + {0xBA, u8"こ"}, + {0xBB, u8"さ"}, + {0xBC, u8"し"}, + {0xBD, u8"す"}, + {0xBE, u8"せ"}, + {0xBF, u8"そ"}, + {0xC0, u8"た"}, + {0xC1, u8"ち"}, + {0xC2, u8"つ"}, + {0xC3, u8"て"}, + {0xC4, u8"と"}, + {0xC5, u8"な"}, + {0xC6, u8"に"}, + {0xC7, u8"ぬ"}, + {0xC8, u8"ね"}, + {0xC9, u8"の"}, + {0xCA, u8"は"}, + {0xCB, u8"ひ"}, + {0xCC, u8"ふ"}, + {0xCD, u8"へ"}, + {0xCE, u8"ほ"}, + {0xCF, u8"ま"}, + {0xD0, u8"み"}, + {0xD1, u8"む"}, + {0xD2, u8"め"}, + {0xD3, u8"も"}, + {0xD4, u8"や"}, + {0xD5, u8"ゆ"}, + {0xD6, u8"よ"}, + {0xD7, u8"ら"}, + {0xD8, u8"リ"}, + {0xD9, u8"る"}, + {0xDA, u8"れ"}, + {0xDB, u8"ろ"}, + {0xDC, u8"わ"}, + {0xDD, u8"を"}, + {0xDE, u8"ん"}, + {0xDF, u8"っ"}, + {0xE0, u8"ゃ"}, + {0xE1, u8"ゅ"}, + {0xE2, u8"ょ"}, + {0xE3, u8"ー"}, + {0xE4, u8"゜"}, + {0xE5, u8"゛"}, + {0xE6, u8"?"}, + {0xE7, u8"!"}, + {0xE8, u8"。"}, + {0xE9, u8"ァ"}, + {0xEA, u8"ゥ"}, + {0xEB, u8"ェ"}, + {0xEC, u8"▷"}, + {0xED, u8"▶"}, + {0xEE, u8"▼"}, + {0xEF, u8"♂"}, + {0xF0, u8"円"}, + {0xF1, u8"×"}, + {0xF2, u8"."}, + {0xF3, u8"/"}, + {0xF4, u8"ォ"}, + {0xF5, u8"♀"}, + {0xF6, u8"0"}, + {0xF7, u8"1"}, + {0xF8, u8"2"}, + {0xF9, u8"3"}, + {0xFA, u8"4"}, + {0xFB, u8"5"}, + {0xFC, u8"6"}, + {0xFD, u8"7"}, + {0xFE, u8"8"}, + {0xFF, u8"9"} +}; + +// Based on https://bulbapedia.bulbagarden.net/wiki/Korean_character_encoding_(Generation_II)#Character_map + +static const TextCodePair gen2TextCodesKorean0[] = { + {0x00, u8"ㄱ"}, + {0x01, u8"ㄴ"}, + {0x02, u8"ㄷ"}, + {0x03, u8"ㄹ"}, + {0x04, u8"ㅁ"}, + {0x05, u8"ㅂ"}, + {0x06, u8"ㅅ"}, + {0x07, u8"ㅇ"}, + {0x08, u8"ㅈ"}, + {0x09, u8"ㅊ"}, + {0x0A, u8"ㅋ"}, + {0x0B, u8"ㅌ"}, + {0x0C, u8"ㅍ"}, + {0x0D, u8"ㅎ"}, + {0x0E, u8"ㄲ"}, + {0x0F, u8"ㄸ"}, + {0x10, u8"ㅃ"}, + {0x11, u8"ㅆ"}, + {0x12, u8"ㅉ"}, + {0x20, u8"ㅏ"}, + {0x21, u8"ㅑ"}, + {0x22, u8"ㅓ"}, + {0x23, u8"ㅕ"}, + {0x24, u8"ㅗ"}, + {0x25, u8"ㅛ"}, + {0x26, u8"ㅜ"}, + {0x27, u8"ㅠ"}, + {0x28, u8"ㅡ"}, + {0x29, u8"ㅣ"}, + {0x2A, u8"ㅐ"}, + {0x2B, u8"ㅒ"}, + {0x2C, u8"ㅔ"}, + {0x2D, u8"ㅖ"}, + {0x2E, u8"ㅘ"}, + {0x2F, u8"ㅙ"}, + {0x30, u8"ㅚ"}, + {0x31, u8"ㅝ"}, + {0x32, u8"ㅞ"}, + {0x33, u8"ㅟ"}, + {0x34, u8"ㅢ"}, + {0x3E, u8"_"}, + {0x3F, u8"-"}, + {0x60, u8"「"}, + {0x61, u8"」"}, + {0x62, u8"『"}, + {0x63, u8"』"}, + {0x64, u8"("}, + {0x65, u8")"}, + {0x66, u8"!"}, + {0x67, u8"?"}, + {0x68, u8"-"}, + {0x69, u8"~"}, + {0x6A, u8"…"}, + {0x6B, u8","}, + {0x6C, u8"."}, + {0xF0, u8"0"}, + {0xF1, u8"1"}, + {0xF2, u8"2"}, + {0xF3, u8"3"}, + {0xF4, u8"4"}, + {0xF5, u8"5"}, + {0xF6, u8"6"}, + {0xF7, u8"7"}, + {0xF8, u8"8"}, + {0xF9, u8"9"} +}; + +static const TextCodePair gen2TextCodesKorean1[] = { + {0x01, u8"가"}, + {0x02, u8"각"}, + {0x03, u8"간"}, + {0x04, u8"갇"}, + {0x05, u8"갈"}, + {0x06, u8"갉"}, + {0x07, u8"갊"}, + {0x08, u8"감"}, + {0x09, u8"갑"}, + {0x0A, u8"값"}, + {0x0B, u8"갓"}, + {0x0C, u8"갔"}, + {0x0D, u8"강"}, + {0x0E, u8"갖"}, + {0x0F, u8"갗"}, + {0x10, u8"같"}, + {0x11, u8"갚"}, + {0x12, u8"갛"}, + {0x13, u8"개"}, + {0x14, u8"객"}, + {0x15, u8"갠"}, + {0x16, u8"갤"}, + {0x17, u8"갬"}, + {0x18, u8"갭"}, + {0x19, u8"갯"}, + {0x1A, u8"갰"}, + {0x1B, u8"갱"}, + {0x1C, u8"갸"}, + {0x1D, u8"갹"}, + {0x1E, u8"갼"}, + {0x1F, u8"걀"}, + {0x20, u8"걀"}, + {0x21, u8"걍"}, + {0x22, u8"걔"}, + {0x23, u8"걘"}, + {0x24, u8"걜"}, + {0x25, u8"거"}, + {0x26, u8"걱"}, + {0x27, u8"건"}, + {0x28, u8"걷"}, + {0x29, u8"걸"}, + {0x2A, u8"걺"}, + {0x2B, u8"검"}, + {0x2C, u8"겁"}, + {0x2D, u8"것"}, + {0x2E, u8"겄"}, + {0x2F, u8"겅"}, + {0x30, u8"겆"}, + {0x31, u8"겉"}, + {0x32, u8"겊"}, + {0x33, u8"겋"}, + {0x34, u8"게"}, + {0x35, u8"겐"}, + {0x36, u8"겔"}, + {0x37, u8"겜"}, + {0x38, u8"겝"}, + {0x39, u8"겟"}, + {0x3A, u8"겠"}, + {0x3B, u8"겡"}, + {0x3C, u8"겨"}, + {0x3D, u8"격"}, + {0x3E, u8"겪"}, + {0x3F, u8"견"}, + {0x40, u8"겯"}, + {0x41, u8"결"}, + {0x42, u8"겹"}, + {0x43, u8"겸"}, + {0x44, u8"겻"}, + {0x45, u8"겼"}, + {0x46, u8"경"}, + {0x47, u8"곁"}, + {0x48, u8"계"}, + {0x49, u8"곈"}, + {0x4A, u8"곌"}, + {0x4B, u8"곕"}, + {0x4C, u8"곗"}, + {0x4D, u8"고"}, + {0x4E, u8"곡"}, + {0x4F, u8"곤"}, + {0x60, u8"곧"}, + {0x61, u8"골"}, + {0x62, u8"곪"}, + {0x63, u8"곬"}, + {0x64, u8"곯"}, + {0x65, u8"곰"}, + {0x66, u8"곱"}, + {0x67, u8"곳"}, + {0x68, u8"공"}, + {0x69, u8"곶"}, + {0x6A, u8"과"}, + {0x6B, u8"곽"}, + {0x6C, u8"관"}, + {0x6D, u8"괄"}, + {0x6E, u8"괆"}, + {0x71, u8"괌"}, + {0x72, u8"괍"}, + {0x73, u8"괏"}, + {0x74, u8"광"}, + {0x75, u8"괘"}, + {0x76, u8"괜"}, + {0x77, u8"괠"}, + {0x78, u8"괩"}, + {0x79, u8"괩"}, + {0x7A, u8"괭"}, + {0x7B, u8"괴"}, + {0x7C, u8"괵"}, + {0x7D, u8"괸"}, + {0x7E, u8"괼"}, + {0x7F, u8"괻"}, + {0x80, u8"굅"}, + {0x81, u8"굇"}, + {0x82, u8"굉"}, + {0x83, u8"교"}, + {0x84, u8"굔"}, + {0x85, u8"굘"}, + {0x86, u8"굡"}, + {0x87, u8"굣"}, + {0x88, u8"구"}, + {0x89, u8"국"}, + {0x8A, u8"군"}, + {0x8B, u8"굳"}, + {0x8C, u8"굴"}, + {0x8D, u8"굵"}, + {0x8E, u8"굶"}, + {0x8F, u8"굻"}, + {0x90, u8"굼"}, + {0x91, u8"굽"}, + {0x92, u8"굿"}, + {0x93, u8"궁"}, + {0x94, u8"궂"}, + {0x95, u8"궈"}, + {0x96, u8"궉"}, + {0x97, u8"권"}, + {0x98, u8"궐"}, + {0x99, u8"궜"}, + {0x9A, u8"궝"}, + {0x9B, u8"궤"}, + {0x9C, u8"궷"}, + {0x9D, u8"귀"}, + {0x9E, u8"귁"}, + {0x9F, u8"귄"}, + {0xA0, u8"귈"}, + {0xA1, u8"귐"}, + {0xA2, u8"귑"}, + {0xA3, u8"귓"}, + {0xA4, u8"규"}, + {0xA5, u8"균"}, + {0xA6, u8"귤"}, + {0xA7, u8"그"}, + {0xA8, u8"극"}, + {0xA9, u8"근"}, + {0xAA, u8"귿"}, + {0xAB, u8"글"}, + {0xAC, u8"긁"}, + {0xAD, u8"금"}, + {0xAE, u8"급"}, + {0xAF, u8"긋"}, + {0xB0, u8"긍"}, + {0xB1, u8"긔"}, + {0xB2, u8"기"}, + {0xB3, u8"긱"}, + {0xB4, u8"긴"}, + {0xB5, u8"긷"}, + {0xB6, u8"길"}, + {0xB7, u8"긺"}, + {0xB8, u8"김"}, + {0xB9, u8"깁"}, + {0xBA, u8"깃"}, + {0xBB, u8"깅"}, + {0xBC, u8"깆"}, + {0xBD, u8"깊"}, + {0xBE, u8"까"}, + {0xBF, u8"깍"}, + {0xC0, u8"깎"}, + {0xC1, u8"깐"}, + {0xC2, u8"깔"}, + {0xC3, u8"깖"}, + {0xC4, u8"깜"}, + {0xC5, u8"깝"}, + {0xC6, u8"깟"}, + {0xC7, u8"깠"}, + {0xC8, u8"깡"}, + {0xC9, u8"깥"}, + {0xCA, u8"깨"}, + {0xCB, u8"깩"}, + {0xCC, u8"깬"}, + {0xCD, u8"깰"}, + {0xCE, u8"깸"}, + {0xD1, u8"깹"}, + {0xD2, u8"깻"}, + {0xD3, u8"깼"}, + {0xD4, u8"깽"}, + {0xD5, u8"꺄"}, + {0xD6, u8"꺅"}, + {0xD7, u8"꺌"}, + {0xD8, u8"꺼"}, + {0xD9, u8"꺽"}, + {0xDA, u8"꺾"}, + {0xDB, u8"껀"}, + {0xDC, u8"껄"}, + {0xDD, u8"껌"}, + {0xDE, u8"껍"}, + {0xDF, u8"껏"}, + {0xE0, u8"껐"}, + {0xE1, u8"껑"}, + {0xE2, u8"께"}, + {0xE3, u8"껙"}, + {0xE4, u8"껜"}, + {0xE5, u8"껨"}, + {0xE6, u8"껫"}, + {0xE7, u8"껭"}, + {0xE8, u8"껴"}, + {0xE9, u8"껸"}, + {0xEA, u8"껼"}, + {0xEB, u8"꼇"}, + {0xEC, u8"꼈"}, + {0xED, u8"꼍"}, + {0xEE, u8"꼐"}, + {0xEF, u8"꼬"}, + {0xF0, u8"꼭"}, + {0xF1, u8"꼰"}, + {0xF2, u8"꼲"}, + {0xF3, u8"꼴"}, + {0xF4, u8"꼼"}, + {0xF5, u8"꼽"}, + {0xF6, u8"꼿"}, + {0xF7, u8"꽁"}, + {0xF8, u8"꽂"}, + {0xF9, u8"꽃"}, + {0xFA, u8"꽈"}, + {0xFB, u8"꽉"}, + {0xFC, u8"꽐"}, + {0xFD, u8"꽜"}, + {0xFE, u8"꽝"}, + {0xFF, u8"꽤"} +}; + +static const TextCodePair gen2TextCodesKorean2[] = { + {0x00, u8"꽥"}, + {0x01, u8"꽹"}, + {0x02, u8"꾀"}, + {0x03, u8"꾄"}, + {0x04, u8"꾈"}, + {0x05, u8"꾐"}, + {0x06, u8"꾑"}, + {0x07, u8"꾕"}, + {0x08, u8"꾜"}, + {0x09, u8"꾸"}, + {0x0A, u8"꾹"}, + {0x0B, u8"꾼"}, + {0x0C, u8"꿀"}, + {0x0D, u8"꿇"}, + {0x0E, u8"꿈"}, + {0x0F, u8"꿈"}, + {0x10, u8"꿋"}, + {0x11, u8"꿍"}, + {0x12, u8"꿎"}, + {0x13, u8"꿔"}, + {0x14, u8"꿜"}, + {0x15, u8"꿨"}, + {0x16, u8"꿩"}, + {0x17, u8"꿰"}, + {0x18, u8"꿱"}, + {0x19, u8"꿴"}, + {0x1A, u8"꿸"}, + {0x1B, u8"뀀"}, + {0x1C, u8"뀁"}, + {0x1D, u8"뀄"}, + {0x1E, u8"뀌"}, + {0x1F, u8"뀐"}, + {0x20, u8"뀔"}, + {0x21, u8"뀜"}, + {0x22, u8"뀝"}, + {0x23, u8"뀨"}, + {0x24, u8"끄"}, + {0x25, u8"끅"}, + {0x26, u8"끈"}, + {0x27, u8"끊"}, + {0x28, u8"끌"}, + {0x29, u8"끎"}, + {0x2A, u8"끓"}, + {0x2B, u8"끔"}, + {0x2C, u8"끕"}, + {0x2D, u8"끗"}, + {0x2E, u8"끙"}, + {0x31, u8"끝"}, + {0x32, u8"끼"}, + {0x33, u8"끽"}, + {0x34, u8"낀"}, + {0x35, u8"낄"}, + {0x36, u8"낌"}, + {0x37, u8"낍"}, + {0x38, u8"낏"}, + {0x39, u8"낑"}, + {0x3A, u8"나"}, + {0x3B, u8"낙"}, + {0x3C, u8"낚"}, + {0x3D, u8"난"}, + {0x3E, u8"낟"}, + {0x3F, u8"날"}, + {0x40, u8"낡"}, + {0x41, u8"낢"}, + {0x42, u8"남"}, + {0x43, u8"납"}, + {0x44, u8"낫"}, + {0x45, u8"났"}, + {0x46, u8"낭"}, + {0x47, u8"낮"}, + {0x48, u8"낯"}, + {0x49, u8"낱"}, + {0x4A, u8"낳"}, + {0x4B, u8"내"}, + {0x4C, u8"낵"}, + {0x4D, u8"낸"}, + {0x4E, u8"낼"}, + {0x4F, u8"냄"}, + {0x60, u8"냅"}, + {0x61, u8"냇"}, + {0x62, u8"냈"}, + {0x63, u8"냉"}, + {0x64, u8"냐"}, + {0x65, u8"냑"}, + {0x66, u8"냔"}, + {0x67, u8"냘"}, + {0x68, u8"냠"}, + {0x69, u8"냥"}, + {0x6A, u8"너"}, + {0x6B, u8"넉"}, + {0x6C, u8"넋"}, + {0x6D, u8"넌"}, + {0x6E, u8"널"}, + {0x6F, u8"넒"}, + {0x70, u8"넓"}, + {0x71, u8"넘"}, + {0x72, u8"넙"}, + {0x73, u8"넛"}, + {0x74, u8"넜"}, + {0x75, u8"넝"}, + {0x76, u8"넣"}, + {0x77, u8"네"}, + {0x78, u8"넥"}, + {0x79, u8"넨"}, + {0x7A, u8"넬"}, + {0x7B, u8"넴"}, + {0x7C, u8"넵"}, + {0x7D, u8"넷"}, + {0x7E, u8"넸"}, + {0x7F, u8"넹"}, + {0x80, u8"녀"}, + {0x81, u8"녁"}, + {0x82, u8"년"}, + {0x83, u8"녈"}, + {0x84, u8"념"}, + {0x85, u8"녑"}, + {0x86, u8"녔"}, + {0x87, u8"녕"}, + {0x88, u8"녘"}, + {0x89, u8"녜"}, + {0x8A, u8"녠"}, + {0x8B, u8"노"}, + {0x8C, u8"녹"}, + {0x8D, u8"논"}, + {0x8E, u8"놀"}, + {0x8F, u8"놂"}, + {0x90, u8"놈"}, + {0x91, u8"놉"}, + {0x92, u8"놋"}, + {0x93, u8"농"}, + {0x94, u8"높"}, + {0x95, u8"놓"}, + {0x96, u8"놔"}, + {0x97, u8"놘"}, + {0x98, u8"놜"}, + {0x99, u8"놨"}, + {0x9A, u8"뇌"}, + {0x9B, u8"뇐"}, + {0x9C, u8"뇔"}, + {0x9D, u8"뇜"}, + {0x9E, u8"뇝"}, + {0xA1, u8"뇟"}, + {0xA2, u8"뇨"}, + {0xA3, u8"뇩"}, + {0xA4, u8"뇬"}, + {0xA5, u8"뇰"}, + {0xA6, u8"뇹"}, + {0xA7, u8"뇻"}, + {0xA8, u8"뇽"}, + {0xA9, u8"누"}, + {0xAA, u8"눅"}, + {0xAB, u8"눈"}, + {0xAC, u8"눋"}, + {0xAD, u8"눌"}, + {0xAE, u8"눔"}, + {0xAF, u8"눕"}, + {0xB0, u8"눗"}, + {0xB1, u8"눙"}, + {0xB2, u8"눠"}, + {0xB3, u8"눴"}, + {0xB4, u8"눼"}, + {0xB5, u8"뉘"}, + {0xB6, u8"뉜"}, + {0xB7, u8"뉠"}, + {0xB8, u8"뉨"}, + {0xB9, u8"뉩"}, + {0xBA, u8"뉴"}, + {0xBB, u8"뉵"}, + {0xBC, u8"뉼"}, + {0xBD, u8"늄"}, + {0xBE, u8"늅"}, + {0xBF, u8"늉"}, + {0xC0, u8"느"}, + {0xC1, u8"늑"}, + {0xC2, u8"는"}, + {0xC3, u8"늘"}, + {0xC4, u8"늙"}, + {0xC5, u8"늚"}, + {0xC6, u8"늠"}, + {0xC7, u8"늡"}, + {0xC8, u8"늣"}, + {0xC9, u8"능"}, + {0xCA, u8"늦"}, + {0xCB, u8"늪"}, + {0xCC, u8"늬"}, + {0xCD, u8"늰"}, + {0xCE, u8"늴"}, + {0xCF, u8"니"}, + {0xD0, u8"닉"}, + {0xD1, u8"닌"}, + {0xD2, u8"닐"}, + {0xD3, u8"닒"}, + {0xD4, u8"님"}, + {0xD5, u8"닙"}, + {0xD6, u8"닛"}, + {0xD7, u8"닝"}, + {0xD8, u8"닢"}, + {0xD9, u8"다"}, + {0xDA, u8"닥"}, + {0xDB, u8"닦"}, + {0xDC, u8"단"}, + {0xDD, u8"닫"}, + {0xDE, u8"달"}, + {0xDF, u8"닭"}, + {0xE0, u8"닮"}, + {0xE1, u8"닯"}, + {0xE2, u8"닳"}, + {0xE3, u8"담"}, + {0xE4, u8"답"}, + {0xE5, u8"닷"}, + {0xE6, u8"닸"}, + {0xE7, u8"당"}, + {0xE8, u8"닺"}, + {0xE9, u8"닻"}, + {0xEA, u8"닿"}, + {0xEB, u8"대"}, + {0xEC, u8"댁"}, + {0xED, u8"댄"}, + {0xEE, u8"댈"}, + {0xEF, u8"댐"}, + {0xF0, u8"댑"}, + {0xF1, u8"댓"}, + {0xF2, u8"댔"}, + {0xF3, u8"댕"}, + {0xF5, u8"더"}, + {0xF6, u8"덕"}, + {0xF7, u8"덖"}, + {0xF8, u8"던"}, + {0xF9, u8"덛"}, + {0xFA, u8"덜"}, + {0xFB, u8"덞"}, + {0xFC, u8"덟"}, + {0xFD, u8"덤"}, + {0xFE, u8"덥"} +}; + +static const TextCodePair gen2TextCodesKorean3[] = { + {0x01, u8"덧"}, + {0x02, u8"덩"}, + {0x03, u8"덫"}, + {0x04, u8"덮"}, + {0x05, u8"데"}, + {0x06, u8"덱"}, + {0x07, u8"덴"}, + {0x08, u8"델"}, + {0x09, u8"뎀"}, + {0x0A, u8"뎁"}, + {0x0B, u8"뎃"}, + {0x0C, u8"뎄"}, + {0x0D, u8"뎅"}, + {0x0E, u8"뎌"}, + {0x0F, u8"뎐"}, + {0x10, u8"뎔"}, + {0x11, u8"뎠"}, + {0x12, u8"뎡"}, + {0x13, u8"뎨"}, + {0x14, u8"뎬"}, + {0x15, u8"도"}, + {0x16, u8"독"}, + {0x17, u8"돈"}, + {0x18, u8"돋"}, + {0x19, u8"돌"}, + {0x1A, u8"돎"}, + {0x1C, u8"돔"}, + {0x1D, u8"돕"}, + {0x1E, u8"돗"}, + {0x1F, u8"동"}, + {0x20, u8"돛"}, + {0x21, u8"돝"}, + {0x22, u8"돠"}, + {0x23, u8"돤"}, + {0x24, u8"돨"}, + {0x25, u8"돼"}, + {0x26, u8"됐"}, + {0x27, u8"되"}, + {0x28, u8"된"}, + {0x29, u8"될"}, + {0x2A, u8"됨"}, + {0x2B, u8"됩"}, + {0x2C, u8"됫"}, + {0x2D, u8"됴"}, + {0x2E, u8"두"}, + {0x2F, u8"둑"}, + {0x30, u8"둔"}, + {0x31, u8"둘"}, + {0x32, u8"둠"}, + {0x33, u8"둡"}, + {0x34, u8"둣"}, + {0x35, u8"둥"}, + {0x36, u8"둬"}, + {0x37, u8"뒀"}, + {0x38, u8"뒈"}, + {0x39, u8"뒝"}, + {0x3A, u8"뒤"}, + {0x3B, u8"뒨"}, + {0x3C, u8"뒬"}, + {0x3D, u8"뒵"}, + {0x3E, u8"뒷"}, + {0x3F, u8"뒹"}, + {0x40, u8"듀"}, + {0x41, u8"듄"}, + {0x42, u8"듈"}, + {0x43, u8"듐"}, + {0x44, u8"듕"}, + {0x45, u8"드"}, + {0x46, u8"득"}, + {0x47, u8"든"}, + {0x48, u8"듣"}, + {0x49, u8"들"}, + {0x4A, u8"듦"}, + {0x4B, u8"듬"}, + {0x4C, u8"듭"}, + {0x4D, u8"듯"}, + {0x4E, u8"등"}, + {0x4F, u8"듸"}, + {0x60, u8"디"}, + {0x61, u8"딕"}, + {0x62, u8"딘"}, + {0x63, u8"딛"}, + {0x64, u8"딜"}, + {0x65, u8"딤"}, + {0x66, u8"딥"}, + {0x67, u8"딧"}, + {0x68, u8"딨"}, + {0x69, u8"딩"}, + {0x6A, u8"딪"}, + {0x6B, u8"따"}, + {0x6C, u8"딱"}, + {0x6D, u8"딴"}, + {0x6E, u8"딸"}, + {0x71, u8"땀"}, + {0x72, u8"땁"}, + {0x73, u8"땃"}, + {0x74, u8"땄"}, + {0x75, u8"땅"}, + {0x76, u8"땋"}, + {0x77, u8"때"}, + {0x78, u8"땍"}, + {0x79, u8"땐"}, + {0x7A, u8"땔"}, + {0x7B, u8"땜"}, + {0x7C, u8"땝"}, + {0x7D, u8"땟"}, + {0x7E, u8"땠"}, + {0x7F, u8"땡"}, + {0x80, u8"떠"}, + {0x81, u8"떡"}, + {0x82, u8"떤"}, + {0x83, u8"떨"}, + {0x84, u8"떪"}, + {0x85, u8"떫"}, + {0x86, u8"떰"}, + {0x87, u8"떱"}, + {0x88, u8"떳"}, + {0x89, u8"떴"}, + {0x8A, u8"떵"}, + {0x8B, u8"떻"}, + {0x8C, u8"떼"}, + {0x8D, u8"떽"}, + {0x8E, u8"뗀"}, + {0x8F, u8"뗄"}, + {0x90, u8"뗌"}, + {0x91, u8"뗍"}, + {0x92, u8"뗏"}, + {0x93, u8"뗐"}, + {0x94, u8"뗑"}, + {0x95, u8"뗘"}, + {0x96, u8"뗬"}, + {0x97, u8"또"}, + {0x98, u8"똑"}, + {0x99, u8"똔"}, + {0x9A, u8"똘"}, + {0x9B, u8"똥"}, + {0x9C, u8"똬"}, + {0x9D, u8"똴"}, + {0x9E, u8"뙈"}, + {0x9F, u8"뙤"}, + {0xA0, u8"뙨"}, + {0xA1, u8"뚜"}, + {0xA2, u8"뚝"}, + {0xA3, u8"뚠"}, + {0xA4, u8"뚤"}, + {0xA5, u8"뚫"}, + {0xA6, u8"뚬"}, + {0xA7, u8"뚱"}, + {0xA8, u8"뛔"}, + {0xA9, u8"뛰"}, + {0xAA, u8"뛴"}, + {0xAB, u8"뛸"}, + {0xAC, u8"뜀"}, + {0xAD, u8"뜁"}, + {0xAE, u8"뜅"}, + {0xAF, u8"뜨"}, + {0xB0, u8"뜩"}, + {0xB1, u8"뜬"}, + {0xB2, u8"뜯"}, + {0xB3, u8"뜰"}, + {0xB4, u8"뜸"}, + {0xB5, u8"뜹"}, + {0xB6, u8"뜻"}, + {0xB7, u8"띄"}, + {0xB8, u8"띈"}, + {0xB9, u8"띌"}, + {0xBA, u8"띔"}, + {0xBB, u8"띕"}, + {0xBC, u8"띠"}, + {0xBD, u8"띤"}, + {0xBE, u8"띨"}, + {0xBF, u8"띰"}, + {0xC0, u8"띱"}, + {0xC1, u8"띳"}, + {0xC2, u8"띵"}, + {0xC3, u8"라"}, + {0xC4, u8"락"}, + {0xC5, u8"란"}, + {0xC6, u8"랄"}, + {0xC7, u8"람"}, + {0xC8, u8"랍"}, + {0xC9, u8"랏"}, + {0xCA, u8"랐"}, + {0xCB, u8"랑"}, + {0xCC, u8"랒"}, + {0xCD, u8"랖"}, + {0xCE, u8"랗"}, + {0xD0, u8"뢔"}, + {0xD1, u8"래"}, + {0xD2, u8"랙"}, + {0xD3, u8"랜"}, + {0xD4, u8"랠"}, + {0xD5, u8"램"}, + {0xD6, u8"랩"}, + {0xD7, u8"랫"}, + {0xD8, u8"랬"}, + {0xD9, u8"랭"}, + {0xDA, u8"랴"}, + {0xDB, u8"략"}, + {0xDC, u8"랸"}, + {0xDD, u8"럇"}, + {0xDE, u8"량"}, + {0xDF, u8"러"}, + {0xE0, u8"럭"}, + {0xE1, u8"런"}, + {0xE2, u8"럴"}, + {0xE3, u8"럼"}, + {0xE4, u8"럽"}, + {0xE5, u8"럿"}, + {0xE6, u8"렀"}, + {0xE7, u8"렁"}, + {0xE8, u8"렇"}, + {0xE9, u8"레"}, + {0xEA, u8"렉"}, + {0xEB, u8"렌"}, + {0xEC, u8"렐"}, + {0xED, u8"렘"}, + {0xEE, u8"렙"}, + {0xEF, u8"렛"}, + {0xF0, u8"렝"}, + {0xF1, u8"려"}, + {0xF2, u8"력"}, + {0xF3, u8"련"}, + {0xF4, u8"렬"}, + {0xF5, u8"렴"}, + {0xF6, u8"렵"}, + {0xF7, u8"렷"}, + {0xF8, u8"렸"}, + {0xF9, u8"령"}, + {0xFA, u8"례"}, + {0xFB, u8"롄"}, + {0xFC, u8"롑"}, + {0xFD, u8"롓"}, + {0xFE, u8"로"}, + {0xFF, u8"록"} +}; + +static const TextCodePair gen2TextCodesKorean4[] = { + {0x00, u8"론"}, + {0x01, u8"롤"}, + {0x02, u8"롬"}, + {0x03, u8"롭"}, + {0x04, u8"롯"}, + {0x05, u8"롱"}, + {0x06, u8"롸"}, + {0x07, u8"롼"}, + {0x08, u8"뢍"}, + {0x09, u8"뢨"}, + {0x0A, u8"뢰"}, + {0x0B, u8"뢴"}, + {0x0C, u8"뢸"}, + {0x0D, u8"룀"}, + {0x0E, u8"룁"}, + {0x0F, u8"룃"}, + {0x10, u8"룅"}, + {0x11, u8"료"}, + {0x12, u8"룐"}, + {0x13, u8"룔"}, + {0x14, u8"룝"}, + {0x15, u8"룟"}, + {0x16, u8"룡"}, + {0x17, u8"루"}, + {0x18, u8"룩"}, + {0x19, u8"룬"}, + {0x1A, u8"룰"}, + {0x1B, u8"룸"}, + {0x1C, u8"룹"}, + {0x1D, u8"룻"}, + {0x1E, u8"룽"}, + {0x1F, u8"뤄"}, + {0x20, u8"뤘"}, + {0x21, u8"뤠"}, + {0x22, u8"뤼"}, + {0x23, u8"뤽"}, + {0x24, u8"륀"}, + {0x25, u8"륄"}, + {0x26, u8"륌"}, + {0x27, u8"륏"}, + {0x28, u8"륑"}, + {0x29, u8"류"}, + {0x2A, u8"륙"}, + {0x2B, u8"륜"}, + {0x2C, u8"률"}, + {0x2D, u8"륨"}, + {0x2E, u8"륩"}, + {0x31, u8"륫"}, + {0x32, u8"륭"}, + {0x33, u8"르"}, + {0x34, u8"륵"}, + {0x35, u8"른"}, + {0x36, u8"를"}, + {0x37, u8"름"}, + {0x38, u8"릅"}, + {0x39, u8"릇"}, + {0x3A, u8"릉"}, + {0x3B, u8"릊"}, + {0x3C, u8"릍"}, + {0x3D, u8"릎"}, + {0x3E, u8"리"}, + {0x3F, u8"릭"}, + {0x40, u8"린"}, + {0x41, u8"릴"}, + {0x42, u8"림"}, + {0x43, u8"립"}, + {0x44, u8"릿"}, + {0x45, u8"링"}, + {0x46, u8"마"}, + {0x47, u8"막"}, + {0x48, u8"만"}, + {0x49, u8"많"}, + {0x4A, u8"맏"}, + {0x4B, u8"말"}, + {0x4C, u8"맑"}, + {0x4D, u8"맒"}, + {0x4E, u8"맘"}, + {0x4F, u8"맙"}, + {0x60, u8"맛"}, + {0x61, u8"망"}, + {0x62, u8"맞"}, + {0x63, u8"맡"}, + {0x64, u8"맣"}, + {0x65, u8"매"}, + {0x66, u8"맥"}, + {0x67, u8"맨"}, + {0x68, u8"맬"}, + {0x69, u8"맴"}, + {0x6A, u8"맵"}, + {0x6B, u8"맷"}, + {0x6C, u8"맸"}, + {0x6D, u8"맹"}, + {0x6E, u8"맺"}, + {0x6F, u8"먀"}, + {0x70, u8"먁"}, + {0x71, u8"먈"}, + {0x72, u8"먕"}, + {0x73, u8"머"}, + {0x74, u8"먹"}, + {0x75, u8"먼"}, + {0x76, u8"멀"}, + {0x77, u8"멂"}, + {0x78, u8"멈"}, + {0x79, u8"멉"}, + {0x7A, u8"멋"}, + {0x7B, u8"멍"}, + {0x7C, u8"멎"}, + {0x7D, u8"멓"}, + {0x7E, u8"메"}, + {0x7F, u8"멕"}, + {0x80, u8"멘"}, + {0x81, u8"멜"}, + {0x82, u8"멤"}, + {0x83, u8"멥"}, + {0x84, u8"멧"}, + {0x85, u8"멨"}, + {0x86, u8"멩"}, + {0x87, u8"며"}, + {0x88, u8"멱"}, + {0x89, u8"면"}, + {0x8A, u8"멸"}, + {0x8B, u8"몃"}, + {0x8C, u8"몄"}, + {0x8D, u8"명"}, + {0x8E, u8"몇"}, + {0x8F, u8"몌"}, + {0x90, u8"모"}, + {0x91, u8"목"}, + {0x92, u8"몫"}, + {0x93, u8"몬"}, + {0x94, u8"몰"}, + {0x95, u8"몲"}, + {0x96, u8"몸"}, + {0x97, u8"몹"}, + {0x98, u8"못"}, + {0x99, u8"몽"}, + {0x9A, u8"뫄"}, + {0x9B, u8"뫈"}, + {0x9C, u8"뫘"}, + {0x9D, u8"뫙"}, + {0x9E, u8"뫼"}, + {0xA1, u8"묀"}, + {0xA2, u8"묄"}, + {0xA3, u8"묍"}, + {0xA4, u8"묏"}, + {0xA5, u8"묑"}, + {0xA6, u8"묘"}, + {0xA7, u8"묜"}, + {0xA8, u8"묠"}, + {0xA9, u8"묩"}, + {0xAA, u8"묫"}, + {0xAB, u8"무"}, + {0xAC, u8"묵"}, + {0xAD, u8"묶"}, + {0xAE, u8"문"}, + {0xAF, u8"묻"}, + {0xB0, u8"물"}, + {0xB1, u8"묽"}, + {0xB2, u8"묾"}, + {0xB3, u8"뭄"}, + {0xB4, u8"뭅"}, + {0xB5, u8"뭇"}, + {0xB6, u8"뭉"}, + {0xB7, u8"뭍"}, + {0xB8, u8"뭏"}, + {0xB9, u8"뭐"}, + {0xBA, u8"뭔"}, + {0xBB, u8"뭘"}, + {0xBC, u8"뭡"}, + {0xBD, u8"뭣"}, + {0xBE, u8"뭬"}, + {0xBF, u8"뮈"}, + {0xC0, u8"뮌"}, + {0xC1, u8"뮐"}, + {0xC2, u8"뮤"}, + {0xC3, u8"뮨"}, + {0xC4, u8"뮬"}, + {0xC5, u8"뮴"}, + {0xC6, u8"뮷"}, + {0xC7, u8"므"}, + {0xC8, u8"믄"}, + {0xC9, u8"믈"}, + {0xCA, u8"믐"}, + {0xCB, u8"믓"}, + {0xCC, u8"미"}, + {0xCD, u8"믹"}, + {0xCE, u8"민"}, + {0xCF, u8"믿"}, + {0xD0, u8"밀"}, + {0xD1, u8"밂"}, + {0xD2, u8"밈"}, + {0xD3, u8"밉"}, + {0xD4, u8"밋"}, + {0xD5, u8"밌"}, + {0xD6, u8"밍"}, + {0xD7, u8"및"}, + {0xD8, u8"밑"}, + {0xD9, u8"바"}, + {0xDA, u8"박"}, + {0xDB, u8"밖"}, + {0xDC, u8"밗"}, + {0xDD, u8"반"}, + {0xDE, u8"받"}, + {0xDF, u8"발"}, + {0xE0, u8"밝"}, + {0xE1, u8"밞"}, + {0xE2, u8"밟"}, + {0xE3, u8"밤"}, + {0xE4, u8"밥"}, + {0xE5, u8"밧"}, + {0xE6, u8"방"}, + {0xE7, u8"밭"}, + {0xE8, u8"배"}, + {0xE9, u8"백"}, + {0xEA, u8"밴"}, + {0xEB, u8"밸"}, + {0xEC, u8"뱀"}, + {0xED, u8"뱁"}, + {0xEE, u8"뱃"}, + {0xEF, u8"뱄"}, + {0xF0, u8"뱅"}, + {0xF1, u8"뱉"}, + {0xF2, u8"뱌"}, + {0xF3, u8"뱍"}, + {0xF4, u8"뱐"}, + {0xF5, u8"뱝"}, + {0xF6, u8"버"}, + {0xF7, u8"벅"}, + {0xF8, u8"번"}, + {0xF9, u8"벋"}, + {0xFA, u8"벌"}, + {0xFB, u8"벎"}, + {0xFC, u8"범"}, + {0xFD, u8"법"}, + {0xFE, u8"벗"} +}; + +static const TextCodePair gen2TextCodesKorean5[] = { + {0x01, u8"벙"}, + {0x02, u8"벚"}, + {0x03, u8"베"}, + {0x04, u8"벡"}, + {0x05, u8"벤"}, + {0x06, u8"벧"}, + {0x07, u8"벨"}, + {0x08, u8"벰"}, + {0x09, u8"벱"}, + {0x0A, u8"벳"}, + {0x0B, u8"벴"}, + {0x0C, u8"벵"}, + {0x0D, u8"벼"}, + {0x0E, u8"벽"}, + {0x0F, u8"변"}, + {0x10, u8"별"}, + {0x11, u8"볍"}, + {0x12, u8"볏"}, + {0x13, u8"볐"}, + {0x14, u8"병"}, + {0x15, u8"볕"}, + {0x16, u8"볘"}, + {0x17, u8"볜"}, + {0x18, u8"보"}, + {0x19, u8"복"}, + {0x1A, u8"볶"}, + {0x1B, u8"본"}, + {0x1C, u8"볼"}, + {0x1D, u8"봄"}, + {0x1E, u8"봅"}, + {0x1F, u8"봇"}, + {0x20, u8"봉"}, + {0x21, u8"봐"}, + {0x22, u8"봔"}, + {0x23, u8"봤"}, + {0x24, u8"봬"}, + {0x25, u8"뵀"}, + {0x26, u8"뵈"}, + {0x27, u8"뵉"}, + {0x28, u8"뵌"}, + {0x29, u8"뵐"}, + {0x2A, u8"뵘"}, + {0x2B, u8"뵙"}, + {0x2C, u8"뵤"}, + {0x2D, u8"뵨"}, + {0x2E, u8"부"}, + {0x2F, u8"북"}, + {0x30, u8"분"}, + {0x31, u8"붇"}, + {0x32, u8"불"}, + {0x33, u8"붉"}, + {0x34, u8"붊"}, + {0x35, u8"붐"}, + {0x36, u8"붑"}, + {0x37, u8"붓"}, + {0x38, u8"붕"}, + {0x39, u8"붙"}, + {0x3A, u8"붚"}, + {0x3B, u8"붜"}, + {0x3C, u8"붤"}, + {0x3D, u8"붰"}, + {0x3E, u8"붸"}, + {0x3F, u8"뷔"}, + {0x40, u8"뷕"}, + {0x41, u8"뷘"}, + {0x42, u8"뷜"}, + {0x43, u8"뷩"}, + {0x44, u8"뷰"}, + {0x45, u8"뷴"}, + {0x46, u8"뷸"}, + {0x47, u8"븀"}, + {0x48, u8"븃"}, + {0x49, u8"븅"}, + {0x4A, u8"브"}, + {0x4B, u8"븍"}, + {0x4C, u8"븐"}, + {0x4D, u8"블"}, + {0x4E, u8"븜"}, + {0x4F, u8"븝"}, + {0x60, u8"븟"}, + {0x61, u8"비"}, + {0x62, u8"빅"}, + {0x63, u8"빈"}, + {0x64, u8"빌"}, + {0x65, u8"빎"}, + {0x66, u8"빔"}, + {0x67, u8"빕"}, + {0x68, u8"빗"}, + {0x69, u8"빙"}, + {0x6A, u8"빚"}, + {0x6B, u8"빛"}, + {0x6C, u8"빠"}, + {0x6D, u8"빡"}, + {0x6E, u8"빤"}, + {0x71, u8"빨"}, + {0x72, u8"빪"}, + {0x73, u8"빰"}, + {0x74, u8"빱"}, + {0x75, u8"빳"}, + {0x76, u8"빴"}, + {0x77, u8"빵"}, + {0x78, u8"빻"}, + {0x79, u8"빼"}, + {0x7A, u8"빽"}, + {0x7B, u8"뺀"}, + {0x7C, u8"뺄"}, + {0x7D, u8"뺌"}, + {0x7E, u8"뺍"}, + {0x7F, u8"뺏"}, + {0x80, u8"뺐"}, + {0x81, u8"뺑"}, + {0x82, u8"뺘"}, + {0x83, u8"뺙"}, + {0x84, u8"뺨"}, + {0x85, u8"뻐"}, + {0x86, u8"뻑"}, + {0x87, u8"뻔"}, + {0x88, u8"뻗"}, + {0x89, u8"뻘"}, + {0x8A, u8"뻠"}, + {0x8B, u8"뻣"}, + {0x8C, u8"뻤"}, + {0x8D, u8"뻥"}, + {0x8E, u8"뻬"}, + {0x8F, u8"뼁"}, + {0x90, u8"뼈"}, + {0x91, u8"뼉"}, + {0x92, u8"뼘"}, + {0x93, u8"뼙"}, + {0x94, u8"뼛"}, + {0x95, u8"뼜"}, + {0x96, u8"뼝"}, + {0x97, u8"뽀"}, + {0x98, u8"뽁"}, + {0x99, u8"뽄"}, + {0x9A, u8"뽈"}, + {0x9B, u8"뽐"}, + {0x9C, u8"뽑"}, + {0x9D, u8"뽕"}, + {0x9E, u8"뾔"}, + {0x9F, u8"뾰"}, + {0xA0, u8"뿅"}, + {0xA1, u8"뿌"}, + {0xA2, u8"뿍"}, + {0xA3, u8"뿐"}, + {0xA4, u8"뿔"}, + {0xA5, u8"뿜"}, + {0xA6, u8"뿟"}, + {0xA7, u8"뿡"}, + {0xA8, u8"쀼"}, + {0xA9, u8"쁑"}, + {0xAA, u8"쁘"}, + {0xAB, u8"쁜"}, + {0xAC, u8"쁠"}, + {0xAD, u8"쁨"}, + {0xAE, u8"쁩"}, + {0xAF, u8"삐"}, + {0xB0, u8"삑"}, + {0xB1, u8"삔"}, + {0xB2, u8"삘"}, + {0xB3, u8"삠"}, + {0xB4, u8"삡"}, + {0xB5, u8"삣"}, + {0xB6, u8"삥"}, + {0xB7, u8"사"}, + {0xB8, u8"삭"}, + {0xB9, u8"삯"}, + {0xBA, u8"산"}, + {0xBB, u8"삳"}, + {0xBC, u8"살"}, + {0xBD, u8"삵"}, + {0xBE, u8"삶"}, + {0xBF, u8"삼"}, + {0xC0, u8"삽"}, + {0xC1, u8"삿"}, + {0xC2, u8"샀"}, + {0xC3, u8"상"}, + {0xC4, u8"샅"}, + {0xC5, u8"새"}, + {0xC6, u8"색"}, + {0xC7, u8"샌"}, + {0xC8, u8"샐"}, + {0xC9, u8"샘"}, + {0xCA, u8"샙"}, + {0xCB, u8"샛"}, + {0xCC, u8"샜"}, + {0xCD, u8"생"}, + {0xCE, u8"샤"}, + {0xD1, u8"샥"}, + {0xD2, u8"샨"}, + {0xD3, u8"샬"}, + {0xD4, u8"샴"}, + {0xD5, u8"샵"}, + {0xD6, u8"샷"}, + {0xD7, u8"샹"}, + {0xD8, u8"섀"}, + {0xD9, u8"섄"}, + {0xDA, u8"섈"}, + {0xDB, u8"섐"}, + {0xDC, u8"섕"}, + {0xDD, u8"서"}, + {0xDE, u8"석"}, + {0xDF, u8"섞"}, + {0xE0, u8"섟"}, + {0xE1, u8"선"}, + {0xE2, u8"섣"}, + {0xE3, u8"설"}, + {0xE4, u8"섦"}, + {0xE5, u8"섧"}, + {0xE6, u8"섬"}, + {0xE7, u8"섭"}, + {0xE8, u8"섯"}, + {0xE9, u8"섰"}, + {0xEA, u8"성"}, + {0xEB, u8"섶"}, + {0xEC, u8"세"}, + {0xED, u8"섹"}, + {0xEE, u8"센"}, + {0xEF, u8"셀"}, + {0xF0, u8"셈"}, + {0xF1, u8"셉"}, + {0xF2, u8"셋"}, + {0xF3, u8"셌"}, + {0xF4, u8"셍"}, + {0xF5, u8"셔"}, + {0xF6, u8"셕"}, + {0xF7, u8"션"}, + {0xF8, u8"셜"}, + {0xF9, u8"셤"}, + {0xFA, u8"셥"}, + {0xFB, u8"셧"}, + {0xFC, u8"셨"}, + {0xFD, u8"셩"}, + {0xFE, u8"셰"}, + {0xFF, u8"셴"} +}; + +static const TextCodePair gen2TextCodesKorean6[] = { + {0x00, u8"셸"}, + {0x01, u8"솅"}, + {0x02, u8"소"}, + {0x03, u8"속"}, + {0x04, u8"솎"}, + {0x05, u8"손"}, + {0x06, u8"솔"}, + {0x07, u8"솖"}, + {0x08, u8"솜"}, + {0x09, u8"솝"}, + {0x0A, u8"솟"}, + {0x0B, u8"송"}, + {0x0C, u8"솥"}, + {0x0D, u8"솨"}, + {0x0E, u8"솩"}, + {0x0F, u8"솬"}, + {0x10, u8"솰"}, + {0x11, u8"솽"}, + {0x12, u8"쇄"}, + {0x13, u8"쇈"}, + {0x14, u8"쇌"}, + {0x15, u8"쇔"}, + {0x16, u8"쇗"}, + {0x17, u8"쇘"}, + {0x18, u8"쇠"}, + {0x19, u8"쇤"}, + {0x1A, u8"쇨"}, + {0x1B, u8"쇰"}, + {0x1C, u8"쇱"}, + {0x1D, u8"쇳"}, + {0x1E, u8"쇼"}, + {0x1F, u8"쇽"}, + {0x20, u8"숀"}, + {0x21, u8"숄"}, + {0x22, u8"숌"}, + {0x23, u8"숍"}, + {0x24, u8"숏"}, + {0x25, u8"숑"}, + {0x26, u8"수"}, + {0x27, u8"숙"}, + {0x28, u8"순"}, + {0x29, u8"숟"}, + {0x2A, u8"술"}, + {0x2B, u8"숨"}, + {0x2C, u8"숩"}, + {0x2D, u8"숫"}, + {0x2E, u8"숭"}, + {0x2F, u8"쌰"}, + {0x30, u8"쎼"}, + {0x31, u8"숯"}, + {0x32, u8"숱"}, + {0x33, u8"숲"}, + {0x34, u8"숴"}, + {0x35, u8"쉈"}, + {0x36, u8"쉐"}, + {0x37, u8"쉑"}, + {0x38, u8"쉔"}, + {0x39, u8"쉘"}, + {0x3A, u8"쉠"}, + {0x3B, u8"쉥"}, + {0x3C, u8"쉬"}, + {0x3D, u8"쉭"}, + {0x3E, u8"쉰"}, + {0x3F, u8"쉴"}, + {0x40, u8"쉼"}, + {0x41, u8"쉽"}, + {0x42, u8"쉿"}, + {0x43, u8"슁"}, + {0x44, u8"슈"}, + {0x45, u8"슉"}, + {0x46, u8"슐"}, + {0x47, u8"슘"}, + {0x48, u8"슛"}, + {0x49, u8"슝"}, + {0x4A, u8"스"}, + {0x4B, u8"슥"}, + {0x4C, u8"슨"}, + {0x4D, u8"슬"}, + {0x4E, u8"슭"}, + {0x4F, u8"슴"}, + {0x60, u8"습"}, + {0x61, u8"슷"}, + {0x62, u8"승"}, + {0x63, u8"시"}, + {0x64, u8"식"}, + {0x65, u8"신"}, + {0x66, u8"싣"}, + {0x67, u8"실"}, + {0x68, u8"싫"}, + {0x69, u8"심"}, + {0x6A, u8"십"}, + {0x6B, u8"싯"}, + {0x6C, u8"싱"}, + {0x6D, u8"싶"}, + {0x6E, u8"싸"}, + {0x6F, u8"싹"}, + {0x70, u8"싻"}, + {0x71, u8"싼"}, + {0x72, u8"쌀"}, + {0x73, u8"쌈"}, + {0x74, u8"쌉"}, + {0x75, u8"쌌"}, + {0x76, u8"쌍"}, + {0x77, u8"쌓"}, + {0x78, u8"쌔"}, + {0x79, u8"쌕"}, + {0x7A, u8"쌘"}, + {0x7B, u8"쌜"}, + {0x7C, u8"쌤"}, + {0x7D, u8"쌥"}, + {0x7E, u8"쌨"}, + {0x7F, u8"쌩"}, + {0x80, u8"썅"}, + {0x81, u8"써"}, + {0x82, u8"썩"}, + {0x83, u8"썬"}, + {0x84, u8"썰"}, + {0x85, u8"썲"}, + {0x86, u8"썸"}, + {0x87, u8"썹"}, + {0x88, u8"썼"}, + {0x89, u8"썽"}, + {0x8A, u8"쎄"}, + {0x8B, u8"쎈"}, + {0x8C, u8"쎌"}, + {0x8D, u8"쏀"}, + {0x8E, u8"쏘"}, + {0x8F, u8"쏙"}, + {0x90, u8"쏜"}, + {0x91, u8"쏟"}, + {0x92, u8"쏠"}, + {0x93, u8"쏢"}, + {0x94, u8"쏨"}, + {0x95, u8"쏩"}, + {0x96, u8"쏭"}, + {0x97, u8"쏴"}, + {0x98, u8"쏵"}, + {0x99, u8"쏸"}, + {0x9A, u8"쐈"}, + {0x9B, u8"쐐"}, + {0x9C, u8"쐤"}, + {0x9D, u8"쐬"}, + {0x9E, u8"쐰"}, + {0xA0, u8"쓔"}, + {0xA1, u8"쐴"}, + {0xA2, u8"쐼"}, + {0xA3, u8"쐽"}, + {0xA4, u8"쑈"}, + {0xA5, u8"쑤"}, + {0xA6, u8"쑥"}, + {0xA7, u8"쑨"}, + {0xA8, u8"쑬"}, + {0xA9, u8"쑴"}, + {0xAA, u8"쑵"}, + {0xAB, u8"쑹"}, + {0xAC, u8"쒀"}, + {0xAD, u8"쒔"}, + {0xAE, u8"쒜"}, + {0xAF, u8"쒸"}, + {0xB0, u8"쒼"}, + {0xB1, u8"쓩"}, + {0xB2, u8"쓰"}, + {0xB3, u8"쓱"}, + {0xB4, u8"쓴"}, + {0xB5, u8"쓸"}, + {0xB6, u8"쓺"}, + {0xB7, u8"쓿"}, + {0xB8, u8"씀"}, + {0xB9, u8"씁"}, + {0xBA, u8"씌"}, + {0xBB, u8"씐"}, + {0xBC, u8"씔"}, + {0xBD, u8"씜"}, + {0xBE, u8"씨"}, + {0xBF, u8"씩"}, + {0xC0, u8"씬"}, + {0xC1, u8"씰"}, + {0xC2, u8"씸"}, + {0xC3, u8"씹"}, + {0xC4, u8"씻"}, + {0xC5, u8"씽"}, + {0xC6, u8"아"}, + {0xC7, u8"악"}, + {0xC8, u8"안"}, + {0xC9, u8"앉"}, + {0xCA, u8"않"}, + {0xCB, u8"알"}, + {0xCC, u8"앍"}, + {0xCD, u8"앎"}, + {0xCE, u8"앓"}, + {0xCF, u8"암"}, + {0xD0, u8"압"}, + {0xD1, u8"앗"}, + {0xD2, u8"았"}, + {0xD3, u8"앙"}, + {0xD4, u8"앝"}, + {0xD5, u8"앞"}, + {0xD6, u8"애"}, + {0xD7, u8"액"}, + {0xD8, u8"앤"}, + {0xD9, u8"앨"}, + {0xDA, u8"앰"}, + {0xDB, u8"앱"}, + {0xDC, u8"앳"}, + {0xDD, u8"앴"}, + {0xDE, u8"앵"}, + {0xDF, u8"야"}, + {0xE0, u8"약"}, + {0xE1, u8"얀"}, + {0xE2, u8"얄"}, + {0xE3, u8"얇"}, + {0xE4, u8"얌"}, + {0xE5, u8"얍"}, + {0xE6, u8"얏"}, + {0xE7, u8"양"}, + {0xE8, u8"얕"}, + {0xE9, u8"얗"}, + {0xEA, u8"얘"}, + {0xEB, u8"얜"}, + {0xEC, u8"얠"}, + {0xED, u8"얩"}, + {0xEE, u8"어"}, + {0xEF, u8"억"}, + {0xF0, u8"언"}, + {0xF1, u8"얹"}, + {0xF2, u8"얻"}, + {0xF3, u8"얼"}, + {0xF4, u8"얽"}, + {0xF5, u8"얾"}, + {0xF6, u8"엄"}, + {0xF7, u8"업"}, + {0xF8, u8"없"}, + {0xF9, u8"엇"}, + {0xFA, u8"었"}, + {0xFB, u8"엉"}, + {0xFC, u8"엊"}, + {0xFD, u8"엌"}, + {0xFE, u8"엎"} +}; + +static const TextCodePair gen2TextCodesKorean7[] = { + {0x01, u8"에"}, + {0x02, u8"엑"}, + {0x03, u8"엔"}, + {0x04, u8"엘"}, + {0x05, u8"엠"}, + {0x06, u8"엡"}, + {0x07, u8"엣"}, + {0x08, u8"엥"}, + {0x09, u8"여"}, + {0x0A, u8"역"}, + {0x0B, u8"엮"}, + {0x0C, u8"연"}, + {0x0D, u8"열"}, + {0x0E, u8"엶"}, + {0x0F, u8"엷"}, + {0x10, u8"염"}, + {0x11, u8"엽"}, + {0x12, u8"엾"}, + {0x13, u8"엿"}, + {0x14, u8"였"}, + {0x15, u8"영"}, + {0x16, u8"옅"}, + {0x17, u8"옆"}, + {0x18, u8"옇"}, + {0x19, u8"예"}, + {0x1A, u8"옌"}, + {0x1B, u8"옐"}, + {0x1C, u8"옘"}, + {0x1D, u8"옙"}, + {0x1E, u8"옛"}, + {0x1F, u8"옜"}, + {0x20, u8"오"}, + {0x21, u8"옥"}, + {0x22, u8"온"}, + {0x23, u8"올"}, + {0x24, u8"옭"}, + {0x25, u8"옮"}, + {0x26, u8"옰"}, + {0x27, u8"옳"}, + {0x28, u8"옴"}, + {0x29, u8"옵"}, + {0x2A, u8"옷"}, + {0x2B, u8"옹"}, + {0x2C, u8"옻"}, + {0x2D, u8"와"}, + {0x2E, u8"왁"}, + {0x2F, u8"완"}, + {0x30, u8"왈"}, + {0x31, u8"왐"}, + {0x32, u8"왑"}, + {0x33, u8"왓"}, + {0x34, u8"왔"}, + {0x35, u8"왕"}, + {0x36, u8"왜"}, + {0x37, u8"왝"}, + {0x38, u8"왠"}, + {0x39, u8"왬"}, + {0x3A, u8"왯"}, + {0x3B, u8"왱"}, + {0x3C, u8"외"}, + {0x3D, u8"왹"}, + {0x3E, u8"왼"}, + {0x3F, u8"욀"}, + {0x40, u8"욈"}, + {0x41, u8"욉"}, + {0x42, u8"욋"}, + {0x43, u8"욍"}, + {0x44, u8"요"}, + {0x45, u8"욕"}, + {0x46, u8"욘"}, + {0x47, u8"욜"}, + {0x48, u8"욤"}, + {0x49, u8"욥"}, + {0x4A, u8"욧"}, + {0x4B, u8"용"}, + {0x4C, u8"우"}, + {0x4D, u8"욱"}, + {0x4E, u8"운"}, + {0x4F, u8"울"}, + {0x60, u8"욹"}, + {0x61, u8"욺"}, + {0x62, u8"움"}, + {0x63, u8"웁"}, + {0x64, u8"웃"}, + {0x65, u8"웅"}, + {0x66, u8"워"}, + {0x67, u8"웍"}, + {0x68, u8"원"}, + {0x69, u8"월"}, + {0x6A, u8"웜"}, + {0x6B, u8"웝"}, + {0x6C, u8"웠"}, + {0x6D, u8"웡"}, + {0x6E, u8"웨"}, + {0x71, u8"웩"}, + {0x72, u8"웬"}, + {0x73, u8"웰"}, + {0x74, u8"웸"}, + {0x75, u8"웹"}, + {0x76, u8"웽"}, + {0x77, u8"위"}, + {0x78, u8"윅"}, + {0x79, u8"윈"}, + {0x7A, u8"윌"}, + {0x7B, u8"윔"}, + {0x7C, u8"윕"}, + {0x7D, u8"윗"}, + {0x7E, u8"윙"}, + {0x7F, u8"유"}, + {0x80, u8"육"}, + {0x81, u8"윤"}, + {0x82, u8"율"}, + {0x83, u8"윰"}, + {0x84, u8"윱"}, + {0x85, u8"윳"}, + {0x86, u8"융"}, + {0x87, u8"윷"}, + {0x88, u8"으"}, + {0x89, u8"윽"}, + {0x8A, u8"은"}, + {0x8B, u8"을"}, + {0x8C, u8"읆"}, + {0x8D, u8"음"}, + {0x8E, u8"읍"}, + {0x8F, u8"읏"}, + {0x90, u8"응"}, + {0x91, u8"읒"}, + {0x92, u8"읓"}, + {0x93, u8"읔"}, + {0x94, u8"읕"}, + {0x95, u8"읖"}, + {0x96, u8"읗"}, + {0x97, u8"의"}, + {0x98, u8"읜"}, + {0x99, u8"읠"}, + {0x9A, u8"읨"}, + {0x9B, u8"읫"}, + {0x9C, u8"이"}, + {0x9D, u8"익"}, + {0x9E, u8"인"}, + {0x9F, u8"일"}, + {0xA0, u8"읽"}, + {0xA1, u8"읾"}, + {0xA2, u8"잃"}, + {0xA3, u8"임"}, + {0xA4, u8"입"}, + {0xA5, u8"잇"}, + {0xA6, u8"있"}, + {0xA7, u8"잉"}, + {0xA8, u8"잊"}, + {0xA9, u8"잎"}, + {0xAA, u8"자"}, + {0xAB, u8"작"}, + {0xAC, u8"잔"}, + {0xAD, u8"잖"}, + {0xAE, u8"잗"}, + {0xAF, u8"잘"}, + {0xB0, u8"잚"}, + {0xB1, u8"잠"}, + {0xB2, u8"잡"}, + {0xB3, u8"잣"}, + {0xB4, u8"잤"}, + {0xB5, u8"장"}, + {0xB6, u8"잦"}, + {0xB7, u8"재"}, + {0xB8, u8"잭"}, + {0xB9, u8"잰"}, + {0xBA, u8"잴"}, + {0xBB, u8"잼"}, + {0xBC, u8"잽"}, + {0xBD, u8"잿"}, + {0xBE, u8"쟀"}, + {0xBF, u8"쟁"}, + {0xC0, u8"쟈"}, + {0xC1, u8"쟉"}, + {0xC2, u8"쟌"}, + {0xC3, u8"쟎"}, + {0xC4, u8"쟐"}, + {0xC5, u8"쟘"}, + {0xC6, u8"쟝"}, + {0xC7, u8"쟤"}, + {0xC8, u8"쟨"}, + {0xC9, u8"쟬"}, + {0xCA, u8"저"}, + {0xCB, u8"적"}, + {0xCC, u8"전"}, + {0xCD, u8"절"}, + {0xCE, u8"젊"}, + {0xD1, u8"점"}, + {0xD2, u8"접"}, + {0xD3, u8"젓"}, + {0xD4, u8"정"}, + {0xD5, u8"젖"}, + {0xD6, u8"제"}, + {0xD7, u8"젝"}, + {0xD8, u8"젠"}, + {0xD9, u8"젤"}, + {0xDA, u8"젬"}, + {0xDB, u8"젭"}, + {0xDC, u8"젯"}, + {0xDD, u8"젱"}, + {0xDE, u8"져"}, + {0xDF, u8"젼"}, + {0xE0, u8"졀"}, + {0xE1, u8"졈"}, + {0xE2, u8"졉"}, + {0xE3, u8"졌"}, + {0xE4, u8"졍"}, + {0xE5, u8"졔"}, + {0xE6, u8"조"}, + {0xE7, u8"족"}, + {0xE8, u8"존"}, + {0xE9, u8"졸"}, + {0xEA, u8"졺"}, + {0xEB, u8"좀"}, + {0xEC, u8"좁"}, + {0xED, u8"좃"}, + {0xEE, u8"종"}, + {0xEF, u8"좆"}, + {0xF0, u8"좇"}, + {0xF1, u8"좋"}, + {0xF2, u8"좌"}, + {0xF3, u8"좍"}, + {0xF4, u8"좔"}, + {0xF5, u8"좝"}, + {0xF6, u8"좟"}, + {0xF7, u8"좡"}, + {0xF8, u8"좨"}, + {0xF9, u8"좼"}, + {0xFA, u8"좽"}, + {0xFB, u8"죄"}, + {0xFC, u8"죈"}, + {0xFD, u8"죌"}, + {0xFE, u8"죔"}, + {0xFF, u8"죕"} +}; + +static const TextCodePair gen2TextCodesKorean8[] = { + {0x00, u8"죗"}, + {0x01, u8"죙"}, + {0x02, u8"죠"}, + {0x03, u8"죡"}, + {0x04, u8"죤"}, + {0x05, u8"죵"}, + {0x06, u8"주"}, + {0x07, u8"죽"}, + {0x08, u8"준"}, + {0x09, u8"줄"}, + {0x0A, u8"줅"}, + {0x0B, u8"줆"}, + {0x0C, u8"줌"}, + {0x0D, u8"줍"}, + {0x0E, u8"줏"}, + {0x0F, u8"중"}, + {0x10, u8"줘"}, + {0x11, u8"줬"}, + {0x12, u8"줴"}, + {0x13, u8"쥐"}, + {0x14, u8"쥑"}, + {0x15, u8"쥔"}, + {0x16, u8"쥘"}, + {0x17, u8"쥠"}, + {0x18, u8"쥡"}, + {0x19, u8"쥣"}, + {0x1A, u8"쥬"}, + {0x1B, u8"쥰"}, + {0x1C, u8"쥴"}, + {0x1D, u8"쥼"}, + {0x1E, u8"즈"}, + {0x1F, u8"즉"}, + {0x20, u8"즌"}, + {0x21, u8"즐"}, + {0x22, u8"즘"}, + {0x23, u8"즙"}, + {0x24, u8"즛"}, + {0x25, u8"증"}, + {0x26, u8"지"}, + {0x27, u8"직"}, + {0x28, u8"진"}, + {0x29, u8"짇"}, + {0x2A, u8"질"}, + {0x2B, u8"짊"}, + {0x2C, u8"짐"}, + {0x2D, u8"집"}, + {0x2E, u8"짓"}, + {0x30, u8"쬬"}, + {0x31, u8"징"}, + {0x32, u8"짖"}, + {0x33, u8"짙"}, + {0x34, u8"짚"}, + {0x35, u8"짜"}, + {0x36, u8"짝"}, + {0x37, u8"짠"}, + {0x38, u8"짢"}, + {0x39, u8"짤"}, + {0x3A, u8"짧"}, + {0x3B, u8"짬"}, + {0x3C, u8"짭"}, + {0x3D, u8"짯"}, + {0x3E, u8"짰"}, + {0x3F, u8"짱"}, + {0x40, u8"째"}, + {0x41, u8"짹"}, + {0x42, u8"짼"}, + {0x43, u8"쨀"}, + {0x44, u8"쨈"}, + {0x45, u8"쨉"}, + {0x46, u8"쨋"}, + {0x47, u8"쨌"}, + {0x48, u8"쨍"}, + {0x49, u8"쨔"}, + {0x4A, u8"쨘"}, + {0x4B, u8"쨩"}, + {0x4C, u8"쩌"}, + {0x4D, u8"쩍"}, + {0x4E, u8"쩐"}, + {0x4F, u8"쩔"}, + {0x60, u8"쩜"}, + {0x61, u8"쩝"}, + {0x62, u8"쩟"}, + {0x63, u8"쩠"}, + {0x64, u8"쩡"}, + {0x65, u8"쩨"}, + {0x66, u8"쩽"}, + {0x67, u8"쪄"}, + {0x68, u8"쪘"}, + {0x69, u8"쪼"}, + {0x6A, u8"쪽"}, + {0x6B, u8"쫀"}, + {0x6C, u8"쫄"}, + {0x6D, u8"쫌"}, + {0x6E, u8"쫍"}, + {0x6F, u8"쫏"}, + {0x70, u8"쫑"}, + {0x71, u8"쫓"}, + {0x72, u8"쫘"}, + {0x73, u8"쫙"}, + {0x74, u8"쫠"}, + {0x75, u8"쫬"}, + {0x76, u8"쫴"}, + {0x77, u8"쫴"}, + {0x78, u8"쬐"}, + {0x79, u8"쬔"}, + {0x7A, u8"쬘"}, + {0x7B, u8"쬠"}, + {0x7C, u8"쬡"}, + {0x7D, u8"쭁"}, + {0x7E, u8"쭈"}, + {0x7F, u8"쭉"}, + {0x80, u8"쭌"}, + {0x81, u8"쭐"}, + {0x82, u8"쭘"}, + {0x83, u8"쭙"}, + {0x84, u8"쭝"}, + {0x85, u8"쭤"}, + {0x86, u8"쭸"}, + {0x87, u8"쭹"}, + {0x88, u8"쮜"}, + {0x89, u8"쮸"}, + {0x8A, u8"쯔"}, + {0x8B, u8"쯤"}, + {0x8C, u8"쯧"}, + {0x8D, u8"쯩"}, + {0x8E, u8"찌"}, + {0x8F, u8"찍"}, + {0x90, u8"찐"}, + {0x91, u8"찔"}, + {0x92, u8"찜"}, + {0x93, u8"찝"}, + {0x94, u8"찡"}, + {0x95, u8"찢"}, + {0x96, u8"찧"}, + {0x97, u8"차"}, + {0x98, u8"착"}, + {0x99, u8"찬"}, + {0x9A, u8"찮"}, + {0x9B, u8"찰"}, + {0x9C, u8"참"}, + {0x9D, u8"찹"}, + {0x9E, u8"찻"}, + {0xA1, u8"찼"}, + {0xA2, u8"창"}, + {0xA3, u8"찾"}, + {0xA4, u8"채"}, + {0xA5, u8"책"}, + {0xA6, u8"챈"}, + {0xA7, u8"챌"}, + {0xA8, u8"챔"}, + {0xA9, u8"챕"}, + {0xAA, u8"챗"}, + {0xAB, u8"챘"}, + {0xAC, u8"챙"}, + {0xAD, u8"챠"}, + {0xAE, u8"챤"}, + {0xAF, u8"챦"}, + {0xB0, u8"챨"}, + {0xB1, u8"챰"}, + {0xB2, u8"챵"}, + {0xB3, u8"처"}, + {0xB4, u8"척"}, + {0xB5, u8"천"}, + {0xB6, u8"철"}, + {0xB7, u8"첨"}, + {0xB8, u8"첩"}, + {0xB9, u8"첫"}, + {0xBA, u8"첬"}, + {0xBB, u8"청"}, + {0xBC, u8"체"}, + {0xBD, u8"첵"}, + {0xBE, u8"첸"}, + {0xBF, u8"첼"}, + {0xC0, u8"쳄"}, + {0xC1, u8"쳅"}, + {0xC2, u8"쳇"}, + {0xC3, u8"쳉"}, + {0xC4, u8"쳐"}, + {0xC5, u8"쳔"}, + {0xC6, u8"쳤"}, + {0xC7, u8"쳬"}, + {0xC8, u8"쳰"}, + {0xC9, u8"촁"}, + {0xCA, u8"초"}, + {0xCB, u8"촉"}, + {0xCC, u8"촌"}, + {0xCD, u8"촐"}, + {0xCE, u8"촘"}, + {0xCF, u8"촙"}, + {0xD0, u8"촛"}, + {0xD1, u8"총"}, + {0xD2, u8"촤"}, + {0xD3, u8"촨"}, + {0xD4, u8"촬"}, + {0xD5, u8"촹"}, + {0xD6, u8"최"}, + {0xD7, u8"쵠"}, + {0xD8, u8"쵤"}, + {0xD9, u8"쵬"}, + {0xDA, u8"쵭"}, + {0xDB, u8"쵯"}, + {0xDC, u8"쵱"}, + {0xDD, u8"쵸"}, + {0xDE, u8"춈"}, + {0xDF, u8"추"}, + {0xE0, u8"축"}, + {0xE1, u8"춘"}, + {0xE2, u8"출"}, + {0xE3, u8"춤"}, + {0xE4, u8"춥"}, + {0xE5, u8"춧"}, + {0xE6, u8"충"}, + {0xE7, u8"춰"}, + {0xE8, u8"췄"}, + {0xE9, u8"췌"}, + {0xEA, u8"췐"}, + {0xEB, u8"취"}, + {0xEC, u8"췬"}, + {0xED, u8"췰"}, + {0xEE, u8"췸"}, + {0xEF, u8"췹"}, + {0xF0, u8"췻"}, + {0xF1, u8"췽"}, + {0xF2, u8"츄"}, + {0xF3, u8"츈"}, + {0xF4, u8"츌"}, + {0xF5, u8"츔"}, + {0xF6, u8"츙"}, + {0xF7, u8"츠"}, + {0xF8, u8"측"}, + {0xF9, u8"츤"}, + {0xFA, u8"츨"}, + {0xFB, u8"츰"}, + {0xFC, u8"츱"}, + {0xFD, u8"츳"}, + {0xFE, u8"층"} +}; + +static const TextCodePair gen2TextCodesKorean9[] = { + {0x01, u8"치"}, + {0x02, u8"칙"}, + {0x03, u8"친"}, + {0x04, u8"칟"}, + {0x05, u8"칠"}, + {0x06, u8"칡"}, + {0x07, u8"침"}, + {0x08, u8"칩"}, + {0x09, u8"칫"}, + {0x0A, u8"칭"}, + {0x0B, u8"카"}, + {0x0C, u8"칵"}, + {0x0D, u8"칸"}, + {0x0E, u8"칼"}, + {0x0F, u8"캄"}, + {0x10, u8"캅"}, + {0x11, u8"캇"}, + {0x12, u8"캉"}, + {0x13, u8"캐"}, + {0x14, u8"캑"}, + {0x15, u8"캔"}, + {0x16, u8"캘"}, + {0x17, u8"캠"}, + {0x18, u8"캡"}, + {0x19, u8"캣"}, + {0x1A, u8"캤"}, + {0x1B, u8"캥"}, + {0x1C, u8"캬"}, + {0x1D, u8"캭"}, + {0x1E, u8"컁"}, + {0x1F, u8"커"}, + {0x20, u8"컥"}, + {0x21, u8"컨"}, + {0x22, u8"컫"}, + {0x23, u8"컬"}, + {0x24, u8"컴"}, + {0x25, u8"컵"}, + {0x26, u8"컷"}, + {0x27, u8"컸"}, + {0x28, u8"컹"}, + {0x29, u8"케"}, + {0x2A, u8"켁"}, + {0x2B, u8"켄"}, + {0x2C, u8"켈"}, + {0x2D, u8"켐"}, + {0x2E, u8"켑"}, + {0x2F, u8"켓"}, + {0x30, u8"켕"}, + {0x31, u8"켜"}, + {0x32, u8"켠"}, + {0x33, u8"켤"}, + {0x34, u8"켬"}, + {0x35, u8"켭"}, + {0x36, u8"켯"}, + {0x37, u8"켰"}, + {0x38, u8"켱"}, + {0x39, u8"켸"}, + {0x3A, u8"코"}, + {0x3B, u8"콕"}, + {0x3C, u8"콘"}, + {0x3D, u8"콜"}, + {0x3E, u8"콤"}, + {0x3F, u8"콥"}, + {0x40, u8"콧"}, + {0x41, u8"콩"}, + {0x42, u8"콰"}, + {0x43, u8"콱"}, + {0x44, u8"콴"}, + {0x45, u8"콸"}, + {0x46, u8"쾀"}, + {0x47, u8"쾅"}, + {0x48, u8"쾌"}, + {0x49, u8"쾡"}, + {0x4A, u8"쾨"}, + {0x4B, u8"쾰"}, + {0x4C, u8"쿄"}, + {0x4D, u8"쿠"}, + {0x4E, u8"쿡"}, + {0x4F, u8"쿤"}, + {0x60, u8"쿨"}, + {0x61, u8"쿰"}, + {0x62, u8"쿱"}, + {0x63, u8"쿳"}, + {0x64, u8"쿵"}, + {0x65, u8"쿼"}, + {0x66, u8"퀀"}, + {0x67, u8"퀄"}, + {0x68, u8"퀑"}, + {0x69, u8"퀘"}, + {0x6A, u8"퀭"}, + {0x6B, u8"퀴"}, + {0x6C, u8"퀵"}, + {0x6D, u8"퀸"}, + {0x6E, u8"퀼"}, + {0x71, u8"큄"}, + {0x72, u8"큅"}, + {0x73, u8"큇"}, + {0x74, u8"큉"}, + {0x75, u8"큐"}, + {0x76, u8"큔"}, + {0x77, u8"큘"}, + {0x78, u8"큠"}, + {0x79, u8"크"}, + {0x7A, u8"큭"}, + {0x7B, u8"큰"}, + {0x7C, u8"클"}, + {0x7D, u8"큼"}, + {0x7E, u8"큽"}, + {0x7F, u8"킁"}, + {0x80, u8"키"}, + {0x81, u8"킥"}, + {0x82, u8"킨"}, + {0x83, u8"킬"}, + {0x84, u8"킴"}, + {0x85, u8"킵"}, + {0x86, u8"킷"}, + {0x87, u8"킹"}, + {0x88, u8"타"}, + {0x89, u8"탁"}, + {0x8A, u8"탄"}, + {0x8B, u8"탈"}, + {0x8C, u8"탉"}, + {0x8D, u8"탐"}, + {0x8E, u8"탑"}, + {0x8F, u8"탓"}, + {0x90, u8"탔"}, + {0x91, u8"탕"}, + {0x92, u8"태"}, + {0x93, u8"택"}, + {0x94, u8"탠"}, + {0x95, u8"탤"}, + {0x96, u8"탬"}, + {0x97, u8"탭"}, + {0x98, u8"탯"}, + {0x99, u8"탰"}, + {0x9A, u8"탱"}, + {0x9B, u8"탸"}, + {0x9C, u8"턍"}, + {0x9D, u8"터"}, + {0x9E, u8"턱"}, + {0x9F, u8"턴"}, + {0xA0, u8"털"}, + {0xA1, u8"턺"}, + {0xA2, u8"텀"}, + {0xA3, u8"텁"}, + {0xA4, u8"텃"}, + {0xA5, u8"텄"}, + {0xA6, u8"텅"}, + {0xA7, u8"테"}, + {0xA8, u8"텍"}, + {0xA9, u8"텐"}, + {0xAA, u8"텔"}, + {0xAB, u8"템"}, + {0xAC, u8"텝"}, + {0xAD, u8"텟"}, + {0xAE, u8"텡"}, + {0xAF, u8"텨"}, + {0xB0, u8"텬"}, + {0xB1, u8"텼"}, + {0xB2, u8"톄"}, + {0xB3, u8"톈"}, + {0xB4, u8"토"}, + {0xB5, u8"톡"}, + {0xB6, u8"톤"}, + {0xB7, u8"톨"}, + {0xB8, u8"톰"}, + {0xB9, u8"톱"}, + {0xBA, u8"톳"}, + {0xBB, u8"통"}, + {0xBC, u8"톺"}, + {0xBD, u8"톼"}, + {0xBE, u8"퇀"}, + {0xBF, u8"퇘"}, + {0xC0, u8"퇴"}, + {0xC1, u8"퇸"}, + {0xC2, u8"툇"}, + {0xC3, u8"툉"}, + {0xC4, u8"툐"}, + {0xC5, u8"투"}, + {0xC6, u8"툭"}, + {0xC7, u8"툰"}, + {0xC8, u8"툴"}, + {0xC9, u8"툼"}, + {0xCA, u8"툽"}, + {0xCB, u8"툿"}, + {0xCC, u8"퉁"}, + {0xCD, u8"퉈"}, + {0xCE, u8"퉜"}, + {0xD1, u8"퉤"}, + {0xD2, u8"튀"}, + {0xD3, u8"튁"}, + {0xD4, u8"튄"}, + {0xD5, u8"튈"}, + {0xD6, u8"튐"}, + {0xD7, u8"튑"}, + {0xD8, u8"튕"}, + {0xD9, u8"튜"}, + {0xDA, u8"튠"}, + {0xDB, u8"튤"}, + {0xDC, u8"튬"}, + {0xDD, u8"튱"}, + {0xDE, u8"트"}, + {0xDF, u8"특"}, + {0xE0, u8"튼"}, + {0xE1, u8"튿"}, + {0xE2, u8"틀"}, + {0xE3, u8"틂"}, + {0xE4, u8"틈"}, + {0xE5, u8"틉"}, + {0xE6, u8"틋"}, + {0xE7, u8"틔"}, + {0xE8, u8"틘"}, + {0xE9, u8"틜"}, + {0xEA, u8"틤"}, + {0xEB, u8"틥"}, + {0xEC, u8"티"}, + {0xED, u8"틱"}, + {0xEE, u8"틴"}, + {0xEF, u8"틸"}, + {0xF0, u8"팀"}, + {0xF1, u8"팁"}, + {0xF2, u8"팃"}, + {0xF3, u8"팅"}, + {0xF4, u8"파"}, + {0xF5, u8"팍"}, + {0xF6, u8"팎"}, + {0xF7, u8"판"}, + {0xF8, u8"팔"}, + {0xF9, u8"팖"}, + {0xFA, u8"팜"}, + {0xFB, u8"팝"}, + {0xFC, u8"팟"}, + {0xFD, u8"팠"}, + {0xFE, u8"팡"}, + {0xFF, u8"팥"} +}; + +static const TextCodePair gen2TextCodesKoreanA[] = { + {0x00, u8"패"}, + {0x01, u8"팩"}, + {0x02, u8"팬"}, + {0x03, u8"팰"}, + {0x04, u8"팸"}, + {0x05, u8"팹"}, + {0x06, u8"팻"}, + {0x07, u8"팼"}, + {0x08, u8"팽"}, + {0x09, u8"퍄"}, + {0x0A, u8"퍅"}, + {0x0B, u8"퍼"}, + {0x0C, u8"퍽"}, + {0x0D, u8"펀"}, + {0x0E, u8"펄"}, + {0x0F, u8"펌"}, + {0x10, u8"펍"}, + {0x11, u8"펏"}, + {0x12, u8"펐"}, + {0x13, u8"펑"}, + {0x14, u8"페"}, + {0x15, u8"펙"}, + {0x16, u8"펜"}, + {0x17, u8"펠"}, + {0x18, u8"펨"}, + {0x19, u8"펩"}, + {0x1A, u8"펫"}, + {0x1B, u8"펭"}, + {0x1C, u8"펴"}, + {0x1D, u8"편"}, + {0x1E, u8"펼"}, + {0x1F, u8"폄"}, + {0x20, u8"폅"}, + {0x21, u8"폈"}, + {0x22, u8"평"}, + {0x23, u8"폐"}, + {0x24, u8"폘"}, + {0x25, u8"폡"}, + {0x26, u8"폣"}, + {0x27, u8"포"}, + {0x28, u8"폭"}, + {0x29, u8"폰"}, + {0x2A, u8"폴"}, + {0x2B, u8"폼"}, + {0x2C, u8"폽"}, + {0x2D, u8"폿"}, + {0x2E, u8"퐁"}, + {0x31, u8"퐈"}, + {0x32, u8"퐝"}, + {0x33, u8"푀"}, + {0x34, u8"푄"}, + {0x35, u8"표"}, + {0x36, u8"푠"}, + {0x37, u8"푤"}, + {0x38, u8"푭"}, + {0x39, u8"푯"}, + {0x3A, u8"푸"}, + {0x3B, u8"푹"}, + {0x3C, u8"푼"}, + {0x3D, u8"푿"}, + {0x3E, u8"풀"}, + {0x3F, u8"풂"}, + {0x40, u8"품"}, + {0x41, u8"풉"}, + {0x42, u8"풋"}, + {0x43, u8"풍"}, + {0x44, u8"풔"}, + {0x45, u8"풩"}, + {0x46, u8"퓌"}, + {0x47, u8"퓐"}, + {0x48, u8"퓔"}, + {0x49, u8"퓜"}, + {0x4A, u8"퓟"}, + {0x4B, u8"퓨"}, + {0x4C, u8"퓬"}, + {0x4D, u8"퓰"}, + {0x4E, u8"퓸"}, + {0x4F, u8"퓻"}, + {0x60, u8"퓽"}, + {0x61, u8"프"}, + {0x62, u8"픈"}, + {0x63, u8"플"}, + {0x64, u8"픔"}, + {0x65, u8"픕"}, + {0x66, u8"픗"}, + {0x67, u8"피"}, + {0x68, u8"픽"}, + {0x69, u8"핀"}, + {0x6A, u8"필"}, + {0x6B, u8"핌"}, + {0x6C, u8"핍"}, + {0x6D, u8"핏"}, + {0x6E, u8"핑"}, + {0x6F, u8"하"}, + {0x70, u8"학"}, + {0x71, u8"한"}, + {0x72, u8"할"}, + {0x73, u8"핥"}, + {0x74, u8"함"}, + {0x75, u8"합"}, + {0x76, u8"핫"}, + {0x77, u8"항"}, + {0x78, u8"해"}, + {0x79, u8"핵"}, + {0x7A, u8"핸"}, + {0x7B, u8"핼"}, + {0x7C, u8"햄"}, + {0x7D, u8"햅"}, + {0x7E, u8"햇"}, + {0x7F, u8"했"}, + {0x80, u8"행"}, + {0x81, u8"햐"}, + {0x82, u8"향"}, + {0x83, u8"허"}, + {0x84, u8"헉"}, + {0x85, u8"헌"}, + {0x86, u8"헐"}, + {0x87, u8"헒"}, + {0x88, u8"험"}, + {0x89, u8"헙"}, + {0x8A, u8"헛"}, + {0x8B, u8"헝"}, + {0x8C, u8"헤"}, + {0x8D, u8"헥"}, + {0x8E, u8"헨"}, + {0x8F, u8"헬"}, + {0x90, u8"헴"}, + {0x91, u8"헵"}, + {0x92, u8"헷"}, + {0x93, u8"헹"}, + {0x94, u8"혀"}, + {0x95, u8"혁"}, + {0x96, u8"현"}, + {0x97, u8"혈"}, + {0x98, u8"혐"}, + {0x99, u8"협"}, + {0x9A, u8"혓"}, + {0x9B, u8"혔"}, + {0x9C, u8"형"}, + {0x9D, u8"혜"}, + {0x9E, u8"혠"}, + {0xA1, u8"혤"}, + {0xA2, u8"혭"}, + {0xA3, u8"호"}, + {0xA4, u8"혹"}, + {0xA5, u8"혼"}, + {0xA6, u8"홀"}, + {0xA7, u8"홅"}, + {0xA8, u8"홈"}, + {0xA9, u8"홉"}, + {0xAA, u8"홋"}, + {0xAB, u8"홍"}, + {0xAC, u8"홑"}, + {0xAD, u8"화"}, + {0xAE, u8"확"}, + {0xAF, u8"환"}, + {0xB0, u8"활"}, + {0xB1, u8"홧"}, + {0xB2, u8"황"}, + {0xB3, u8"홰"}, + {0xB4, u8"홱"}, + {0xB5, u8"홴"}, + {0xB6, u8"횃"}, + {0xB7, u8"횅"}, + {0xB8, u8"회"}, + {0xB9, u8"획"}, + {0xBA, u8"횐"}, + {0xBB, u8"횔"}, + {0xBC, u8"횝"}, + {0xBD, u8"횟"}, + {0xBE, u8"횡"}, + {0xBF, u8"효"}, + {0xC0, u8"횬"}, + {0xC1, u8"횰"}, + {0xC2, u8"횹"}, + {0xC3, u8"횻"}, + {0xC4, u8"후"}, + {0xC5, u8"훅"}, + {0xC6, u8"훈"}, + {0xC7, u8"훌"}, + {0xC8, u8"훑"}, + {0xC9, u8"훔"}, + {0xCA, u8"훗"}, + {0xCB, u8"훙"}, + {0xCC, u8"훠"}, + {0xCD, u8"훤"}, + {0xCE, u8"훨"}, + {0xCF, u8"훰"}, + {0xD0, u8"훵"}, + {0xD1, u8"훼"}, + {0xD2, u8"훽"}, + {0xD3, u8"휀"}, + {0xD4, u8"휄"}, + {0xD5, u8"휑"}, + {0xD6, u8"휘"}, + {0xD7, u8"휙"}, + {0xD8, u8"휜"}, + {0xD9, u8"휠"}, + {0xDA, u8"휨"}, + {0xDB, u8"휩"}, + {0xDC, u8"휫"}, + {0xDD, u8"휭"}, + {0xDE, u8"휴"}, + {0xDF, u8"휵"}, + {0xE0, u8"휸"}, + {0xE1, u8"휼"}, + {0xE2, u8"흄"}, + {0xE3, u8"흇"}, + {0xE4, u8"흉"}, + {0xE5, u8"흐"}, + {0xE6, u8"흑"}, + {0xE7, u8"흔"}, + {0xE8, u8"흖"}, + {0xE9, u8"흗"}, + {0xEA, u8"흘"}, + {0xEB, u8"흙"}, + {0xEC, u8"흠"}, + {0xED, u8"흡"}, + {0xEE, u8"흣"}, + {0xEF, u8"흥"}, + {0xF0, u8"흩"}, + {0xF1, u8"희"}, + {0xF2, u8"흰"}, + {0xF3, u8"흴"}, + {0xF4, u8"흼"}, + {0xF5, u8"흽"}, + {0xF6, u8"힁"}, + {0xF7, u8"히"}, + {0xF8, u8"힉"}, + {0xF9, u8"힌"}, + {0xFA, u8"힐"}, + {0xFB, u8"힘"}, + {0xFC, u8"힙"}, + {0xFD, u8"힛"}, + {0xFE, u8"힝"} +}; + +static const TextCodePair gen2TextCodesKoreanB[] = { + {0x00, u8"ㄱ"}, + {0x01, u8"ㄴ"}, + {0x02, u8"ㄷ"}, + {0x03, u8"ㄹ"}, + {0x04, u8"ㅁ"}, + {0x05, u8"ㅂ"}, + {0x06, u8"ㅅ"}, + {0x07, u8"ㅇ"}, + {0x08, u8"ㅈ"}, + {0x09, u8"ㅊ"}, + {0x0A, u8"ㅋ"}, + {0x0B, u8"ㅌ"}, + {0x0C, u8"ㅍ"}, + {0x0D, u8"ㅎ"}, + {0x0E, u8"ㄲ"}, + {0x0F, u8"ㄸ"}, + {0x10, u8"ㅃ"}, + {0x11, u8"ㅆ"}, + {0x12, u8"ㅉ"}, + {0x20, u8"ㅏ"}, + {0x21, u8"ㅑ"}, + {0x22, u8"ㅓ"}, + {0x23, u8"ㅕ"}, + {0x24, u8"ㅗ"}, + {0x25, u8"ㅛ"}, + {0x26, u8"ㅜ"}, + {0x27, u8"ㅠ"}, + {0x28, u8"ㅡ"}, + {0x29, u8"ㅣ"}, + {0x2A, u8"ㅐ"}, + {0x2B, u8"ㅒ"}, + {0x2C, u8"ㅔ"}, + {0x2D, u8"ㅖ"}, + {0x2E, u8"ㅘ"}, + {0x2F, u8"ㅙ"}, + {0x30, u8"ㅚ"}, + {0x31, u8"ㅝ"}, + {0x32, u8"ㅞ"}, + {0x33, u8"ㅟ"}, + {0x34, u8"ㅢ"}, + {0x3E, u8"_"}, + {0x3F, u8"-"}, + {0x60, u8"「"}, + {0x61, u8"」"}, + {0x62, u8"『"}, + {0x63, u8"』"}, + {0x64, u8"("}, + {0x65, u8")"}, + {0x66, u8"!"}, + {0x67, u8"?"}, + {0x68, u8"-"}, + {0x69, u8"~"}, + {0x6A, u8"…"}, + {0x6B, u8","}, + {0x6C, u8"."}, + {0xF0, u8"0"}, + {0xF1, u8"1"}, + {0xF2, u8"2"}, + {0xF3, u8"3"}, + {0xF4, u8"4"}, + {0xF5, u8"5"}, + {0xF6, u8"6"}, + {0xF7, u8"7"}, + {0xF8, u8"8"}, + {0xF9, u8"9"} +}; + +static uint16_t decodeKoreanPokeText(const uint8_t *inputBuffer, uint16_t inputBufferLength, char *outputBuffer, uint16_t outputBufferLength) +{ + uint16_t result = 0; + if(inputBufferLength > outputBufferLength) + { + return result; + } + + const uint8_t* curInput = (const uint8_t*)inputBuffer; + const uint8_t* endInput = curInput + inputBufferLength; + uint8_t* curOutput = (uint8_t*)outputBuffer; + uint16_t remainingBufferSpace = outputBufferLength; + const struct TextCodePair* textCodes; + uint16_t numTextCodes; + + const TextCodePair* charSets[] = { + gen2TextCodesKorean0, + gen2TextCodesKorean1, + gen2TextCodesKorean2, + gen2TextCodesKorean3, + gen2TextCodesKorean4, + gen2TextCodesKorean5, + gen2TextCodesKorean6, + gen2TextCodesKorean7, + gen2TextCodesKorean8, + gen2TextCodesKorean9, + gen2TextCodesKoreanA, + gen2TextCodesKoreanB + }; + + const uint16_t charSetNumChars[] = { + sizeof(gen2TextCodesKorean0) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean1) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean2) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean3) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean4) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean5) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean6) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean7) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean8) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean9) / sizeof(TextCodePair), + sizeof(gen2TextCodesKoreanA) / sizeof(TextCodePair), + sizeof(gen2TextCodesKoreanB) / sizeof(TextCodePair) + }; + + while(curInput < endInput) + { + if((*curInput) == 0x50) + { + break; + } + else if((*curInput) < 0xC) + { + // the current byte refers to the prefix of one of those additional Korean + // character sets + textCodes = charSets[(*curInput)]; + numTextCodes = charSetNumChars[(*curInput)]; + // The next input byte value refers to the offset in the character set + ++curInput; + } + else + { + // allright, the current byte instead refers to the actual offset + // in the main character set. + textCodes = gen2TextCodesMain; + numTextCodes = sizeof(gen2TextCodesMain) / sizeof(TextCodePair); + } + + const char* outputText = findCharsByTextCode(textCodes, numTextCodes, (*curInput)); + if(!outputText) + { + outputText = " "; + } + const size_t outputTextLength = strlen(outputText); + + if(remainingBufferSpace < outputTextLength + 1) + { + // not enough space left: truncated + memcpy(curOutput, outputText, remainingBufferSpace - 1); + outputBuffer[outputBufferLength -1] = '\0'; + return outputBufferLength - 1; + } + + memcpy(curOutput, outputText, outputTextLength); + curOutput += outputTextLength; + result += outputTextLength; + remainingBufferSpace -= outputTextLength; + + ++curInput; + } + // terminator byte + (*curOutput) = '\0'; + return result; +} + +/** + * This function determines whether the given byte is the first byte of a UTF8 character. + * It does this by checking the first 2 bits for the 10 sequence. + * + * The first byte will NEVER have the first 2 bits set to 10, regardless of the number of bytes of the character. + * But all subsequent bytes DO. + * + * https://www.quora.com/In-Unicode-UTF-8-how-do-we-know-the-start-and-the-end-of-a-character-since-characters-vary-in-length + * https://en.wikipedia.org/wiki/UTF-8 + */ +static bool isStartOfUTF8Character(uint8_t byte) +{ + return (byte & 0xC0) != 0x80; +} + +/** + * This function encodes a Korean text into the game-specific format using the available Korean character sets + * https://bulbapedia.bulbagarden.net/wiki/Korean_character_encoding_(Generation_II) + */ +static uint16_t encodeKoreanPokeText(const char *inputBuffer, uint16_t inputBufferLength, uint8_t *outputBuffer, uint16_t outputBufferLength, uint8_t terminator) +{ + // a UTF8 encodes a character in 1-4 bytes. We'll reserve another one for the null terminator + char singleChar[5]; + uint8_t byteCount = 0; + const char* inCur = inputBuffer; + const char* const inEnd = inputBuffer + inputBufferLength; + uint8_t* outputCur = outputBuffer; + uint8_t* const outputBufferEnd = outputBuffer + outputBufferLength; + uint16_t needleLength; + uint8_t code; + uint8_t i; + bool foundCode; + + // to avoid repeating the findTextcodeByString pattern below in an if-else structure + // we'll just define a list of charsets and go through this list. + const TextCodePair* charSets[] = { + gen2TextCodesMain, + gen2TextCodesKorean0, + gen2TextCodesKorean1, + gen2TextCodesKorean2, + gen2TextCodesKorean3, + gen2TextCodesKorean4, + gen2TextCodesKorean5, + gen2TextCodesKorean6, + gen2TextCodesKorean7, + gen2TextCodesKorean8, + gen2TextCodesKorean9, + gen2TextCodesKoreanA, + gen2TextCodesKoreanB + }; + + const uint16_t charSetNumChars[] = { + sizeof(gen2TextCodesMain) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean0) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean1) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean2) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean3) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean4) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean5) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean6) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean7) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean8) / sizeof(TextCodePair), + sizeof(gen2TextCodesKorean9) / sizeof(TextCodePair), + sizeof(gen2TextCodesKoreanA) / sizeof(TextCodePair), + sizeof(gen2TextCodesKoreanB) / sizeof(TextCodePair) + }; + + // Yes, the = in the while condition is not a bug. We need it to deal with the last character + // (I can't rely on the detection of a next character, but I don't want to repeat code) + // it does mean that I'll have to be careful to deal with this use case (inCur == inEnd) properly. I should make sure not to go out of bounds + while(inCur <= inEnd) + { + // There are 3 cases to deal with here: + // - the current byte is the first byte of the string + // - the current byte is the first byte of the next utf8 character (meaning we fully copied all bytes of the previous one) + // - we're actually at the end of the inputBuffer. But we still need to process the last character. + // + // In 2nd and 3rd case, we can start looking up the textcode of the previous char (contained in singleChar) and add it to the outputBuffer + if(((inCur == inEnd) || isStartOfUTF8Character(*inCur)) && byteCount) + { + // We copied a complete utf8 character into singleChar + // now we can try finding it + singleChar[byteCount] = '\0'; + foundCode = false; + + // try to find the character in all the relevant charsets. + for(i = 0; i < sizeof(charSetNumChars) / sizeof(uint16_t); ++i) + { + if(findTextcodeByString(charSets[i], charSetNumChars[i], singleChar, byteCount, code, needleLength)) + { + // if i > 0, then it means the character was not found in the main charset. + // Therefore we need to insert the index - 1 into the output. The -1 is needed because + // the to-be-inserted index byte only refers to the index of gen2TextCodesKoreanX charsets. So gen2TextCodesMain should not be included. + if(i) + { + (*outputCur) = i - 1; + ++outputCur; + } + (*outputCur) = code; + ++outputCur; + foundCode = true; + break; + } + } + + byteCount = 0; + + if(!foundCode) + { + // many empty spots in the korean character maps end up outputting a space character. + // however, for most dialog, 0x7F is used to represent a space. + // And for player input, 0x0B 0xFF is used to represent a space. + // in our code, we assume the latter use case if we can't find a textCode + // source: https://bulbapedia.bulbagarden.net/wiki/Korean_character_encoding_(Generation_II) + (*outputCur) = 0x0B; + ++outputCur; + (*outputCur) = 0xFF; + ++outputCur; + } + + if(inCur == inEnd) + { + // allright, we dealt with the last character in the input string. There are no more characters, so + // we need to bail out of the while loop before we end up dereferencing inCur while being out of bounds + break; + } + + // We stop trying to process input if we don't have any space left in our outputBuffer + // This is not entirely foolproof because we aren't checking this for every to-be-inserted byte. + // so in case the code above ends up inserting 2 bytes while we only have room for 1, we'll go out of bounds. + // For security reasons this might be problematic. However, I don't expect us to use this function for processing + // actual user input. Instead I think it might just be used to look up some strings we expect in the rom. + // So I don't think we need to care much about it. + // TODO: revisit this if it turns out to be a problem. + if(outputCur >= outputBufferEnd) + { + break; + } + } + singleChar[byteCount] = *inCur; + ++inCur; + ++byteCount; + } + // if there's room for a terminator, add it. + if(outputCur < outputBufferEnd) + { + (*outputCur) = terminator; + // for consistency, we need to include the terminator in the byte count returned + ++outputCur; + } + return static_cast(outputCur - outputBuffer); +} + +Gen2ItemList::Gen2ItemList(ISaveManager &saveManager, Gen2ItemListType type, Gen2GameType gameType, Gen2LocalizationLanguage localization) : saveManager_(saveManager) , type_(type) - , isCrystal_(isCrystal) + , gameType_(gameType) + , localization_(localization) { } @@ -351,19 +3371,23 @@ bool Gen2ItemList::remove(uint8_t itemId) bool Gen2ItemList::seekToBasePos() { uint32_t offset; + const Gen2LocalizationSRAMOffsets& sramOffsets = gen2_getSRAMOffsets(gameType_, localization_); switch (type_) { + case GEN2_ITEMLISTTYPE_TMHM: + offset = sramOffsets.itemPocketTMHM; + break; case GEN2_ITEMLISTTYPE_ITEMPOCKET: - offset = (isCrystal_) ? 0x2420 : 0x241F; + offset = sramOffsets.itemPocketItem; break; case GEN2_ITEMLISTTYPE_KEYITEMPOCKET: - offset = (isCrystal_) ? 0x244A : 0x2449; + offset = sramOffsets.itemPocketKey; break; case GEN2_ITEMLISTTYPE_BALLPOCKET: - offset = (isCrystal_) ? 0x2465 : 0x2464; + offset = sramOffsets.itemPocketBall; break; case GEN2_ITEMLISTTYPE_PC: - offset = (isCrystal_) ? 0x247F : 0x247E; + offset = sramOffsets.itemPocketPC; break; default: return false; @@ -409,6 +3433,52 @@ Gen2GameType gen2_determineGameType(const GameboyCartridgeHeader& cartridgeHeade return result; } +Gen2LocalizationLanguage gen2_determineGameLanguage(IRomReader& romReader, Gen2GameType gameType) +{ + // The pokemon index-to-pokedex-number map has a unique rom offset in each of the game localizations. + // It also should have the exact same data in all gen 1 games and all of their localizations. + // Therefore we can use a fingerprint byte pattern to check these locations to figure out which localization we have. + uint8_t buffer[sizeof(gen2_statsFingerPrint)]; + + for(uint8_t i=0; i < static_cast(Gen2LocalizationLanguage::MAX); ++i) + { + const Gen2LocalizationRomOffsets& romOffsetList = gen2_getRomOffsets(gameType, (Gen2LocalizationLanguage)i); + romReader.seek(romOffsetList.stats); + romReader.read(buffer, sizeof(gen2_statsFingerPrint)); + if(memcmp(buffer, gen2_statsFingerPrint, sizeof(gen2_statsFingerPrint)) == 0) + { + // stats fingerprint match at the current languages' stats offset. + + // For pokémon gold & pokémon silver, however, the spanish and italian localizations use the exact same stats rom offset. + // The only difference we can really check for -given the rom offsets we possess right now- is the names offset. + // that's the only one that is unique for these + // both of these games use the original English names though. So it's just a matter of the offsets being different + if(gameType != Gen2GameType::CRYSTAL && (i == (uint8_t)Gen2LocalizationLanguage::ITALIAN || i == (uint8_t)Gen2LocalizationLanguage::SPANISH)) + { + uint8_t nameBuffer[0xB]; + const char* firstPokemon = "BULBASAUR"; + // both languages are luckily also using our main character set. So we don't have to worry about that + const uint16_t nameLength = gen2_encodePokeText(firstPokemon, strlen(firstPokemon), nameBuffer, sizeof(nameBuffer), 0x50); + + romReader.seek(romOffsetList.names); + romReader.read(buffer, nameLength); + + const int ret = memcmp(buffer, nameBuffer, nameLength - 1); + if(ret != 0) + { + // no match at the current offset. Skip to the next language + continue; + } + // if we get here, we have a full match. So we know which language we're dealing with now! + // Therefore we will end up at the return statement below + } + return (Gen2LocalizationLanguage)i; + } + } + + return Gen2LocalizationLanguage::MAX; +} + void gen2_recalculatePokeStats(Gen2GameReader &reader, Gen2TrainerPokemon &poke) { Gen2PokeStats stats; @@ -422,16 +3492,47 @@ void gen2_recalculatePokeStats(Gen2GameReader &reader, Gen2TrainerPokemon &poke) poke.special_def = calculatePokeStat(PokeStat::SPECIAL_DEF, stats.base_special_defense, getStatIV(PokeStat::SPECIAL_DEF, poke.iv_data), poke.special_effort_value, poke.level); } -uint16_t gen2_decodePokeText(const uint8_t *inputBuffer, uint16_t inputBufferLength, char *outputBuffer, uint16_t outputBufferLength) +uint16_t gen2_decodePokeText(const uint8_t *inputBuffer, uint16_t inputBufferLength, char *outputBuffer, uint16_t outputBufferLength, Gen2LocalizationLanguage language) { - const uint16_t numEntries = sizeof(gen2TextCodes) / sizeof(struct TextCodePair); - return decodeText(gen2TextCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength); + const TextCodePair* textCodes; + uint16_t numEntries; + + switch(language) + { + case Gen2LocalizationLanguage::KOREAN: + return decodeKoreanPokeText(inputBuffer, inputBufferLength, outputBuffer, outputBufferLength); + case Gen2LocalizationLanguage::JAPANESE: + textCodes = gen2TextCodesJpn; + numEntries = sizeof(gen2TextCodesJpn) / sizeof(struct TextCodePair); + break; + default: + textCodes = gen2TextCodesMain; + numEntries = sizeof(gen2TextCodesMain) / sizeof(struct TextCodePair); + } + + return decodeText(textCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength); } -uint16_t gen2_encodePokeText(const char *inputBuffer, uint16_t inputBufferLength, uint8_t *outputBuffer, uint16_t outputBufferLength, uint8_t terminator) +uint16_t gen2_encodePokeText(const char *inputBuffer, uint16_t inputBufferLength, uint8_t *outputBuffer, uint16_t outputBufferLength, uint8_t terminator, Gen2LocalizationLanguage language) { - const uint16_t numEntries = sizeof(gen2TextCodes) / sizeof(struct TextCodePair); - return encodeText(gen2TextCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, terminator); + const TextCodePair* textCodes; + uint16_t numEntries; + + switch(language) + { + case Gen2LocalizationLanguage::KOREAN: + return encodeKoreanPokeText(inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, terminator); + case Gen2LocalizationLanguage::JAPANESE: + textCodes = gen2TextCodesJpn; + numEntries = sizeof(gen2TextCodesJpn) / sizeof(struct TextCodePair); + break; + default: + textCodes = gen2TextCodesMain; + numEntries = sizeof(gen2TextCodesMain) / sizeof(struct TextCodePair); + break; + } + + return encodeText(textCodes, numEntries, inputBuffer, inputBufferLength, outputBuffer, outputBufferLength, terminator); } bool gen2_isPokemonShiny(Gen2TrainerPokemon& poke) @@ -497,6 +3598,8 @@ const char* gen2_getItemListTypeString(Gen2ItemListType type) { switch(type) { + case Gen2ItemListType::GEN2_ITEMLISTTYPE_TMHM: + return "TM/HM Pocket"; case Gen2ItemListType::GEN2_ITEMLISTTYPE_ITEMPOCKET: return "Item Pocket"; case Gen2ItemListType::GEN2_ITEMLISTTYPE_KEYITEMPOCKET: @@ -519,7 +3622,28 @@ void gen2_prepareDistributionPokemon(Gen2GameReader& gameReader, const Gen2Distr } else { - originalTrainerName = distributionPoke.originalTrainer; + const Gen2LocalizationLanguage gameLang = gameReader.getGameLanguage(); + + if(gameLang == Gen2LocalizationLanguage::JAPANESE && !distributionPoke.isJapanese) + { + // The Japanese games don't have all the latin characters in their character set. + // So if transferring a non-japanese distribution event pokémon to a japanese cartridge, + // we need to replace the OT. + // If Google Translate is any reliable, ポケメ64 should translate to "Pokeme 64". + originalTrainerName = "ポケメ64"; + } + else if(gameLang != Gen2LocalizationLanguage::JAPANESE && distributionPoke.isJapanese) + { + // Obviously non-japanese cartridges/roms don't have the japanese characters in their character set. + // So if transferring a japanese distribution event pokémon to a non-japanese cartridge, + // we need to replace the OT. + // We'll replace it with PM64 + originalTrainerName = "PM64"; + } + else + { + originalTrainerName = distributionPoke.originalTrainer; + } if (distributionPoke.regenerateTrainerID) { diff --git a/src/gen2/Gen2DistributionPokemon.cpp b/src/gen2/Gen2DistributionPokemon.cpp index 675c762..c480c89 100644 --- a/src/gen2/Gen2DistributionPokemon.cpp +++ b/src/gen2/Gen2DistributionPokemon.cpp @@ -1,5 +1,6 @@ #include "gen2/Gen2DistributionPokemon.h" #include "common.h" +#include "Moves.h" // https://bulbapedia.bulbagarden.net/wiki/List_of_European_language_event_Pok%C3%A9mon_distributions_(Generation_II) // https://bulbapedia.bulbagarden.net/wiki/List_of_moves @@ -8,7 +9,7 @@ static const Gen2TrainerPokemon commonMewDefinition = { .poke_index = 151, .held_item_index = 0x53, // bitter berry - .index_move1 = 1, // pound, + .index_move1 = (uint8_t)Move::POUND, .index_move2 = 0, .index_move3 = 0, .index_move4 = 0, @@ -42,10 +43,10 @@ static const Gen2TrainerPokemon commonMewDefinition = { static const Gen2TrainerPokemon commonCelebiDefinition = { .poke_index = 251, .held_item_index = 0, - .index_move1 = 73, // leech seed, - .index_move2 = 93, // confusion - .index_move3 = 215, // heal bell - .index_move4 = 105, // recover + .index_move1 = (uint8_t)Move::LEECH_SEED, + .index_move2 = (uint8_t)Move::CONFUSION, + .index_move3 = (uint8_t)Move::HEAL_BELL, + .index_move4 = (uint8_t)Move::RECOVER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -73,7 +74,7 @@ static const Gen2TrainerPokemon commonCelebiDefinition = { .special_def = 0 }; -const Gen2DistributionPokemon g2_clubNintendoMexico_Mew = { +static const Gen2DistributionPokemon g2_clubNintendoMexico_Mew = { .name = "Club Nintendo Mexico Mew", .originalTrainer = "ASH", .originalTrainerID = 45515, @@ -87,10 +88,11 @@ const Gen2DistributionPokemon g2_clubNintendoMexico_Mew = { // it only makes sense that the same common IVs were used here. .iv_data = {0xA1, 0xC5}, .randomizeIVs = false, + .isJapanese = false, .poke = commonMewDefinition }; -const Gen2DistributionPokemon g2_clubNintendoMexico_Celebi = { +static const Gen2DistributionPokemon g2_clubNintendoMexico_Celebi = { .name = "Club Nintendo Mexico Celebi", .originalTrainer = "HIRO", .originalTrainerID = 42534, @@ -101,10 +103,11 @@ const Gen2DistributionPokemon g2_clubNintendoMexico_Celebi = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_swedenCelebi = { +static const Gen2DistributionPokemon g2_swedenCelebi = { .name = "Sweden Celebi Tour Celebi", .originalTrainer = "", .originalTrainerID = 0, @@ -115,10 +118,11 @@ const Gen2DistributionPokemon g2_swedenCelebi = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_westfieldShopping = { +static const Gen2DistributionPokemon g2_westfieldShopping = { .name = "Westfield Shopping Centre Celebi", .originalTrainer = "", .originalTrainerID = 0, @@ -129,10 +133,11 @@ const Gen2DistributionPokemon g2_westfieldShopping = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_celebiTour = { +static const Gen2DistributionPokemon g2_celebiTour = { .name = "Celebi Tour Celebi", .originalTrainer = "CTOUR", .originalTrainerID = 0, @@ -143,10 +148,11 @@ const Gen2DistributionPokemon g2_celebiTour = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_pokemonFunFestCelebi = { +static const Gen2DistributionPokemon g2_pokemonFunFestCelebi = { .name = "Pokémon Fun Fest Celebi", .originalTrainer = "FLAGSA", .originalTrainerID = 1000, @@ -157,10 +163,11 @@ const Gen2DistributionPokemon g2_pokemonFunFestCelebi = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_nintendoPowerCelebi = { +static const Gen2DistributionPokemon g2_nintendoPowerCelebi = { .name = "Nintendo Power Celebi", .originalTrainer = "", .originalTrainerID = 0, @@ -171,10 +178,11 @@ const Gen2DistributionPokemon g2_nintendoPowerCelebi = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -const Gen2DistributionPokemon g2_celebiSweepstakes = { +static const Gen2DistributionPokemon g2_celebiSweepstakes = { .name = "Celebi Sweepstakes Celebi", .originalTrainer = "", .originalTrainerID = 0, @@ -185,23 +193,12 @@ const Gen2DistributionPokemon g2_celebiSweepstakes = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = commonCelebiDefinition }; -static const Gen2DistributionPokemon* mainList[] = { - &g2_clubNintendoMexico_Mew, - &g2_clubNintendoMexico_Celebi, - &g2_swedenCelebi, - &g2_westfieldShopping, - &g2_celebiTour, - &g2_pokemonFunFestCelebi, - &g2_nintendoPowerCelebi, - &g2_celebiSweepstakes -}; - -// PCNY (Pokemon Center New York) Gotta Catch 'em all! station distribution pokémon below -const Gen2DistributionPokemon g2_pcny_ancientpowerBulbasaur = { - .name = "AncientPower Bulbasaur", +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggBulbasaur = { + .name = "PC Mystery Egg #3 Bulbasaur (JPN)", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -211,12 +208,13 @@ const Gen2DistributionPokemon g2_pcny_ancientpowerBulbasaur = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = true, .poke = { .poke_index = 1, .held_item_index = 0, - .index_move1 = 33, // tackle - .index_move2 = 45, // growl - .index_move3 = 246, // Ancient Power + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::ANCIENT_POWER, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -246,54 +244,8 @@ const Gen2DistributionPokemon g2_pcny_ancientpowerBulbasaur = { } }; -const Gen2DistributionPokemon g2_pcny_shinyVenusaur = { - .name = "Shiny Venusaur", - .originalTrainer = "PCNYa", - .originalTrainerID = 0, - .setPlayerAsOriginalTrainer = false, - .regenerateTrainerID = true, - .shinyChance = 100, - .overrideLevel = 0, - .isEgg = false, - .iv_data = {0, 0}, - .randomizeIVs = true, - .poke = { - .poke_index = 3, - .held_item_index = 0, - .index_move1 = 77, // Poison Powder - .index_move2 = 79, // Sleep Powder - .index_move3 = 75, // Razor Leaf - .index_move4 = 230, // Sweet Scent - .original_trainer_ID = 0, - .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), - .hp_effort_value = 0, - .atk_effort_value = 0, - .def_effort_value = 0, - .speed_effort_value = 0, - .special_effort_value = 0, - .iv_data = {0, 0}, - .pp_move1 = 35, - .pp_move2 = 15, - .pp_move3 = 25, - .pp_move4 = 20, - .friendship_or_remaining_egg_cycles = 0, - .pokerus = 0, - .caught_data = 0, - .level = 40, - .status_condition = 0, - .unused_byte = 0, - .current_hp = 0, - .max_hp = 0, - .atk = 0, - .def = 0, - .speed = 0, - .special_atk = 0, - .special_def = 0 - } -}; - -const Gen2DistributionPokemon g2_pcny_crunchCharmander = { - .name = "Crunch Charmander", +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggCharmander = { + .name = "PC Mystery Egg #3 Charmander (JPN)", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -303,12 +255,13 @@ const Gen2DistributionPokemon g2_pcny_crunchCharmander = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = true, .poke = { .poke_index = 4, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 45, // growl - .index_move3 = 242, // Crunch + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::CRUNCH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -338,7 +291,837 @@ const Gen2DistributionPokemon g2_pcny_crunchCharmander = { } }; -const Gen2DistributionPokemon g2_pcny_shinyCharizard = { +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggTotodile = { + .name = "PC Mystery Egg #3 Totodile (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 158, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SUBMISSION, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 30, + .pp_move3 = 20, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggHoothoot = { + .name = "PC Mystery Egg #3 Hoothoot (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 163, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::NIGHT_SHADE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg13Pichu = { + .name = "PC Mystery Egg #1|#3 Pichu (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 172, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::SING, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 20, + .pp_move3 = 15, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggPsyduck = { + .name = "PC Mystery Egg #2 Psyduck (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 54, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::PETAL_DANCE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 30, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggChikorita = { + .name = "PC Mystery Egg #1|#2 Chikorita (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 152, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::PETAL_DANCE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 40, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg2Pichu = { + .name = "PC Mystery Egg #2 Pichu (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 172, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::PETAL_DANCE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 30, + .pp_move2 = 20, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg2Cleffa = { + .name = "PC Mystery Egg #2 Cleffa (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 173, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = (uint8_t)Move::PETAL_DANCE, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 20, + .pp_move3 = 5, + .pp_move4 = 10, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggIgglybuff = { + .name = "PC Mystery Egg #2 Igglybuff (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 174, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::SING, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::DEFENSE_CURL, + .index_move4 = (uint8_t)Move::PETAL_DANCE, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 15, + .pp_move2 = 20, + .pp_move3 = 40, + .pp_move4 = 10, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg2Smoochum = { + .name = "PC Mystery Egg #2 Smoochum (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 238, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::LICK, + .index_move3 = (uint8_t)Move::PETAL_DANCE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 30, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg1Cleffa = { + .name = "PC Mystery Egg #1 Cleffa (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 173, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = (uint8_t)Move::SWIFT, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 20, + .pp_move3 = 5, + .pp_move4 = 20, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggWooper = { + .name = "PC Mystery Egg #1 Wooper (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 194, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::BELLY_DRUM, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 25, + .pp_move2 = 30, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEggPhanpy = { + .name = "PC Mystery Egg #1 Phanpy (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 231, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 40, + .pp_move3 = 5, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_pcMysteryEgg1Smoochum = { + .name = "PC Mystery Egg #1 Smoochum (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = { + .poke_index = 238, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::LICK, + .index_move3 = (uint8_t)Move::METRONOME, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 30, + .pp_move3 = 10, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_jpn_worldHobbyFairCelebi = { + .name = "World Hobby Fair Celebi (JPN)", + .originalTrainer = "トウキョー", + .originalTrainerID = 12006, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .shinyChance = 0, + .overrideLevel = 2, + .isEgg = false, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = commonCelebiDefinition +}; + +static const Gen2DistributionPokemon g2_jpn_shogakukanAnnualEventCelebi = { + .name = "Shogakukan Annual Event Celebi (JPN)", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 0, + .overrideLevel = 2, + .isEgg = false, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = commonCelebiDefinition +}; + +// https://bulbapedia.bulbagarden.net/wiki/List_of_Japanese_event_Pok%C3%A9mon_distributions_in_Generation_II +// the wiki page indicates that trainer ids with a odd number get a specific OT and even numbers get another. +// I can't replicate this with my current functionality. So I'm going to only hand out a specifid trainer number for this one +static const Gen2DistributionPokemon g2_jpn_nintendoSpaceWorld2000Celebi = { + .name = "Nintendo Space World 2000 (JPN)", + .originalTrainer = "ワールド", + .originalTrainerID = 13463, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = false, + .shinyChance = 0, + .overrideLevel = 2, + .isEgg = false, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = true, + .poke = commonCelebiDefinition +}; + +static const Gen2DistributionPokemon* mainList[] = { + &g2_clubNintendoMexico_Mew, + &g2_clubNintendoMexico_Celebi, + &g2_swedenCelebi, + &g2_westfieldShopping, + &g2_celebiTour, + &g2_pokemonFunFestCelebi, + &g2_nintendoPowerCelebi, + &g2_celebiSweepstakes, + &g2_jpn_pcMysteryEggBulbasaur, + &g2_jpn_pcMysteryEggCharmander, + &g2_jpn_pcMysteryEggTotodile, + &g2_jpn_pcMysteryEggHoothoot, + &g2_jpn_pcMysteryEgg13Pichu, + &g2_jpn_pcMysteryEggPsyduck, + &g2_jpn_pcMysteryEggChikorita, + &g2_jpn_pcMysteryEgg2Pichu, + &g2_jpn_pcMysteryEgg2Cleffa, + &g2_jpn_pcMysteryEggIgglybuff, + &g2_jpn_pcMysteryEgg2Smoochum, + &g2_jpn_pcMysteryEgg1Cleffa, + &g2_jpn_pcMysteryEggWooper, + &g2_jpn_pcMysteryEggPhanpy, + &g2_jpn_pcMysteryEgg1Smoochum, + &g2_jpn_worldHobbyFairCelebi, + &g2_jpn_shogakukanAnnualEventCelebi, + &g2_jpn_nintendoSpaceWorld2000Celebi +}; + +// PCNY (Pokemon Center New York) Gotta Catch 'em all! station distribution pokémon below +static const Gen2DistributionPokemon g2_pcny_ancientpowerBulbasaur = { + .name = "AncientPower Bulbasaur", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = false, + .poke = { + .poke_index = 1, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::ANCIENT_POWER, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 40, + .pp_move3 = 5, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_pcny_shinyVenusaur = { + .name = "Shiny Venusaur", + .originalTrainer = "PCNYa", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = false, + .regenerateTrainerID = true, + .shinyChance = 100, + .overrideLevel = 0, + .isEgg = false, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = false, + .poke = { + .poke_index = 3, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::POISON_POWDER, + .index_move2 = (uint8_t)Move::SLEEP_POWDER, + .index_move3 = (uint8_t)Move::RAZOR_LEAF, + .index_move4 = (uint8_t)Move::SWEET_SCENT, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 15, + .pp_move3 = 25, + .pp_move4 = 20, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 40, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_pcny_crunchCharmander = { + .name = "Crunch Charmander", + .originalTrainer = "", + .originalTrainerID = 0, + .setPlayerAsOriginalTrainer = true, + .regenerateTrainerID = false, + .shinyChance = 15, + .overrideLevel = 0, + .isEgg = true, + .iv_data = {0, 0}, + .randomizeIVs = true, + .isJapanese = false, + .poke = { + .poke_index = 4, + .held_item_index = 0, + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::CRUNCH, + .index_move4 = 0, + .original_trainer_ID = 0, + .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), + .hp_effort_value = 0, + .atk_effort_value = 0, + .def_effort_value = 0, + .speed_effort_value = 0, + .special_effort_value = 0, + .iv_data = {0, 0}, + .pp_move1 = 35, + .pp_move2 = 40, + .pp_move3 = 15, + .pp_move4 = 0, + .friendship_or_remaining_egg_cycles = 0, + .pokerus = 0, + .caught_data = 0, + .level = 5, + .status_condition = 0, + .unused_byte = 0, + .current_hp = 0, + .max_hp = 0, + .atk = 0, + .def = 0, + .speed = 0, + .special_atk = 0, + .special_def = 0 + } +}; + +static const Gen2DistributionPokemon g2_pcny_shinyCharizard = { .name = "Shiny Charizard", .originalTrainer = "PCNYd", .originalTrainerID = 0, @@ -349,13 +1132,14 @@ const Gen2DistributionPokemon g2_pcny_shinyCharizard = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 6, .held_item_index = 0, - .index_move1 = 99, // Rage - .index_move2 = 184, // Scary Face - .index_move3 = 53, // Flamethrower - .index_move4 = 17, // Wing Attack + .index_move1 = (uint8_t)Move::RAGE, + .index_move2 = (uint8_t)Move::SCARY_FACE, + .index_move3 = (uint8_t)Move::FLAMETHROWER, + .index_move4 = (uint8_t)Move::WING_ATTACK, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -384,7 +1168,7 @@ const Gen2DistributionPokemon g2_pcny_shinyCharizard = { } }; -const Gen2DistributionPokemon g2_pcny_zapCannonSquirtle = { +static const Gen2DistributionPokemon g2_pcny_zapCannonSquirtle = { .name = "Zap Cannon Squirtle", .originalTrainer = "", .originalTrainerID = 0, @@ -395,12 +1179,13 @@ const Gen2DistributionPokemon g2_pcny_zapCannonSquirtle = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 7, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 39, // Tail Whip - .index_move3 = 192, // Zap Cannon + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::ZAP_CANNON, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -430,7 +1215,7 @@ const Gen2DistributionPokemon g2_pcny_zapCannonSquirtle = { } }; -const Gen2DistributionPokemon g2_pcny_shinyBlastoise = { +static const Gen2DistributionPokemon g2_pcny_shinyBlastoise = { .name = "Shiny Blastoise", .originalTrainer = "PCNYc", .originalTrainerID = 0, @@ -441,13 +1226,14 @@ const Gen2DistributionPokemon g2_pcny_shinyBlastoise = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 9, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 44, // Bite - .index_move3 = 229, // Rapid Spin - .index_move4 = 182, // Protect + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::BITE, + .index_move3 = (uint8_t)Move::RAPID_SPIN, + .index_move4 = (uint8_t)Move::PROTECT, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -476,7 +1262,7 @@ const Gen2DistributionPokemon g2_pcny_shinyBlastoise = { } }; -const Gen2DistributionPokemon g2_pcny_sonicboomSpearow = { +static const Gen2DistributionPokemon g2_pcny_sonicboomSpearow = { .name = "SonicBoom Spearow", .originalTrainer = "", .originalTrainerID = 0, @@ -487,12 +1273,13 @@ const Gen2DistributionPokemon g2_pcny_sonicboomSpearow = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 21, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 45, // Growl - .index_move3 = 49, // SonicBoom + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SONIC_BOOM, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -522,7 +1309,7 @@ const Gen2DistributionPokemon g2_pcny_sonicboomSpearow = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranFem = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranFem = { .name = "Lovely Kiss Nidoran♀", .originalTrainer = "", .originalTrainerID = 0, @@ -533,12 +1320,13 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranFem = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 29, .held_item_index = 0, - .index_move1 = 45, // Growl - .index_move2 = 33, // Tackle - .index_move3 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::LOVELY_KISS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -568,7 +1356,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranFem = { } }; -const Gen2DistributionPokemon g2_pcny_moonlightNidoranFem = { +static const Gen2DistributionPokemon g2_pcny_moonlightNidoranFem = { .name = "Moonlight Nidoran♀", .originalTrainer = "", .originalTrainerID = 0, @@ -579,12 +1367,13 @@ const Gen2DistributionPokemon g2_pcny_moonlightNidoranFem = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 29, .held_item_index = 0, - .index_move1 = 45, // Growl - .index_move2 = 33, // Tackle - .index_move3 = 236, // Moonlight + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::MOONLIGHT, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -614,7 +1403,7 @@ const Gen2DistributionPokemon g2_pcny_moonlightNidoranFem = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissNidoranFem = { +static const Gen2DistributionPokemon g2_pcny_sweetKissNidoranFem = { .name = "Sweet Kiss Nidoran♀", .originalTrainer = "", .originalTrainerID = 0, @@ -625,12 +1414,13 @@ const Gen2DistributionPokemon g2_pcny_sweetKissNidoranFem = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 29, .held_item_index = 0, - .index_move1 = 45, // Growl - .index_move2 = 33, // Tackle - .index_move3 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::SWEET_KISS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -660,7 +1450,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissNidoranFem = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranMale = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranMale = { .name = "Lovely Kiss Nidoran♂", .originalTrainer = "", .originalTrainerID = 0, @@ -671,12 +1461,13 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranMale = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 32, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 33, // Tackle - .index_move3 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::LOVELY_KISS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -706,7 +1497,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissNidoranMale = { } }; -const Gen2DistributionPokemon g2_pcny_morningSunNidoranMale = { +static const Gen2DistributionPokemon g2_pcny_morningSunNidoranMale = { .name = "Morning Sun Nidoran♂", .originalTrainer = "", .originalTrainerID = 0, @@ -717,12 +1508,13 @@ const Gen2DistributionPokemon g2_pcny_morningSunNidoranMale = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 32, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 33, // Tackle - .index_move3 = 234, // Morning Sun + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::MORNING_SUN, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -752,7 +1544,7 @@ const Gen2DistributionPokemon g2_pcny_morningSunNidoranMale = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissNidoranMale = { +static const Gen2DistributionPokemon g2_pcny_sweetKissNidoranMale = { .name = "Sweet Kiss Nidoran♂", .originalTrainer = "", .originalTrainerID = 0, @@ -763,12 +1555,13 @@ const Gen2DistributionPokemon g2_pcny_sweetKissNidoranMale = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 32, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 33, // Tackle - .index_move3 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::TACKLE, + .index_move3 = (uint8_t)Move::SWEET_KISS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -798,7 +1591,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissNidoranMale = { } }; -const Gen2DistributionPokemon g2_pcny_flailZubat = { +static const Gen2DistributionPokemon g2_pcny_flailZubat = { .name = "Flail Zubat", .originalTrainer = "", .originalTrainerID = 0, @@ -809,11 +1602,12 @@ const Gen2DistributionPokemon g2_pcny_flailZubat = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 41, .held_item_index = 0, - .index_move1 = 141, // Leech Life - .index_move2 = 175, // Flail + .index_move1 = (uint8_t)Move::LEECH_LIFE, + .index_move2 = (uint8_t)Move::FLAIL, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -844,7 +1638,7 @@ const Gen2DistributionPokemon g2_pcny_flailZubat = { } }; -const Gen2DistributionPokemon g2_pcny_leechSeedOddish = { +static const Gen2DistributionPokemon g2_pcny_leechSeedOddish = { .name = "Leech Seed Oddish", .originalTrainer = "", .originalTrainerID = 0, @@ -855,11 +1649,12 @@ const Gen2DistributionPokemon g2_pcny_leechSeedOddish = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 43, .held_item_index = 0, - .index_move1 = 71, // Absorb - .index_move2 = 73, // Leech Seed + .index_move1 = (uint8_t)Move::ABSORB, + .index_move2 = (uint8_t)Move::LEECH_SEED, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -890,7 +1685,7 @@ const Gen2DistributionPokemon g2_pcny_leechSeedOddish = { } }; -const Gen2DistributionPokemon g2_pcny_synthesisParas = { +static const Gen2DistributionPokemon g2_pcny_synthesisParas = { .name = "Synthesis Paras", .originalTrainer = "", .originalTrainerID = 0, @@ -901,11 +1696,12 @@ const Gen2DistributionPokemon g2_pcny_synthesisParas = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 46, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 235, // Synthesis + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::SYNTHESIS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -936,7 +1732,7 @@ const Gen2DistributionPokemon g2_pcny_synthesisParas = { } }; -const Gen2DistributionPokemon g2_pcny_petalDancePsyduck = { +static const Gen2DistributionPokemon g2_pcny_petalDancePsyduck = { .name = "Petal Dance Psyduck", .originalTrainer = "", .originalTrainerID = 0, @@ -947,12 +1743,13 @@ const Gen2DistributionPokemon g2_pcny_petalDancePsyduck = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 54, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 39, // Tail Whip - .index_move3 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::PETAL_DANCE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -982,8 +1779,8 @@ const Gen2DistributionPokemon g2_pcny_petalDancePsyduck = { } }; -const Gen2DistributionPokemon g2_pcny_triAttackPsyduck = { - .name = "Tri Attach Psyduck", +static const Gen2DistributionPokemon g2_pcny_triAttackPsyduck = { + .name = "Tri Attack Psyduck", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -993,12 +1790,13 @@ const Gen2DistributionPokemon g2_pcny_triAttackPsyduck = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 54, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 39, // Tail Whip - .index_move3 = 161, // Tri Attack + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::TRI_ATTACK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1028,7 +1826,7 @@ const Gen2DistributionPokemon g2_pcny_triAttackPsyduck = { } }; -const Gen2DistributionPokemon g2_pcny_growthPoliwag = { +static const Gen2DistributionPokemon g2_pcny_growthPoliwag = { .name = "Growth Poliwag", .originalTrainer = "", .originalTrainerID = 0, @@ -1039,11 +1837,12 @@ const Gen2DistributionPokemon g2_pcny_growthPoliwag = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 60, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 74, // Growth + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::GROWTH, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1074,7 +1873,7 @@ const Gen2DistributionPokemon g2_pcny_growthPoliwag = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissPoliwag = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissPoliwag = { .name = "Lovely Kiss Poliwag", .originalTrainer = "", .originalTrainerID = 0, @@ -1085,11 +1884,12 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissPoliwag = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 60, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::LOVELY_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1120,7 +1920,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissPoliwag = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissPoliwag = { +static const Gen2DistributionPokemon g2_pcny_sweetKissPoliwag = { .name = "Sweet Kiss Poliwag", .originalTrainer = "", .originalTrainerID = 0, @@ -1131,11 +1931,12 @@ const Gen2DistributionPokemon g2_pcny_sweetKissPoliwag = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 60, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::SWEET_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1166,7 +1967,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissPoliwag = { } }; -const Gen2DistributionPokemon g2_pcny_foresightAbra = { +static const Gen2DistributionPokemon g2_pcny_foresightAbra = { .name = "Foresight Abra", .originalTrainer = "", .originalTrainerID = 0, @@ -1177,11 +1978,12 @@ const Gen2DistributionPokemon g2_pcny_foresightAbra = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 63, .held_item_index = 0, - .index_move1 = 100, // Teleport - .index_move2 = 193, // Foresight + .index_move1 = (uint8_t)Move::TELEPORT, + .index_move2 = (uint8_t)Move::FORESIGHT, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1212,7 +2014,7 @@ const Gen2DistributionPokemon g2_pcny_foresightAbra = { } }; -const Gen2DistributionPokemon g2_pcny_falseSwipeMachop = { +static const Gen2DistributionPokemon g2_pcny_falseSwipeMachop = { .name = "False Swipe Machop", .originalTrainer = "", .originalTrainerID = 0, @@ -1223,12 +2025,13 @@ const Gen2DistributionPokemon g2_pcny_falseSwipeMachop = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 66, .held_item_index = 0, - .index_move1 = 67, // Low Kick - .index_move2 = 43, // Leer - .index_move3 = 206, // False Swipe + .index_move1 = (uint8_t)Move::LOW_KICK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::FALSE_SWIPE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -1258,7 +2061,7 @@ const Gen2DistributionPokemon g2_pcny_falseSwipeMachop = { } }; -const Gen2DistributionPokemon g2_pcny_thrashMachop = { +static const Gen2DistributionPokemon g2_pcny_thrashMachop = { .name = "Thrash Swipe Machop", .originalTrainer = "", .originalTrainerID = 0, @@ -1269,12 +2072,13 @@ const Gen2DistributionPokemon g2_pcny_thrashMachop = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 66, .held_item_index = 0, - .index_move1 = 67, // Low Kick - .index_move2 = 43, // Leer - .index_move3 = 37, // Thrash + .index_move1 = (uint8_t)Move::LOW_KICK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::THRASH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -1304,7 +2108,7 @@ const Gen2DistributionPokemon g2_pcny_thrashMachop = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissBellsprout = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissBellsprout = { .name = "Lovely Kiss Bellsprout", .originalTrainer = "", .originalTrainerID = 0, @@ -1315,11 +2119,12 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissBellsprout = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 69, .held_item_index = 0, - .index_move1 = 22, // Vine Whip - .index_move2 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::VINE_WHIP, + .index_move2 = (uint8_t)Move::LOVELY_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1350,7 +2155,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissBellsprout = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissBellsprout = { +static const Gen2DistributionPokemon g2_pcny_sweetKissBellsprout = { .name = "Sweet Kiss Bellsprout", .originalTrainer = "", .originalTrainerID = 0, @@ -1361,11 +2166,12 @@ const Gen2DistributionPokemon g2_pcny_sweetKissBellsprout = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 69, .held_item_index = 0, - .index_move1 = 22, // Vine Whip - .index_move2 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::VINE_WHIP, + .index_move2 = (uint8_t)Move::SWEET_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1396,7 +2202,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissBellsprout = { } }; -const Gen2DistributionPokemon g2_pcny_confuseRayTentacool = { +static const Gen2DistributionPokemon g2_pcny_confuseRayTentacool = { .name = "Confuse Ray Tentacool", .originalTrainer = "", .originalTrainerID = 0, @@ -1407,11 +2213,12 @@ const Gen2DistributionPokemon g2_pcny_confuseRayTentacool = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 72, .held_item_index = 0, - .index_move1 = 40, // Poison Sting - .index_move2 = 109, // Confuse Ray + .index_move1 = (uint8_t)Move::POISON_STING, + .index_move2 = (uint8_t)Move::CONFUSE_RAY, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1442,7 +2249,7 @@ const Gen2DistributionPokemon g2_pcny_confuseRayTentacool = { } }; -const Gen2DistributionPokemon g2_pcny_rapidSpinGeodude = { +static const Gen2DistributionPokemon g2_pcny_rapidSpinGeodude = { .name = "Rapid Spin Geodude", .originalTrainer = "", .originalTrainerID = 0, @@ -1453,11 +2260,12 @@ const Gen2DistributionPokemon g2_pcny_rapidSpinGeodude = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 74, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 229, // Rapid Spin + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::RAPID_SPIN, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1488,7 +2296,7 @@ const Gen2DistributionPokemon g2_pcny_rapidSpinGeodude = { } }; -const Gen2DistributionPokemon g2_pcny_lowKickPonyta = { +static const Gen2DistributionPokemon g2_pcny_lowKickPonyta = { .name = "Low Kick Ponyta", .originalTrainer = "", .originalTrainerID = 0, @@ -1499,12 +2307,13 @@ const Gen2DistributionPokemon g2_pcny_lowKickPonyta = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 77, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 45, // Growl - .index_move3 = 67, // Low Kick + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::LOW_KICK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1534,7 +2343,7 @@ const Gen2DistributionPokemon g2_pcny_lowKickPonyta = { } }; -const Gen2DistributionPokemon g2_pcny_agilityMagnemite = { +static const Gen2DistributionPokemon g2_pcny_agilityMagnemite = { .name = "Agility Magnemite", .originalTrainer = "", .originalTrainerID = 0, @@ -1545,11 +2354,12 @@ const Gen2DistributionPokemon g2_pcny_agilityMagnemite = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 81, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 97, // Agility + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::AGILITY, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1580,7 +2390,7 @@ const Gen2DistributionPokemon g2_pcny_agilityMagnemite = { } }; -const Gen2DistributionPokemon g2_pcny_furyCutterFarfetchd = { +static const Gen2DistributionPokemon g2_pcny_furyCutterFarfetchd = { .name = "Fury Cutter Farfetch'd", .originalTrainer = "", .originalTrainerID = 0, @@ -1591,11 +2401,12 @@ const Gen2DistributionPokemon g2_pcny_furyCutterFarfetchd = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 83, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 210, // Fury Cutter + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::FURY_CUTTER, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1626,7 +2437,7 @@ const Gen2DistributionPokemon g2_pcny_furyCutterFarfetchd = { } }; -const Gen2DistributionPokemon g2_pcny_lowKickDoduo = { +static const Gen2DistributionPokemon g2_pcny_lowKickDoduo = { .name = "Low Kick Doduo", .originalTrainer = "", .originalTrainerID = 0, @@ -1637,12 +2448,13 @@ const Gen2DistributionPokemon g2_pcny_lowKickDoduo = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 84, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 45, // Growl - .index_move3 = 67, // Low Kick + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::LOW_KICK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1672,7 +2484,7 @@ const Gen2DistributionPokemon g2_pcny_lowKickDoduo = { } }; -const Gen2DistributionPokemon g2_pcny_flailSeel = { +static const Gen2DistributionPokemon g2_pcny_flailSeel = { .name = "Flail Seel", .originalTrainer = "", .originalTrainerID = 0, @@ -1683,12 +2495,13 @@ const Gen2DistributionPokemon g2_pcny_flailSeel = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 86, .held_item_index = 0, - .index_move1 = 29, // Headbutt - .index_move2 = 45, // Growl - .index_move3 = 175, // Flail + .index_move1 = (uint8_t)Move::HEADBUTT, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::FLAIL, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1718,7 +2531,7 @@ const Gen2DistributionPokemon g2_pcny_flailSeel = { } }; -const Gen2DistributionPokemon g2_pcny_sharpenOnix = { +static const Gen2DistributionPokemon g2_pcny_sharpenOnix = { .name = "Sharpen Onix", .originalTrainer = "", .originalTrainerID = 0, @@ -1729,12 +2542,13 @@ const Gen2DistributionPokemon g2_pcny_sharpenOnix = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 95, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 103, // Screech - .index_move3 = 159, // Sharpen + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::SCREECH, + .index_move3 = (uint8_t)Move::SHARPEN, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1764,7 +2578,7 @@ const Gen2DistributionPokemon g2_pcny_sharpenOnix = { } }; -const Gen2DistributionPokemon g2_pcny_amnesiaDrowsee = { +static const Gen2DistributionPokemon g2_pcny_amnesiaDrowsee = { .name = "Amnesia Drowsee", .originalTrainer = "", .originalTrainerID = 0, @@ -1775,12 +2589,13 @@ const Gen2DistributionPokemon g2_pcny_amnesiaDrowsee = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 96, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 95, // Hypnosis - .index_move3 = 133, // Amnesia + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::HYPNOSIS, + .index_move3 = (uint8_t)Move::AMNESIA, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1810,7 +2625,7 @@ const Gen2DistributionPokemon g2_pcny_amnesiaDrowsee = { } }; -const Gen2DistributionPokemon g2_pcny_metalClawKrabby = { +static const Gen2DistributionPokemon g2_pcny_metalClawKrabby = { .name = "Metal Claw Crabby", .originalTrainer = "", .originalTrainerID = 0, @@ -1821,12 +2636,13 @@ const Gen2DistributionPokemon g2_pcny_metalClawKrabby = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 98, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 43, // Leer - .index_move3 = 232, // Metal Claw + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::METAL_CLAW, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1856,7 +2672,7 @@ const Gen2DistributionPokemon g2_pcny_metalClawKrabby = { } }; -const Gen2DistributionPokemon g2_pcny_agilityVoltorb = { +static const Gen2DistributionPokemon g2_pcny_agilityVoltorb = { .name = "Agility Voltorb", .originalTrainer = "", .originalTrainerID = 0, @@ -1867,11 +2683,12 @@ const Gen2DistributionPokemon g2_pcny_agilityVoltorb = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 100, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 97, // Agility + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::AGILITY, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -1902,7 +2719,7 @@ const Gen2DistributionPokemon g2_pcny_agilityVoltorb = { } }; -const Gen2DistributionPokemon g2_pcny_sweetScentExeggcute = { +static const Gen2DistributionPokemon g2_pcny_sweetScentExeggcute = { .name = "Sweet Scent Exeggcute", .originalTrainer = "", .originalTrainerID = 0, @@ -1913,12 +2730,13 @@ const Gen2DistributionPokemon g2_pcny_sweetScentExeggcute = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 102, .held_item_index = 0, - .index_move1 = 140, // Barrage - .index_move2 = 95, // Hypnosis - .index_move3 = 230, // Sweet Scent + .index_move1 = (uint8_t)Move::BARRAGE, + .index_move2 = (uint8_t)Move::HYPNOSIS, + .index_move3 = (uint8_t)Move::SWEET_SCENT, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -1948,7 +2766,7 @@ const Gen2DistributionPokemon g2_pcny_sweetScentExeggcute = { } }; -const Gen2DistributionPokemon g2_pcny_furyAttackCubone = { +static const Gen2DistributionPokemon g2_pcny_furyAttackCubone = { .name = "Fury Attack Cubone", .originalTrainer = "", .originalTrainerID = 0, @@ -1959,12 +2777,13 @@ const Gen2DistributionPokemon g2_pcny_furyAttackCubone = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 104, .held_item_index = 0, - .index_move1 = 45, // Growl - .index_move2 = 39, // Tail Whip - .index_move3 = 31, // Fury Attack + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::FURY_ATTACK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -1994,7 +2813,7 @@ const Gen2DistributionPokemon g2_pcny_furyAttackCubone = { } }; -const Gen2DistributionPokemon g2_pcny_doubleSlapLickitung = { +static const Gen2DistributionPokemon g2_pcny_doubleSlapLickitung = { .name = "DoubleSlap Lickitung", .originalTrainer = "", .originalTrainerID = 0, @@ -2005,11 +2824,12 @@ const Gen2DistributionPokemon g2_pcny_doubleSlapLickitung = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 108, .held_item_index = 0, - .index_move1 = 122, // Lick - .index_move2 = 3, // DoubleSlap + .index_move1 = (uint8_t)Move::LICK, + .index_move2 = (uint8_t)Move::DOUBLE_SLAP, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2040,7 +2860,7 @@ const Gen2DistributionPokemon g2_pcny_doubleSlapLickitung = { } }; -const Gen2DistributionPokemon g2_pcny_sweetScentChansey = { +static const Gen2DistributionPokemon g2_pcny_sweetScentChansey = { .name = "Sweet Scent Chansey", .originalTrainer = "", .originalTrainerID = 0, @@ -2051,11 +2871,12 @@ const Gen2DistributionPokemon g2_pcny_sweetScentChansey = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 113, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 230, // Sweet Scent + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::SWEET_SCENT, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2086,7 +2907,7 @@ const Gen2DistributionPokemon g2_pcny_sweetScentChansey = { } }; -const Gen2DistributionPokemon g2_pcny_synthesisTangela = { +static const Gen2DistributionPokemon g2_pcny_synthesisTangela = { .name = "Synthesis Tangela", .originalTrainer = "", .originalTrainerID = 0, @@ -2097,12 +2918,13 @@ const Gen2DistributionPokemon g2_pcny_synthesisTangela = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 114, .held_item_index = 0, - .index_move1 = 132, // Constrict - .index_move2 = 79, // Sleep Powder - .index_move3 = 235, // Synthesis + .index_move1 = (uint8_t)Move::CONSTRICT, + .index_move2 = (uint8_t)Move::SLEEP_POWDER, + .index_move3 = (uint8_t)Move::SYNTHESIS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2132,8 +2954,8 @@ const Gen2DistributionPokemon g2_pcny_synthesisTangela = { } }; -const Gen2DistributionPokemon g2_pcny_faintAttackKangaskhan = { - .name = "Faint Attack Kangaskhan", +static const Gen2DistributionPokemon g2_pcny_faintAttackKangaskhan = { + .name = "Feint Attack Kangaskhan", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -2143,11 +2965,12 @@ const Gen2DistributionPokemon g2_pcny_faintAttackKangaskhan = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 115, .held_item_index = 0, - .index_move1 = 4, // Comet Punch - .index_move2 = 185, // Feint Attack + .index_move1 = (uint8_t)Move::COMET_PUNCH, + .index_move2 = (uint8_t)Move::FEINT_ATTACK, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2178,7 +3001,7 @@ const Gen2DistributionPokemon g2_pcny_faintAttackKangaskhan = { } }; -const Gen2DistributionPokemon g2_pcny_hazeHorsea = { +static const Gen2DistributionPokemon g2_pcny_hazeHorsea = { .name = "Haze Horsea", .originalTrainer = "", .originalTrainerID = 0, @@ -2189,11 +3012,12 @@ const Gen2DistributionPokemon g2_pcny_hazeHorsea = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 116, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 114, // Haze + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::HAZE, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2224,7 +3048,7 @@ const Gen2DistributionPokemon g2_pcny_hazeHorsea = { } }; -const Gen2DistributionPokemon g2_pcny_swordsDanceGoldeen = { +static const Gen2DistributionPokemon g2_pcny_swordsDanceGoldeen = { .name = "Swords Dance Goldeen", .originalTrainer = "", .originalTrainerID = 0, @@ -2235,12 +3059,13 @@ const Gen2DistributionPokemon g2_pcny_swordsDanceGoldeen = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 118, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 39, // Tail Whip - .index_move3 = 14, // Swords Dance + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::SWORDS_DANCE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2270,7 +3095,7 @@ const Gen2DistributionPokemon g2_pcny_swordsDanceGoldeen = { } }; -const Gen2DistributionPokemon g2_pcny_twisterStaryu = { +static const Gen2DistributionPokemon g2_pcny_twisterStaryu = { .name = "Twister Staryu", .originalTrainer = "", .originalTrainerID = 0, @@ -2281,12 +3106,13 @@ const Gen2DistributionPokemon g2_pcny_twisterStaryu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 120, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 106, // Harden - .index_move3 = 239, // Twister + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::HARDEN, + .index_move3 = (uint8_t)Move::TWISTER, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -2316,7 +3142,7 @@ const Gen2DistributionPokemon g2_pcny_twisterStaryu = { } }; -const Gen2DistributionPokemon g2_pcny_mindReaderMrMime = { +static const Gen2DistributionPokemon g2_pcny_mindReaderMrMime = { .name = "Mind Reader Mr. Mime", .originalTrainer = "", .originalTrainerID = 0, @@ -2327,11 +3153,12 @@ const Gen2DistributionPokemon g2_pcny_mindReaderMrMime = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 122, .held_item_index = 0, - .index_move1 = 112, // Barrier - .index_move2 = 170, // Mind Reader + .index_move1 = (uint8_t)Move::BARRIER, + .index_move2 = (uint8_t)Move::MIND_READER, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2362,7 +3189,7 @@ const Gen2DistributionPokemon g2_pcny_mindReaderMrMime = { } }; -const Gen2DistributionPokemon g2_pcny_sonicBoomScyther = { +static const Gen2DistributionPokemon g2_pcny_sonicBoomScyther = { .name = "SonicBoom Scyther", .originalTrainer = "", .originalTrainerID = 0, @@ -2373,12 +3200,13 @@ const Gen2DistributionPokemon g2_pcny_sonicBoomScyther = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 123, .held_item_index = 0, - .index_move1 = 98, // Quick Attack - .index_move2 = 43, // Leer - .index_move3 = 49, // SonicBoom + .index_move1 = (uint8_t)Move::QUICK_ATTACK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SONIC_BOOM, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2408,7 +3236,7 @@ const Gen2DistributionPokemon g2_pcny_sonicBoomScyther = { } }; -const Gen2DistributionPokemon g2_pcny_rockThrowPinsir = { +static const Gen2DistributionPokemon g2_pcny_rockThrowPinsir = { .name = "Rock Throw Pinsir", .originalTrainer = "", .originalTrainerID = 0, @@ -2419,11 +3247,12 @@ const Gen2DistributionPokemon g2_pcny_rockThrowPinsir = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 127, .held_item_index = 0, - .index_move1 = 11, // Vise Grip - .index_move2 = 88, // Rock Throw + .index_move1 = (uint8_t)Move::VICE_GRIP, + .index_move2 = (uint8_t)Move::ROCK_THROW, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2454,7 +3283,7 @@ const Gen2DistributionPokemon g2_pcny_rockThrowPinsir = { } }; -const Gen2DistributionPokemon g2_pcny_quickAttackTauros = { +static const Gen2DistributionPokemon g2_pcny_quickAttackTauros = { .name = "Quick Attack Tauros", .originalTrainer = "", .originalTrainerID = 0, @@ -2465,12 +3294,13 @@ const Gen2DistributionPokemon g2_pcny_quickAttackTauros = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 128, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 39, // Tail Whip - .index_move3 = 98, // Quick Attack + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::QUICK_ATTACK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -2500,7 +3330,7 @@ const Gen2DistributionPokemon g2_pcny_quickAttackTauros = { } }; -const Gen2DistributionPokemon g2_pcny_bubbleMagikarp = { +static const Gen2DistributionPokemon g2_pcny_bubbleMagikarp = { .name = "Bubble Magikarp", .originalTrainer = "", .originalTrainerID = 0, @@ -2511,11 +3341,12 @@ const Gen2DistributionPokemon g2_pcny_bubbleMagikarp = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 129, .held_item_index = 0, - .index_move1 = 150, // Splash - .index_move2 = 145, // Bubble + .index_move1 = (uint8_t)Move::SPLASH, + .index_move2 = (uint8_t)Move::BUBBLE, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2546,7 +3377,7 @@ const Gen2DistributionPokemon g2_pcny_bubbleMagikarp = { } }; -const Gen2DistributionPokemon g2_pcny_reversalMagikarp = { +static const Gen2DistributionPokemon g2_pcny_reversalMagikarp = { .name = "Reversal Magikarp", .originalTrainer = "", .originalTrainerID = 0, @@ -2557,11 +3388,12 @@ const Gen2DistributionPokemon g2_pcny_reversalMagikarp = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 129, .held_item_index = 0, - .index_move1 = 150, // Splash - .index_move2 = 179, // Reversal + .index_move1 = (uint8_t)Move::SPLASH, + .index_move2 = (uint8_t)Move::REVERSAL, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2592,7 +3424,7 @@ const Gen2DistributionPokemon g2_pcny_reversalMagikarp = { } }; -const Gen2DistributionPokemon g2_pcny_biteLapras = { +static const Gen2DistributionPokemon g2_pcny_biteLapras = { .name = "Bite Lapras", .originalTrainer = "", .originalTrainerID = 0, @@ -2603,13 +3435,14 @@ const Gen2DistributionPokemon g2_pcny_biteLapras = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 131, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 45, // Growl - .index_move3 = 47, // Sing - .index_move4 = 44, // Bite + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SING, + .index_move4 = (uint8_t)Move::BITE, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -2638,7 +3471,7 @@ const Gen2DistributionPokemon g2_pcny_biteLapras = { } }; -const Gen2DistributionPokemon g2_pcny_futureSightLapras = { +static const Gen2DistributionPokemon g2_pcny_futureSightLapras = { .name = "Future Sight Lapras", .originalTrainer = "", .originalTrainerID = 0, @@ -2649,13 +3482,14 @@ const Gen2DistributionPokemon g2_pcny_futureSightLapras = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 131, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 45, // Growl - .index_move3 = 47, // Sing - .index_move4 = 248, // Future Sight + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::SING, + .index_move4 = (uint8_t)Move::FUTURE_SIGHT, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -2684,7 +3518,7 @@ const Gen2DistributionPokemon g2_pcny_futureSightLapras = { } }; -const Gen2DistributionPokemon g2_pcny_growthEevee = { +static const Gen2DistributionPokemon g2_pcny_growthEevee = { .name = "Growth Eevee", .originalTrainer = "", .originalTrainerID = 0, @@ -2695,12 +3529,13 @@ const Gen2DistributionPokemon g2_pcny_growthEevee = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 133, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 39, // Tail Whip - .index_move3 = 74, // Growth + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::GROWTH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2730,7 +3565,7 @@ const Gen2DistributionPokemon g2_pcny_growthEevee = { } }; -const Gen2DistributionPokemon g2_pcny_barrierPorygon = { +static const Gen2DistributionPokemon g2_pcny_barrierPorygon = { .name = "Barrier Porygon", .originalTrainer = "", .originalTrainerID = 0, @@ -2741,13 +3576,14 @@ const Gen2DistributionPokemon g2_pcny_barrierPorygon = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 137, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 160, // Conversion - .index_move3 = 176, // Conversion 2 - .index_move4 = 112, // Barrier + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::CONVERSION, + .index_move3 = (uint8_t)Move::CONVERSION_2, + .index_move4 = (uint8_t)Move::BARRIER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), .hp_effort_value = 0, @@ -2776,7 +3612,7 @@ const Gen2DistributionPokemon g2_pcny_barrierPorygon = { } }; -const Gen2DistributionPokemon g2_pcny_rockThrowOmanyte = { +static const Gen2DistributionPokemon g2_pcny_rockThrowOmanyte = { .name = "Rock Throw Omanyte", .originalTrainer = "", .originalTrainerID = 0, @@ -2787,12 +3623,13 @@ const Gen2DistributionPokemon g2_pcny_rockThrowOmanyte = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 138, .held_item_index = 0, - .index_move1 = 132, // Constrict - .index_move2 = 110, // Withdraw - .index_move3 = 88, // Rock Throw + .index_move1 = (uint8_t)Move::CONSTRICT, + .index_move2 = (uint8_t)Move::WITHDRAW, + .index_move3 = (uint8_t)Move::ROCK_THROW, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2822,7 +3659,7 @@ const Gen2DistributionPokemon g2_pcny_rockThrowOmanyte = { } }; -const Gen2DistributionPokemon g2_pcny_rockThrowKabuto = { +static const Gen2DistributionPokemon g2_pcny_rockThrowKabuto = { .name = "Rock Throw Kabuto", .originalTrainer = "", .originalTrainerID = 0, @@ -2833,12 +3670,13 @@ const Gen2DistributionPokemon g2_pcny_rockThrowKabuto = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 140, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 106, // Harden - .index_move3 = 88, // Rock Throw + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::HARDEN, + .index_move3 = (uint8_t)Move::ROCK_THROW, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -2868,7 +3706,7 @@ const Gen2DistributionPokemon g2_pcny_rockThrowKabuto = { } }; -const Gen2DistributionPokemon g2_pcny_rockThrowAerodactyl = { +static const Gen2DistributionPokemon g2_pcny_rockThrowAerodactyl = { .name = "Rock Throw Aerodactyl", .originalTrainer = "", .originalTrainerID = 0, @@ -2879,11 +3717,12 @@ const Gen2DistributionPokemon g2_pcny_rockThrowAerodactyl = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 142, .held_item_index = 0, - .index_move1 = 17, // Wing Attack - .index_move2 = 88, // Rock Throw + .index_move1 = (uint8_t)Move::WING_ATTACK, + .index_move2 = (uint8_t)Move::ROCK_THROW, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2914,7 +3753,7 @@ const Gen2DistributionPokemon g2_pcny_rockThrowAerodactyl = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissSnorlax = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissSnorlax = { .name = "Lovely Kiss Snorlax", .originalTrainer = "", .originalTrainerID = 0, @@ -2925,11 +3764,12 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissSnorlax = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 143, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::LOVELY_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -2960,7 +3800,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissSnorlax = { } }; -const Gen2DistributionPokemon g2_pcny_splashSnorlax = { +static const Gen2DistributionPokemon g2_pcny_splashSnorlax = { .name = "Splash Snorlax", .originalTrainer = "", .originalTrainerID = 0, @@ -2971,11 +3811,12 @@ const Gen2DistributionPokemon g2_pcny_splashSnorlax = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 143, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 150, // Splash + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::SPLASH, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -3006,7 +3847,7 @@ const Gen2DistributionPokemon g2_pcny_splashSnorlax = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissSnorlax = { +static const Gen2DistributionPokemon g2_pcny_sweetKissSnorlax = { .name = "Sweet Kiss Snorlax", .originalTrainer = "", .originalTrainerID = 0, @@ -3017,11 +3858,12 @@ const Gen2DistributionPokemon g2_pcny_sweetKissSnorlax = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 143, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::SWEET_KISS, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -3052,7 +3894,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissSnorlax = { } }; -const Gen2DistributionPokemon g2_pcny_shinyArticuno = { +static const Gen2DistributionPokemon g2_pcny_shinyArticuno = { .name = "Shiny Articuno", .originalTrainer = "PCNYb", .originalTrainerID = 0, @@ -3063,13 +3905,14 @@ const Gen2DistributionPokemon g2_pcny_shinyArticuno = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 144, .held_item_index = 0, - .index_move1 = 54, // Mist - .index_move2 = 97, // Agility - .index_move3 = 170, // Mind Reader - .index_move4 = 58, // Ice Beam + .index_move1 = (uint8_t)Move::MIST, + .index_move2 = (uint8_t)Move::AGILITY, + .index_move3 = (uint8_t)Move::MIND_READER, + .index_move4 = (uint8_t)Move::ICE_BEAM, .original_trainer_ID = 0, .exp = getExpNeededForLevel(50, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -3098,7 +3941,7 @@ const Gen2DistributionPokemon g2_pcny_shinyArticuno = { } }; -const Gen2DistributionPokemon g2_pcny_shinyZapdos = { +static const Gen2DistributionPokemon g2_pcny_shinyZapdos = { .name = "Shiny Zapdos", .originalTrainer = "PCNYc", .originalTrainerID = 0, @@ -3109,13 +3952,14 @@ const Gen2DistributionPokemon g2_pcny_shinyZapdos = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 145, .held_item_index = 0, - .index_move1 = 86, // Thunder Wave - .index_move2 = 97, // Agility - .index_move3 = 197, // Detect - .index_move4 = 65, // Drill Peck + .index_move1 = (uint8_t)Move::THUNDER_WAVE, + .index_move2 = (uint8_t)Move::AGILITY, + .index_move3 = (uint8_t)Move::DETECT, + .index_move4 = (uint8_t)Move::DRILL_PECK, .original_trainer_ID = 0, .exp = getExpNeededForLevel(50, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -3144,7 +3988,7 @@ const Gen2DistributionPokemon g2_pcny_shinyZapdos = { } }; -const Gen2DistributionPokemon g2_pcny_shinyMoltres = { +static const Gen2DistributionPokemon g2_pcny_shinyMoltres = { .name = "Shiny Moltres", .originalTrainer = "PCNYd", .originalTrainerID = 0, @@ -3155,13 +3999,14 @@ const Gen2DistributionPokemon g2_pcny_shinyMoltres = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 146, .held_item_index = 0, - .index_move1 = 83, // Fire Spin - .index_move2 = 97, // Agility - .index_move3 = 203, // Endure - .index_move4 = 53, // Flamethrower + .index_move1 = (uint8_t)Move::FIRE_SPIN, + .index_move2 = (uint8_t)Move::AGILITY, + .index_move3 = (uint8_t)Move::ENDURE, + .index_move4 = (uint8_t)Move::FLAMETHROWER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(50, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -3190,7 +4035,7 @@ const Gen2DistributionPokemon g2_pcny_shinyMoltres = { } }; -const Gen2DistributionPokemon g2_pcny_hydroPumpDratini = { +static const Gen2DistributionPokemon g2_pcny_hydroPumpDratini = { .name = "Hydro Pump Dratini", .originalTrainer = "", .originalTrainerID = 0, @@ -3201,12 +4046,13 @@ const Gen2DistributionPokemon g2_pcny_hydroPumpDratini = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 147, .held_item_index = 0, - .index_move1 = 35, // Wrap - .index_move2 = 43, // Leer - .index_move3 = 56, // Hydro Pump + .index_move1 = (uint8_t)Move::WRAP, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::HYDRO_PUMP, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -3236,7 +4082,7 @@ const Gen2DistributionPokemon g2_pcny_hydroPumpDratini = { } }; -const Gen2DistributionPokemon g2_pcny_shinyMewtwo = { +static const Gen2DistributionPokemon g2_pcny_shinyMewtwo = { .name = "Shiny Mewtwo", .originalTrainer = "PCNYa", .originalTrainerID = 0, @@ -3247,13 +4093,14 @@ const Gen2DistributionPokemon g2_pcny_shinyMewtwo = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 150, .held_item_index = 0, - .index_move1 = 244, // Psych Up - .index_move2 = 248, // Future Sight - .index_move3 = 54, // Mist - .index_move4 = 94, // Psychic + .index_move1 = (uint8_t)Move::PSYCH_UP, + .index_move2 = (uint8_t)Move::FUTURE_SIGHT, + .index_move3 = (uint8_t)Move::MIST, + .index_move4 = (uint8_t)Move::PSYCHIC, .original_trainer_ID = 0, .exp = getExpNeededForLevel(70, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -3282,7 +4129,7 @@ const Gen2DistributionPokemon g2_pcny_shinyMewtwo = { } }; -const Gen2DistributionPokemon g2_pcny_shinyMew = { +static const Gen2DistributionPokemon g2_pcny_shinyMew = { .name = "Shiny Mew", .originalTrainer = "PCNYa", .originalTrainerID = 0, @@ -3293,10 +4140,11 @@ const Gen2DistributionPokemon g2_pcny_shinyMew = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 151, .held_item_index = 0, - .index_move1 = 244, // Pound + .index_move1 = (uint8_t)Move::POUND, .index_move2 = 0, .index_move3 = 0, .index_move4 = 0, @@ -3328,7 +4176,7 @@ const Gen2DistributionPokemon g2_pcny_shinyMew = { } }; -const Gen2DistributionPokemon g2_pcny_petalDanceChikorita = { +static const Gen2DistributionPokemon g2_pcny_petalDanceChikorita = { .name = "Petal Dance Chikorita", .originalTrainer = "", .originalTrainerID = 0, @@ -3339,12 +4187,13 @@ const Gen2DistributionPokemon g2_pcny_petalDanceChikorita = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 152, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 45, // Growl - .index_move3 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::PETAL_DANCE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -3374,7 +4223,7 @@ const Gen2DistributionPokemon g2_pcny_petalDanceChikorita = { } }; -const Gen2DistributionPokemon g2_pcny_shinyMeganium = { +static const Gen2DistributionPokemon g2_pcny_shinyMeganium = { .name = "Shiny Meganium", .originalTrainer = "PCNYb", .originalTrainerID = 0, @@ -3385,13 +4234,14 @@ const Gen2DistributionPokemon g2_pcny_shinyMeganium = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 154, .held_item_index = 0, - .index_move1 = 115, // Reflect - .index_move2 = 77, // PoisonPowder - .index_move3 = 235, // Synthesis - .index_move4 = 34, // Body Slam + .index_move1 = (uint8_t)Move::REFLECT, + .index_move2 = (uint8_t)Move::POISON_POWDER, + .index_move3 = (uint8_t)Move::SYNTHESIS, + .index_move4 = (uint8_t)Move::BODY_SLAM, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -3420,7 +4270,7 @@ const Gen2DistributionPokemon g2_pcny_shinyMeganium = { } }; -const Gen2DistributionPokemon g2_pcny_doubleEdgeCyndaquil = { +static const Gen2DistributionPokemon g2_pcny_doubleEdgeCyndaquil = { .name = "Double-Edge Cyndaquil", .originalTrainer = "", .originalTrainerID = 0, @@ -3431,12 +4281,13 @@ const Gen2DistributionPokemon g2_pcny_doubleEdgeCyndaquil = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 155, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 43, // Leer - .index_move3 = 38, // Double Edge + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::DOUBLE_EDGE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -3466,7 +4317,7 @@ const Gen2DistributionPokemon g2_pcny_doubleEdgeCyndaquil = { } }; -const Gen2DistributionPokemon g2_pcny_shinyTyphlosion = { +static const Gen2DistributionPokemon g2_pcny_shinyTyphlosion = { .name = "Shiny Typhlosion", .originalTrainer = "PCNYc", .originalTrainerID = 0, @@ -3477,13 +4328,14 @@ const Gen2DistributionPokemon g2_pcny_shinyTyphlosion = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 157, .held_item_index = 0, - .index_move1 = 108, // SmokeScreen - .index_move2 = 52, // Ember - .index_move3 = 98, // Quick Attack - .index_move4 = 172, // Flame Wheel + .index_move1 = (uint8_t)Move::SMOKESCREEN, + .index_move2 = (uint8_t)Move::EMBER, + .index_move3 = (uint8_t)Move::QUICK_ATTACK, + .index_move4 = (uint8_t)Move::FLAME_WHEEL, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -3512,7 +4364,7 @@ const Gen2DistributionPokemon g2_pcny_shinyTyphlosion = { } }; -const Gen2DistributionPokemon g2_pcny_submissionTotodile = { +static const Gen2DistributionPokemon g2_pcny_submissionTotodile = { .name = "Submission Totodile", .originalTrainer = "", .originalTrainerID = 0, @@ -3523,12 +4375,13 @@ const Gen2DistributionPokemon g2_pcny_submissionTotodile = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 158, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 43, // Leer - .index_move3 = 66, // Submission + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SUBMISSION, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -3558,7 +4411,7 @@ const Gen2DistributionPokemon g2_pcny_submissionTotodile = { } }; -const Gen2DistributionPokemon g2_pcny_shinyFeraligatr = { +static const Gen2DistributionPokemon g2_pcny_shinyFeraligatr = { .name = "Shiny Feraligatr", .originalTrainer = "PCNYd", .originalTrainerID = 0, @@ -3569,13 +4422,14 @@ const Gen2DistributionPokemon g2_pcny_shinyFeraligatr = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 160, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 44, // Bite - .index_move3 = 184, // Scary Face - .index_move4 = 163, // Slash + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::BITE, + .index_move3 = (uint8_t)Move::SCARY_FACE, + .index_move4 = (uint8_t)Move::SLASH, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -3604,7 +4458,7 @@ const Gen2DistributionPokemon g2_pcny_shinyFeraligatr = { } }; -const Gen2DistributionPokemon g2_pcny_dizzyPunchSentret = { +static const Gen2DistributionPokemon g2_pcny_dizzyPunchSentret = { .name = "Dizzy Punch Sentret", .originalTrainer = "", .originalTrainerID = 0, @@ -3615,12 +4469,13 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchSentret = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 161, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 111, // Defense Curl - .index_move3 = 146, // Dizzy Punch + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::DIZZY_PUNCH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -3650,7 +4505,7 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchSentret = { } }; -const Gen2DistributionPokemon g2_pcny_nightShadeHoothoot = { +static const Gen2DistributionPokemon g2_pcny_nightShadeHoothoot = { .name = "Night Shade Hoothoot", .originalTrainer = "", .originalTrainerID = 0, @@ -3661,12 +4516,13 @@ const Gen2DistributionPokemon g2_pcny_nightShadeHoothoot = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 163, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 45, // Growl - .index_move3 = 101, // Night Shade + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::NIGHT_SHADE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -3696,7 +4552,7 @@ const Gen2DistributionPokemon g2_pcny_nightShadeHoothoot = { } }; -const Gen2DistributionPokemon g2_pcny_barrierLedyba = { +static const Gen2DistributionPokemon g2_pcny_barrierLedyba = { .name = "Barrier Ledyba", .originalTrainer = "", .originalTrainerID = 0, @@ -3707,11 +4563,12 @@ const Gen2DistributionPokemon g2_pcny_barrierLedyba = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 165, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 112, // Barrier + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::BARRIER, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -3742,7 +4599,7 @@ const Gen2DistributionPokemon g2_pcny_barrierLedyba = { } }; -const Gen2DistributionPokemon g2_pcny_growthSpinarak = { +static const Gen2DistributionPokemon g2_pcny_growthSpinarak = { .name = "Growth Spinarak", .originalTrainer = "", .originalTrainerID = 0, @@ -3753,12 +4610,13 @@ const Gen2DistributionPokemon g2_pcny_growthSpinarak = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 167, .held_item_index = 0, - .index_move1 = 40, // Poison Sting - .index_move2 = 81, // String Shot - .index_move3 = 74, // Growth + .index_move1 = (uint8_t)Move::POISON_STING, + .index_move2 = (uint8_t)Move::STRING_SHOT, + .index_move3 = (uint8_t)Move::GROWTH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -3788,7 +4646,7 @@ const Gen2DistributionPokemon g2_pcny_growthSpinarak = { } }; -const Gen2DistributionPokemon g2_pcny_lightScreenChinchou = { +static const Gen2DistributionPokemon g2_pcny_lightScreenChinchou = { .name = "Light Screen Chinchou", .originalTrainer = "", .originalTrainerID = 0, @@ -3799,13 +4657,14 @@ const Gen2DistributionPokemon g2_pcny_lightScreenChinchou = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 170, .held_item_index = 0, - .index_move1 = 145, // Bubble - .index_move2 = 86, // Thunder Wave - .index_move3 = 48, // Supersonic - .index_move4 = 113, // Light Screen + .index_move1 = (uint8_t)Move::BUBBLE, + .index_move2 = (uint8_t)Move::THUNDER_WAVE, + .index_move3 = (uint8_t)Move::SUPERSONIC, + .index_move4 = (uint8_t)Move::LIGHT_SCREEN, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -3834,7 +4693,7 @@ const Gen2DistributionPokemon g2_pcny_lightScreenChinchou = { } }; -const Gen2DistributionPokemon g2_pcny_dizzyPunchPichu = { +static const Gen2DistributionPokemon g2_pcny_dizzyPunchPichu = { .name = "Dizzy Punch Pichu", .originalTrainer = "", .originalTrainerID = 0, @@ -3845,12 +4704,13 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchPichu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 172, .held_item_index = 0, - .index_move1 = 84, // Thundershock - .index_move2 = 204, // Charm - .index_move3 = 146, // Dizzy Punch + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::DIZZY_PUNCH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -3880,7 +4740,7 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchPichu = { } }; -const Gen2DistributionPokemon g2_pcny_petalDancePichu = { +static const Gen2DistributionPokemon g2_pcny_petalDancePichu = { .name = "Petal Dance Pichu", .originalTrainer = "", .originalTrainerID = 0, @@ -3891,12 +4751,13 @@ const Gen2DistributionPokemon g2_pcny_petalDancePichu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 172, .held_item_index = 0, - .index_move1 = 84, // Thundershock - .index_move2 = 204, // Charm - .index_move3 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::PETAL_DANCE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -3926,7 +4787,7 @@ const Gen2DistributionPokemon g2_pcny_petalDancePichu = { } }; -const Gen2DistributionPokemon g2_pcny_scaryFacePichu = { +static const Gen2DistributionPokemon g2_pcny_scaryFacePichu = { .name = "Scary Face Pichu", .originalTrainer = "", .originalTrainerID = 0, @@ -3937,12 +4798,13 @@ const Gen2DistributionPokemon g2_pcny_scaryFacePichu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 172, .held_item_index = 0, - .index_move1 = 84, // Thundershock - .index_move2 = 204, // Charm - .index_move3 = 184, // Scary Face + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::SCARY_FACE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -3972,7 +4834,7 @@ const Gen2DistributionPokemon g2_pcny_scaryFacePichu = { } }; -const Gen2DistributionPokemon g2_pcny_singPichu = { +static const Gen2DistributionPokemon g2_pcny_singPichu = { .name = "Sing Pichu", .originalTrainer = "", .originalTrainerID = 0, @@ -3983,12 +4845,13 @@ const Gen2DistributionPokemon g2_pcny_singPichu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 172, .held_item_index = 0, - .index_move1 = 84, // Thundershock - .index_move2 = 204, // Charm - .index_move3 = 47, // Sing + .index_move1 = (uint8_t)Move::THUNDER_SHOCK, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::SING, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4018,7 +4881,7 @@ const Gen2DistributionPokemon g2_pcny_singPichu = { } }; -const Gen2DistributionPokemon g2_pcny_petalDanceCleffa = { +static const Gen2DistributionPokemon g2_pcny_petalDanceCleffa = { .name = "Petal Dance Cleffa", .originalTrainer = "", .originalTrainerID = 0, @@ -4029,13 +4892,14 @@ const Gen2DistributionPokemon g2_pcny_petalDanceCleffa = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 173, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 204, // Charm - .index_move3 = 227, // Encore - .index_move4 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = (uint8_t)Move::PETAL_DANCE, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4064,8 +4928,8 @@ const Gen2DistributionPokemon g2_pcny_petalDanceCleffa = { } }; -const Gen2DistributionPokemon g2_pcny_scaryFaceCleffa = { - .name = "Petal Dance Cleffa", +static const Gen2DistributionPokemon g2_pcny_scaryFaceCleffa = { + .name = "Scary Face Cleffa", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -4075,13 +4939,14 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceCleffa = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 173, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 204, // Charm - .index_move3 = 227, // Encore - .index_move4 = 184, // Scary Face + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = (uint8_t)Move::SCARY_FACE, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4110,7 +4975,7 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceCleffa = { } }; -const Gen2DistributionPokemon g2_pcny_swiftCleffa = { +static const Gen2DistributionPokemon g2_pcny_swiftCleffa = { .name = "Swift Cleffa", .originalTrainer = "", .originalTrainerID = 0, @@ -4121,13 +4986,14 @@ const Gen2DistributionPokemon g2_pcny_swiftCleffa = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 173, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 204, // Charm - .index_move3 = 227, // Encore - .index_move4 = 129, // Swift + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::ENCORE, + .index_move4 = (uint8_t)Move::SWIFT, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4156,7 +5022,7 @@ const Gen2DistributionPokemon g2_pcny_swiftCleffa = { } }; -const Gen2DistributionPokemon g2_pcny_mimicIgglybuff = { +static const Gen2DistributionPokemon g2_pcny_mimicIgglybuff = { .name = "Mimic Igglybuff", .originalTrainer = "", .originalTrainerID = 0, @@ -4167,13 +5033,14 @@ const Gen2DistributionPokemon g2_pcny_mimicIgglybuff = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 174, .held_item_index = 0, - .index_move1 = 47, // Sing - .index_move2 = 204, // Charm - .index_move3 = 111, // Defense Curl - .index_move4 = 102, // Mimic + .index_move1 = (uint8_t)Move::SING, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::DEFENSE_CURL, + .index_move4 = (uint8_t)Move::MIMIC, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4202,7 +5069,7 @@ const Gen2DistributionPokemon g2_pcny_mimicIgglybuff = { } }; -const Gen2DistributionPokemon g2_pcny_petalDanceIgglybuff = { +static const Gen2DistributionPokemon g2_pcny_petalDanceIgglybuff = { .name = "Petal Dance Igglybuff", .originalTrainer = "", .originalTrainerID = 0, @@ -4213,13 +5080,14 @@ const Gen2DistributionPokemon g2_pcny_petalDanceIgglybuff = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 174, .held_item_index = 0, - .index_move1 = 47, // Sing - .index_move2 = 204, // Charm - .index_move3 = 111, // Defense Curl - .index_move4 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::SING, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::DEFENSE_CURL, + .index_move4 = (uint8_t)Move::PETAL_DANCE, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4248,8 +5116,8 @@ const Gen2DistributionPokemon g2_pcny_petalDanceIgglybuff = { } }; -const Gen2DistributionPokemon g2_pcny_scaryFaceIgglybuff = { - .name = "Petal Dance Igglybuff", +static const Gen2DistributionPokemon g2_pcny_scaryFaceIgglybuff = { + .name = "Scary Face Igglybuff", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -4259,13 +5127,14 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceIgglybuff = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 174, .held_item_index = 0, - .index_move1 = 47, // Sing - .index_move2 = 204, // Charm - .index_move3 = 111, // Defense Curl - .index_move4 = 184, // Scary Face + .index_move1 = (uint8_t)Move::SING, + .index_move2 = (uint8_t)Move::CHARM, + .index_move3 = (uint8_t)Move::DEFENSE_CURL, + .index_move4 = (uint8_t)Move::SCARY_FACE, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -4294,7 +5163,7 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceIgglybuff = { } }; -const Gen2DistributionPokemon g2_pcny_safeguardNatu = { +static const Gen2DistributionPokemon g2_pcny_safeguardNatu = { .name = "Safeguard Natu", .originalTrainer = "", .originalTrainerID = 0, @@ -4305,12 +5174,13 @@ const Gen2DistributionPokemon g2_pcny_safeguardNatu = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 177, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 43, // Leer - .index_move3 = 219, // Safeguard + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SAFEGUARD, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4340,7 +5210,7 @@ const Gen2DistributionPokemon g2_pcny_safeguardNatu = { } }; -const Gen2DistributionPokemon g2_pcny_dizzyPunchMarill = { +static const Gen2DistributionPokemon g2_pcny_dizzyPunchMarill = { .name = "Dizzy Punch Marill", .originalTrainer = "", .originalTrainerID = 0, @@ -4351,12 +5221,13 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchMarill = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 183, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 111, // Defense Curl - .index_move3 = 146, // Dizzy Punch + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::DIZZY_PUNCH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -4386,7 +5257,7 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchMarill = { } }; -const Gen2DistributionPokemon g2_pcny_hydroPumpMarill = { +static const Gen2DistributionPokemon g2_pcny_hydroPumpMarill = { .name = "Hydro Pump Marill", .originalTrainer = "", .originalTrainerID = 0, @@ -4397,12 +5268,13 @@ const Gen2DistributionPokemon g2_pcny_hydroPumpMarill = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 183, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 111, // Defense Curl - .index_move3 = 56, // Hydro Pump + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::HYDRO_PUMP, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -4432,8 +5304,8 @@ const Gen2DistributionPokemon g2_pcny_hydroPumpMarill = { } }; -const Gen2DistributionPokemon g2_pcny_scaryFaceMarill = { - .name = "Hydro Pump Marill", +static const Gen2DistributionPokemon g2_pcny_scaryFaceMarill = { + .name = "Scary Face Marill", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -4443,12 +5315,13 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceMarill = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 183, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 111, // Defense Curl - .index_move3 = 184, // Scary Face + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::SCARY_FACE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -4478,7 +5351,7 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceMarill = { } }; -const Gen2DistributionPokemon g2_pcny_substituteSudowoodo = { +static const Gen2DistributionPokemon g2_pcny_substituteSudowoodo = { .name = "Substitute Sudowoodo", .originalTrainer = "", .originalTrainerID = 0, @@ -4489,12 +5362,13 @@ const Gen2DistributionPokemon g2_pcny_substituteSudowoodo = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 185, .held_item_index = 0, - .index_move1 = 88, // Rock Throw - .index_move2 = 102, // Mimic - .index_move3 = 164, // Substitute + .index_move1 = (uint8_t)Move::ROCK_THROW, + .index_move2 = (uint8_t)Move::MIMIC, + .index_move3 = (uint8_t)Move::SUBSTITUTE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4524,7 +5398,7 @@ const Gen2DistributionPokemon g2_pcny_substituteSudowoodo = { } }; -const Gen2DistributionPokemon g2_pcny_agilityHoppip = { +static const Gen2DistributionPokemon g2_pcny_agilityHoppip = { .name = "Agility Hoppip", .originalTrainer = "", .originalTrainerID = 0, @@ -4535,13 +5409,14 @@ const Gen2DistributionPokemon g2_pcny_agilityHoppip = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 187, .held_item_index = 0, - .index_move1 = 150, // Splash - .index_move2 = 235, // Synthesis - .index_move3 = 39, // Tail Whip - .index_move4 = 97, // Agility + .index_move1 = (uint8_t)Move::SPLASH, + .index_move2 = (uint8_t)Move::SYNTHESIS, + .index_move3 = (uint8_t)Move::TAIL_WHIP, + .index_move4 = (uint8_t)Move::AGILITY, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -4570,7 +5445,7 @@ const Gen2DistributionPokemon g2_pcny_agilityHoppip = { } }; -const Gen2DistributionPokemon g2_pcny_mimicAipom = { +static const Gen2DistributionPokemon g2_pcny_mimicAipom = { .name = "Mimic Aipom", .originalTrainer = "", .originalTrainerID = 0, @@ -4581,12 +5456,13 @@ const Gen2DistributionPokemon g2_pcny_mimicAipom = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 190, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 39, // Tail Whip - .index_move3 = 102, // Mimic + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::MIMIC, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -4616,7 +5492,7 @@ const Gen2DistributionPokemon g2_pcny_mimicAipom = { } }; -const Gen2DistributionPokemon g2_pcny_splashSunkern = { +static const Gen2DistributionPokemon g2_pcny_splashSunkern = { .name = "Splash Sunkern", .originalTrainer = "", .originalTrainerID = 0, @@ -4627,12 +5503,13 @@ const Gen2DistributionPokemon g2_pcny_splashSunkern = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 191, .held_item_index = 0, - .index_move1 = 71, // Absorb - .index_move2 = 74, // Growth - .index_move3 = 150, // Splash + .index_move1 = (uint8_t)Move::ABSORB, + .index_move2 = (uint8_t)Move::GROWTH, + .index_move3 = (uint8_t)Move::SPLASH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -4662,7 +5539,7 @@ const Gen2DistributionPokemon g2_pcny_splashSunkern = { } }; -const Gen2DistributionPokemon g2_pcny_steelWingYanma = { +static const Gen2DistributionPokemon g2_pcny_steelWingYanma = { .name = "Steel Wing Yanma", .originalTrainer = "", .originalTrainerID = 0, @@ -4673,12 +5550,13 @@ const Gen2DistributionPokemon g2_pcny_steelWingYanma = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 193, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 193, // Foresight - .index_move3 = 211, // Steel Wing + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::FORESIGHT, + .index_move3 = (uint8_t)Move::STEEL_WING, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4708,8 +5586,8 @@ const Gen2DistributionPokemon g2_pcny_steelWingYanma = { } }; -const Gen2DistributionPokemon g2_pcny_sweetKissYanma = { - .name = "Steel Wing Yanma", +static const Gen2DistributionPokemon g2_pcny_sweetKissYanma = { + .name = "Sweet Kiss Yanma", .originalTrainer = "", .originalTrainerID = 0, .setPlayerAsOriginalTrainer = true, @@ -4719,12 +5597,13 @@ const Gen2DistributionPokemon g2_pcny_sweetKissYanma = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 193, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 193, // Foresight - .index_move3 = 186, // Sweet Kiss + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::FORESIGHT, + .index_move3 = (uint8_t)Move::SWEET_KISS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4754,7 +5633,7 @@ const Gen2DistributionPokemon g2_pcny_sweetKissYanma = { } }; -const Gen2DistributionPokemon g2_pcny_bellyDrumWooper = { +static const Gen2DistributionPokemon g2_pcny_bellyDrumWooper = { .name = "Belly Drum Wooper", .originalTrainer = "", .originalTrainerID = 0, @@ -4765,12 +5644,13 @@ const Gen2DistributionPokemon g2_pcny_bellyDrumWooper = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 194, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 39, // Tail Whip - .index_move3 = 187, // Belly Drum + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::BELLY_DRUM, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4800,7 +5680,7 @@ const Gen2DistributionPokemon g2_pcny_bellyDrumWooper = { } }; -const Gen2DistributionPokemon g2_pcny_scaryFaceWooper = { +static const Gen2DistributionPokemon g2_pcny_scaryFaceWooper = { .name = "Scary Face Wooper", .originalTrainer = "", .originalTrainerID = 0, @@ -4811,12 +5691,13 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceWooper = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 194, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 39, // Tail Whip - .index_move3 = 184, // Scary Face + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::TAIL_WHIP, + .index_move3 = (uint8_t)Move::SCARY_FACE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -4846,7 +5727,7 @@ const Gen2DistributionPokemon g2_pcny_scaryFaceWooper = { } }; -const Gen2DistributionPokemon g2_pcny_beatUpMurkrow = { +static const Gen2DistributionPokemon g2_pcny_beatUpMurkrow = { .name = "Beat Up Murkrow", .originalTrainer = "", .originalTrainerID = 0, @@ -4857,11 +5738,12 @@ const Gen2DistributionPokemon g2_pcny_beatUpMurkrow = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 198, .held_item_index = 0, - .index_move1 = 64, // Peck - .index_move2 = 251, // Beat Up + .index_move1 = (uint8_t)Move::PECK, + .index_move2 = (uint8_t)Move::BEAT_UP, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -4892,7 +5774,7 @@ const Gen2DistributionPokemon g2_pcny_beatUpMurkrow = { } }; -const Gen2DistributionPokemon g2_pcny_hypnosisMisdreavus = { +static const Gen2DistributionPokemon g2_pcny_hypnosisMisdreavus = { .name = "Hypnosis Misdreavus", .originalTrainer = "", .originalTrainerID = 0, @@ -4903,12 +5785,13 @@ const Gen2DistributionPokemon g2_pcny_hypnosisMisdreavus = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 200, .held_item_index = 0, - .index_move1 = 45, // Growl - .index_move2 = 149, // Psywave - .index_move3 = 95, // Hypnosis + .index_move1 = (uint8_t)Move::GROWL, + .index_move2 = (uint8_t)Move::PSYWAVE, + .index_move3 = (uint8_t)Move::HYPNOSIS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), @@ -4938,7 +5821,7 @@ const Gen2DistributionPokemon g2_pcny_hypnosisMisdreavus = { } }; -const Gen2DistributionPokemon g2_pcny_mimicWobbuffet = { +static const Gen2DistributionPokemon g2_pcny_mimicWobbuffet = { .name = "Mimic Wobbuffet", .originalTrainer = "", .originalTrainerID = 0, @@ -4949,13 +5832,14 @@ const Gen2DistributionPokemon g2_pcny_mimicWobbuffet = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 202, .held_item_index = 0, - .index_move1 = 243, // Mirror Coat - .index_move2 = 219, // Safeguard - .index_move3 = 194, // Destiny Bond - .index_move4 = 102, // Mimic + .index_move1 = (uint8_t)Move::MIRROR_COAT, + .index_move2 = (uint8_t)Move::SAFEGUARD, + .index_move3 = (uint8_t)Move::DESTINY_BOND, + .index_move4 = (uint8_t)Move::MIMIC, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), .hp_effort_value = 0, @@ -4984,7 +5868,7 @@ const Gen2DistributionPokemon g2_pcny_mimicWobbuffet = { } }; -const Gen2DistributionPokemon g2_pcny_substitutePineco = { +static const Gen2DistributionPokemon g2_pcny_substitutePineco = { .name = "Substitute Pineco", .originalTrainer = "", .originalTrainerID = 0, @@ -4995,12 +5879,13 @@ const Gen2DistributionPokemon g2_pcny_substitutePineco = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 204, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 182, // Protect - .index_move3 = 164, // Substitute + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::PROTECT, + .index_move3 = (uint8_t)Move::SUBSTITUTE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5030,7 +5915,7 @@ const Gen2DistributionPokemon g2_pcny_substitutePineco = { } }; -const Gen2DistributionPokemon g2_pcny_furyAttackDunsparce = { +static const Gen2DistributionPokemon g2_pcny_furyAttackDunsparce = { .name = "Fury Attack Dunsparce", .originalTrainer = "", .originalTrainerID = 0, @@ -5041,12 +5926,13 @@ const Gen2DistributionPokemon g2_pcny_furyAttackDunsparce = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 206, .held_item_index = 0, - .index_move1 = 99, // Rage - .index_move2 = 111, // Defense Curl - .index_move3 = 31, // Fury Attack + .index_move1 = (uint8_t)Move::RAGE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::FURY_ATTACK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5076,7 +5962,7 @@ const Gen2DistributionPokemon g2_pcny_furyAttackDunsparce = { } }; -const Gen2DistributionPokemon g2_pcny_hornDrillDunsparce = { +static const Gen2DistributionPokemon g2_pcny_hornDrillDunsparce = { .name = "Horn Drill Dunsparce", .originalTrainer = "", .originalTrainerID = 0, @@ -5087,12 +5973,13 @@ const Gen2DistributionPokemon g2_pcny_hornDrillDunsparce = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 206, .held_item_index = 0, - .index_move1 = 99, // Rage - .index_move2 = 111, // Defense Curl - .index_move3 = 32, // Horn Drill + .index_move1 = (uint8_t)Move::RAGE, + .index_move2 = (uint8_t)Move::DEFENSE_CURL, + .index_move3 = (uint8_t)Move::HORN_DRILL, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5122,7 +6009,7 @@ const Gen2DistributionPokemon g2_pcny_hornDrillDunsparce = { } }; -const Gen2DistributionPokemon g2_pcny_lovelyKissSnubbull = { +static const Gen2DistributionPokemon g2_pcny_lovelyKissSnubbull = { .name = "Lovely Kiss Snubbull", .originalTrainer = "", .originalTrainerID = 0, @@ -5133,13 +6020,14 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissSnubbull = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 209, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 184, // Scary Face - .index_move3 = 39, // Tail Whip - .index_move4 = 142, // Lovely Kiss + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::SCARY_FACE, + .index_move3 = (uint8_t)Move::TAIL_WHIP, + .index_move4 = (uint8_t)Move::LOVELY_KISS, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_FAST), .hp_effort_value = 0, @@ -5168,7 +6056,7 @@ const Gen2DistributionPokemon g2_pcny_lovelyKissSnubbull = { } }; -const Gen2DistributionPokemon g2_pcny_doubleEdgeQwilfish = { +static const Gen2DistributionPokemon g2_pcny_doubleEdgeQwilfish = { .name = "Double-Edge Qwilfish", .originalTrainer = "", .originalTrainerID = 0, @@ -5179,12 +6067,13 @@ const Gen2DistributionPokemon g2_pcny_doubleEdgeQwilfish = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 211, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 40, // Poison Sting - .index_move3 = 38, // Double-Edge + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::POISON_STING, + .index_move3 = (uint8_t)Move::DOUBLE_EDGE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5214,7 +6103,7 @@ const Gen2DistributionPokemon g2_pcny_doubleEdgeQwilfish = { } }; -const Gen2DistributionPokemon g2_pcny_seismicTossHeracross = { +static const Gen2DistributionPokemon g2_pcny_seismicTossHeracross = { .name = "Seismic Toss Heracross", .originalTrainer = "", .originalTrainerID = 0, @@ -5225,12 +6114,13 @@ const Gen2DistributionPokemon g2_pcny_seismicTossHeracross = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 214, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 43, // Leer - .index_move3 = 69, // Seismic Toss + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SEISMIC_TOSS, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -5260,7 +6150,7 @@ const Gen2DistributionPokemon g2_pcny_seismicTossHeracross = { } }; -const Gen2DistributionPokemon g2_pcny_moonlightSneasel = { +static const Gen2DistributionPokemon g2_pcny_moonlightSneasel = { .name = "Moonlight Sneasel", .originalTrainer = "", .originalTrainerID = 0, @@ -5271,12 +6161,13 @@ const Gen2DistributionPokemon g2_pcny_moonlightSneasel = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 215, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 43, // Leer - .index_move3 = 236, // Moonlight + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::MOONLIGHT, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), @@ -5306,7 +6197,7 @@ const Gen2DistributionPokemon g2_pcny_moonlightSneasel = { } }; -const Gen2DistributionPokemon g2_pcny_sweetScentTeddiursa = { +static const Gen2DistributionPokemon g2_pcny_sweetScentTeddiursa = { .name = "Sweet Scent Teddiursa", .originalTrainer = "", .originalTrainerID = 0, @@ -5317,12 +6208,13 @@ const Gen2DistributionPokemon g2_pcny_sweetScentTeddiursa = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 216, .held_item_index = 0, - .index_move1 = 10, // Scratch - .index_move2 = 43, // Leer - .index_move3 = 230, // Sweet Scent + .index_move1 = (uint8_t)Move::SCRATCH, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::SWEET_SCENT, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5352,7 +6244,7 @@ const Gen2DistributionPokemon g2_pcny_sweetScentTeddiursa = { } }; -const Gen2DistributionPokemon g2_pcny_whirlwindSwinub = { +static const Gen2DistributionPokemon g2_pcny_whirlwindSwinub = { .name = "Whirlwind Swinub", .originalTrainer = "", .originalTrainerID = 0, @@ -5363,11 +6255,12 @@ const Gen2DistributionPokemon g2_pcny_whirlwindSwinub = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 220, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 18, // Whirlwind + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::WHIRLWIND, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5398,7 +6291,7 @@ const Gen2DistributionPokemon g2_pcny_whirlwindSwinub = { } }; -const Gen2DistributionPokemon g2_pcny_amnesiaRemoraid = { +static const Gen2DistributionPokemon g2_pcny_amnesiaRemoraid = { .name = "Amnesia Remoraid", .originalTrainer = "", .originalTrainerID = 0, @@ -5409,11 +6302,12 @@ const Gen2DistributionPokemon g2_pcny_amnesiaRemoraid = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 223, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 133, // Amnesia + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::AMNESIA, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5444,7 +6338,7 @@ const Gen2DistributionPokemon g2_pcny_amnesiaRemoraid = { } }; -const Gen2DistributionPokemon g2_pcny_mistRemoraid = { +static const Gen2DistributionPokemon g2_pcny_mistRemoraid = { .name = "Mist Remoraid", .originalTrainer = "", .originalTrainerID = 0, @@ -5455,11 +6349,12 @@ const Gen2DistributionPokemon g2_pcny_mistRemoraid = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 223, .held_item_index = 0, - .index_move1 = 55, // Water Gun - .index_move2 = 54, // Mist + .index_move1 = (uint8_t)Move::WATER_GUN, + .index_move2 = (uint8_t)Move::MIST, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5490,7 +6385,7 @@ const Gen2DistributionPokemon g2_pcny_mistRemoraid = { } }; -const Gen2DistributionPokemon g2_pcny_payDayDelibird = { +static const Gen2DistributionPokemon g2_pcny_payDayDelibird = { .name = "Pay Day Delibird", .originalTrainer = "", .originalTrainerID = 0, @@ -5501,11 +6396,12 @@ const Gen2DistributionPokemon g2_pcny_payDayDelibird = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 225, .held_item_index = 0, - .index_move1 = 217, // Present - .index_move2 = 6, // Pay Day + .index_move1 = (uint8_t)Move::PRESENT, + .index_move2 = (uint8_t)Move::PAY_DAY, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5536,7 +6432,7 @@ const Gen2DistributionPokemon g2_pcny_payDayDelibird = { } }; -const Gen2DistributionPokemon g2_pcny_spikesDelibird = { +static const Gen2DistributionPokemon g2_pcny_spikesDelibird = { .name = "Spikes Delibird", .originalTrainer = "", .originalTrainerID = 0, @@ -5547,11 +6443,12 @@ const Gen2DistributionPokemon g2_pcny_spikesDelibird = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 225, .held_item_index = 0, - .index_move1 = 217, // Present - .index_move2 = 191, // Spikes + .index_move1 = (uint8_t)Move::PRESENT, + .index_move2 = (uint8_t)Move::SPIKES, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5582,7 +6479,7 @@ const Gen2DistributionPokemon g2_pcny_spikesDelibird = { } }; -const Gen2DistributionPokemon g2_pcny_gustMantine = { +static const Gen2DistributionPokemon g2_pcny_gustMantine = { .name = "Gust Mantine", .originalTrainer = "", .originalTrainerID = 0, @@ -5593,12 +6490,13 @@ const Gen2DistributionPokemon g2_pcny_gustMantine = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 226, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 145, // Bubble - .index_move3 = 16, // Gust + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::BUBBLE, + .index_move3 = (uint8_t)Move::GUST, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -5628,7 +6526,7 @@ const Gen2DistributionPokemon g2_pcny_gustMantine = { } }; -const Gen2DistributionPokemon g2_pcny_furyCutterSkarmory = { +static const Gen2DistributionPokemon g2_pcny_furyCutterSkarmory = { .name = "Fury Cutter Skarmory", .originalTrainer = "", .originalTrainerID = 0, @@ -5639,12 +6537,13 @@ const Gen2DistributionPokemon g2_pcny_furyCutterSkarmory = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 227, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 64, // Peck - .index_move3 = 210, // Fury Cutter + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::PECK, + .index_move3 = (uint8_t)Move::FURY_CUTTER, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -5674,7 +6573,7 @@ const Gen2DistributionPokemon g2_pcny_furyCutterSkarmory = { } }; -const Gen2DistributionPokemon g2_pcny_absorbPhanpy = { +static const Gen2DistributionPokemon g2_pcny_absorbPhanpy = { .name = "Absorb Phanpy", .originalTrainer = "", .originalTrainerID = 0, @@ -5685,12 +6584,13 @@ const Gen2DistributionPokemon g2_pcny_absorbPhanpy = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 231, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 45, // Growl - .index_move3 = 71, // Absorb + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::ABSORB, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5720,7 +6620,7 @@ const Gen2DistributionPokemon g2_pcny_absorbPhanpy = { } }; -const Gen2DistributionPokemon g2_pcny_safeguardStantler = { +static const Gen2DistributionPokemon g2_pcny_safeguardStantler = { .name = "Safeguard Stantler", .originalTrainer = "", .originalTrainerID = 0, @@ -5731,11 +6631,12 @@ const Gen2DistributionPokemon g2_pcny_safeguardStantler = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 234, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 219, // Safeguard + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::SAFEGUARD, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5766,7 +6667,7 @@ const Gen2DistributionPokemon g2_pcny_safeguardStantler = { } }; -const Gen2DistributionPokemon g2_pcny_rageTyrogue = { +static const Gen2DistributionPokemon g2_pcny_rageTyrogue = { .name = "Rage Tyrogue", .originalTrainer = "", .originalTrainerID = 0, @@ -5777,11 +6678,12 @@ const Gen2DistributionPokemon g2_pcny_rageTyrogue = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 236, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 99, // Rage + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::RAGE, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -5812,7 +6714,7 @@ const Gen2DistributionPokemon g2_pcny_rageTyrogue = { } }; -const Gen2DistributionPokemon g2_pcny_metronomeSmoochum = { +static const Gen2DistributionPokemon g2_pcny_metronomeSmoochum = { .name = "Metronome Smoochum", .originalTrainer = "", .originalTrainerID = 0, @@ -5823,12 +6725,13 @@ const Gen2DistributionPokemon g2_pcny_metronomeSmoochum = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 238, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 122, // Lick - .index_move3 = 118, // Metronome + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::LICK, + .index_move3 = (uint8_t)Move::METRONOME, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5858,7 +6761,7 @@ const Gen2DistributionPokemon g2_pcny_metronomeSmoochum = { } }; -const Gen2DistributionPokemon g2_pcny_petalDanceSmoochum = { +static const Gen2DistributionPokemon g2_pcny_petalDanceSmoochum = { .name = "Petal Dance Smoochum", .originalTrainer = "", .originalTrainerID = 0, @@ -5869,12 +6772,13 @@ const Gen2DistributionPokemon g2_pcny_petalDanceSmoochum = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 238, .held_item_index = 0, - .index_move1 = 1, // Pound - .index_move2 = 122, // Lick - .index_move3 = 80, // Petal Dance + .index_move1 = (uint8_t)Move::POUND, + .index_move2 = (uint8_t)Move::LICK, + .index_move3 = (uint8_t)Move::PETAL_DANCE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5904,7 +6808,7 @@ const Gen2DistributionPokemon g2_pcny_petalDanceSmoochum = { } }; -const Gen2DistributionPokemon g2_pcny_dizzyPunchElekid = { +static const Gen2DistributionPokemon g2_pcny_dizzyPunchElekid = { .name = "Dizzy Punch Elekid", .originalTrainer = "", .originalTrainerID = 0, @@ -5915,12 +6819,13 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchElekid = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 239, .held_item_index = 0, - .index_move1 = 98, // Quick Attack - .index_move2 = 43, // Leer - .index_move3 = 146, // Dizzy Punch + .index_move1 = (uint8_t)Move::QUICK_ATTACK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::DIZZY_PUNCH, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5950,7 +6855,7 @@ const Gen2DistributionPokemon g2_pcny_dizzyPunchElekid = { } }; -const Gen2DistributionPokemon g2_pcny_pursuitElekid = { +static const Gen2DistributionPokemon g2_pcny_pursuitElekid = { .name = "Pursuit Elekid", .originalTrainer = "", .originalTrainerID = 0, @@ -5961,12 +6866,13 @@ const Gen2DistributionPokemon g2_pcny_pursuitElekid = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 239, .held_item_index = 0, - .index_move1 = 98, // Quick Attack - .index_move2 = 43, // Leer - .index_move3 = 228, // Pursuit + .index_move1 = (uint8_t)Move::QUICK_ATTACK, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::PURSUIT, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_FAST), @@ -5996,7 +6902,7 @@ const Gen2DistributionPokemon g2_pcny_pursuitElekid = { } }; -const Gen2DistributionPokemon g2_pcny_faintAttackMagby = { +static const Gen2DistributionPokemon g2_pcny_faintAttackMagby = { .name = "Feint Attack Magby", .originalTrainer = "", .originalTrainerID = 0, @@ -6007,11 +6913,12 @@ const Gen2DistributionPokemon g2_pcny_faintAttackMagby = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 240, .held_item_index = 0, - .index_move1 = 52, // Ember - .index_move2 = 185, // Feint Attack + .index_move1 = (uint8_t)Move::EMBER, + .index_move2 = (uint8_t)Move::FEINT_ATTACK, .index_move3 = 0, .index_move4 = 0, .original_trainer_ID = 0, @@ -6042,7 +6949,7 @@ const Gen2DistributionPokemon g2_pcny_faintAttackMagby = { } }; -const Gen2DistributionPokemon g2_pcny_megaKickMiltank = { +static const Gen2DistributionPokemon g2_pcny_megaKickMiltank = { .name = "Mega Kick Miltank", .originalTrainer = "", .originalTrainerID = 0, @@ -6053,12 +6960,13 @@ const Gen2DistributionPokemon g2_pcny_megaKickMiltank = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 241, .held_item_index = 0, - .index_move1 = 33, // Tackle - .index_move2 = 45, // Growl - .index_move3 = 25, // Mega Kick + .index_move1 = (uint8_t)Move::TACKLE, + .index_move2 = (uint8_t)Move::GROWL, + .index_move3 = (uint8_t)Move::MEGA_KICK, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -6088,7 +6996,7 @@ const Gen2DistributionPokemon g2_pcny_megaKickMiltank = { } }; -const Gen2DistributionPokemon g2_pcny_shinyRaikou = { +static const Gen2DistributionPokemon g2_pcny_shinyRaikou = { .name = "Shiny Raikou", .originalTrainer = "PCNYa", .originalTrainerID = 0, @@ -6099,13 +7007,14 @@ const Gen2DistributionPokemon g2_pcny_shinyRaikou = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 243, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 84, // Thundershock - .index_move3 = 46, // Roar - .index_move4 = 98, // Quick Attack + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::THUNDER_SHOCK, + .index_move3 = (uint8_t)Move::ROAR, + .index_move4 = (uint8_t)Move::QUICK_ATTACK, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -6134,7 +7043,7 @@ const Gen2DistributionPokemon g2_pcny_shinyRaikou = { } }; -const Gen2DistributionPokemon g2_pcny_shinyEntei = { +static const Gen2DistributionPokemon g2_pcny_shinyEntei = { .name = "Shiny Entei", .originalTrainer = "PCNYb", .originalTrainerID = 0, @@ -6145,13 +7054,14 @@ const Gen2DistributionPokemon g2_pcny_shinyEntei = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 244, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 52, // Ember - .index_move3 = 46, // Roar - .index_move4 = 83, // Fire Spin + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::EMBER, + .index_move3 = (uint8_t)Move::ROAR, + .index_move4 = (uint8_t)Move::FIRE_SPIN, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -6180,7 +7090,7 @@ const Gen2DistributionPokemon g2_pcny_shinyEntei = { } }; -const Gen2DistributionPokemon g2_pcny_shinySuicune = { +static const Gen2DistributionPokemon g2_pcny_shinySuicune = { .name = "Shiny Suicune", .originalTrainer = "PCNYc", .originalTrainerID = 0, @@ -6191,13 +7101,14 @@ const Gen2DistributionPokemon g2_pcny_shinySuicune = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 245, .held_item_index = 0, - .index_move1 = 43, // Leer - .index_move2 = 55, // Water Gun - .index_move3 = 46, // Roar - .index_move4 = 16, // Gust + .index_move1 = (uint8_t)Move::LEER, + .index_move2 = (uint8_t)Move::WATER_GUN, + .index_move3 = (uint8_t)Move::ROAR, + .index_move4 = (uint8_t)Move::GUST, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -6226,7 +7137,7 @@ const Gen2DistributionPokemon g2_pcny_shinySuicune = { } }; -const Gen2DistributionPokemon g2_pcny_rageLarvitar = { +static const Gen2DistributionPokemon g2_pcny_rageLarvitar = { .name = "Rage Larvitar", .originalTrainer = "", .originalTrainerID = 0, @@ -6237,12 +7148,13 @@ const Gen2DistributionPokemon g2_pcny_rageLarvitar = { .isEgg = true, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 246, .held_item_index = 0, - .index_move1 = 44, // Bite - .index_move2 = 43, // Leer - .index_move3 = 99, // Rage + .index_move1 = (uint8_t)Move::BITE, + .index_move2 = (uint8_t)Move::LEER, + .index_move3 = (uint8_t)Move::RAGE, .index_move4 = 0, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_SLOW), @@ -6272,7 +7184,7 @@ const Gen2DistributionPokemon g2_pcny_rageLarvitar = { } }; -const Gen2DistributionPokemon g2_pcny_shinyLugia = { +static const Gen2DistributionPokemon g2_pcny_shinyLugia = { .name = "Shiny Lugia", .originalTrainer = "PCNYd", .originalTrainerID = 0, @@ -6283,13 +7195,14 @@ const Gen2DistributionPokemon g2_pcny_shinyLugia = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 249, .held_item_index = 0, - .index_move1 = 177, // Aeroblast - .index_move2 = 219, // Safeguard - .index_move3 = 16, // Gust - .index_move4 = 105, // Recover + .index_move1 = (uint8_t)Move::AEROBLAST, + .index_move2 = (uint8_t)Move::SAFEGUARD, + .index_move3 = (uint8_t)Move::GUST, + .index_move4 = (uint8_t)Move::RECOVER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -6318,7 +7231,7 @@ const Gen2DistributionPokemon g2_pcny_shinyLugia = { } }; -const Gen2DistributionPokemon g2_pcny_shinyHoOh = { +static const Gen2DistributionPokemon g2_pcny_shinyHoOh = { .name = "Shiny Ho-Oh", .originalTrainer = "PCNYa", .originalTrainerID = 0, @@ -6329,13 +7242,14 @@ const Gen2DistributionPokemon g2_pcny_shinyHoOh = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { .poke_index = 250, .held_item_index = 0, - .index_move1 = 221, // Sacred Fire - .index_move2 = 219, // Safeguard - .index_move3 = 16, // Gust - .index_move4 = 105, // Recover + .index_move1 = (uint8_t)Move::SACRED_FIRE, + .index_move2 = (uint8_t)Move::SAFEGUARD, + .index_move3 = (uint8_t)Move::GUST, + .index_move4 = (uint8_t)Move::RECOVER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(40, GEN2_GRW_SLOW), .hp_effort_value = 0, @@ -6364,7 +7278,7 @@ const Gen2DistributionPokemon g2_pcny_shinyHoOh = { } }; -const Gen2DistributionPokemon g2_pcny_Celebi = { +static const Gen2DistributionPokemon g2_pcny_Celebi = { .name = "Celebi", .originalTrainer = "PCNYb", .originalTrainerID = 0, @@ -6375,13 +7289,14 @@ const Gen2DistributionPokemon g2_pcny_Celebi = { .isEgg = false, .iv_data = {0, 0}, .randomizeIVs = true, + .isJapanese = false, .poke = { - .poke_index = 246, + .poke_index = 251, .held_item_index = 0, - .index_move1 = 44, // Leech Seed - .index_move2 = 43, // Confusion - .index_move3 = 99, // Heal Bell - .index_move4 = 0, // Recover + .index_move1 = (uint8_t)Move::LEECH_SEED, + .index_move2 = (uint8_t)Move::CONFUSION, + .index_move3 = (uint8_t)Move::HEAL_BELL, + .index_move4 = (uint8_t)Move::RECOVER, .original_trainer_ID = 0, .exp = getExpNeededForLevel(5, GEN2_GRW_MEDIUM_SLOW), .hp_effort_value = 0, @@ -6390,10 +7305,10 @@ const Gen2DistributionPokemon g2_pcny_Celebi = { .speed_effort_value = 0, .special_effort_value = 0, .iv_data = {0, 0}, - .pp_move1 = 25, - .pp_move2 = 30, - .pp_move3 = 20, - .pp_move4 = 0, + .pp_move1 = 10, + .pp_move2 = 25, + .pp_move3 = 5, + .pp_move4 = 5, .friendship_or_remaining_egg_cycles = 0, .pokerus = 0, .caught_data = 0, diff --git a/src/gen2/Gen2GameReader.cpp b/src/gen2/Gen2GameReader.cpp index 43d9ae9..6de4e57 100644 --- a/src/gen2/Gen2GameReader.cpp +++ b/src/gen2/Gen2GameReader.cpp @@ -4,14 +4,12 @@ #include "utils.h" #include +#include // needed for crystal_fixPicBank() below #define CRYSTAL_PICS_FIX 0x36 #define CRYSTAL_BANK_PICS_1 72 -#define EVENT_FLAGS_OFFSET_GOLDSILVER 0x261F -#define EVENT_FLAGS_OFFSET_CRYSTAL 0x2600 - #define SAVE_CORRUPTION_CHECK1_EXPECTED_VALUE 99 #define SAVE_CORRUPTION_CHECK2_EXPECTED_VALUE 127 @@ -89,7 +87,7 @@ static uint8_t gold_silver_fixPicBank(uint8_t bankIndex) return result; } -static void addBytesToChecksum(ISaveManager &saveManager, uint16_t savOffsetStart, uint16_t savOffsetEnd, Gen2Checksum &checksum) +static void addBytesToChecksum(ISaveManager &saveManager, uint32_t savOffsetStart, uint32_t savOffsetEnd, Gen2Checksum &checksum) { if (!saveManager.seek(savOffsetStart)) { @@ -105,11 +103,11 @@ static void addBytesToChecksum(ISaveManager &saveManager, uint16_t savOffsetStar } // based on https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_II)#Gold_and_Silver -static uint16_t calculateMainRegionChecksum(ISaveManager &saveManager, bool isGameCrystal) +static uint16_t calculateMainRegionChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) { Gen2Checksum checksum; - const uint16_t savOffsetStart = 0x2009; - const uint16_t savOffsetEnd = (isGameCrystal) ? 0x2B82 : 0x2D68; + const uint32_t savOffsetStart = 0x2009; + const uint32_t savOffsetEnd = gen2_getSRAMOffsets(gameType, localization).mainChecksumDataEndPos; addBytesToChecksum(saveManager, savOffsetStart, savOffsetEnd, checksum); @@ -117,104 +115,157 @@ static uint16_t calculateMainRegionChecksum(ISaveManager &saveManager, bool isGa } // based on https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_II)#Gold_and_Silver -static uint16_t calculateBackupRegionChecksum(ISaveManager &saveManager, bool isGameCrystal) +static uint16_t calculateBackupRegionChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) { Gen2Checksum checksum; - if (isGameCrystal) + if(localization == Gen2LocalizationLanguage::JAPANESE) { - // for crystal, the backup save region is a contiguous address range - addBytesToChecksum(saveManager, 0x1209, 0x1D82, checksum); + if(gameType == Gen2GameType::CRYSTAL) + { + addBytesToChecksum(saveManager, 0x7209, 0x7CE3, checksum); + } + else + { + addBytesToChecksum(saveManager, 0x7209, 0x7E8C, checksum); + } + } + else if(localization == Gen2LocalizationLanguage::KOREAN) + { + addBytesToChecksum(saveManager, 0x106B, 0x1533, checksum); // 1224 bytes + addBytesToChecksum(saveManager, 0x1534, 0x1A12, checksum); // 1246 bytes + addBytesToChecksum(saveManager, 0x1A13, 0x1C38, checksum); // 549 bytes + addBytesToChecksum(saveManager, 0x3DD8, 0x3F79, checksum); // 417 bytes + addBytesToChecksum(saveManager, 0x7E39, 0x7E6A, checksum); // 49 bytes } else { - // for Gold/Silver the backup save region is split up into 5 blocks - addBytesToChecksum(saveManager, 0x15C7, 0x17EC, checksum); // 550 bytes - addBytesToChecksum(saveManager, 0x3D96, 0x3F3F, checksum); // 426 bytes - addBytesToChecksum(saveManager, 0x0C6B, 0x10E7, checksum); // 1149 bytes - addBytesToChecksum(saveManager, 0x7E39, 0x7E6C, checksum); // 52 bytes - addBytesToChecksum(saveManager, 0x10E8, 0x15C6, checksum); // 1247 bytes + if (gameType == Gen2GameType::CRYSTAL) + { + // for crystal, the backup save region is a contiguous address range + addBytesToChecksum(saveManager, 0x1209, 0x1D82, checksum); + } + else + { + // for Gold/Silver the backup save region is split up into 5 blocks + addBytesToChecksum(saveManager, 0x15C7, 0x17EC, checksum); // 550 bytes + addBytesToChecksum(saveManager, 0x3D96, 0x3F3F, checksum); // 426 bytes + addBytesToChecksum(saveManager, 0x0C6B, 0x10E7, checksum); // 1149 bytes + addBytesToChecksum(saveManager, 0x7E39, 0x7E6C, checksum); // 52 bytes + addBytesToChecksum(saveManager, 0x10E8, 0x15C6, checksum); // 1247 bytes + } } return checksum.get(); } -static uint16_t readMainChecksum(ISaveManager &saveManager, bool isGameCrystal) +static uint16_t readMainChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) { uint16_t result; + const uint32_t savOffset = gen2_getSRAMOffsets(gameType, localization).mainChecksum; + + saveManager.seek(savOffset); + saveManager.readUint16(result, Endianness::LITTLE); - if (isGameCrystal) - { - saveManager.seek(0x2D0D); - saveManager.readUint16(result, Endianness::LITTLE); - } - else - { - saveManager.seek(0x2D69); - saveManager.readUint16(result, Endianness::LITTLE); - } return result; } -static uint16_t readBackupChecksum(ISaveManager &saveManager, bool isGameCrystal) +static uint16_t readBackupChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) { uint16_t result; + const uint32_t savOffset = gen2_getSRAMOffsets(gameType, localization).backupChecksum; + + saveManager.seek(savOffset); + saveManager.readUint16(result, Endianness::LITTLE); - if (isGameCrystal) - { - saveManager.seek(0x1F0D); - saveManager.readUint16(result, Endianness::LITTLE); - } - else - { - saveManager.seek(0x7E6D); - saveManager.readUint16(result, Endianness::LITTLE); - } return result; } -static void writeMainChecksum(ISaveManager &saveManager, uint16_t checksum, bool isGameCrystal) +static void writeMainChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization, uint16_t checksum) { - if (isGameCrystal) + const uint32_t savOffset = gen2_getSRAMOffsets(gameType, localization).mainChecksum; + + saveManager.seek(savOffset); + saveManager.writeUint16(checksum, Endianness::LITTLE); +} + +static void writeBackupChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization, uint16_t checksum) +{ + const uint32_t savOffset = gen2_getSRAMOffsets(gameType, localization).backupChecksum; + + saveManager.seek(savOffset); + saveManager.writeUint16(checksum, Endianness::LITTLE); +} + +static void updateMainChecksum(ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) +{ + const uint16_t calculatedChecksum = calculateMainRegionChecksum(saveManager, gameType, localization); + writeMainChecksum(saveManager, gameType, localization, calculatedChecksum); +} + +/** + * This function copies the main save region to the backup save region. + * It therefore overwrites the current backup save region. + */ +static void copyMainSaveRegionToBackupSaveRegion(ISaveManager& saveManager, Gen2GameType gameType, Gen2LocalizationLanguage localization) +{ + if(localization == Gen2LocalizationLanguage::JAPANESE) { - saveManager.seek(0x2D0D); - saveManager.writeUint16(checksum, Endianness::LITTLE); + if(gameType == Gen2GameType::CRYSTAL) + { + saveManager.copyRegion(0x2009, 0x7209, 2778); + } + else + { + saveManager.copyRegion(0x2009, 0x7209, 3203); + } + } + else if(localization == Gen2LocalizationLanguage::KOREAN) + { + saveManager.copyRegion(0x2009, 0x106B, 1224); // 1224 bytes + saveManager.copyRegion(0x24D1, 0x1534, 1246); // 1246 bytes + saveManager.copyRegion(0x29AF, 0x1A13, 549); // 549 bytes + saveManager.copyRegion(0x2BD4, 0x3DD8, 417); // 417 bytes + saveManager.copyRegion(0x2D75, 0x7E39, 49); // 49 bytes } else { - saveManager.seek(0x2D69); - saveManager.writeUint16(checksum, Endianness::LITTLE); + if (gameType == Gen2GameType::CRYSTAL) + { + // for crystal, the backup save region is a contiguous address range + saveManager.copyRegion(0x2009, 0x1209, 2938); + } + else + { + // for Gold/Silver the backup save region is split up into 3 blocks + saveManager.copyRegion(0x2009, 0x15C7, 550); // 550 bytes + saveManager.copyRegion(0x222F, 0x3D96, 426); // 426 bytes + saveManager.copyRegion(0x23D9, 0x0C6B, 1149); // 1149 bytes + saveManager.copyRegion(0x2856, 0x7E39, 52); // 52 bytes + saveManager.copyRegion(0x288A, 0x10E8, 1247); // 1247 bytes + } } -} -static void writeBackupChecksum(ISaveManager &saveManager, uint16_t checksum, bool isGameCrystal) -{ - if (isGameCrystal) - { - saveManager.seek(0x1F0D); - saveManager.writeUint16(checksum, Endianness::LITTLE); - } - else - { - saveManager.seek(0x7E6D); - saveManager.writeUint16(checksum, Endianness::LITTLE); - } -} - -static void updateMainChecksum(ISaveManager &saveManager, bool isCrystal) -{ - const uint16_t calculatedChecksum = calculateMainRegionChecksum(saveManager, isCrystal); - writeMainChecksum(saveManager, calculatedChecksum, isCrystal); + // read main data checksum and copy that to the backup checksum + writeBackupChecksum(saveManager, gameType, localization, readMainChecksum(saveManager, gameType, localization)); } /** * Gen 2 has 2 canary values for the main save to check whether SRAM actually contains a save */ -static bool hasMainSave(ISaveManager& saveManager, bool isCrystal) +static bool hasMainSave(ISaveManager& saveManager, Gen2LocalizationLanguage localization, bool isCrystal) { // check first canary value uint8_t saveCorruptionCheckValue1; uint8_t saveCorruptionCheckValue2; + if(localization == Gen2LocalizationLanguage::JAPANESE || localization == Gen2LocalizationLanguage::KOREAN) + { + // I can't check for the canary values in a japanese or Korean save because I don't know the save offset nor the expected value. + // I can't rely on the decompilation projects (Pret's pokegold/pokecrystal) because they only do the international version. + // I also can't rely on PkHex's source code because they're not checking this. + return true; + } + // this value is at the same offset for Gold/Silver/Crystal saveManager.seekToBankOffset(1, 0x8); saveManager.readByte(saveCorruptionCheckValue1); @@ -243,12 +294,20 @@ static bool hasMainSave(ISaveManager& saveManager, bool isCrystal) /** * Gen 2 has 2 canary values for the main save to check whether SRAM actually contains a save */ -static bool hasBackupSave(ISaveManager& saveManager, bool isCrystal) +static bool hasBackupSave(ISaveManager& saveManager, Gen2LocalizationLanguage localization, bool isCrystal) { // check first canary value uint8_t saveCorruptionCheckValue1; uint8_t saveCorruptionCheckValue2; + if(localization == Gen2LocalizationLanguage::JAPANESE || localization == Gen2LocalizationLanguage::KOREAN) + { + // I can't check for the canary values in a japanese or Korean save because I don't know the save offset nor the expected value. + // I can't rely on the decompilation projects (Pret's pokegold/pokecrystal) because they only do the international version. + // I also can't rely on PkHex's source code because they're not checking this. + return true; + } + if(isCrystal) { saveManager.seekToBankOffset(0, 0x1208); @@ -280,52 +339,79 @@ static bool hasBackupSave(ISaveManager& saveManager, bool isCrystal) } -Gen2GameReader::Gen2GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen2GameType gameType) +Gen2GameReader::Gen2GameReader(IRomReader &romReader, ISaveManager &saveManager, Gen2GameType gameType, Gen2LocalizationLanguage language) : romReader_(romReader) , saveManager_(saveManager) , spriteDecoder_(romReader) , iconDecoder_(romReader, gameType) , gameType_(gameType) + , localization_(language) { + if(language == Gen2LocalizationLanguage::MAX) + { + localization_ = gen2_determineGameLanguage(romReader, gameType); +// printf("Detected localization=%d\n", (int)localization_); + } } Gen2GameReader::~Gen2GameReader() { } +Gen2GameType Gen2GameReader::getGameType() const +{ + return gameType_; +} + +Gen2LocalizationLanguage Gen2GameReader::getGameLanguage() const +{ + return localization_; +} + const char *Gen2GameReader::getPokemonName(uint8_t index) const { static char result[20]; uint8_t encodedText[0xA]; uint32_t numRead; + uint8_t maxNumBytesPerName; - const uint32_t romOffset = (isGameCrystal()) ? 0x53384 : 0x1B0B74; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, localization_).names; + if(!romOffset) + { + snprintf(result, sizeof(result) - 1, "poke-%hhu", index); + return result; + } - romReader_.seek(romOffset + (0xA * (index - 1))); - numRead = romReader_.readUntil(encodedText, 0x50, 0xA); + // The max name length is only 5 chars (and therefore bytes) in Japanese roms. + // But it's 10 for every other language + switch(localization_) + { + case Gen2LocalizationLanguage::JAPANESE: + maxNumBytesPerName = 5; + break; + default: + maxNumBytesPerName = 10; + break; + } - gen2_decodePokeText(encodedText, numRead, result, sizeof(result) - 1); + romReader_.seek(romOffset + (maxNumBytesPerName * (index - 1))); + + // based on what I encountered so far. 0x50 is default, however it turns out 0x75 (…) is used too! + const uint8_t nameTerminators[] = {0x50, 0x75}; + numRead = romReader_.readUntil(encodedText, nameTerminators, sizeof(nameTerminators), maxNumBytesPerName); + + gen2_decodePokeText(encodedText, numRead, result, sizeof(result) - 1, localization_); return result; } Gen2PokemonIconType Gen2GameReader::getPokemonIconType(uint8_t index) const { - uint32_t romOffset; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, localization_).iconTypes; uint8_t byteVal; - switch(gameType_) + if(!romOffset) { - case Gen2GameType::GOLD: - romOffset = 0x8E975; - break; - case Gen2GameType::SILVER: - romOffset = 0x8E95B; - break; - case Gen2GameType::CRYSTAL: - romOffset = 0x8EAC4; - break; - default: - return Gen2PokemonIconType::GEN2_ICONTYPE_MAX; + return Gen2PokemonIconType::GEN2_ICONTYPE_MAX; } romReader_.seek(romOffset); @@ -343,7 +429,7 @@ bool Gen2GameReader::isValidIndex(uint8_t index) const bool Gen2GameReader::readPokemonStatsForIndex(uint8_t index, Gen2PokeStats &outStats) const { const uint8_t statsStructSize = 32; - const uint32_t romOffset = (isGameCrystal()) ? 0x051424 : 0x51B0B; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, localization_).stats; romReader_.seek(romOffset); @@ -389,7 +475,7 @@ bool Gen2GameReader::readFrontSpritePointer(uint8_t index, uint8_t &outBankIndex // https://raw.githubusercontent.com/pret/pokecrystal/symbols/pokecrystal.map // https://github.com/pret/pokecrystal/blob/master/data/pokemon/pic_pointers.asm // - const uint32_t romOffset = (isGameCrystal()) ? 0x120000 : 0x48000; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, localization_).spritePointers; // I don't support Unown sprite decoding right now, because unown is stored separately based on the variant. if (index == 201) @@ -445,7 +531,7 @@ bool Gen2GameReader::readColorPaletteForPokemon(uint8_t index, bool shiny, uint1 // that's because for every color palette, the first color is white and the last color is black. // so the rom only stores the 2nd and 3rd colors // each of these colors is an uint16_t - const uint32_t romOffset = (isGameCrystal()) ? 0xA8D6 : 0xAD45; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, localization_).spritePalettes; if (!romReader_.seek(romOffset + ((index - 1) * 8))) { @@ -483,7 +569,7 @@ uint8_t *Gen2GameReader::decodeSprite(uint8_t bankIndex, uint16_t pointer) uint8_t *Gen2GameReader::decodePokemonIcon(Gen2PokemonIconType iconType, bool firstFrame) { - return iconDecoder_.decode(iconType, firstFrame); + return iconDecoder_.decode(localization_, iconType, firstFrame); } uint8_t Gen2GameReader::addPokemon(Gen2TrainerPokemon &poke, bool isEgg, const char *originalTrainerID, const char *nickname) @@ -548,10 +634,11 @@ const char *Gen2GameReader::getTrainerName() const { static char result[20]; uint8_t encodedPlayerName[0xB]; + saveManager_.seek(0x200B); saveManager_.readUntil(encodedPlayerName, 0x50, 0xB); - gen2_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result)); + gen2_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result), localization_); return result; } @@ -559,17 +646,19 @@ const char *Gen2GameReader::getRivalName() const { static char result[20]; uint8_t encodedPlayerName[0xB]; - saveManager_.seek(0x2021); + const uint32_t savOffset = gen2_getSRAMOffsets(gameType_, localization_).rivalName; + saveManager_.seek(savOffset); saveManager_.readUntil(encodedPlayerName, 0x50, 0xB); - gen2_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result)); + + gen2_decodePokeText(encodedPlayerName, sizeof(encodedPlayerName), result, sizeof(result), localization_); return result; } uint8_t Gen2GameReader::getCurrentBoxIndex() { uint8_t result; - const uint16_t saveOffset = (isGameCrystal()) ? 0x2700 : 0x2724; + const uint16_t saveOffset = gen2_getSRAMOffsets(gameType_, localization_).currentBoxIndex; if (!saveManager_.seek(saveOffset)) { return 0; @@ -582,30 +671,30 @@ uint8_t Gen2GameReader::getCurrentBoxIndex() Gen2Party Gen2GameReader::getParty() { - return Gen2Party((*this), saveManager_); + return Gen2Party((*this), saveManager_, localization_); } Gen2Box Gen2GameReader::getBox(uint8_t boxIndex) { - return Gen2Box((*this), saveManager_, boxIndex); + return Gen2Box((*this), saveManager_, boxIndex, localization_); } Gen2ItemList Gen2GameReader::getItemList(Gen2ItemListType type) { - return Gen2ItemList(saveManager_, type, isGameCrystal()); + return Gen2ItemList(saveManager_, type, gameType_, localization_); } bool Gen2GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) { - uint16_t saveOffset; + uint32_t saveOffset; - if (isGameCrystal()) + if(dexFlag == POKEDEX_SEEN) { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A47 : 0x2A27; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexSeen; } else { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A6C : 0x2A4C; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexOwned; } uint8_t byte; @@ -626,15 +715,15 @@ bool Gen2GameReader::getPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) void Gen2GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) { - uint16_t saveOffset; + uint32_t saveOffset; - if (isGameCrystal()) + if(dexFlag == POKEDEX_SEEN) { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A47 : 0x2A27; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexSeen; } else { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A6C : 0x2A4C; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexOwned; } uint8_t byte; @@ -656,15 +745,15 @@ void Gen2GameReader::setPokedexFlag(PokedexFlag dexFlag, uint8_t pokedexNumber) uint8_t Gen2GameReader::getPokedexCounter(PokedexFlag dexFlag) { - uint16_t saveOffset; + uint32_t saveOffset; - if (isGameCrystal()) + if(dexFlag == POKEDEX_SEEN) { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A47 : 0x2A27; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexSeen; } else { - saveOffset = (dexFlag == POKEDEX_SEEN) ? 0x2A6C : 0x2A4C; + saveOffset = gen2_getSRAMOffsets(gameType_, localization_).dexOwned; } uint8_t bytes[32]; @@ -705,19 +794,17 @@ uint8_t Gen2GameReader::getPokedexCounter(PokedexFlag dexFlag) return result; } - -#include bool Gen2GameReader::isMainChecksumValid() { const bool isCrystal = isGameCrystal(); - if(!hasMainSave(saveManager_, isCrystal)) + if(!hasMainSave(saveManager_, localization_, isCrystal)) { return false; } - const uint16_t storedChecksum = readMainChecksum(saveManager_, isCrystal); - const uint16_t calculatedChecksum = calculateMainRegionChecksum(saveManager_, isCrystal); + const uint16_t storedChecksum = readMainChecksum(saveManager_, gameType_, localization_); + const uint16_t calculatedChecksum = calculateMainRegionChecksum(saveManager_, gameType_, localization_); return (storedChecksum == calculatedChecksum); } @@ -726,21 +813,20 @@ bool Gen2GameReader::isBackupChecksumValid() { const bool isCrystal = isGameCrystal(); - if(!hasBackupSave(saveManager_, isCrystal)) + if(!hasBackupSave(saveManager_, localization_, isCrystal)) { return false; } - const uint16_t storedChecksum = readBackupChecksum(saveManager_, isCrystal); - const uint16_t calculatedChecksum = calculateBackupRegionChecksum(saveManager_, isCrystal); + const uint16_t storedChecksum = readBackupChecksum(saveManager_, gameType_, localization_); + const uint16_t calculatedChecksum = calculateBackupRegionChecksum(saveManager_, gameType_, localization_); return (storedChecksum == calculatedChecksum); } void Gen2GameReader::finishSave() { - const bool isCrystal = isGameCrystal(); - const uint16_t currentPcBoxSaveOffset = (isCrystal) ? 0x2D10 : 0x2D6C; + const uint16_t currentPcBoxSaveOffset = gen2_getSRAMOffsets(gameType_, localization_).currentBox; Gen2Box currentBox = getBox(getCurrentBoxIndex()); #if 1 @@ -749,26 +835,10 @@ void Gen2GameReader::finishSave() // and we rely on this function (finishSave) to copy the changed PC box to the current PC box save region. saveManager_.copyRegion(currentBox.getSaveOffset(), currentPcBoxSaveOffset, GEN2_PC_BOX_SIZE_IN_BYTES); #endif - updateMainChecksum(saveManager_, isCrystal); + updateMainChecksum(saveManager_, gameType_, localization_); // now start copying the main save region to the backup save region(s) - if (isCrystal) - { - // for crystal, the backup save region is a contiguous address range - saveManager_.copyRegion(0x2009, 0x1209, 2938); - } - else - { - // for Gold/Silver the backup save region is split up into 3 blocks - saveManager_.copyRegion(0x2009, 0x15C7, 550); // 550 bytes - saveManager_.copyRegion(0x222F, 0x3D96, 426); // 426 bytes - saveManager_.copyRegion(0x23D9, 0x0C6B, 1149); // 1149 bytes - saveManager_.copyRegion(0x2856, 0x7E39, 52); // 52 bytes - saveManager_.copyRegion(0x288A, 0x10E8, 1247); // 1247 bytes - } - - // read main data checksum and copy that to the backup checksum - writeBackupChecksum(saveManager_, readMainChecksum(saveManager_, isCrystal), isCrystal); + copyMainSaveRegionToBackupSaveRegion(saveManager_, gameType_, localization_); } bool Gen2GameReader::isGameCrystal() const @@ -782,6 +852,7 @@ void Gen2GameReader::unlockGsBallEvent() { return; } + const Gen2LocalizationSRAMOffsets& sramOffsets = gen2_getSRAMOffsets(gameType_, localization_); // let's remove the GS ball from the players' inventory first (if it exists) // this is to cover up my earlier fuck up with PokeMe64 which only handed out the item. @@ -798,16 +869,16 @@ void Gen2GameReader::unlockGsBallEvent() // unlock the event itself. This triggers the NPC to stop you when trying to leave the Golden Rod pokémon center // to give you the GS Ball. // to be honest I have no clue what the exact meaning of this byte is. But hey, it works! - saveManager_.seek(0x3E3C); - saveManager_.writeByte(0xB); + saveManager_.seek(sramOffsets.gsBallMain); + saveManager_.writeByte(CRYSTAL_GS_BALL_ENABLE_VALUE); // this is a mirror of the previous byte. It's supposedly not needed. But let's just set it to be safe. - saveManager_.seek(0x3E44); - saveManager_.writeByte(0xB); + saveManager_.seek(sramOffsets.gsBallBackup); + saveManager_.writeByte(CRYSTAL_GS_BALL_ENABLE_VALUE); } bool Gen2GameReader::getEventFlag(uint16_t flagNumber) { - const uint16_t saveOffset = (isGameCrystal()) ? EVENT_FLAGS_OFFSET_CRYSTAL : EVENT_FLAGS_OFFSET_GOLDSILVER; + const uint32_t saveOffset = gen2_getSRAMOffsets(gameType_, localization_).eventFlags; uint8_t byteVal; const uint8_t flag = 1 << (flagNumber % 8); @@ -819,7 +890,7 @@ bool Gen2GameReader::getEventFlag(uint16_t flagNumber) void Gen2GameReader::setEventFlag(uint16_t flagNumber, bool enabled) { - const uint16_t saveOffset = (isGameCrystal()) ? EVENT_FLAGS_OFFSET_CRYSTAL : EVENT_FLAGS_OFFSET_GOLDSILVER; + const uint32_t saveOffset = gen2_getSRAMOffsets(gameType_, localization_).eventFlags; uint8_t byteVal; uint8_t resultVal; const uint8_t flag = 1 << (flagNumber % 8); @@ -848,8 +919,9 @@ void Gen2GameReader::resetRTC() // Based on sRTCStatusFlags, RecordRTCStatus, .set_bit_7 in // https://github.com/pret/pokecrystal // https://github.com/pret/pokegold + const uint32_t saveOffset = gen2_getSRAMOffsets(gameType_, localization_).rtcFlags; const uint8_t rtcStatusFieldValue = 0xC0; - saveManager_.seekToBankOffset(0, 0xC60); + saveManager_.seekToBankOffset(0, saveOffset); saveManager_.writeByte(rtcStatusFieldValue); } \ No newline at end of file diff --git a/src/gen2/Gen2IconDecoder.cpp b/src/gen2/Gen2IconDecoder.cpp index d9800a5..bb5592d 100644 --- a/src/gen2/Gen2IconDecoder.cpp +++ b/src/gen2/Gen2IconDecoder.cpp @@ -10,9 +10,9 @@ Gen2IconDecoder::Gen2IconDecoder(IRomReader& romReader, Gen2GameType gameType) { } -uint8_t* Gen2IconDecoder::decode(Gen2PokemonIconType iconType, bool firstFrame) +uint8_t* Gen2IconDecoder::decode(Gen2LocalizationLanguage language, Gen2PokemonIconType iconType, bool firstFrame) { - uint32_t romOffset; + const uint32_t romOffset = gen2_getRomOffsets(gameType_, language).icons; const uint8_t MAX_NUM_TILES = 4; const uint8_t TILE_WIDTH = 8; const uint8_t TILE_HEIGHT = 8; @@ -24,19 +24,9 @@ uint8_t* Gen2IconDecoder::decode(Gen2PokemonIconType iconType, bool firstFrame) const uint8_t bankIndex = 0x23; uint16_t pointer; - switch(gameType_) + if(!romOffset) { - case Gen2GameType::GOLD: - romOffset = 0x8EA70; - break; - case Gen2GameType::SILVER: - romOffset = 0x8EA56; - break; - case Gen2GameType::CRYSTAL: - romOffset = 0x8EBBF; - break; - default: - return buffer_; + return buffer_; } // read IconPointers table entry diff --git a/src/gen2/Gen2Localization.cpp b/src/gen2/Gen2Localization.cpp new file mode 100644 index 0000000..0cda8e4 --- /dev/null +++ b/src/gen2/Gen2Localization.cpp @@ -0,0 +1,411 @@ +#include "gen2/Gen2Localization.h" +#include "gen2/Gen2Common.h" + +static const Gen2LocalizationRomOffsets g2_dummyROMOffsets = { + .stats = 0, + .names = 0, + .iconTypes = 0, + .icons = 0, + .spritePointers = 0, + .spritePalettes = 0 +}; + +/** + * @brief Pokemon Gold rom offsets + */ +static const Gen2LocalizationRomOffsets g2_localizationROMOffsetsG[] = { + // ENGLISH + { + .stats = 0x51B0B, + .names = 0x1B0B74, + .iconTypes = 0x8E975, + .icons = 0x8EA70, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // FRENCH + { + .stats = 0x51B10, + .names = 0x1B0BC5, + .iconTypes = 0x8E977, + .icons = 0x8EA72, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // ITALIAN + { + .stats = 0x51B19, + .names = 0x1B0BD2, + .iconTypes = 0x8E973, + .icons = 0x8EA6E, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // SPANISH + { + .stats = 0x51B19, + .names = 0x1B0BB8, + .iconTypes = 0x8E973, + .icons = 0x8EA6E, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // GERMAN + { + .stats = 0x51B00, + .names = 0x1b0b6b, + .iconTypes = 0x8E977, + .icons = 0x8EA72, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // KOREAN + { + .stats = 0x51BDF, + .names = 0x1B0C4A, + .iconTypes = 0x8E96D, + .icons = 0x8EA68, + .spritePointers = 0x48000, + .spritePalettes = 0xAD15 + }, + // JAPANESE + { + .stats = 0x51AA9, + .names = 0x53A09, + .iconTypes = 0x8E795, + .icons = 0x8E890, + .spritePointers = 0x48000, + .spritePalettes = 0xACCB + } +}; + +/** + * @brief Pokemon Silver rom offsets + */ +static const Gen2LocalizationRomOffsets g2_localizationROMOffsetsS[] = { + // ENGLISH + { + .stats = 0x51B0B, + .names = 0x1B0B74, + .iconTypes = 0x8E95B, + .icons = 0x8EA56, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // FRENCH + { + .stats = 0x51B10, + .names = 0x1B0BC5, + .iconTypes = 0x8E95D, + .icons = 0x8EA58, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // ITALIAN + { + .stats = 0x51B19, + .names = 0x1B0BD2, + .iconTypes = 0x8E959, + .icons = 0x8EA54, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // SPANISH + { + .stats = 0x51B19, + .names = 0x1B0BB8, + .iconTypes = 0x8E959, + .icons = 0x8EA54, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // GERMAN + { + .stats = 0x51B00, + .names = 0x1B0B6B, + .iconTypes = 0x8E95D, + .icons = 0x8EA58, + .spritePointers = 0x48000, + .spritePalettes = 0xAD45 + }, + // KOREAN + { + .stats = 0x51BDF, + .names = 0x1B0C4A, + .iconTypes = 0x8E953, + .icons = 0x8EA4E, + .spritePointers = 0x48000, + .spritePalettes = 0xAD15 + }, + // JAPANESE + { + .stats = 0x51AA9, + .names = 0x53A09, + .iconTypes = 0x8E780, + .icons = 0x8E87B, + .spritePointers = 0x48000, + .spritePalettes = 0xACCB + } +}; + +/** + * @brief Pokemon Crystal rom offsets + */ +static const Gen2LocalizationRomOffsets g2_localizationROMOffsetsC[] = { + // ENGLISH + { + .stats = 0x051424, + .names = 0x53384, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA8D6 + }, + // FRENCH + { + .stats = 0x051417, + .names = 0x53377, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA8D6 + }, + // ITALIAN + { + .stats = 0x051433, + .names = 0x53393, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA8D6 + }, + // SPANISH + { + .stats = 0x5142D, + .names = 0x5338D, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA8D6 + }, + // GERMAN + { + .stats = 0x5140E, + .names = 0x5336E, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA8D6 + }, + // KOREAN + { // NOT SUPPORTED! Apparently Pokémon Crystal was never released in Korea! + .stats = 0, + .names = 0, + .iconTypes = 0, + .icons = 0, + .spritePointers = 0, + .spritePalettes = 0 + }, + // JAPANESE + { + .stats = 0x514BA, + .names = 0x5341A, + .iconTypes = 0x8EAC4, + .icons = 0x8EBBF, + .spritePointers = 0x120000, + .spritePalettes = 0xA893 + } +}; + +static const Gen2LocalizationSRAMOffsets g2_dummySRAMOffsets = { + .currentBoxIndex = 0, + .currentBox = 0, + .dexSeen = 0, + .dexOwned = 0, + .party = 0, + .eventFlags = 0, + .mainChecksum = 0, + .mainChecksumDataEndPos = 0, + .backupChecksum = 0, + .rivalName = 0, + .gsBallMain = 0, + .gsBallBackup = 0, + .itemPocketTMHM = 0, + .itemPocketItem = 0, + .itemPocketKey = 0, + .itemPocketBall = 0, + .itemPocketPC = 0, + .rtcFlags = 0 +}; + +static const Gen2LocalizationSRAMOffsets g2_internationalSRAMOffsetsGS = { + .currentBoxIndex = 0x2724, + .currentBox = 0x2D6C, + .dexSeen = 0x2A6C, + .dexOwned = 0x2A4C, + .party = 0x288A, + .eventFlags = 0x261F, + .mainChecksum = 0x2D69, + .mainChecksumDataEndPos = 0x2D68, + .backupChecksum = 0x7E6D, + .rivalName = 0x2021, + .gsBallMain = 0, // N/A for Gold & Silver + .gsBallBackup = 0, // N/A for Gold & Silver + .itemPocketTMHM = 0x23E6, + .itemPocketItem = 0x241F, + .itemPocketKey = 0x2449, + .itemPocketBall = 0x2464, + .itemPocketPC = 0x247E, + .rtcFlags = 0xC60 +}; + +static const Gen2LocalizationSRAMOffsets g2_internationalSRAMOffsetsC = { + .currentBoxIndex = 0x2700, + .currentBox = 0x2D10, + .dexSeen = 0x2A47, + .dexOwned = 0x2A27, + .party = 0x2865, + .eventFlags = 0x2600, + .mainChecksum = 0x2D0D, + .mainChecksumDataEndPos = 0x2B82, + .backupChecksum = 0x1F0D, + .rivalName = 0x2021, + .gsBallMain = 0x3E3C, + .gsBallBackup = 0x3E44, + .itemPocketTMHM = 0x23E7, + .itemPocketItem = 0x2420, + .itemPocketKey = 0x244A, + .itemPocketBall = 0x2465, + .itemPocketPC = 0x247F, + .rtcFlags = 0xC60 +}; + +static const Gen2LocalizationSRAMOffsets g2_localizationSRAMOffsetsGS[] = { + // ENGLISH + g2_internationalSRAMOffsetsGS, + // FRENCH + g2_internationalSRAMOffsetsGS, + // ITALIAN + g2_internationalSRAMOffsetsGS, + // SPANISH + g2_internationalSRAMOffsetsGS, + // GERMAN + g2_internationalSRAMOffsetsGS, + // KOREAN + { + .currentBoxIndex = 0x26FC, + .currentBox = 0x2DAE, + .dexSeen = 0x2AAE, + .dexOwned = 0x2A8E, + .party = 0x28CC, + .eventFlags = 0x25F7, + .mainChecksum = 0x2DAB, + .mainChecksumDataEndPos = 0x2DAA, + .backupChecksum = 0x7E6B, + .rivalName = 0x2021, + .gsBallMain = 0, // N/A for Gold & Silver + .gsBallBackup = 0, // N/A for Gold & Silver + .itemPocketTMHM = 0x23DE, + .itemPocketItem = 0x2417, + .itemPocketKey = 0x2441, + .itemPocketBall = 0x245C, + .itemPocketPC = 0x2476, + .rtcFlags = 0x1060 + }, + // JAPANESE + { + .currentBoxIndex = 0x2705, + .currentBox = 0x2D10, + .dexSeen = 0x29EE, + .dexOwned = 0x29CE, + .party = 0x283E, + .eventFlags = 0x2600, + .mainChecksum = 0x2D0D, + .mainChecksumDataEndPos = 0x2C8B, + .backupChecksum = 0x7F0D, + .rivalName = 0x2017, + .gsBallMain = 0, // N/A for Gold & Silver + .gsBallBackup = 0, // N/A for Gold & Silver + .itemPocketTMHM = 0x23C7, + .itemPocketItem = 0x2400, + .itemPocketKey = 0x242A, + .itemPocketBall = 0x2445, + .itemPocketPC = 0x245F, + .rtcFlags = 0x1000 + } +}; + +static const Gen2LocalizationSRAMOffsets g2_localizationSRAMOffsetsC[] = { + // ENGLISH + g2_internationalSRAMOffsetsC, + // FRENCH + g2_internationalSRAMOffsetsC, + // ITALIAN + g2_internationalSRAMOffsetsC, + // SPANISH + g2_internationalSRAMOffsetsC, + // GERMAN + g2_internationalSRAMOffsetsC, + // KOREAN + g2_dummySRAMOffsets, // Crystal was never released in Korea!! + // JAPANESE + { + .currentBoxIndex = 0x26E2, + .currentBox = 0x2D10, + .dexSeen = 0x29CA, + .dexOwned = 0x29AA, + .party = 0x281A, + .eventFlags = 0x25E2, + .mainChecksum = 0x2D0D, + .mainChecksumDataEndPos = 0x2AE2, + .backupChecksum = 0x7F0D, + .rivalName = 0x2017, + .gsBallMain = 0xA000, + .gsBallBackup = 0xA083, + .itemPocketTMHM = 0x23C9, + .itemPocketItem = 0x2402, + .itemPocketKey = 0x242C, + .itemPocketBall = 0x2447, + .itemPocketPC = 0x2461, + .rtcFlags = 0x0C80 + } +}; + +const Gen2LocalizationRomOffsets& gen2_getRomOffsets(Gen2GameType gameType, Gen2LocalizationLanguage language) +{ + if(language == Gen2LocalizationLanguage::MAX) + { + return g2_dummyROMOffsets; + } + + switch(gameType) + { + case Gen2GameType::GOLD: + return g2_localizationROMOffsetsG[(uint8_t)language]; + case Gen2GameType::SILVER: + return g2_localizationROMOffsetsS[(uint8_t)language]; + case Gen2GameType::CRYSTAL: + return g2_localizationROMOffsetsC[(uint8_t)language]; + default: + return g2_dummyROMOffsets; + } +} + +const Gen2LocalizationSRAMOffsets& gen2_getSRAMOffsets(Gen2GameType gameType, Gen2LocalizationLanguage language) +{ + if(language == Gen2LocalizationLanguage::MAX) + { + return g2_dummySRAMOffsets; + } + + switch(gameType) + { + case Gen2GameType::GOLD: + case Gen2GameType::SILVER: + return g2_localizationSRAMOffsetsGS[(uint8_t)language]; + case Gen2GameType::CRYSTAL: + return g2_localizationSRAMOffsetsC[(uint8_t)language]; + default: + return g2_dummySRAMOffsets; + } +} \ No newline at end of file diff --git a/src/gen2/Gen2PlayerPokemonStorage.cpp b/src/gen2/Gen2PlayerPokemonStorage.cpp index d3557dd..f0165b8 100644 --- a/src/gen2/Gen2PlayerPokemonStorage.cpp +++ b/src/gen2/Gen2PlayerPokemonStorage.cpp @@ -4,17 +4,21 @@ #include -static const int ORIGINAL_TRAINER_NAME_SIZE = 0xB; -static const int NICKNAME_SIZE = 0xB; +static const uint8_t ORIGINAL_TRAINER_NAME_SIZE = 0xB; +static const uint8_t ORIGINAL_TRAINER_NAME_SIZE_JPN = 0x6; +static const uint8_t NICKNAME_SIZE = 0xB; +static const uint8_t NICKNAME_SIZE_JPN = 0x6; static const uint8_t PARTY_LIST_CAPACITY = 6; static const uint8_t PARTY_LIST_PKMN_ENTRY_SIZE = 48; static const uint8_t PC_BOX_LIST_CAPACITY = 20; +static const uint8_t PC_BOX_LIST_CAPACITY_JPN = 30; static const uint8_t PC_BOX_LIST_PKMN_ENTRY_SIZE = 32; -Gen2PokemonList::Gen2PokemonList(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t listCapacity, uint8_t entrySize) +Gen2PokemonList::Gen2PokemonList(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t listCapacity, uint8_t entrySize, Gen2LocalizationLanguage language) : gameReader_(gameReader) , saveManager_(saveManager) + , localization_(language) , listCapacity_(listCapacity) , entrySize_(entrySize) { @@ -264,18 +268,20 @@ const char* Gen2PokemonList::getPokemonNickname(uint8_t index) { static char result[20]; const uint8_t pokeTextTerminator = 0x50; + const uint8_t otSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? ORIGINAL_TRAINER_NAME_SIZE : ORIGINAL_TRAINER_NAME_SIZE_JPN; + const uint8_t nickNameSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; uint8_t encodedNickName[NICKNAME_SIZE]; - const uint16_t nicknameOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (listCapacity_ * ORIGINAL_TRAINER_NAME_SIZE) + (index * NICKNAME_SIZE); + const uint16_t nicknameOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (listCapacity_ * otSize) + (index * nickNameSize); if(!saveManager_.seek(getSaveOffset() + nicknameOffset)) { return nullptr; } - saveManager_.readUntil(encodedNickName, pokeTextTerminator, NICKNAME_SIZE); + saveManager_.readUntil(encodedNickName, pokeTextTerminator, nickNameSize); - gen2_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result)); + gen2_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result), localization_); return result; } @@ -284,6 +290,8 @@ bool Gen2PokemonList::setPokemonNickname(uint8_t index, const char* nickname) { uint8_t encodedNickName[NICKNAME_SIZE]; const uint8_t pokeTextTerminator = 0x50; + const uint8_t otSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? ORIGINAL_TRAINER_NAME_SIZE : ORIGINAL_TRAINER_NAME_SIZE_JPN; + const uint8_t nickNameSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? NICKNAME_SIZE : NICKNAME_SIZE_JPN; if(!nickname) { @@ -292,8 +300,8 @@ bool Gen2PokemonList::setPokemonNickname(uint8_t index, const char* nickname) nickname = gameReader_.getPokemonName(poke.poke_index); } - const uint16_t encodedLength = gen2_encodePokeText(nickname, strlen(nickname), encodedNickName, NICKNAME_SIZE, pokeTextTerminator); - const uint16_t nicknameOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (listCapacity_ * ORIGINAL_TRAINER_NAME_SIZE) + (index * NICKNAME_SIZE); + const uint16_t encodedLength = gen2_encodePokeText(nickname, strlen(nickname), encodedNickName, nickNameSize, pokeTextTerminator, localization_); + const uint16_t nicknameOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (listCapacity_ * otSize) + (index * nickNameSize); if(!saveManager_.seek(getSaveOffset() + nicknameOffset)) { @@ -308,18 +316,19 @@ const char* Gen2PokemonList::getOriginalTrainerOfPokemon(uint8_t index) { static char result[20]; const uint8_t pokeTextTerminator = 0x50; + const uint8_t otSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? ORIGINAL_TRAINER_NAME_SIZE : ORIGINAL_TRAINER_NAME_SIZE_JPN; uint8_t encodedOTName[ORIGINAL_TRAINER_NAME_SIZE]; - const uint16_t originalTrainerOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (index * ORIGINAL_TRAINER_NAME_SIZE); + const uint16_t originalTrainerOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (index * otSize); if(!saveManager_.seek(getSaveOffset() + originalTrainerOffset)) { return nullptr; } - saveManager_.readUntil(encodedOTName, pokeTextTerminator, NICKNAME_SIZE); + saveManager_.readUntil(encodedOTName, pokeTextTerminator, otSize); - gen2_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result)); + gen2_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result), localization_); return result; } @@ -328,9 +337,10 @@ bool Gen2PokemonList::setOriginalTrainerOfPokemon(uint8_t index, const char* ori { uint8_t encodedOTName[ORIGINAL_TRAINER_NAME_SIZE]; const uint8_t pokeTextTerminator = 0x50; + const uint8_t otSize = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? ORIGINAL_TRAINER_NAME_SIZE : ORIGINAL_TRAINER_NAME_SIZE_JPN; - const uint16_t encodedLength = gen2_encodePokeText(originalTrainerID, strlen(originalTrainerID), encodedOTName, ORIGINAL_TRAINER_NAME_SIZE, pokeTextTerminator); - const uint16_t originalTrainerOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (index * ORIGINAL_TRAINER_NAME_SIZE); + const uint16_t encodedLength = gen2_encodePokeText(originalTrainerID, strlen(originalTrainerID), encodedOTName, otSize, pokeTextTerminator, localization_); + const uint16_t originalTrainerOffset = listCapacity_ + 2 + (listCapacity_ * entrySize_) + (index * otSize); if(!saveManager_.seek(getSaveOffset() + originalTrainerOffset)) { @@ -340,8 +350,8 @@ bool Gen2PokemonList::setOriginalTrainerOfPokemon(uint8_t index, const char* ori return true; } -Gen2Party::Gen2Party(Gen2GameReader& gameReader, ISaveManager& saveManager) - : Gen2PokemonList(gameReader, saveManager, PARTY_LIST_CAPACITY, PARTY_LIST_PKMN_ENTRY_SIZE) +Gen2Party::Gen2Party(Gen2GameReader& gameReader, ISaveManager& saveManager, Gen2LocalizationLanguage language) + : Gen2PokemonList(gameReader, saveManager, PARTY_LIST_CAPACITY, PARTY_LIST_PKMN_ENTRY_SIZE, language) { } @@ -351,11 +361,11 @@ Gen2Party::~Gen2Party() uint32_t Gen2Party::getSaveOffset() { - return (gameReader_.isGameCrystal()) ? 0x2865 : 0x288A; + return gen2_getSRAMOffsets(gameReader_.getGameType(), localization_).party; } -Gen2Box::Gen2Box(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex) - : Gen2PokemonList(gameReader, saveManager, PC_BOX_LIST_CAPACITY, PC_BOX_LIST_PKMN_ENTRY_SIZE) +Gen2Box::Gen2Box(Gen2GameReader& gameReader, ISaveManager& saveManager, uint8_t boxIndex, Gen2LocalizationLanguage language) + : Gen2PokemonList(gameReader, saveManager, (language != Gen2LocalizationLanguage::JAPANESE) ? PC_BOX_LIST_CAPACITY : PC_BOX_LIST_CAPACITY_JPN, PC_BOX_LIST_PKMN_ENTRY_SIZE, language) , boxIndex_(boxIndex) { } @@ -371,46 +381,46 @@ uint32_t Gen2Box::getSaveOffset() switch(boxIndex_) { case 0: - result = 0x4000; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x4000 : 0x4000; break; case 1: - result = 0x4450; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x4450 : 0x454A; break; case 2: - result = 0x48A0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x48A0 : 0x4A94; break; case 3: - result = 0x4CF0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x4CF0 : 0x4FDE; break; case 4: - result = 0x5140; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x5140 : 0x5528; break; case 5: - result = 0x5590; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x5590 : 0x5A72; break; case 6: - result = 0x59E0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x59E0 : 0x6000; break; case 7: - result = 0x6000; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x6000 : 0x654A; break; case 8: - result = 0x6450; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x6450 : 0x6A94; break; case 9: - result = 0x68A0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x68A0 : 0x0; break; case 10: - result = 0x6CF0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x6CF0 : 0x0; break; case 11: - result = 0x7140; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x7140 : 0x0; break; case 12: - result = 0x7590; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x7590 : 0x0; break; case 13: - result = 0x79E0; + result = (localization_ != Gen2LocalizationLanguage::JAPANESE) ? 0x79E0 : 0x0; break; default: result = 0; diff --git a/src/utils.cpp b/src/utils.cpp index be1b275..bcbd0cf 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -11,7 +11,7 @@ #include #endif -static const char* findCharsByTextCode(TextCodePair* textCodes, uint16_t numEntries, uint8_t code) +const char* findCharsByTextCode(const TextCodePair* textCodes, uint16_t numEntries, uint8_t code) { const TextCodePair* const end = textCodes + numEntries; const TextCodePair* cur = textCodes; @@ -27,7 +27,7 @@ static const char* findCharsByTextCode(TextCodePair* textCodes, uint16_t numEntr return nullptr; } -static void findTextcodeByString(TextCodePair* textCodes, uint16_t numEntries, const char* input, uint16_t inputLength, uint8_t& outCode, uint16_t& needleLength) +bool findTextcodeByString(const TextCodePair* textCodes, uint16_t numEntries, const char* input, uint16_t inputLength, uint8_t& outCode, uint16_t& needleLength) { const TextCodePair* const end = textCodes + numEntries; const TextCodePair* cur = textCodes; @@ -45,13 +45,14 @@ static void findTextcodeByString(TextCodePair* textCodes, uint16_t numEntries, c if(strncmp(input, cur->chars, needleLength) == 0) { outCode = cur->code; - return; + return true; } ++cur; } needleLength = 0; outCode = 0; + return false; } const uint8_t* memSearch(const uint8_t* haystack, uint32_t haystackLength, const uint8_t* needle, uint32_t needleLength) @@ -79,7 +80,7 @@ const uint8_t* memSearch(const uint8_t* haystack, uint32_t haystackLength, const return NULL; } -uint16_t decodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength) +uint16_t decodeText(const struct TextCodePair* textCodes, uint16_t numTextCodes, const uint8_t* inputBuffer, uint16_t inputBufferLength, char* outputBuffer, uint16_t outputBufferLength) { uint16_t result = 0; if(inputBufferLength > outputBufferLength) @@ -124,7 +125,7 @@ uint16_t decodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const return result; } -uint16_t encodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator) +uint16_t encodeText(const struct TextCodePair* textCodes, uint16_t numTextCodes, const char* inputBuffer, uint16_t inputBufferLength, uint8_t* outputBuffer, uint16_t outputBufferLength, uint8_t terminator) { uint16_t needleLength; uint8_t code; @@ -134,6 +135,12 @@ uint16_t encodeText(struct TextCodePair* textCodes, uint16_t numTextCodes, const uint8_t* const outputBufferEnd = outputBuffer + outputBufferLength; bool match; + // turns out for nicknames, any unsed bytes after the actual string should be filled with the terminator byte + // otherwise nickname detection won't work correctly. + // https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_I)#bank1_sec_party + // "A Pokémon's name is a nickname if it does not perfectly match the default name for a Pokémon (typically all uppercase) with any unused bytes of the entry's 11-byte capacity filled with 0x50 terminators." + memset(outputBuffer, terminator, outputBufferLength); + while(cur < inputBufferEnd) { match = false;