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:
David 2018-08-22 11:31:49 +10:00 committed by bunnei
parent ac68c8a605
commit 99fc32428a

View File

@ -5,31 +5,97 @@
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/romfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/pl_u.h"
namespace Service::NS {
enum class FontArchives : u64 {
Extension = 0x0100000000000810,
Standard = 0x0100000000000811,
Korean = 0x0100000000000812,
ChineseTraditional = 0x0100000000000813,
ChineseSimple = 0x0100000000000814,
};
struct FontRegion {
u32 offset;
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
// Virtual address and offsets/sizes likely will vary by dump
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 std::array<FontRegion, 6> SHARED_FONT_REGIONS{
FontRegion{0x00000008, 0x001fe764}, FontRegion{0x001fe774, 0x00773e58},
FontRegion{0x009725d4, 0x0001aca8}, FontRegion{0x0098d284, 0x00369cec},
FontRegion{0x00cf6f78, 0x0039b858}, FontRegion{0x010927d8, 0x00019e80},
};
static constexpr FontRegion EMPTY_REGION{0, 0};
std::vector<FontRegion>
SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives
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 {
Loading = 0,
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") {
static const FunctionInfo functions[] = {
{0, &PL_U::RequestLoad, "RequestLoad"},
@ -40,26 +106,78 @@ PL_U::PL_U() : ServiceFramework("pl:u") {
{5, &PL_U::GetSharedFontInOrderOfPriority, "GetSharedFontInOrderOfPriority"},
};
RegisterHandlers(functions);
// Attempt to load shared font data from disk
const std::string filepath{FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SHARED_FONT};
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE);
if (file.IsOpen()) {
// Read shared font data
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
file.ReadBytes(shared_font->data(), shared_font->size());
const auto nand = FileSystem::GetSystemNANDContents();
// 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 {
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
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");
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()) {
// Read shared font data
ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE);
file.ReadBytes(shared_font->data(), shared_font->size());
BuildSharedFontsRawRegions(*shared_font);
} else {
LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath);
}
}
}
void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
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);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@ -82,7 +200,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
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) {
@ -92,14 +210,10 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NS, "called, font_id={}", font_id);
IPC::ResponseBuilder rb{ctx, 3};
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) {
// 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
Core::CurrentProcess()->vm_manager.MapMemoryBlock(
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
for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) {
font_codes.push_back(static_cast<u32>(i));
font_offsets.push_back(SHARED_FONT_REGIONS[i].offset);
font_sizes.push_back(SHARED_FONT_REGIONS[i].size);
auto region = GetSharedFontRegion(i);
font_offsets.push_back(region.offset);
font_sizes.push_back(region.size);
}
ctx.WriteBuffer(font_codes, 0);