#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; }