From 59a9aaf388b71444116a42fe97a969947230908e Mon Sep 17 00:00:00 2001 From: wwylele Date: Wed, 2 Aug 2017 22:56:44 +0300 Subject: [PATCH] APT: load different shared font depending on the region --- src/core/hle/service/apt/apt.cpp | 286 ++++++++++++++++--------------- src/core/hle/service/cfg/cfg.cpp | 2 +- src/core/hle/service/cfg/cfg.h | 2 + 3 files changed, 155 insertions(+), 135 deletions(-) diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 58d94768c..8c0ba73f2 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -19,6 +19,7 @@ #include "core/hle/service/apt/apt_s.h" #include "core/hle/service/apt/apt_u.h" #include "core/hle/service/apt/bcfnt/bcfnt.h" +#include "core/hle/service/cfg/cfg.h" #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" @@ -198,6 +199,143 @@ void Initialize(Service::Interface* self) { Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap()); } +static u32 DecompressLZ11(const u8* in, u8* out) { + u32_le decompressed_size; + memcpy(&decompressed_size, in, sizeof(u32)); + in += 4; + + u8 type = decompressed_size & 0xFF; + ASSERT(type == 0x11); + decompressed_size >>= 8; + + u32 current_out_size = 0; + u8 flags = 0, mask = 1; + while (current_out_size < decompressed_size) { + if (mask == 1) { + flags = *(in++); + mask = 0x80; + } else { + mask >>= 1; + } + + if (flags & mask) { + u8 byte1 = *(in++); + u32 length = byte1 >> 4; + u32 offset; + if (length == 0) { + u8 byte2 = *(in++); + u8 byte3 = *(in++); + length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; + offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1; + } else if (length == 1) { + u8 byte2 = *(in++); + u8 byte3 = *(in++); + u8 byte4 = *(in++); + length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; + offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1; + } else { + u8 byte2 = *(in++); + length = (byte1 >> 4) + 0x1; + offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1; + } + + for (u32 i = 0; i < length; i++) { + *out = *(out - offset); + ++out; + } + + current_out_size += length; + } else { + *(out++) = *(in++); + current_out_size++; + } + } + return decompressed_size; +} + +static bool LoadSharedFont() { + u8 font_region_code; + switch (CFG::GetRegionValue()) { + case 4: // CHN + font_region_code = 2; + break; + case 5: // KOR + font_region_code = 3; + break; + case 6: // TWN + font_region_code = 4; + break; + default: // JPN/EUR/USA + font_region_code = 1; + break; + } + + const u64_le shared_font_archive_id_low = 0x0004009b00014002 | ((font_region_code - 1) << 8); + const u64_le shared_font_archive_id_high = 0x00000001ffffff00; + std::vector shared_font_archive_id(16); + std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64)); + std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64)); + FileSys::Path archive_path(shared_font_archive_id); + auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path); + if (archive_result.Failed()) + return false; + + std::vector romfs_path(20, 0); // 20-byte all zero path for opening RomFS + FileSys::Path file_path(romfs_path); + FileSys::Mode open_mode = {}; + open_mode.read_flag.Assign(1); + auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode); + if (file_result.Failed()) + return false; + + auto romfs = std::move(file_result).Unwrap(); + std::vector romfs_buffer(romfs->backend->GetSize()); + romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data()); + romfs->backend->Close(); + + const char16_t* file_name[4] = {u"cbf_std.bcfnt.lz", u"cbf_zh-Hans-CN.bcfnt.lz", + u"cbf_ko-Hang-KR.bcfnt.lz", u"cbf_zh-Hant-TW.bcfnt.lz"}; + const u8* font_file = + RomFS::GetFilePointer(romfs_buffer.data(), {file_name[font_region_code - 1]}); + if (font_file == nullptr) + return false; + + struct { + u32_le status; + u32_le region; + u32_le decompressed_size; + INSERT_PADDING_WORDS(0x1D); + } shared_font_header{}; + static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size"); + + shared_font_header.status = 2; // successfully loaded + shared_font_header.region = font_region_code; + shared_font_header.decompressed_size = + DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80)); + std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header)); + *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU" + + return true; +} + +static bool LoadLegacySharedFont() { + // This is the legacy method to load shared font. + // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header + // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided + // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file + // "shared_font.bin" in the Citra "sysdata" directory. + std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; + + FileUtil::CreateFullPath(filepath); // Create path if not already created + FileUtil::IOFile file(filepath, "rb"); + if (file.IsOpen()) { + file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); + return true; + } + + return false; +} + void GetSharedFont(Service::Interface* self) { IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); @@ -206,11 +344,20 @@ void GetSharedFont(Service::Interface* self) { Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true); if (!shared_font_loaded) { - LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); - rb.Push(-1); // TODO: Find the right error code - rb.Skip(1 + 2, true); - Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont); - return; + // On real 3DS, font loading happens on booting. However, we load it on demand to coordinate + // with CFG region auto configuration, which happens later than APT initialization. + if (LoadSharedFont()) { + shared_font_loaded = true; + } else if (LoadLegacySharedFont()) { + LOG_WARNING(Service_APT, "Loaded shared font by legacy method"); + shared_font_loaded = true; + } else { + LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); + rb.Push(-1); // TODO: Find the right error code + rb.Skip(1 + 2, true); + Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont); + return; + } } // The shared font has to be relocated to the new address before being passed to the @@ -863,125 +1010,6 @@ void CheckNew3DS(Service::Interface* self) { LOG_WARNING(Service_APT, "(STUBBED) called"); } -static u32 DecompressLZ11(const u8* in, u8* out) { - u32_le decompressed_size; - memcpy(&decompressed_size, in, sizeof(u32)); - in += 4; - - u8 type = decompressed_size & 0xFF; - ASSERT(type == 0x11); - decompressed_size >>= 8; - - u32 current_out_size = 0; - u8 flags = 0, mask = 1; - while (current_out_size < decompressed_size) { - if (mask == 1) { - flags = *(in++); - mask = 0x80; - } else { - mask >>= 1; - } - - if (flags & mask) { - u8 byte1 = *(in++); - u32 length = byte1 >> 4; - u32 offset; - if (length == 0) { - u8 byte2 = *(in++); - u8 byte3 = *(in++); - length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; - offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1; - } else if (length == 1) { - u8 byte2 = *(in++); - u8 byte3 = *(in++); - u8 byte4 = *(in++); - length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; - offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1; - } else { - u8 byte2 = *(in++); - length = (byte1 >> 4) + 0x1; - offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1; - } - - for (u32 i = 0; i < length; i++) { - *out = *(out - offset); - ++out; - } - - current_out_size += length; - } else { - *(out++) = *(in++); - current_out_size++; - } - } - return decompressed_size; -} - -static bool LoadSharedFont() { - // TODO (wwylele): load different font archive for region CHN/KOR/TWN - const u64_le shared_font_archive_id_low = 0x0004009b00014002; - const u64_le shared_font_archive_id_high = 0x00000001ffffff00; - std::vector shared_font_archive_id(16); - std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64)); - std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64)); - FileSys::Path archive_path(shared_font_archive_id); - auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path); - if (archive_result.Failed()) - return false; - - std::vector romfs_path(20, 0); // 20-byte all zero path for opening RomFS - FileSys::Path file_path(romfs_path); - FileSys::Mode open_mode = {}; - open_mode.read_flag.Assign(1); - auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode); - if (file_result.Failed()) - return false; - - auto romfs = std::move(file_result).Unwrap(); - std::vector romfs_buffer(romfs->backend->GetSize()); - romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data()); - romfs->backend->Close(); - - const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"}); - if (font_file == nullptr) - return false; - - struct { - u32_le status; - u32_le region; - u32_le decompressed_size; - INSERT_PADDING_WORDS(0x1D); - } shared_font_header{}; - static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size"); - - shared_font_header.status = 2; // successfully loaded - shared_font_header.region = 1; // region JPN/EUR/USA - shared_font_header.decompressed_size = - DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80)); - std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header)); - *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU" - - return true; -} - -static bool LoadLegacySharedFont() { - // This is the legacy method to load shared font. - // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header - // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided - // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file - // "shared_font.bin" in the Citra "sysdata" directory. - std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; - - FileUtil::CreateFullPath(filepath); // Create path if not already created - FileUtil::IOFile file(filepath, "rb"); - if (file.IsOpen()) { - file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); - return true; - } - - return false; -} - void Init() { AddService(new APT_A_Interface); AddService(new APT_S_Interface); @@ -995,16 +1023,6 @@ void Init() { MemoryPermission::ReadWrite, MemoryPermission::Read, 0, Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); - if (LoadSharedFont()) { - shared_font_loaded = true; - } else if (LoadLegacySharedFont()) { - LOG_WARNING(Service_APT, "Loaded shared font by legacy method"); - shared_font_loaded = true; - } else { - LOG_WARNING(Service_APT, "Unable to load shared font"); - shared_font_loaded = false; - } - lock = Kernel::Mutex::Create(false, "APT_U:Lock"); cpu_percent = 0; diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 3dbeb27cc..f26a1f65f 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp @@ -168,7 +168,7 @@ void GetCountryCodeID(Service::Interface* self) { cmd_buff[2] = country_code_id; } -static u32 GetRegionValue() { +u32 GetRegionValue() { if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) return preferred_region_code; diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 1659ebf32..282b6936b 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h @@ -101,6 +101,8 @@ void GetCountryCodeString(Service::Interface* self); */ void GetCountryCodeID(Service::Interface* self); +u32 GetRegionValue(); + /** * CFG::SecureInfoGetRegion service function * Inputs: