Add support for gen I localizations

There currently is no way to automatically detect the localization yet though.
This commit is contained in:
Philippe Symons 2024-11-14 21:16:57 +01:00
parent 2ba33f26f7
commit 3065163b1b
17 changed files with 742 additions and 68 deletions

View File

@ -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

View File

@ -11,9 +11,11 @@
using OutputFormat = SpriteRenderer::OutputFormat;
static const LocalizationLanguage g_localization = LocalizationLanguage::ENGLISH;
static void decodeGen1Icon(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gen1Type, Gen1PokemonIconType iconType, bool firstFrame)
{
Gen1GameReader gameReader(romReader, saveManager, gen1Type);
Gen1GameReader gameReader(romReader, saveManager, gen1Type, g_localization);
SpriteRenderer renderer;
char fileNameBuf[100];
uint8_t* spriteBuffer;

View File

@ -3,6 +3,8 @@
#include <cstdio>
#include <cstring>
static const LocalizationLanguage g_localization = LocalizationLanguage::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, g_localization);
print_hex(outputBuffer, size);
}

View File

@ -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)

View File

@ -0,0 +1,142 @@
#include "utils.h"
#include "RomReader.h"
#include "SaveManager.h"
#include "gen1/Gen1Common.h"
#include "gen1/Gen1Localization.h"
#include "gen2/Gen2Common.h"
#include <cstdio>
#include <cstring>
static const LocalizationLanguage g_localization = LocalizationLanguage::ENGLISH;
static void findBinaryPattern(uint8_t* buffer, size_t bufferSize, uint8_t* pattern, size_t patternSize)
{
uint8_t* cur = buffer;
while(cur)
{
cur = (uint8_t*)memmem(cur, bufferSize - (cur - buffer), pattern, patternSize);
if(cur)
{
printf("%s: found pattern at offset 0x%08x\n", __FUNCTION__, (uint32_t)(cur - buffer));
cur += patternSize;
}
}
}
static uint32_t findNames(uint8_t* buffer, size_t bufferSize, const char** nameList, uint8_t nameListSize)
{
uint8_t nameBuffer[0xB];
uint8_t i;
uint16_t nameLength;
const uint8_t nameEntrySize = 0xA;
uint8_t* firstEntry;
uint8_t* cur = buffer;
bool match;
nameLength = gen1_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g_localization);
firstEntry = (uint8_t*)memmem(cur, bufferSize - (cur - buffer), nameBuffer, nameLength);
while(firstEntry)
{
cur = firstEntry + nameEntrySize;
match = true;
for(i = 1; i < nameListSize; ++i)
{
nameLength = gen1_encodePokeText(nameList[i], strlen(nameList[i]), nameBuffer, sizeof(nameBuffer), 0x50, g_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
nameLength = gen1_encodePokeText(nameList[0], strlen(nameList[0]), nameBuffer, sizeof(nameBuffer), 0x50, g_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;
}
int main(int argc, char** argv)
{
if(argc != 3)
{
fprintf(stderr, "Usage: findLocalizationOffsets <path/to/english_rom.gbc> <path/to/localized_rom.gbc>\n");
return 1;
}
uint8_t* romBuffer;
uint8_t* localizedRomBuffer;
uint8_t* saveBuffer = nullptr;
uint32_t romFileSize;
uint32_t localizedRomBufferSize;
uint32_t saveFileSize = 0;
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);
BufferBasedSaveManager saveManager(saveBuffer, saveFileSize);
readGameboyCartridgeHeader(romReader, cartridgeHeader);
//check if we're dealing with gen 1
const Gen1GameType gen1Type = gen1_determineGameType(cartridgeHeader);
const Gen2GameType gen2Type = gen2_determineGameType(cartridgeHeader);
if(gen2Type != Gen2GameType::INVALID)
{
fprintf(stderr, "ERROR: sorry, this tool only supports gen 1!\n");
return 1;
}
else if(gen1Type == Gen1GameType::INVALID)
{
fprintf(stderr, "ERROR: this is not a Gen 2 pokémon game!\n");
return 1;
}
// When searching for Red/Blue offsets, use this line.
const Gen1LocalizationRomOffsets* eng = g1_localizationOffsetsRB;
// For yellow, use this one instead:
// const Gen1LocalizationRomOffsets* eng = g1_localizationOffsetsY;
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);
const char* pokeNames[] = {"サイドン"};
printf("Names: 0x%08x\n", findNames(localizedRomBuffer, localizedRomBufferSize, pokeNames, 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);
return 0;
}

View File

@ -2,6 +2,7 @@
#define _GEN1COMMON_H
#include "common.h"
#include "Gen1Localization.h"
class Gen1GameReader;
typedef struct Gen1DistributionPokemon Gen1DistributionPokemon;
@ -129,11 +130,11 @@ void gen1_recalculatePokeStats(Gen1GameReader& gameReader, Gen1TrainerPokemon& p
* @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, LocalizationLanguage 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, LocalizationLanguage language);
/**
* Prepares the gen 1 distribution pokemon for injection with Gen1GameReader::addPokemon()

View File

@ -5,6 +5,7 @@
#include "gen1/Gen1IconDecoder.h"
#include "gen1/Gen1PlayerPokemonStorage.h"
#include "gen1/Gen1DistributionPokemon.h"
#include "gen1/Gen1Localization.h"
class IRomReader;
class ISaveManager;
@ -12,7 +13,7 @@ class ISaveManager;
class Gen1GameReader
{
public:
Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType);
Gen1GameReader(IRomReader& romReader, ISaveManager& saveManager, Gen1GameType gameType, LocalizationLanguage language = LocalizationLanguage::ENGLISH);
/**
* @brief get the name of a pokémon based on an index number
@ -186,6 +187,7 @@ private:
Gen1SpriteDecoder spriteDecoder_;
Gen1IconDecoder iconDecoder_;
Gen1GameType gameType_;
uint8_t localization_;
};
#endif

View File

@ -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
@ -17,7 +18,7 @@ class IRomReader;
class Gen1IconDecoder
{
public:
Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType);
Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType, LocalizationLanguage language);
/**
* @brief Decodes the specified icon type. either frame 1 or frame 2.
@ -33,6 +34,7 @@ private:
uint8_t buffer_[GEN1_ICON_NUM_BYTES];
IRomReader& romReader_;
Gen1GameType gameType_;
uint8_t localization_;
};
#endif

View File

@ -0,0 +1,79 @@
#ifndef _GEN1LOCALIZATION_H
#define _GEN1LOCALIZATION_H
#include <cstdint>
/**
* @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 LocalizationLanguage
{
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;
} Gen1Localization;
extern const Gen1LocalizationRomOffsets g1_localizationOffsetsRB[];
extern const Gen1LocalizationRomOffsets g1_localizationOffsetsY[];
#endif

View File

@ -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, LocalizationLanguage language);
~Gen1Party();
/**
@ -41,6 +41,7 @@ protected:
private:
Gen1GameReader& gameReader_;
ISaveManager& saveManager_;
LocalizationLanguage 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, LocalizationLanguage language);
~Gen1Box();
/**
@ -73,6 +74,7 @@ private:
Gen1GameReader& gameReader_;
ISaveManager& saveManager_;
uint8_t boxIndex_;
LocalizationLanguage localization_;
};
#endif

View File

@ -15,12 +15,12 @@ const uint8_t* memSearch(const uint8_t* haystack, uint32_t hayStackLength, const
* @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();

View File

@ -11,7 +11,7 @@
#define POKEMON_YELLOW_CARTRIDGE_TITLE "POKEMON YELLOW"
static TextCodePair gen1TextCodes[] = {
static const TextCodePair gen1TextCodesMain[] = {
{0x4F, " "},
{0x57, "#"},
{0x51, "*"},
@ -116,6 +116,247 @@ static TextCodePair gen1TextCodes[] = {
{0xFF, "9"}
};
static const TextCodePair gen1TextCodesJpn[] = {
{0x01, "イ゙"},
{0x02, ""},
{0x03, "エ゙"},
{0x04, "オ゙"},
{0x05, ""},
{0x06, ""},
{0x07, ""},
{0x08, ""},
{0x09, ""},
{0x0A, ""},
{0x0B, ""},
{0x0C, ""},
{0x0D, ""},
{0x0E, ""},
{0x0F, ""},
{0x10, ""},
{0x11, ""},
{0x12, ""},
{0x13, ""},
{0x14, "ナ゙"},
{0x15, "ニ゙"},
{0x16, "ヌ゙"},
{0x17, "ネ゙"},
{0x18, "ノ゙"},
{0x19, ""},
{0x1A, ""},
{0x1B, ""},
{0x1C, ""},
{0x1D, "マ゙"},
{0x1E, "ミ゙"},
{0x1F, "ム゙"},
{0x20, "ィ゙"},
{0x21, "あ゙"},
{0x22, "い゙"},
{0x23, ""},
{0x24, "え゙"},
{0x25, "お゙"},
{0x26, ""},
{0x27, ""},
{0x28, ""},
{0x29, ""},
{0x2A, ""},
{0x2B, ""},
{0x2C, ""},
{0x2D, ""},
{0x2E, ""},
{0x2F, ""},
{0x30, ""},
{0x31, ""},
{0x32, ""},
{0x33, ""},
{0x34, ""},
{0x35, "な゙"},
{0x36, "に゙"},
{0x37, "ぬ゙"},
{0x38, "ね゙"},
{0x39, "の゙"},
{0x3A, ""},
{0x3B, ""},
{0x3C, ""},
{0x3D, ""},
{0x3E, ""},
{0x3F, "ま゙"},
{0x40, ""},
{0x41, ""},
{0x42, ""},
{0x43, ""},
{0x44, ""},
{0x45, ""},
{0x46, ""},
{0x47, ""},
{0x48, ""},
{0x49, "ま゚"},
{0x4D, "も゚"},
{0x4F, " "},
{0x57, "#"},
{0x51, "*"},
{0x52, "A1"},
{0x53, "A2"},
{0x54, "POKé"},
{0x55, "+"},
{0x58, "$"},
{0x5D, "TRAINER"},
{0x75, ""},
{0x7F, " "},
{0x60, "A"},
{0x61, "B"},
{0x62, "C"},
{0x63, "D"},
{0x64, "E"},
{0x65, "F"},
{0x66, "G"},
{0x67, "H"},
{0x68, "I"},
{0x69, "V"},
{0x6A, "S"},
{0x6B, "L"},
{0x6C, "M"},
{0x6D, ""},
{0x6E, ""},
{0x6F, ""},
{0x70, ""},
{0x71, ""},
{0x72, ""},
{0x73, ""},
{0x74, ""},
{0x75, ""},
{0x76, ""},
{0x77, ""},
{0x78, ""},
{0x80, ""},
{0x81, ""},
{0x82, ""},
{0x83, ""},
{0x84, ""},
{0x85, ""},
{0x86, ""},
{0x87, ""},
{0x88, ""},
{0x89, ""},
{0x8A, ""},
{0x8B, ""},
{0x8C, ""},
{0x8D, ""},
{0x8E, ""},
{0x8F, ""},
{0x90, ""},
{0x91, ""},
{0x92, ""},
{0x93, ""},
{0x94, ""},
{0x95, ""},
{0x96, ""},
{0x97, ""},
{0x98, ""},
{0x99, ""},
{0x9A, ""},
{0x9B, ""},
{0x9C, ""},
{0x9D, ""},
{0x9E, ""},
{0x9F, ""},
{0xA0, ""},
{0xA1, ""},
{0xA2, ""},
{0xA3, ""},
{0xA4, ""},
{0xA5, ""},
{0xA6, ""},
{0xA7, ""},
{0xA8, ""},
{0xA9, ""},
{0xAA, ""},
{0xAB, ""},
{0xAC, ""},
{0xAD, ""},
{0xAE, ""},
{0xAF, ""},
{0xB0, ""},
{0xB1, ""},
{0xB2, ""},
{0xB3, ""},
{0xB4, ""},
{0xB5, ""},
{0xB6, ""},
{0xB7, ""},
{0xB8, ""},
{0xB9, ""},
{0xBA, ""},
{0xBB, ""},
{0xBC, ""},
{0xBD, ""},
{0xBE, ""},
{0xBF, ""},
{0xC0, ""},
{0xC1, ""},
{0xC2, ""},
{0xC3, ""},
{0xC4, ""},
{0xC5, ""},
{0xC6, ""},
{0xC7, ""},
{0xC8, ""},
{0xC9, ""},
{0xCA, ""},
{0xCB, ""},
{0xCC, ""},
{0xCD, ""},
{0xCE, ""},
{0xCF, ""},
{0xD0, ""},
{0xD1, ""},
{0xD2, ""},
{0xD3, ""},
{0xD4, ""},
{0xD5, ""},
{0xD6, ""},
{0xD7, ""},
{0xD8, ""},
{0xD9, ""},
{0xDA, ""},
{0xDB, ""},
{0xDC, ""},
{0xDD, ""},
{0xDE, ""},
{0xDF, ""},
{0xE0, ""},
{0xE1, ""},
{0xE2, ""},
{0xE3, ""},
{0xE4, ""},
{0xE5, ""},
{0xE6, "?"},
{0xE7, "!"},
{0xE8, ""},
{0xE9, ""},
{0xEA, ""},
{0xEB, ""},
{0xEC, ""},
{0xED, ""},
{0xEE, ""},
{0xEF, ""},
{0xF0, ""},
{0xF1, "×"},
{0xF2, "."},
{0xF3, "/"},
{0xF4, ""},
{0xF5, ""},
{0xF6, "0"},
{0xF7, "1"},
{0xF8, "2"},
{0xF9, "3"},
{0xFA, "4"},
{0xFB, "5"},
{0xFC, "6"},
{0xFD, "7"},
{0xFE, "8"},
{0xFF, "9"}
};
Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeader)
{
Gen1GameType result;
@ -151,16 +392,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, LocalizationLanguage 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 != LocalizationLanguage::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, LocalizationLanguage 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 != LocalizationLanguage::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)

View File

@ -47,12 +47,13 @@ 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, LocalizationLanguage language)
: romReader_(romReader)
, saveManager_(saveManager)
, spriteDecoder_(romReader_)
, iconDecoder_(romReader, gameType)
, iconDecoder_(romReader, gameType, language)
, gameType_(gameType)
, localization_((uint8_t)language)
{
}
@ -62,23 +63,15 @@ const char *Gen1GameReader::getPokemonName(uint8_t index) const
static char result[20];
uint8_t encodedText[0xA];
uint32_t numRead;
uint16_t pointer;
if(gameType_ == Gen1GameType::BLUE || gameType_ == Gen1GameType::RED)
{
romReader_.seek(0x2FA3);
const uint8_t bankByte = romReader_.peek();
romReader_.seek(0x2FAE);
romReader_.readUint16(pointer);
// seek to the right location
romReader_.seekToRomPointer(pointer, bankByte);
romReader_.seek(g1_localizationOffsetsRB[localization_].names);
}
else
{
// Pkmn Yellow
romReader_.seek(0xE8000);
romReader_.seek(g1_localizationOffsetsY[localization_].names);
}
romReader_.advance((index - 1) * 0xA);
@ -86,7 +79,7 @@ const char *Gen1GameReader::getPokemonName(uint8_t index) const
// max 10 bytes
numRead = romReader_.readUntil(encodedText, 0x50, 0xA);
gen1_decodePokeText(encodedText, numRead, result, sizeof(result));
gen1_decodePokeText(encodedText, numRead, result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -94,7 +87,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 = (gameType_ == Gen1GameType::YELLOW) ? g1_localizationOffsetsY[localization_].numbers : g1_localizationOffsetsRB[localization_].numbers;
romReader_.seek(romOffset + (index - 1));
romReader_.readByte(result);
return result;
@ -105,7 +98,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 = (gameType_ == Gen1GameType::YELLOW) ? g1_localizationOffsetsY[localization_].iconTypes : g1_localizationOffsetsRB[localization_].iconTypes;
uint8_t byteVal;
Gen1PokemonIconType result;
@ -198,8 +191,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 +226,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(g1_localizationOffsetsRB[localization_].stats);
}
else
{
// mew stats
romReader_.seek(0x159C);
romReader_.readByte(statsBank);
romReader_.seek(0x1593);
romReader_.readUint16(statsPointer);
romReader_.seek(g1_localizationOffsetsRB[localization_].statsMew);
}
romReader_.seekToRomPointer(statsPointer, statsBank);
if (pokeNumber != 151)
{
// the number is 1-based.
@ -261,7 +243,7 @@ bool Gen1GameReader::readPokemonStatsForIndex(uint8_t index, Gen1PokeStats &outS
else
{
// Dealing with Pokemon yellow
romReader_.seek(0x383DE);
romReader_.seek(g1_localizationOffsetsY[localization_].stats);
// the number is 1-based.
romReader_.advance(statsStructSize * (pokeNumber - 1));
}
@ -290,7 +272,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 = (gameType_ == Gen1GameType::YELLOW) ? g1_localizationOffsetsY[localization_].paletteIndices : g1_localizationOffsetsRB[localization_].paletteIndices;
if(!romReader_.seek(romOffset + (pokeNumber - 1)))
{
return 0xFF;
@ -311,7 +293,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 = (gameType_ == Gen1GameType::YELLOW) ? g1_localizationOffsetsY[localization_].palettes : g1_localizationOffsetsRB[localization_].palettes;
romReader_.seek(romOffset + (paletteId * 8));
while(cur < end)
{
@ -328,7 +310,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), (LocalizationLanguage)localization_);
return result;
}
@ -339,7 +321,7 @@ const char *Gen1GameReader::getRivalName() const
saveManager_.seek(0x25F6);
saveManager_.readUntil(encodedRivalName, 0x50, sizeof(encodedRivalName));
gen1_decodePokeText(encodedRivalName, sizeof(encodedRivalName), result, sizeof(result));
gen1_decodePokeText(encodedRivalName, sizeof(encodedRivalName), result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -355,12 +337,12 @@ uint16_t Gen1GameReader::getTrainerID() const
Gen1Party Gen1GameReader::getParty()
{
return Gen1Party((*this), saveManager_);
return Gen1Party((*this), saveManager_, (LocalizationLanguage)localization_);
}
Gen1Box Gen1GameReader::getBox(uint8_t boxIndex)
{
return Gen1Box((*this), saveManager_, boxIndex);
return Gen1Box((*this), saveManager_, boxIndex, (LocalizationLanguage)localization_);
}
uint8_t Gen1GameReader::getCurrentBoxIndex()

View File

@ -20,10 +20,11 @@ static uint8_t reverseByte(uint8_t incoming)
return result;
}
Gen1IconDecoder::Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType)
Gen1IconDecoder::Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType, LocalizationLanguage language)
: buffer_()
, romReader_(romReader)
, gameType_(gameType)
, localization_((uint8_t)language)
{
}
@ -43,7 +44,7 @@ Gen1IconDecoder::Gen1IconDecoder(IRomReader& romReader, Gen1GameType gameType)
*/
uint8_t* Gen1IconDecoder::decode(Gen1PokemonIconType iconType, bool firstFrame)
{
const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? 0x7184D : 0x717C0;
const uint32_t romOffset = (gameType_ == Gen1GameType::YELLOW) ? g1_localizationOffsetsY[localization_].icons : g1_localizationOffsetsRB[localization_].icons;
const uint32_t ENTRY_SIZE = 6;
const uint8_t MAX_NUM_TILES = 8;

View File

@ -0,0 +1,139 @@
#include "gen1/Gen1Localization.h"
const Gen1LocalizationRomOffsets g1_localizationOffsetsRB[] = {
// 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 = 0x383DE,
.statsMew = 0x425B,
.numbers = 0x42784,
.names = 0x39446,
.iconTypes = 0x71DC1,
.icons = 0x71C74,
.paletteIndices = 0x72A0E,
.palettes = 0x72AA5
}
};
const Gen1LocalizationRomOffsets g1_localizationOffsetsY[] = {
// 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
}
};

View File

@ -188,9 +188,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, LocalizationLanguage language)
: gameReader_(gameReader)
, saveManager_(savManager)
, localization_(language)
{
}
@ -310,7 +311,7 @@ const char* Gen1Party::getPokemonNickname(uint8_t partyIndex)
saveManager_.seek(0x2F2C + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * NICKNAME_SIZE));
saveManager_.readUntil(encodedNickName, 0x50, NICKNAME_SIZE);
gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result));
gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -326,7 +327,7 @@ 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);
const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, NICKNAME_SIZE, 0x50, (LocalizationLanguage)localization_);
saveManager_.seek(0x2F2C + FIRST_NICKNAME_NAME_OFFSET + (partyIndex * NICKNAME_SIZE));
saveManager_.write(encodedNickName, encodedLength);
}
@ -340,7 +341,7 @@ const char* Gen1Party::getOriginalTrainerOfPokemon(uint8_t partyIndex)
saveManager_.seek(0x2F2C + FIRST_OT_NAME_OFFSET + (partyIndex * OT_NAME_SIZE));
saveManager_.readUntil(encodedOTName, 0x50, OT_NAME_SIZE);
gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result));
gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -350,7 +351,7 @@ void Gen1Party::setOriginalTrainerOfPokemon(uint8_t partyIndex, const char* orig
uint8_t encodedOTName[OT_NAME_SIZE];
const uint16_t FIRST_OT_NAME_OFFSET = 0x110;
const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50);
const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50, (LocalizationLanguage)localization_);
saveManager_.seek(0x2F2C + FIRST_OT_NAME_OFFSET + (partyIndex * OT_NAME_SIZE));
saveManager_.write(encodedOTName, encodedLength);
@ -388,10 +389,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, LocalizationLanguage language)
: gameReader_(gameReader)
, saveManager_(saveManager)
, boxIndex_(boxIndex)
, localization_(language)
{
}
@ -505,7 +507,7 @@ const char* Gen1Box::getPokemonNickname(uint8_t index)
saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * NICKNAME_SIZE));
saveManager_.readUntil(encodedNickName, 0x50, NICKNAME_SIZE);
gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result));
gen1_decodePokeText(encodedNickName, sizeof(encodedNickName), result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -524,7 +526,7 @@ 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);
const uint16_t encodedLength = gen1_encodePokeText(name, strlen(name), encodedNickName, NICKNAME_SIZE, 0x50, (LocalizationLanguage)localization_);
saveManager_.seekToBankOffset(bankIndex, boxOffset + nicknameOffset + (index * NICKNAME_SIZE));
saveManager_.write(encodedNickName, encodedLength);
}
@ -542,7 +544,7 @@ const char* Gen1Box::getOriginalTrainerOfPokemon(uint8_t index)
saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * OT_NAME_SIZE));
saveManager_.readUntil(encodedOTName, 0x50, OT_NAME_SIZE);
gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result));
gen1_decodePokeText(encodedOTName, sizeof(encodedOTName), result, sizeof(result), (LocalizationLanguage)localization_);
return result;
}
@ -556,7 +558,7 @@ void Gen1Box::setOriginalTrainerOfPokemon(uint8_t index, const char* originalTra
const uint16_t boxOffset = getBoxBankOffset(boxIndex_, currentBoxIndex);
const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50);
const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50, (LocalizationLanguage)localization_);
saveManager_.seekToBankOffset(bankIndex, boxOffset + otOffset + (index * OT_NAME_SIZE));
saveManager_.write(encodedOTName, encodedLength);

View File

@ -11,7 +11,7 @@
#include <png.h>
#endif
static const char* findCharsByTextCode(TextCodePair* textCodes, uint16_t numEntries, uint8_t code)
static 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)
static void 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;
@ -79,7 +79,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 +124,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;