diff --git a/PCCS b/PCCS index 3a71aa4..552fa4c 160000 --- a/PCCS +++ b/PCCS @@ -1 +1 @@ -Subproject commit 3a71aa42686464c874651503d9664adaefface40 +Subproject commit 552fa4c4e91ffa5f1cde850e6cfe7180c3d84886 diff --git a/include/Gen3CartridgeSaveReader.h b/include/Gen3CartridgeSaveReader.h new file mode 100644 index 0000000..f13ab99 --- /dev/null +++ b/include/Gen3CartridgeSaveReader.h @@ -0,0 +1,59 @@ +#ifndef _GEN3CARTRIDGE_SAVE_READER_H +#define _GEN3CARTRIDGE_SAVE_READER_H + +#include "IGen3SaveFileReader.h" +#include + +/** + * @brief This class is an adapter implementation of IGen3SaveFileReader, + * which is used by libPCCS' Gen3SaveManager class. + * + * This specific implementation will allow read/write operations to the cartridge save. + * That way, actual data origin is abstracted away from libPCCS' Gen3SaveManager. + * + * Note: to avoid runtime bounds-checking overhead, we assume that the caller takes care + * not to cross sector boundaries with its read/write operations. + * + * When moving to a different sector with seek(), flush() will get called if there are pending changes in + * the current buffer. + * + * Note: this class won't automatically load any sector when initialized. + * Make sure to trigger seek() before doing anything with it. + */ +class Gen3CartridgeSaveReader : public IGen3SaveFileReader +{ +public: + Gen3CartridgeSaveReader(u8 *sector_buffer); + ~Gen3CartridgeSaveReader() override; + + void read(u8 *buffer, u32 size) override; + void readUint8(u8& outByte) override; + void readUint16(u16& outWord, Endianness fieldEndianness) override; + void readUint32(u32& outDWord, Endianness fieldEndianness) override; + + void write(const u8 *buffer, u32 size) override; + void writeUint8(u8 value) override; + void writeUint16(u16 value, Endianness fieldEndianness) override; + void writeUint32(u32 value, Endianness fieldEndianness) override; + + void seek(u32 offset) override; + + // WARNING: advance and rewind won't cross sector boundaries, so make sure to trigger seek() + // if you want to move to a different sector. + void advance(u32 numBytes) override; + void rewind(u32 numBytes) override; + + /** + * @brief Flush the current buffer to the cartridge save. + */ + void flush(); +protected: +private: + uintptr_t sector_start_; + u8 *sector_buffer_; + u8 *cur_; + // whether the current buffer has pending changes that need to be flushed + bool dirty_; +}; + +#endif \ No newline at end of file diff --git a/include/dbg/debug_menu_functions.h b/include/dbg/debug_menu_functions.h index fbcad70..54b04fe 100644 --- a/include/dbg/debug_menu_functions.h +++ b/include/dbg/debug_menu_functions.h @@ -27,4 +27,10 @@ void dbg_set_boolean_flag(void *context, unsigned user_param); */ void dbg_play_song(void *context, unsigned user_param); +/** + * @brief This function will inject a Pokémon into the game. + * (right now this is just Jirachi) + */ +void dbg_inject_pkmn(void *context, unsigned user_param); + #endif \ No newline at end of file diff --git a/include/flash_mem.h b/include/flash_mem.h index d587e59..a0e9b16 100644 --- a/include/flash_mem.h +++ b/include/flash_mem.h @@ -29,12 +29,13 @@ void print_mem_section(); void reverse_endian(u8 *data, size_t size); /** - * @brief This function will update the checksum in global_memory_buffer. - * It assumes that global_memory_buffer contains one of the save data sections. + * @brief This function will update the checksum in the given sector buffer. + * It assumes that the sector buffer contains one of the save data sections. * + * @param sector_buffer The buffer containing the save data section. * @param hall_of_fame This is needed to indicate if you're trying to save the hall of fame section. The checksum offset is slightly different there. */ -void update_memory_buffer_checksum(bool hall_of_fame); +void update_memory_buffer_checksum(u8 *sector_buffer, bool hall_of_fame); bool read_flag(u16 flag_id); bool compare_map_and_npc_data(int map_bank, int map_id, int npc_id); diff --git a/source/Gen3CartridgeSaveReader.cpp b/source/Gen3CartridgeSaveReader.cpp new file mode 100644 index 0000000..764b0f6 --- /dev/null +++ b/source/Gen3CartridgeSaveReader.cpp @@ -0,0 +1,123 @@ +#include "Gen3CartridgeSaveReader.h" +#include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h" +#include "flash_mem.h" +#include + +#define HALL_OF_FAME 0x01C000 + +Gen3CartridgeSaveReader::Gen3CartridgeSaveReader(u8 *sector_buffer) + : sector_start_(0) + , sector_buffer_(sector_buffer) + , cur_(sector_buffer) + , dirty_(false) +{ +} + +Gen3CartridgeSaveReader::~Gen3CartridgeSaveReader() +{ +} + +void Gen3CartridgeSaveReader::read(u8 *buffer, u32 size) +{ + memcpy(buffer, cur_, size); + cur_ += size; +} + +void Gen3CartridgeSaveReader::readUint8(u8& outByte) +{ + outByte = *cur_; + ++cur_; +} + +void Gen3CartridgeSaveReader::readUint16(u16& outWord, Endianness fieldEndianness) +{ + // Right now we only support little endian (no need for big endian thus far) + outWord = *((u16*)cur_); + cur_ += sizeof(u16); +} + +void Gen3CartridgeSaveReader::readUint32(u32& outDWord, Endianness fieldEndianness) +{ + // Right now we only support little endian (no need for big endian thus far) + outDWord = *((u32*)cur_); + cur_ += sizeof(u32); +} + +void Gen3CartridgeSaveReader::write(const u8 *buffer, u32 size) +{ + memcpy(cur_, buffer, size); + cur_ += size; + dirty_ = true; + +} + +void Gen3CartridgeSaveReader::writeUint8(u8 value) +{ + *cur_ = value; + ++cur_; + dirty_ = true; +} + +void Gen3CartridgeSaveReader::writeUint16(u16 value, Endianness fieldEndianness) +{ + // Right now we only support little endian (no need for big endian thus far) + *((u16*)cur_) = value; + cur_ += sizeof(u16); + dirty_ = true; +} + +void Gen3CartridgeSaveReader::writeUint32(u32 value, Endianness fieldEndianness) +{ + // Right now we only support little endian (no need for big endian thus far) + *((u32*)cur_) = value; + cur_ += sizeof(u32); + dirty_ = true; +} + +void Gen3CartridgeSaveReader::seek(u32 offset) +{ + const uintptr_t sector_start = offset / SECTOR_SIZE; + if(sector_start != sector_start_) + { + // write any pending changes. + flush(); + sector_start_ = sector_start; + copy_save_to_ram(sector_start, sector_buffer_, SECTOR_SIZE); + } + + cur_ = sector_buffer_ + (offset - sector_start); +} + +void Gen3CartridgeSaveReader::advance(u32 numBytes) +{ + u8 *sector_end = sector_buffer_ + SECTOR_SIZE; + cur_ += numBytes; + if (cur_ > sector_end) + { + cur_ = sector_end; + } +} + +void Gen3CartridgeSaveReader::rewind(u32 numBytes) +{ + if(static_cast(cur_ - sector_buffer_) >= numBytes) + { + cur_ -= numBytes; + } + else + { + cur_ = sector_buffer_; + } +} + +void Gen3CartridgeSaveReader::flush() +{ + if(!dirty_) + { + return; + } + + update_memory_buffer_checksum(sector_buffer_, (sector_start_ == HALL_OF_FAME)); + copy_ram_to_save(sector_buffer_, sector_start_, SECTOR_SIZE); + dirty_ = false; +} \ No newline at end of file diff --git a/source/dbg/debug_menu_functions.cpp b/source/dbg/debug_menu_functions.cpp index f6f9b03..5b1652b 100644 --- a/source/dbg/debug_menu_functions.cpp +++ b/source/dbg/debug_menu_functions.cpp @@ -9,6 +9,85 @@ #include "background_engine.h" #include "libstd_replacements.h" #include "sound.h" +#include "rom_data.h" +#include "Gen3CartridgeSaveReader.h" +#include "Gen3SaveManager.h" +#include "Gen3Pokemon.h" +#include "pokemon_data.h" + +extern rom_data curr_GBA_rom; + +static const byte RSEFL_10_ANIV_Celebi_0BF5_ENG_[] = { + 0xbe, 0x2b, 0x28, 0x78, 0x0a, 0x00, 0x00, 0x00, 0xbd, 0xbf, 0xc6, 0xbf, 0xbc, 0xc3, 0xff, 0x34, + 0x01, 0x31, 0x02, 0x02, 0xa2, 0xa1, 0x00, 0xbb, 0xc8, 0xc3, 0xd0, 0x00, 0x17, 0x35, 0x00, 0x00, + 0xfb, 0x00, 0x00, 0x00, 0x80, 0x43, 0x05, 0x00, 0x00, 0x64, 0x00, 0x00, 0xf6, 0x00, 0xf8, 0x00, + 0xe2, 0x00, 0xc3, 0x00, 0x05, 0x0f, 0x28, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x46, 0x21, 0xe2, 0x31, 0xaf, 0x22, 0x00, 0x00, 0x00, 0x00 +}; + +static Game convert_rom_game_id_to_ptgb_game(int game_id) +{ + switch (game_id) + { + case RUBY_ID: + return RUBY; + case SAPPHIRE_ID: + return SAPPHIRE; + case FIRERED_ID: + return FIRERED; + case LEAFGREEN_ID: + return LEAFGREEN; + case EMERALD_ID: + return EMERALD; + default: + return GAME_UNKNOWN; // Default to unknown if the game ID is unrecognized + } +} + +static Language convert_rom_lang_to_ptgb_lang(int rom_lang) +{ + switch (rom_lang) + { + 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 LANGUAGE_UNKNOWN; // Default to unknown if the language is unrecognized + } +} + +/** + * The trainer OT is encoded in the gen3 proprietary charset. + * But if we want to print it with npf_snprintf, we need to convert it to UTF-8 first. + */ +static void convert_OT_to_utf8(const u8 *encoded_OT, u8 *output_buffer, const u16 *gen3_charset) +{ + u16 current_char; + u32 num_bytes; + u8 utf8_codepoint_buffer[4]; + u8 *cur_out = output_buffer; + + for (u32 i = 0; i < 7 && encoded_OT[i] != 0xFF; ++i) + { + // the charset maps each gen 3 character (represented as a byte) to a UTF-16 codepoint. + // So we first convert the encoded OT character to UTF-16, and then convert that UTF-16 codepoint to UTF-8. + current_char = convert_gen_3_char_to_utf16(gen3_charset, encoded_OT[i]); + num_bytes = convert_utf16_to_utf8_char(current_char, utf8_codepoint_buffer); + + memcpy(cur_out, utf8_codepoint_buffer, num_bytes); + cur_out += num_bytes; + } + *cur_out = '\0'; +} void show_text_debug_screen(void *context, unsigned user_param) { @@ -117,4 +196,63 @@ void dbg_play_song(void *context, unsigned user_param) return; } play_song(user_param, true); +} + +void dbg_inject_pkmn(void *context, unsigned user_param) +{ + const Game game_type = convert_rom_game_id_to_ptgb_game(curr_GBA_rom.gamecode); + const Language game_lang = convert_rom_lang_to_ptgb_lang(curr_GBA_rom.language); + u32 ret; + char text_buffer[256]; + u8 encoded_OT[8]; + u8 decoded_OT_utf8[32]; + int boxIndex = 0; + + Gen3CartridgeSaveReader reader(global_memory_buffer); + Gen3SaveManager save_manager(game_type, game_lang, reader); + + PokemonTables tables; + tables.load_gen3_charset(game_lang); + + Gen3Pokemon celebi(&tables); + celebi.loadData(RSEFL_10_ANIV_Celebi_0BF5_ENG_, false); + + do + { + ret = save_manager.addPokemonToBox(boxIndex, celebi); + ++boxIndex; + + } while (ret == UINT32_MAX && boxIndex < 14); + + if(ret == UINT32_MAX) + { + // Failed to inject the Pokémon, all boxes are full. You can handle this case as needed (e.g., show an error message). + return; + } + + save_manager.finishSave(); + reader.flush(); + save_manager.readTrainerName(encoded_OT, ret); + + reset_textbox(); + show_text_box(); + + convert_OT_to_utf8(encoded_OT, decoded_OT_utf8, tables.gen3_charset); + + npf_snprintf(text_buffer, sizeof(text_buffer), "%s received a Celebi!\n Celebi was sent to box %d!", decoded_OT_utf8, boxIndex); + + tte_set_pos(LEFT, TOP); + tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM); + ptgb_write_debug(tables.gen3_charset, text_buffer, false); + + while(true) + { + if (key_hit(KEY_A)) + { + hide_text_box(); + reset_textbox(); + break; + } + global_next_frame(); + } } \ No newline at end of file diff --git a/source/flash_mem.cpp b/source/flash_mem.cpp index 81507ae..90ae725 100644 --- a/source/flash_mem.cpp +++ b/source/flash_mem.cpp @@ -123,18 +123,18 @@ void reverse_endian(u8 *data, size_t size) } } -void update_memory_buffer_checksum(bool hall_of_fame) +void update_memory_buffer_checksum(u8 *sector_buffer, bool hall_of_fame) { u32 checksum = 0x00; // Section 13 is the last PC buffer (I) and that one only has 2000 bytes of data. // source: https://bulbapedia.bulbagarden.net/wiki/Save_data_structure_(Generation_III)#Section_ID - const u32 num_of_bytes = (global_memory_buffer[SECTION_ID_OFFSET] != 13) ? 3968 : 2000; + const u32 num_of_bytes = (sector_buffer[SECTION_ID_OFFSET] != 13) ? 3968 : 2000; // the cpu is little endian and the data is read as little endian too. // therefore, we can do a straightforward sum of the data as u32's. - const u32 *cur = (const u32 *)global_memory_buffer; - const u32 * const end = (const u32 *)(global_memory_buffer + num_of_bytes); + const u32 *cur = (const u32 *)sector_buffer; + const u32 * const end = (const u32 *)(sector_buffer + num_of_bytes); while (cur < end) { checksum += *cur; @@ -144,8 +144,8 @@ void update_memory_buffer_checksum(bool hall_of_fame) const u16 small_checksum = ((checksum & 0xFFFF0000) >> 16) + (checksum & 0x0000FFFF); const u32 checksum_offset = hall_of_fame ? 0x0FF4 : 0x0FF6; - global_memory_buffer[checksum_offset] = small_checksum & 0x00FF; - global_memory_buffer[checksum_offset + 1] = (small_checksum & 0xFF00) >> 8; + sector_buffer[checksum_offset] = small_checksum & 0x00FF; + sector_buffer[checksum_offset + 1] = (small_checksum & 0xFF00) >> 8; } diff --git a/source/mystery_gift_injector.cpp b/source/mystery_gift_injector.cpp index 40565a1..d2b4f0c 100644 --- a/source/mystery_gift_injector.cpp +++ b/source/mystery_gift_injector.cpp @@ -40,7 +40,7 @@ bool inject_mystery(PokeBox* box) memcpy(global_memory_buffer, script.get_section30(), 0x1000); - update_memory_buffer_checksum(false); + update_memory_buffer_checksum(global_memory_buffer, false); erase_sector(0x1E000); copy_ram_to_save(&global_memory_buffer[0], 0x1E000, 0x1000); @@ -74,7 +74,7 @@ bool inject_mystery(PokeBox* box) // Add in Mystery Script data memcpy(global_memory_buffer + curr_GBA_rom.offset_script + 4, script.get_script(), MG_SCRIPT_SIZE); - update_memory_buffer_checksum(false); + 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); @@ -94,7 +94,7 @@ bool inject_mystery(PokeBox* box) } } - update_memory_buffer_checksum(false); + 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); diff --git a/source/ptgb_save_data_manager.cpp b/source/ptgb_save_data_manager.cpp index 30a3658..92bc76c 100644 --- a/source/ptgb_save_data_manager.cpp +++ b/source/ptgb_save_data_manager.cpp @@ -19,7 +19,7 @@ void write_custom_save_data() copy_save_to_ram(HALL_OF_FAME + 0x1000, &global_memory_buffer[0], 0x1000); memcpy(global_memory_buffer + HOF_SECTION, save_data_array, SAVE_DATA_SIZE); - update_memory_buffer_checksum(true); + update_memory_buffer_checksum(global_memory_buffer, true); erase_sector(HALL_OF_FAME + 0x1000); copy_ram_to_save(&global_memory_buffer[0], HALL_OF_FAME + 0x1000, 0x1000); } diff --git a/source/sprite_data.cpp b/source/sprite_data.cpp index 76c2163..8a49e5d 100644 --- a/source/sprite_data.cpp +++ b/source/sprite_data.cpp @@ -874,6 +874,8 @@ void update_front_box_sprite(GBPokemon *curr_pkmn) return; // We don't want to look into garbage data, get out of here. } + // in load_temp_sprites(), we reserve 30 box sprite icons after global_tile_id_end. Each of them occupy 16 tiles. + // the grabbed_front_sprite comes right after it. And we load the pokémon front sprite directly into its tiles. u32 curr_tile_id = global_tile_id_end + (30 * 16); int dex_num = curr_pkmn->getSpeciesIndexNumber(); diff --git a/source/text_engine.cpp b/source/text_engine.cpp index 0b20c73..856b07f 100644 --- a/source/text_engine.cpp +++ b/source/text_engine.cpp @@ -11,6 +11,7 @@ #include "fonts.h" #include "text_data_table.h" #include "background_engine.h" +#include "pokemon_data.h" #define TEXT_CBB 0 #define TEXT_SBB 10 @@ -418,7 +419,14 @@ int ptgb_write_debug(const u16 *charset, const char *text, bool instant) } else { - temp_holding[i] = get_char_from_charset(charset, text[i]); + u16 utf16_char; + // we need to use this conversion function in order to convert char values >= 0x80 + // correctly to UTF-16 (which is used by the charset) + convert_utf8_to_utf16_char((const u8*)(text + i), utf16_char); + // WARNING: the conversion of the text characters from u8 to u16 done here, is incorrect + // for every character value >= 0x80. The character set uses UTF-16 to represent characters. + // But the input text is in UTF-8. + temp_holding[i] = get_char_from_charset(charset, utf16_char); } } return ptgb_write(temp_holding, instant);