mirror of
https://github.com/GearsProgress/Poke_Transporter_GB.git
synced 2026-10-23 16:03:34 -05:00
869 lines
39 KiB
C++
869 lines
39 KiB
C++
#include "mystery_gift_injector.h"
|
|
#include "FileContainerReader.h"
|
|
#include "compression_chunks.h"
|
|
#include "flash_mem.h"
|
|
#include "global_frame_controller.h"
|
|
#include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h"
|
|
#include "pokemon_data.h"
|
|
#include "ptgb_save_data_manager.h"
|
|
#include "rom_data.h"
|
|
#include "script_patches.h"
|
|
#include "script_ruby_english_1_0_lz10_bin.h"
|
|
#include "section30_patches.h"
|
|
#include "section30_ruby_english_1_0_lz10_bin.h"
|
|
#include "translated_text.h"
|
|
#include <array>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <tonc.h>
|
|
|
|
extern "C"
|
|
{
|
|
void bps_patch(const u8 *src, u8 *out, const u8 *patch, u32 patch_size);
|
|
}
|
|
|
|
#define MG_SCRIPT_SIZE 0x3E8
|
|
#define BPS_PATCH_FILECONTAINER_CHUNK_SIZE 0x1000
|
|
|
|
// These defines are there because rom_data defines values that
|
|
// occupy more bytes/bits than needed.
|
|
// We use these to create a table with a packed key below: patchFileTable
|
|
// also refer to the comment on PACK_PATCH_KEY() for more details on how the key is packed.
|
|
#define MAP_RUBY 0
|
|
#define MAP_SAPPHIRE 1
|
|
#define MAP_FIRERED 2
|
|
#define MAP_LEAFGREEN 3
|
|
#define MAP_EMERALD 4
|
|
|
|
#define MAP_JAPANESE 0
|
|
#define MAP_ENGLISH 1
|
|
#define MAP_FRENCH 2
|
|
#define MAP_GERMAN 3
|
|
#define MAP_ITALIAN 4
|
|
#define MAP_SPANISH 5
|
|
|
|
// So, we need to associate the rom_data values with a specific patch file to convert the
|
|
// game-based payload to our specific language variant.
|
|
// But we only have indexes and their enum namings.
|
|
// So we need to map them.
|
|
// The thing is: using the existing RUBY_ID and LANG_ENG defines, would make this table much larger
|
|
// than it needs to be. There are 5 game variants (3 bits), and 6 languages (3 bits) and the highest
|
|
// version of those roms is 1.2 (2 bits) So we can pack this data into a single byte.
|
|
#define PACK_PATCH_KEY(game, lang, version) (((game) << 5) | ((lang) << 2) | (version))
|
|
|
|
#define SKIP_PATCH_INDEX 0xFF
|
|
|
|
typedef struct PatchFileTableEntry
|
|
{
|
|
u8 key;
|
|
u8 languagePatchIndex;
|
|
u8 specificPatchIndex;
|
|
} PatchFileTableEntry;
|
|
|
|
// We only need to have a single map of the patch files, because the order and size of the section30
|
|
// and script patches are exactly the same.
|
|
static constexpr PatchFileTableEntry patchFileTable[] = {
|
|
// english: game-specific transformation happens outside of this table
|
|
// no language specific transformation required
|
|
// only version-specific one
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_ENGLISH, VERS_1_0), SKIP_PATCH_INDEX, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_ENGLISH, VERS_1_1), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ENGLISH_1_1},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_ENGLISH, VERS_1_2), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ENGLISH_1_2},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_ENGLISH, VERS_1_0), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_ENGLISH_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_ENGLISH, VERS_1_1), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_ENGLISH_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_ENGLISH, VERS_1_2), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_ENGLISH_1_2},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_ENGLISH, VERS_1_0), SKIP_PATCH_INDEX, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_ENGLISH, VERS_1_0), SKIP_PATCH_INDEX, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_ENGLISH, VERS_1_1), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_ENGLISH_1_1},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_ENGLISH, VERS_1_0), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_ENGLISH_1_0},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_ENGLISH, VERS_1_1), SKIP_PATCH_INDEX,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_ENGLISH_1_1},
|
|
// Other languages: game-specific transformation (e.g. english ruby -> english fire red) happens
|
|
// outside of this table language-specific transformation transforms the english game specific
|
|
// payload to the french one version-specific transformation transforms ruby->sapphire
|
|
// or 1.0->1.1 or both. French:
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_FRENCH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_FRENCH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_FRENCH, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_FRENCH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_FRENCH_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_FRENCH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_FRENCH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_FRENCH_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_FRENCH, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_FRENCH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_FRENCH_1_1},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_FRENCH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_EMERALD_FRENCH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_FRENCH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_FRENCH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_FRENCH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_FRENCH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_FRENCH_1_0},
|
|
// German:
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_GERMAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_GERMAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_GERMAN, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_GERMAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_GERMAN_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_GERMAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_GERMAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_GERMAN_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_GERMAN, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_GERMAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_GERMAN_1_1},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_GERMAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_EMERALD_GERMAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_GERMAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_GERMAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_GERMAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_GERMAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_GERMAN_1_0},
|
|
// Italian:
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_ITALIAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ITALIAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_ITALIAN, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ITALIAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ITALIAN_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_ITALIAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ITALIAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_ITALIAN_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_ITALIAN, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_ITALIAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_ITALIAN_1_1},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_ITALIAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_EMERALD_ITALIAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_ITALIAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_ITALIAN_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_ITALIAN, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_ITALIAN_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_ITALIAN_1_0},
|
|
// Japanese:
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_JAPANESE, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_JAPANESE_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_JAPANESE, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_JAPANESE_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_JAPANESE, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_JAPANESE_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_JAPANESE, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_JAPANESE_1_1},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_JAPANESE, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_EMERALD_JAPANESE_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_JAPANESE, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_JAPANESE_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_JAPANESE, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_JAPANESE_1_1},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_JAPANESE, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_JAPANESE_1_0},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_JAPANESE, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_JAPANESE_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_JAPANESE_1_1},
|
|
// Spanish:
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_SPANISH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_SPANISH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_RUBY, MAP_SPANISH, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_SPANISH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_SPANISH_1_1},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_SPANISH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_SPANISH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_SPANISH_1_0},
|
|
{PACK_PATCH_KEY(MAP_SAPPHIRE, MAP_SPANISH, VERS_1_1),
|
|
(u8)Script_patchesFiles::SCRIPT_RUBY_SPANISH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_SAPPHIRE_SPANISH_1_1},
|
|
{PACK_PATCH_KEY(MAP_EMERALD, MAP_SPANISH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_EMERALD_SPANISH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_FIRERED, MAP_SPANISH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_SPANISH_1_0, SKIP_PATCH_INDEX},
|
|
{PACK_PATCH_KEY(MAP_LEAFGREEN, MAP_SPANISH, VERS_1_0),
|
|
(u8)Script_patchesFiles::SCRIPT_FIRERED_SPANISH_1_0,
|
|
(u8)Script_patchesFiles::SCRIPT_LEAFGREEN_SPANISH_1_0}};
|
|
|
|
static constexpr std::pair<u16, u16> firstTimeTextEntryPairs[] = {
|
|
{RSEFRLG_dia_textIAm_first_rs, RSEFRLG_dia_textIAm_second_rs},
|
|
{RSEFRLG_dia_textIAm_first_frlge, RSEFRLG_dia_textIAm_second_frlge},
|
|
{RSEFRLG_dia_textYouMustBe_first, RSEFRLG_dia_textYouMustBe_second}};
|
|
|
|
#if BUILD_LANG == 7
|
|
static const u8 **RSEFRLG_spanish_chunk_list = RSEFRLG_spanishLA_chunk_list;
|
|
#else
|
|
static const u8 **RSEFRLG_spanish_chunk_list = RSEFRLG_spanishEU_chunk_list;
|
|
#endif
|
|
|
|
// This will need to be modified for the JP releases
|
|
static constexpr u8 em_wonder_card[0x14E] = {0x08, 0x6E, 0x00, 0x00, 0xBA, 0xB4, 0xBE, 0xB9, 0x00,
|
|
0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xCA, 0xCC, 0xC9, 0xC0, 0xBF, 0xCD, 0xCD, 0xC9, 0xCC,
|
|
0x00, 0xC0, 0xBF, 0xC8, 0xC8, 0xBF, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE,
|
|
0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0x00, 0xBD, 0xD9, 0xE6, 0xE8, 0xDD, 0xDA, 0xDD, 0xD7,
|
|
0xD5, 0xE8, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xDD, 0xE7, 0xDD, 0xE8, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xDC,
|
|
0xE3, 0xE9, 0xE7, 0xD9, 0x00, 0xE7, 0xE3, 0xE9, 0xE8, 0xDC, 0xD9, 0xD5, 0xE7, 0xE8, 0x00, 0xE3,
|
|
0xDA, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xBD, 0xBF, 0xC8,
|
|
0xCE, 0xBF, 0xCC, 0x00, 0xDD, 0xE2, 0x00, 0xCD, 0xE3, 0xE3, 0xE8, 0xE3, 0xE4, 0xE3, 0xE0, 0xDD,
|
|
0xE7, 0x00, 0xBD, 0xDD, 0xE8, 0xED, 0x00, 0xE8, 0xE3, 0x00, 0xE6, 0xD9, 0xD7, 0xDD, 0xD9, 0xEA,
|
|
0xD9, 0x00, 0x00, 0x00, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA,
|
|
0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xE3, 0x00, 0xE2, 0xE3, 0xE8, 0x00, 0xE8, 0xE3, 0xE7, 0xE7,
|
|
0x00, 0xE8, 0xDC, 0xDD, 0xE7, 0x00, 0xBF, 0xEC, 0xD7, 0xDC, 0xD5, 0xE2, 0xDB, 0xD9, 0x00, 0xBD,
|
|
0xD5, 0xE6, 0xD8, 0x00, 0xD6, 0xD9, 0xDA, 0xE3, 0xE6, 0xD9, 0x00, 0x00, 0x00, 0xE6, 0xD9, 0xD7,
|
|
0xD9, 0xDD, 0xEA, 0xDD, 0xE2, 0xDB, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2,
|
|
0xE7, 0xDA, 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00};
|
|
// static u8 rs_wonder_card[0x14E] = {
|
|
// 0xCE, 0x7C, 0x00, 0x00, 0xBA, 0xB4, 0xBE, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
|
|
// 0x00, 0xCA, 0xCC, 0xC9, 0xC0, 0xBF, 0xCD, 0xCD, 0xC9, 0xCC, 0x00, 0xC0, 0xBF, 0xC8, 0xC8,
|
|
// 0xBF, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xE6, 0xD5, 0xE2,
|
|
// 0xE7, 0xDA, 0xD9, 0xE6, 0x00, 0xBD, 0xD9, 0xE6, 0xE8, 0xDD, 0xDA, 0xDD, 0xD7, 0xD5, 0xE8,
|
|
// 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00, 0xD0, 0xDD, 0xE7, 0xDD, 0xE8, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xDC,
|
|
// 0xE3, 0xE9, 0xE7, 0xD9, 0x00, 0xE7, 0xE3, 0xE9, 0xE8, 0xDC, 0xD9, 0xD5, 0xE7, 0xE8, 0x00,
|
|
// 0xE3, 0xDA, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xBD,
|
|
// 0xBF, 0xC8, 0xCE, 0xBF, 0xCC, 0x00, 0xDD, 0xE2, 0x00, 0xC7, 0xE3, 0xE7, 0xE7, 0xD8, 0xD9,
|
|
// 0xD9, 0xE4, 0x00, 0xBD, 0xDD, 0xE8, 0xED, 0x00, 0xE8, 0xE3, 0x00, 0xE6, 0xD9, 0xD7, 0xDD,
|
|
// 0xD9, 0xEA, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8,
|
|
// 0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7,
|
|
// 0xC9, 0xC8, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE,
|
|
// 0xE3, 0x00, 0xE2, 0xE3, 0xE8, 0x00, 0xE8, 0xE3, 0xE7, 0xE7, 0x00, 0xE8, 0xDC, 0xDD, 0xE7,
|
|
// 0x00, 0xBF, 0xEC, 0xD7, 0xDC, 0xD5, 0xE2, 0xDB, 0xD9, 0x00, 0xBD, 0xD5, 0xE6, 0xD8, 0x00,
|
|
// 0xD6, 0xD9, 0xDA, 0xE3, 0xE6, 0xD9, 0x00, 0x00, 0x00, 0xE6, 0xD9, 0xD7, 0xD9, 0xDD, 0xEA,
|
|
// 0xDD, 0xE2, 0xDB, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA,
|
|
// 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00, 0x00,
|
|
// 0x00, 0x00, 0x00, 0x00}; // checksum
|
|
static constexpr u8 frlg_wonder_card[0x14E] = {0x67, 0x18, 0x00, 0x00, 0xBA, 0xB4, 0xBE, 0xB9, 0x00,
|
|
0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0xCA, 0xCC, 0xC9, 0xC0, 0xBF, 0xCD, 0xCD, 0xC9, 0xCC,
|
|
0x00, 0xC0, 0xBF, 0xC8, 0xC8, 0xBF, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE,
|
|
0xE6, 0xD5, 0xE2, 0xE7, 0xDA, 0xD9, 0xE6, 0x00, 0xBD, 0xD9, 0xE6, 0xE8, 0xDD, 0xDA, 0xDD, 0xD7,
|
|
0xD5, 0xE8, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xDD, 0xE7, 0xDD, 0xE8, 0x00, 0xE8, 0xDC, 0xD9, 0x00, 0xDC,
|
|
0xE3, 0xE9, 0xE7, 0xD9, 0x00, 0xE7, 0xE3, 0xE9, 0xE8, 0xDC, 0x00, 0xE3, 0xDA, 0x00, 0xE8, 0xDC,
|
|
0xD9, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0x00, 0x00, 0x00, 0x00, 0xBD, 0xBF, 0xC8,
|
|
0xCE, 0xBF, 0xCC, 0x00, 0xE3, 0xE2, 0x00, 0xCD, 0xD9, 0xEA, 0xD9, 0xE2, 0x00, 0xC3, 0xE7, 0xE0,
|
|
0xD5, 0xE2, 0xD8, 0x00, 0xE8, 0xE3, 0x00, 0xE6, 0xD9, 0xD7, 0xDD, 0xD9, 0xEA, 0xD9, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2, 0xE7, 0xDA,
|
|
0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0xE3, 0x00, 0xE2, 0xE3, 0xE8, 0x00, 0xE8, 0xE3, 0xE7, 0xE7,
|
|
0x00, 0xE8, 0xDC, 0xDD, 0xE7, 0x00, 0xBF, 0xEC, 0xD7, 0xDC, 0xD5, 0xE2, 0xDB, 0xD9, 0x00, 0xBD,
|
|
0xD5, 0xE6, 0xD8, 0x00, 0xD6, 0xD9, 0xDA, 0xE3, 0xE6, 0xD9, 0x00, 0x00, 0x00, 0xE6, 0xD9, 0xD7,
|
|
0xD9, 0xDD, 0xEA, 0xDD, 0xE2, 0xDB, 0x00, 0xED, 0xE3, 0xE9, 0xE6, 0x00, 0xE8, 0xE6, 0xD5, 0xE2,
|
|
0xE7, 0xDA, 0xD9, 0xE6, 0xD9, 0xD8, 0x00, 0xCA, 0xC9, 0xC5, 0x1B, 0xC7, 0xC9, 0xC8, 0xAB, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00}; // checksum
|
|
|
|
/// function to calculate a 32-bit checksum of a script buffer.
|
|
static u32 calc_checksum32(const u8 *buffer, u32 size)
|
|
{
|
|
u32 i;
|
|
u32 checksum = 0;
|
|
|
|
for (i = 0; i < size; ++i)
|
|
{
|
|
checksum += buffer[i];
|
|
}
|
|
|
|
return checksum;
|
|
}
|
|
|
|
/// function to calculate a 16-bit CRC of a script buffer.
|
|
static u16 calc_crc16(const u8 *buffer, u32 size) // Implementation taken from PokeEmerald Decomp
|
|
{
|
|
u32 i, j;
|
|
u16 crc = 0x1121;
|
|
|
|
for (i = 0; i < size; ++i)
|
|
{
|
|
crc ^= buffer[i];
|
|
for (j = 0; j < 8; ++j)
|
|
{
|
|
if (crc & 1)
|
|
crc = (crc >> 1) ^ 0x8408;
|
|
else
|
|
crc >>= 1;
|
|
}
|
|
}
|
|
return ~crc;
|
|
};
|
|
|
|
/** Converts the lang code to the PCCS Language enum */
|
|
static Language convertLangCodeIntoLanguage(int32_t langCode)
|
|
{
|
|
switch (langCode)
|
|
{
|
|
case LANG_JPN:
|
|
return JAPANESE;
|
|
case LANG_ENG:
|
|
return ENGLISH;
|
|
case LANG_FRE:
|
|
return FRENCH;
|
|
case LANG_ITA:
|
|
return ITALIAN;
|
|
case LANG_GER:
|
|
return GERMAN;
|
|
case LANG_SPA:
|
|
return SPANISH;
|
|
default:
|
|
return ENGLISH;
|
|
}
|
|
}
|
|
|
|
static void pickRSEFRLGChunks(Language lang, const u8 **&chunkList, u32 &size)
|
|
{
|
|
size = 1;
|
|
switch (lang)
|
|
{
|
|
case JAPANESE:
|
|
chunkList = RSEFRLG_japanese_chunk_list;
|
|
break;
|
|
case FRENCH:
|
|
chunkList = RSEFRLG_french_chunk_list;
|
|
break;
|
|
case ITALIAN:
|
|
chunkList = RSEFRLG_italian_chunk_list;
|
|
break;
|
|
case GERMAN:
|
|
chunkList = RSEFRLG_german_chunk_list;
|
|
break;
|
|
case SPANISH:
|
|
chunkList = RSEFRLG_spanish_chunk_list;
|
|
break;
|
|
default:
|
|
chunkList = RSEFRLG_english_chunk_list;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief This one loads the specified textEntry index into a newly malloc'ed outBuffer.
|
|
* It removes the colored text from the text entry if necessary and stores the actual buffer depth
|
|
* into outBufferDepth
|
|
*
|
|
* WARNING: the caller becomes owner of the outBuffer pointer. Make sure to free() it!
|
|
*/
|
|
static void loadProcessedTextEntry(const u16 *charset, FileContainerReader &textReader,
|
|
u16 entryIndex, u8 *&outBuffer, u32 &outBufferDepth)
|
|
{
|
|
const u8 *curText = textReader.getPointerToFileInDecompressionBuffer(entryIndex);
|
|
u8 *curBuffer;
|
|
|
|
outBuffer = (u8 *)malloc(textReader.getFileSize(entryIndex));
|
|
curBuffer = outBuffer;
|
|
|
|
// Process the text entry and copy it into a temporary buffer, removing any colored text if
|
|
// necessary.
|
|
while ((*curText) != 0xFF)
|
|
{
|
|
if (curr_GBA_rom.is_hoenn())
|
|
{
|
|
if (*curText == 0xFC && (get_char_from_charset(charset, (u16)(*(curText + 1))) ==
|
|
0x01)) // Removes colored text
|
|
{
|
|
curText += 2;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
*(curBuffer) = *curText;
|
|
++curBuffer;
|
|
++curText;
|
|
}
|
|
*(curBuffer) = 0xFF; // End string
|
|
++curBuffer;
|
|
|
|
outBufferDepth = curBuffer - outBuffer;
|
|
}
|
|
|
|
/**
|
|
* @brief This function injects a text entry generated by text_helper into the provided buffer.
|
|
* NOTE: bufferPos should point to the exact location at which the text entry should be injected.
|
|
* NOTE: this code originated from text_var::insert_text() and has been adapted for use in this
|
|
* context.
|
|
*
|
|
* @param charset a charset buffer to use hen injecting the text entry. It's used to detect colored
|
|
* text and remove it if necessary.
|
|
* @param bufferPos an offset into the buffer where the text entry should be injected.
|
|
* @param textReader a FileContainerReader instance used to read the text entry.
|
|
* @param entryIndex the index of the text entry to inject.
|
|
* @return u32 the number of bytes injected into the buffer.
|
|
*/
|
|
static u32 __attribute__((noinline)) injectTextEntryIntoBuffer(const u16 *charset, u8 *buffer,
|
|
u32 bufferSize, u32 bufferPos, FileContainerReader &textReader, u16 entryIndex,
|
|
bool isFirstTime)
|
|
{
|
|
u8 *curBuffer = buffer + bufferPos;
|
|
u8 *tempBuffer;
|
|
u32 bufferDepth;
|
|
u16 selectedEntryIndex;
|
|
u16 otherEntryIndex;
|
|
u32 otherBufferDepth;
|
|
|
|
// if this is a first_time related text, determine which entry to inject first.
|
|
selectedEntryIndex = entryIndex;
|
|
otherEntryIndex = UINT16_MAX;
|
|
for (u32 i = 0; i < sizeof(firstTimeTextEntryPairs) / sizeof(firstTimeTextEntryPairs[0]); ++i)
|
|
{
|
|
if (entryIndex == firstTimeTextEntryPairs[i].first)
|
|
{
|
|
if (isFirstTime)
|
|
{
|
|
otherEntryIndex = firstTimeTextEntryPairs[i].second;
|
|
}
|
|
else
|
|
{
|
|
selectedEntryIndex = firstTimeTextEntryPairs[i].second;
|
|
otherEntryIndex = entryIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
loadProcessedTextEntry(charset, textReader, selectedEntryIndex, tempBuffer, bufferDepth);
|
|
// Move the existing buffer content to make room for the new text entry
|
|
memmove(curBuffer + bufferDepth, curBuffer, bufferSize - bufferPos - bufferDepth);
|
|
// now copy the processed text into the buffer at the current position
|
|
memcpy(curBuffer, tempBuffer, bufferDepth);
|
|
free(tempBuffer);
|
|
|
|
// mystery_gift_builder in gba-payload-generator reserves space for the largest entry of the
|
|
// first_time/second_time pair. this is done to make sure the pointer references within the
|
|
// payload remain the same, no matter which one we inject. however, we need to take this into
|
|
// account, so we inject the next entry at the right offset as well, taking that reservation of
|
|
// space into account.
|
|
if (otherEntryIndex != UINT16_MAX)
|
|
{
|
|
loadProcessedTextEntry(charset, textReader, otherEntryIndex, tempBuffer, otherBufferDepth);
|
|
free(tempBuffer);
|
|
if (bufferDepth < otherBufferDepth)
|
|
{
|
|
bufferDepth = otherBufferDepth;
|
|
}
|
|
}
|
|
|
|
return bufferDepth;
|
|
}
|
|
|
|
/**
|
|
* @brief This function re-injects the RSEFRLG text table entries into the section30 and script
|
|
* payload buffers. In gba-payload-generator, we are stripping those texts after payload generation
|
|
* to improve compression and while keeping the pointers/offsets inside the payload intact.
|
|
*
|
|
* But in order to use the payload, we actually need to reinsert those at runtime.
|
|
*/
|
|
static void __attribute__((noinline)) reinjectMysteryGiftPayloadTexts(
|
|
u8 *section30Buffer, u8 *mgScriptBuffer, bool isFirstTime)
|
|
{
|
|
u8 decompressionBuffer[4096];
|
|
u16 gen3Charset[256];
|
|
u32 bytesInjected;
|
|
// the reason for using std::array for these is simply to be able to use initializer lists
|
|
// below.
|
|
std::array<u8, 8> section30TextSequence;
|
|
std::array<u8, 4> scriptTextSequence;
|
|
u8 textGreetEntryIndex;
|
|
u8 iAmEntryIndex;
|
|
u32 section30Offset;
|
|
u32 mgScriptOffset;
|
|
const u8 **chunklist;
|
|
u32 chunkListSize;
|
|
Language lang = convertLangCodeIntoLanguage(curr_GBA_rom.language);
|
|
|
|
pickRSEFRLGChunks(lang, chunklist, chunkListSize);
|
|
FileContainerReader textReader(chunklist, chunkListSize, sizeof(decompressionBuffer));
|
|
textReader.init(decompressionBuffer, sizeof(decompressionBuffer));
|
|
|
|
// make the section30Buffer and mgScriptBuffer pointers point to the exact insertion position
|
|
// within them. the section30 text offset (0x97E) remains consistent across languages and games
|
|
// in the payload. the script text offset shifts depending on the game version. (observed based
|
|
// on logs from gba-payload-generator)
|
|
|
|
// Also construct text entry sequences, because we need to maintain the same order of injection
|
|
// as during the payload generation with gba-payload-generator
|
|
section30Offset = 0x97E;
|
|
|
|
switch (curr_GBA_rom.gamecode)
|
|
{
|
|
case RUBY_ID:
|
|
case SAPPHIRE_ID:
|
|
mgScriptOffset = 0x268;
|
|
textGreetEntryIndex = RSEFRLG_dia_textGreet_rse;
|
|
iAmEntryIndex = RSEFRLG_dia_textIAm_first_rs;
|
|
section30TextSequence = {RSEFRLG_dia_textThank_rs, RSEFRLG_dia_textPCFull_rs,
|
|
RSEFRLG_dia_textWeHere_rs, RSEFRLG_dia_textPCConvo_rs, RSEFRLG_dia_textPCThanks_rs,
|
|
RSEFRLG_dia_textLookerFull_rs, RSEFRLG_dia_textMoveBox_rs, RSEFRLG_dia_textRecieved_rs};
|
|
break;
|
|
case FIRERED_ID:
|
|
case LEAFGREEN_ID:
|
|
mgScriptOffset = 0x278;
|
|
textGreetEntryIndex = RSEFRLG_dia_textGreet_frlg;
|
|
iAmEntryIndex = RSEFRLG_dia_textIAm_first_frlge;
|
|
section30TextSequence = {RSEFRLG_dia_textThank_frlge, RSEFRLG_dia_textPCFull_frlge,
|
|
RSEFRLG_dia_textWeHere_frlg, RSEFRLG_dia_textPCConvo_frlge,
|
|
RSEFRLG_dia_textPCThanks_frlge, RSEFRLG_dia_textLookerFull_frlge,
|
|
RSEFRLG_dia_textMoveBox_frlg, RSEFRLG_dia_textRecieved_frlge};
|
|
break;
|
|
default:
|
|
mgScriptOffset = 0x298;
|
|
textGreetEntryIndex = RSEFRLG_dia_textGreet_rse;
|
|
iAmEntryIndex = RSEFRLG_dia_textIAm_first_frlge;
|
|
section30TextSequence = {RSEFRLG_dia_textThank_frlge, RSEFRLG_dia_textPCFull_frlge,
|
|
RSEFRLG_dia_textWeHere_e, RSEFRLG_dia_textPCConvo_frlge, RSEFRLG_dia_textPCThanks_frlge,
|
|
RSEFRLG_dia_textLookerFull_frlge, RSEFRLG_dia_textMoveBox_e,
|
|
RSEFRLG_dia_textRecieved_frlge};
|
|
break;
|
|
}
|
|
|
|
scriptTextSequence = {textGreetEntryIndex, RSEFRLG_dia_textYouMustBe_first, iAmEntryIndex};
|
|
|
|
load_localized_charset(gen3Charset, 3, lang);
|
|
|
|
for (unsigned i = 0; i < 8; ++i)
|
|
{
|
|
bytesInjected = injectTextEntryIntoBuffer(gen3Charset, section30Buffer, 0x1000,
|
|
section30Offset, textReader, section30TextSequence[i], isFirstTime);
|
|
section30Offset += bytesInjected;
|
|
}
|
|
|
|
for (unsigned i = 0; i < 3; ++i)
|
|
{
|
|
bytesInjected = injectTextEntryIntoBuffer(gen3Charset, mgScriptBuffer, MG_SCRIPT_SIZE,
|
|
mgScriptOffset, textReader, scriptTextSequence[i], isFirstTime);
|
|
mgScriptOffset += bytesInjected;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief This function injects the PokeBox pokémons into section30 at offset 0x0, which is what the
|
|
* payload expects. It assumes that all the pokemons in the box are already converted. (should be
|
|
* done by script_array.cpp)
|
|
*
|
|
* @param box pointer to the PokeBox instance containing the Pokémon to inject.
|
|
* @param section30Buffer The buffer representing section 30 of the save file.
|
|
*/
|
|
static void __attribute__((noinline)) injectBoxIntoSection30(PokeBox *box, u8 *section30Buffer)
|
|
{
|
|
u8 dex_nums[MAX_PKMN_IN_BOX] = {};
|
|
u8 *curSection30 = section30Buffer;
|
|
|
|
for (int i = 0; i < MAX_PKMN_IN_BOX; i++) // Add in the Pokemon data
|
|
{
|
|
Gen3Pokemon *curr_pkmn = box->getGen3Pokemon(i);
|
|
if (curr_pkmn->isValid)
|
|
{
|
|
tonccpy(curSection30, curr_pkmn->dataArrayPtr, POKEMON_SIZE);
|
|
|
|
curr_pkmn->decryptSubstructures();
|
|
dex_nums[i] = curr_pkmn->getSpeciesIndexNumber();
|
|
}
|
|
|
|
curSection30 += POKEMON_SIZE;
|
|
}
|
|
|
|
// Add in the dex numbers
|
|
tonccpy(curSection30, dex_nums, MAX_PKMN_IN_BOX);
|
|
}
|
|
|
|
static void pickPatchFiles(u32 &gamePatchFile, u32 &languagePatchFile, u32 &specificPatchFile)
|
|
{
|
|
u32 simplifiedGameCode;
|
|
u32 simplifiedLanguageCode;
|
|
|
|
switch (curr_GBA_rom.gamecode)
|
|
{
|
|
case RUBY_ID:
|
|
gamePatchFile = (u32)Script_patchesFiles::SCRIPT_RUBY_ENGLISH_1_0;
|
|
simplifiedGameCode = MAP_RUBY;
|
|
break;
|
|
case SAPPHIRE_ID:
|
|
gamePatchFile = (u32)Script_patchesFiles::SCRIPT_RUBY_ENGLISH_1_0;
|
|
simplifiedGameCode = MAP_SAPPHIRE;
|
|
break;
|
|
case FIRERED_ID:
|
|
gamePatchFile = (u32)Script_patchesFiles::SCRIPT_FIRERED_ENGLISH_1_0;
|
|
simplifiedGameCode = MAP_FIRERED;
|
|
break;
|
|
case LEAFGREEN_ID:
|
|
gamePatchFile = (u32)Script_patchesFiles::SCRIPT_FIRERED_ENGLISH_1_0;
|
|
simplifiedGameCode = MAP_LEAFGREEN;
|
|
break;
|
|
case EMERALD_ID:
|
|
default:
|
|
gamePatchFile = (u32)Script_patchesFiles::SCRIPT_EMERALD_ENGLISH_1_0;
|
|
simplifiedGameCode = MAP_EMERALD;
|
|
break;
|
|
}
|
|
|
|
switch (curr_GBA_rom.language)
|
|
{
|
|
case LANG_JPN:
|
|
simplifiedLanguageCode = MAP_JAPANESE;
|
|
break;
|
|
case LANG_ENG:
|
|
simplifiedLanguageCode = MAP_ENGLISH;
|
|
break;
|
|
case LANG_FRE:
|
|
simplifiedLanguageCode = MAP_FRENCH;
|
|
break;
|
|
case LANG_GER:
|
|
simplifiedLanguageCode = MAP_GERMAN;
|
|
break;
|
|
case LANG_ITA:
|
|
simplifiedLanguageCode = MAP_ITALIAN;
|
|
break;
|
|
case LANG_SPA:
|
|
simplifiedLanguageCode = MAP_SPANISH;
|
|
break;
|
|
default:
|
|
simplifiedLanguageCode = MAP_ENGLISH;
|
|
break;
|
|
}
|
|
|
|
const u8 mapKey =
|
|
PACK_PATCH_KEY(simplifiedGameCode, simplifiedLanguageCode, (u8)curr_GBA_rom.version);
|
|
for (u32 i = 0; i < sizeof(patchFileTable) / sizeof(patchFileTable[0]); ++i)
|
|
{
|
|
if (patchFileTable[i].key == mapKey)
|
|
{
|
|
languagePatchFile = patchFileTable[i].languagePatchIndex;
|
|
specificPatchFile = patchFileTable[i].specificPatchIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief This helper function will apply the BPS patch with index patchFileIndex stored in reader
|
|
* to the srcBuffer data and store the result in dstBuffer. srcBuffer remains untouched.
|
|
*/
|
|
static void __attribute__((noinline)) applyBPSPatchToBuffer(
|
|
FileContainerReader &reader, u8 *dstBuffer, u8 *srcBuffer, u32 patchFileIndex)
|
|
{
|
|
// Note: you may think it's peculiar that we're malloc'ing patchBuffer below.
|
|
// But here's the dilemma: the files in the file container may span multiple chunks.
|
|
// (mostly the last file in each chunk)
|
|
// Therefore using getPointerToFileInDecompressionBuffer() would be unsafe.
|
|
// We could've used the @noSpanChunks directive in the .containerdef file to overcome that.
|
|
// However, then we're hit by an additional problem: We've constructed patchFileTable above
|
|
// using the script_patches indexes, relying on those to be consistent/representative for both
|
|
// the section30 and script patch filecontainers. but section30 is much larger than script.
|
|
// Therefore the patches don't have the same filesizes either. This would result in
|
|
// @noSpanChunks injecting a different number of dummy files in each filecontainer, which would
|
|
// break the index consistency.
|
|
//
|
|
// You might argue that we might as well use a statically allocated buffer for the patch.
|
|
// However, our IWRAM consumption is already quite high here. And malloc() will just use unused
|
|
// space at the end of EWRAM.
|
|
// (=where our multiboot rom resides)
|
|
const u32 patchFileSize = reader.getFileSize(patchFileIndex);
|
|
u8 *patchBuffer = (u8 *)malloc(patchFileSize);
|
|
reader.readFile(patchFileIndex, patchBuffer);
|
|
bps_patch(srcBuffer, dstBuffer, patchBuffer, patchFileSize);
|
|
free(patchBuffer);
|
|
}
|
|
|
|
static void __attribute__((noinline)) patchBuffer(FileContainerReader &reader, u8 *buffer,
|
|
u32 bufferSize, u32 gamePatchFileIndex, u32 languagePatchFileIndex, u32 specificPatchFileIndex)
|
|
{
|
|
u8 tempBuffer[4096];
|
|
memset(tempBuffer, 0, bufferSize);
|
|
|
|
applyBPSPatchToBuffer(reader, tempBuffer, buffer, gamePatchFileIndex);
|
|
|
|
// We shouldn't attempt to apply the same base payload patch twice.
|
|
// that's what the magic SKIP_SPECIFIC_PAYLOAD_PATCH_INDEX value is for.
|
|
if (languagePatchFileIndex != SKIP_PATCH_INDEX)
|
|
{
|
|
applyBPSPatchToBuffer(reader, buffer, tempBuffer, languagePatchFileIndex);
|
|
}
|
|
else
|
|
{
|
|
memcpy(buffer, tempBuffer, bufferSize);
|
|
}
|
|
|
|
if (specificPatchFileIndex != SKIP_PATCH_INDEX)
|
|
{
|
|
// now turn the english <gametype> payload into the specific language payload.
|
|
// the result will be stored in tempBuffer
|
|
applyBPSPatchToBuffer(reader, tempBuffer, buffer, specificPatchFileIndex);
|
|
memcpy(buffer, tempBuffer, bufferSize);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief This function reconstructs the pregenerated payload for section30 and the script buffer.
|
|
* The way it works is this:
|
|
* - We have stored the english ruby 1.0 payload (section30 + scriptbuffer) in compressed form.
|
|
* - We have created BPS patches to convert this payload from english ruby to english <gametype> for
|
|
* 3 variants (ruby, emerald and fire red)
|
|
* - There's another BPS patch to convert the english <gameType> variant to the language specific
|
|
* variant (for 3 game types: ruby, emerald and fire red)
|
|
* - Finally, there's another BPS patch to convert the language specific base payload to the final
|
|
* payload (for example: from french ruby 1.0 to french sapphire 1.1)
|
|
*
|
|
* This is all done to conserve space as much as possible and benefit most from compression.
|
|
*
|
|
* So, in order to reconstruct the payload for a specific game and language, we need to:
|
|
* 1. Decompress the english ruby payload into section30Buffer and scriptBuffer.
|
|
* 2. Determine the 3 patch sets required (transform to right gametype, transform to right language,
|
|
* transform to final version) for both scriptBuffer and section30Buffer.
|
|
* 3. uncompress these patches from the filecontainer.
|
|
* 4. Apply these patches.
|
|
*
|
|
* This function attempts to do it in a way that requires the least amount of IWRAM possible.
|
|
* One way we do that is by reusing the same decompression buffer for both file containers.
|
|
*
|
|
* WARNING: This function will require more than 6KB of additional IWRAM for decompression buffer
|
|
* and temp buffer! NOTE: __attribute__((noinline)) is used to ensure this function gets its own
|
|
* stack frame and therefore the local buffers don't leak into the caller's stack frame. This is
|
|
* important because IWRAM consumption is a big concern in PTGB.
|
|
*/
|
|
static void __attribute__((noinline)) reconstructPregeneratedPayloads(
|
|
u8 *section30Buffer, u8 *scriptBuffer)
|
|
{
|
|
const u32 patchContainerChunkSize =
|
|
4096; // defined by @chunkSize directive in the .containerdef files
|
|
u8 decompressionBuffer[patchContainerChunkSize];
|
|
u32 gamePatchFile;
|
|
u32 languagePatchFile;
|
|
u32 specificPatchFile = (u8)Script_patchesFiles::SCRIPT_RUBY_ENGLISH_1_0;
|
|
|
|
FileContainerReader section30PatchesReader(section30_patches_chunk_list,
|
|
sizeof(section30_patches_chunk_list) / sizeof(section30_patches_chunk_list[0]),
|
|
BPS_PATCH_FILECONTAINER_CHUNK_SIZE);
|
|
FileContainerReader scriptPatchesReader(script_patches_chunk_list,
|
|
sizeof(script_patches_chunk_list) / sizeof(script_patches_chunk_list[0]),
|
|
BPS_PATCH_FILECONTAINER_CHUNK_SIZE);
|
|
|
|
// first determine the patch indexes needed to convert the base payloads to the specific game.
|
|
pickPatchFiles(gamePatchFile, languagePatchFile, specificPatchFile);
|
|
|
|
// assume we found the entry.
|
|
memset(section30Buffer, 0, 0x1000);
|
|
memset(scriptBuffer, 0, MG_SCRIPT_SIZE);
|
|
LZ77UnCompWram(section30_ruby_english_1_0_lz10_bin, section30Buffer);
|
|
LZ77UnCompWram(script_ruby_english_1_0_lz10_bin, scriptBuffer);
|
|
|
|
// patch section30
|
|
section30PatchesReader.init(decompressionBuffer, patchContainerChunkSize);
|
|
patchBuffer(section30PatchesReader, section30Buffer, 0x1000, gamePatchFile, languagePatchFile,
|
|
specificPatchFile);
|
|
|
|
// patch script
|
|
scriptPatchesReader.init(decompressionBuffer, patchContainerChunkSize);
|
|
patchBuffer(scriptPatchesReader, scriptBuffer, MG_SCRIPT_SIZE, gamePatchFile, languagePatchFile,
|
|
specificPatchFile);
|
|
}
|
|
|
|
bool inject_mystery(PokeBox *box, bool isFirstTime)
|
|
{
|
|
u32 checksum;
|
|
u8 script_buffer[MG_SCRIPT_SIZE];
|
|
|
|
reconstructPregeneratedPayloads(global_memory_buffer, script_buffer);
|
|
reinjectMysteryGiftPayloadTexts(global_memory_buffer, script_buffer, isFirstTime);
|
|
|
|
// One thing our pregeneration process obviously leaves out is the pokemon data.
|
|
// so let's add that in now.
|
|
injectBoxIntoSection30(box, global_memory_buffer);
|
|
|
|
if (curr_GBA_rom.is_ruby_sapphire())
|
|
{
|
|
checksum = calc_checksum32(script_buffer, MG_SCRIPT_SIZE);
|
|
}
|
|
else
|
|
{
|
|
checksum = calc_crc16(script_buffer, MG_SCRIPT_SIZE);
|
|
}
|
|
|
|
// Add in Pokemon and Dex data
|
|
// In the steps after this, we will be recycling the global_memory_buffer to read and write data
|
|
// to other sections of the save. So we really MUST write the generated data now, before we lose
|
|
// it.
|
|
|
|
update_memory_buffer_checksum(global_memory_buffer, false);
|
|
erase_sector(0x1E000);
|
|
copy_ram_to_save(&global_memory_buffer[0], 0x1E000, 0x1000);
|
|
|
|
// section_30 data has been stored, so now we can safely re-use the global_memory_buffer for
|
|
// other sections. Let's move on to the next step.
|
|
|
|
// Add in Wonder Card
|
|
copy_save_to_ram(memory_section_array[4], &global_memory_buffer[0], 0x1000);
|
|
switch (curr_GBA_rom.gamecode)
|
|
{
|
|
case RUBY_ID:
|
|
case SAPPHIRE_ID:
|
|
// Wonder Card doesn't exist
|
|
break;
|
|
case FIRERED_ID:
|
|
case LEAFGREEN_ID:
|
|
memcpy(global_memory_buffer + curr_GBA_rom.offset_wondercard, frlg_wonder_card, 0x14E);
|
|
break;
|
|
case EMERALD_ID:
|
|
default:
|
|
memcpy(global_memory_buffer + curr_GBA_rom.offset_wondercard, em_wonder_card, 0x14E);
|
|
break;
|
|
}
|
|
|
|
// Set checksum and padding
|
|
global_memory_buffer[curr_GBA_rom.offset_script] = checksum >> 0;
|
|
global_memory_buffer[curr_GBA_rom.offset_script + 1] = checksum >> 8;
|
|
global_memory_buffer[curr_GBA_rom.offset_script + 2] = checksum >> 16;
|
|
global_memory_buffer[curr_GBA_rom.offset_script + 3] = checksum >> 24;
|
|
|
|
// Add in Mystery Script data
|
|
memcpy(global_memory_buffer + curr_GBA_rom.offset_script + 4, script_buffer, MG_SCRIPT_SIZE);
|
|
|
|
update_memory_buffer_checksum(global_memory_buffer, false);
|
|
erase_sector(memory_section_array[4]);
|
|
copy_ram_to_save(&global_memory_buffer[0], memory_section_array[4], 0x1000);
|
|
|
|
// Set flags
|
|
int memory_section = 1 + ((curr_GBA_rom.offset_flags + (curr_GBA_rom.unused_flag_start / 8)) /
|
|
0xF80); // This sets the correct memory section, since flags
|
|
// stretch between section 1 and 2.
|
|
copy_save_to_ram(memory_section_array[memory_section], &global_memory_buffer[0], 0x1000);
|
|
global_memory_buffer[(curr_GBA_rom.offset_flags + (curr_GBA_rom.all_collected_flag / 8)) %
|
|
0xF80] &=
|
|
~(1 << (curr_GBA_rom.all_collected_flag % 8)); // Set "collected all" flag to 0
|
|
|
|
for (int i = 0; i < box->getNumInBox(); i++)
|
|
{
|
|
int curr_flag;
|
|
curr_flag = curr_GBA_rom.pkmn_collected_flag_start + i;
|
|
global_memory_buffer[(curr_GBA_rom.offset_flags + (curr_flag / 8)) % 0xF80] &=
|
|
~(1 << (curr_flag % 8)); // Reset the flag
|
|
if (box->getGen3Pokemon(i)->isValid)
|
|
{
|
|
global_memory_buffer[(curr_GBA_rom.offset_flags + (curr_flag / 8)) % 0xF80] |=
|
|
(1 << (curr_flag % 8)); // Set flag accordingly
|
|
}
|
|
}
|
|
|
|
update_memory_buffer_checksum(global_memory_buffer, false);
|
|
erase_sector(memory_section_array[memory_section]);
|
|
copy_ram_to_save(&global_memory_buffer[0], memory_section_array[memory_section], 0x1000);
|
|
|
|
// Save custom save data
|
|
write_custom_save_data();
|
|
return true;
|
|
}
|