PL:U Added BFTTF loading(Loading from System NAND dumps) (#1088)
* Added bfttf loading We can now load system bfttf fonts from system archives AND shared memory dumps. This allows people who have installed their system nand dumps to yuzu to automatically get shared font support. We also now don't hard code the offsets or the sizes of the shared fonts and it's all calculated for us now. * Addressed plu fixups * Style changes for plu * Fixed logic error for plu and added more error checks.
This commit is contained in:
parent
ac68c8a605
commit
99fc32428a
|
@ -5,31 +5,97 @@
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/romfs.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/service/filesystem/filesystem.h"
|
||||||
#include "core/hle/service/ns/pl_u.h"
|
#include "core/hle/service/ns/pl_u.h"
|
||||||
|
|
||||||
namespace Service::NS {
|
namespace Service::NS {
|
||||||
|
|
||||||
|
enum class FontArchives : u64 {
|
||||||
|
Extension = 0x0100000000000810,
|
||||||
|
Standard = 0x0100000000000811,
|
||||||
|
Korean = 0x0100000000000812,
|
||||||
|
ChineseTraditional = 0x0100000000000813,
|
||||||
|
ChineseSimple = 0x0100000000000814,
|
||||||
|
};
|
||||||
|
|
||||||
struct FontRegion {
|
struct FontRegion {
|
||||||
u32 offset;
|
u32 offset;
|
||||||
u32 size;
|
u32 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{
|
||||||
|
std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"),
|
||||||
|
std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")};
|
||||||
|
|
||||||
// The below data is specific to shared font data dumped from Switch on f/w 2.2
|
// The below data is specific to shared font data dumped from Switch on f/w 2.2
|
||||||
// Virtual address and offsets/sizes likely will vary by dump
|
// Virtual address and offsets/sizes likely will vary by dump
|
||||||
static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
|
static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL};
|
||||||
|
static constexpr u32 EXPECTED_RESULT{
|
||||||
|
0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be
|
||||||
|
static constexpr u32 EXPECTED_MAGIC{
|
||||||
|
0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be
|
||||||
static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
|
static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
|
||||||
static constexpr std::array<FontRegion, 6> SHARED_FONT_REGIONS{
|
static constexpr FontRegion EMPTY_REGION{0, 0};
|
||||||
FontRegion{0x00000008, 0x001fe764}, FontRegion{0x001fe774, 0x00773e58},
|
std::vector<FontRegion>
|
||||||
FontRegion{0x009725d4, 0x0001aca8}, FontRegion{0x0098d284, 0x00369cec},
|
SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives
|
||||||
FontRegion{0x00cf6f78, 0x0039b858}, FontRegion{0x010927d8, 0x00019e80},
|
|
||||||
};
|
const FontRegion& GetSharedFontRegion(size_t index) {
|
||||||
|
if (index >= SHARED_FONT_REGIONS.size() || SHARED_FONT_REGIONS.empty()) {
|
||||||
|
// No font fallback
|
||||||
|
return EMPTY_REGION;
|
||||||
|
}
|
||||||
|
return SHARED_FONT_REGIONS.at(index);
|
||||||
|
}
|
||||||
|
|
||||||
enum class LoadState : u32 {
|
enum class LoadState : u32 {
|
||||||
Loading = 0,
|
Loading = 0,
|
||||||
Done = 1,
|
Done = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, size_t& offset) {
|
||||||
|
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
|
||||||
|
"Shared fonts exceeds 17mb!");
|
||||||
|
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
|
||||||
|
|
||||||
|
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
|
||||||
|
std::vector<u32> transformed_font(input.size());
|
||||||
|
// TODO(ogniK): Figure out a better way to do this
|
||||||
|
std::transform(input.begin(), input.end(), transformed_font.begin(),
|
||||||
|
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
|
||||||
|
transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
|
||||||
|
std::memcpy(output.data() + offset, transformed_font.data(),
|
||||||
|
transformed_font.size() * sizeof(u32));
|
||||||
|
offset += transformed_font.size() * sizeof(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 GetU32Swapped(const u8* data) {
|
||||||
|
u32 value;
|
||||||
|
std::memcpy(&value, data, sizeof(value));
|
||||||
|
return Common::swap32(value); // Helper function to make BuildSharedFontsRawRegions a bit nicer
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildSharedFontsRawRegions(const std::vector<u8>& input) {
|
||||||
|
unsigned cur_offset = 0; // As we can derive the xor key we can just populate the offsets based
|
||||||
|
// on the shared memory dump
|
||||||
|
for (size_t i = 0; i < SHARED_FONTS.size(); i++) {
|
||||||
|
// Out of shared fonts/Invalid font
|
||||||
|
if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT)
|
||||||
|
break;
|
||||||
|
const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^
|
||||||
|
EXPECTED_MAGIC; // Derive key withing inverse xor
|
||||||
|
const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
|
||||||
|
SHARED_FONT_REGIONS.push_back(FontRegion{cur_offset + 8, SIZE});
|
||||||
|
cur_offset += SIZE + 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PL_U::PL_U() : ServiceFramework("pl:u") {
|
PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||||
static const FunctionInfo functions[] = {
|
static const FunctionInfo functions[] = {
|
||||||
{0, &PL_U::RequestLoad, "RequestLoad"},
|
{0, &PL_U::RequestLoad, "RequestLoad"},
|
||||||
|
@ -40,26 +106,78 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
|
||||||
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
|
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
|
||||||
};
|
};
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
// Attempt to load shared font data from disk
|
// Attempt to load shared font data from disk
|
||||||
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SHARED_FONT};
|
const auto nand = FileSystem::GetSystemNANDContents();
|
||||||
FileUtil::CreateFullPath(filepath); // Create path if not already created
|
// Rebuild shared fonts from data ncas
|
||||||
|
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
|
||||||
|
FileSys::ContentRecordType::Data)) {
|
||||||
|
size_t offset = 0;
|
||||||
|
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
|
||||||
|
for (auto font : SHARED_FONTS) {
|
||||||
|
const auto nca =
|
||||||
|
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
|
||||||
|
if (!nca) {
|
||||||
|
LOG_ERROR(Service_NS, "Failed to find {:016X}! Skipping",
|
||||||
|
static_cast<u64>(font.first));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto romfs = nca->GetRomFS();
|
||||||
|
if (!romfs) {
|
||||||
|
LOG_ERROR(Service_NS, "{:016X} has no RomFS! Skipping",
|
||||||
|
static_cast<u64>(font.first));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto extracted_romfs = FileSys::ExtractRomFS(romfs);
|
||||||
|
if (!extracted_romfs) {
|
||||||
|
LOG_ERROR(Service_NS, "Failed to extract RomFS for {:016X}! Skipping",
|
||||||
|
static_cast<u64>(font.first));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto font_fp = extracted_romfs->GetFile(font.second);
|
||||||
|
if (!font_fp) {
|
||||||
|
LOG_ERROR(Service_NS, "{:016X} has no file \"{}\"! Skipping",
|
||||||
|
static_cast<u64>(font.first), font.second);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::vector<u32> font_data_u32(font_fp->GetSize() / sizeof(u32));
|
||||||
|
font_fp->ReadBytes<u32>(font_data_u32.data(), font_fp->GetSize());
|
||||||
|
// We need to be BigEndian as u32s for the xor encryption
|
||||||
|
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
|
||||||
|
Common::swap32);
|
||||||
|
FontRegion region{
|
||||||
|
static_cast<u32>(offset + 8),
|
||||||
|
static_cast<u32>((font_data_u32.size() * sizeof(u32)) -
|
||||||
|
8)}; // Font offset and size do not account for the header
|
||||||
|
DecryptSharedFont(font_data_u32, *shared_font, offset);
|
||||||
|
SHARED_FONT_REGIONS.push_back(region);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) +
|
||||||
|
SHARED_FONT};
|
||||||
|
// Create path if not already created
|
||||||
|
if (!FileUtil::CreateFullPath(filepath)) {
|
||||||
|
LOG_ERROR(Service_NS, "Failed to create sharedfonts path \"{}\"!", filepath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
FileUtil::IOFile file(filepath, "rb");
|
FileUtil::IOFile file(filepath, "rb");
|
||||||
|
|
||||||
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
|
shared_font = std::make_shared<std::vector<u8>>(
|
||||||
|
SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size
|
||||||
if (file.IsOpen()) {
|
if (file.IsOpen()) {
|
||||||
// Read shared font data
|
// Read shared font data
|
||||||
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
|
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
|
||||||
file.ReadBytes(shared_font->data(), shared_font->size());
|
file.ReadBytes(shared_font->data(), shared_font->size());
|
||||||
|
BuildSharedFontsRawRegions(*shared_font);
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
|
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
|
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const u32 shared_font_type{rp.Pop<u32>()};
|
const u32 shared_font_type{rp.Pop<u32>()};
|
||||||
|
// Games don't call this so all fonts should be loaded
|
||||||
LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
|
LOG_DEBUG(Service_NS, "called, shared_font_type={}", shared_font_type);
|
||||||
IPC::ResponseBuilder rb{ctx, 2};
|
IPC::ResponseBuilder rb{ctx, 2};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
@ -82,7 +200,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
|
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].size);
|
rb.Push<u32>(GetSharedFontRegion(font_id).size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
|
void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -92,14 +210,10 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
|
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push<u32>(SHARED_FONT_REGIONS[font_id].offset);
|
rb.Push<u32>(GetSharedFontRegion(font_id).offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
|
void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) {
|
||||||
// TODO(bunnei): This is a less-than-ideal solution to load a RAM dump of the Switch shared
|
|
||||||
// font data. This (likely) relies on exact address, size, and offsets from the original
|
|
||||||
// dump. In the future, we need to replace this with a more robust solution.
|
|
||||||
|
|
||||||
// Map backing memory for the font data
|
// Map backing memory for the font data
|
||||||
Core::CurrentProcess()->vm_manager.MapMemoryBlock(
|
Core::CurrentProcess()->vm_manager.MapMemoryBlock(
|
||||||
SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared);
|
SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared);
|
||||||
|
@ -128,8 +242,9 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) {
|
||||||
// TODO(ogniK): Have actual priority order
|
// TODO(ogniK): Have actual priority order
|
||||||
for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) {
|
for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) {
|
||||||
font_codes.push_back(static_cast<u32>(i));
|
font_codes.push_back(static_cast<u32>(i));
|
||||||
font_offsets.push_back(SHARED_FONT_REGIONS[i].offset);
|
auto region = GetSharedFontRegion(i);
|
||||||
font_sizes.push_back(SHARED_FONT_REGIONS[i].size);
|
font_offsets.push_back(region.offset);
|
||||||
|
font_sizes.push_back(region.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.WriteBuffer(font_codes, 0);
|
ctx.WriteBuffer(font_codes, 0);
|
||||||
|
|
Reference in New Issue