* 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-<number>" 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-<indexnumber>".
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
This commit is contained in:
Philippe Symons 2025-02-17 12:38:21 +01:00 committed by GitHub
parent f31f31deff
commit aedb58832c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 8273 additions and 1515 deletions

View File

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

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

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

View File

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

View File

@ -1,8 +1,10 @@
#include "gen1/Gen1Common.h"
#include "gen1/Gen1Localization.h"
#include <cstdio>
#include <cstring>
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);
}

View File

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

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,224 @@
#include "utils.h"
#include "RomReader.h"
#include "gen1/Gen1Common.h"
#include "gen2/Gen2Common.h"
#include "gen1/Gen1Localization.h"
#include "gen2/Gen2Localization.h"
#include <cstdio>
#include <cstring>
/**
* 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 <path/to/english_rom.gbc> <path/to/localized_rom.gbc>\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;
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

266
include/gen1/Gen1Maps.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,91 @@
#ifndef _GEN2LOCALIZATION_H
#define _GEN2LOCALIZATION_H
#include <cstdint>
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

View File

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

View File

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

View File

@ -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<uint32_t>(cur - outBuffer) < maxRead && read(cur, 1))
{
if((*cur) == terminator)
for(i=0; i < numTerminators; ++i)
{
break;
if((*cur) == terminatorList[i])
{
return (cur - outBuffer);
}
}
++cur;
}

View File

@ -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)
@ -130,6 +377,10 @@ Gen1GameType gen1_determineGameType(const GameboyCartridgeHeader& cartridgeHeade
else if (strncmp(cartridgeHeader.title, POKEMON_YELLOW_CARTRIDGE_TITLE, sizeof(POKEMON_YELLOW_CARTRIDGE_TITLE) - 1) == 0)
{
result = Gen1GameType::YELLOW;
}
else if (strncmp(cartridgeHeader.title, POKEMON_GREEN_CARTRIDGE_TITLE, sizeof(POKEMON_GREEN_CARTRIDGE_TITLE) - 1) == 0)
{
result = Gen1GameType::GREEN;
}
else
{
@ -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<uint8_t>(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)
@ -171,8 +471,29 @@ void gen1_prepareDistributionPokemon(Gen1GameReader& gameReader, const Gen1Distr
poke.original_trainer_ID = gameReader.getTrainerID();
}
else
{
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)
{

View File

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

View File

@ -5,19 +5,21 @@
#include "utils.h"
#include <cstdlib>
#include <cstdio>
/**
* @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<Gen1Maps>(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);

View File

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

View File

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

527
src/gen1/Gen1Maps.cpp Normal file
View File

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

View File

@ -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, entrySize, 0x50, (Gen1LocalizationLanguage)localization_);
const uint16_t encodedLength = gen1_encodePokeText(originalTrainer, strlen(originalTrainer), encodedOTName, OT_NAME_SIZE, 0x50);
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);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,12 @@
#include "utils.h"
#include <cstdlib>
#include <cstdio>
// 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,11 +115,32 @@ 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)
{
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
{
if (gameType == Gen2GameType::CRYSTAL)
{
// for crystal, the backup save region is a contiguous address range
addBytesToChecksum(saveManager, 0x1209, 0x1D82, checksum);
@ -135,86 +154,118 @@ static uint16_t calculateBackupRegionChecksum(ISaveManager &saveManager, bool is
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;
if (isGameCrystal)
{
saveManager.seek(0x2D0D);
saveManager.seek(savOffset);
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;
if (isGameCrystal)
{
saveManager.seek(0x1F0D);
saveManager.seek(savOffset);
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)
{
saveManager.seek(0x2D0D);
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)
{
if(gameType == Gen2GameType::CRYSTAL)
{
saveManager.copyRegion(0x2009, 0x7209, 2778);
}
else
{
saveManager.seek(0x2D69);
saveManager.writeUint16(checksum, Endianness::LITTLE);
saveManager.copyRegion(0x2009, 0x7209, 3203);
}
}
static void writeBackupChecksum(ISaveManager &saveManager, uint16_t checksum, bool isGameCrystal)
{
if (isGameCrystal)
}
else if(localization == Gen2LocalizationLanguage::KOREAN)
{
saveManager.seek(0x1F0D);
saveManager.writeUint16(checksum, Endianness::LITTLE);
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(0x7E6D);
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 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,51 +339,78 @@ 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;
}
@ -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 <cstdio>
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);
}

View File

@ -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,18 +24,8 @@ 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_;
}

View File

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

View File

@ -4,17 +4,21 @@
#include <cstring>
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;

View File

@ -11,7 +11,7 @@
#include <png.h>
#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;