From 1f7ec4be9bf86f26d57555f7fd6b43851557e47d Mon Sep 17 00:00:00 2001
From: Yuri Kunde Schlesner <yuriks@yuriks.net>
Date: Sat, 27 Aug 2016 01:04:26 -0700
Subject: [PATCH] Auto-detect original shared_font.bin memory base

This allows a file dumped from either an o3DS or a n3DS (and potentially
even an original unrebased file) to be used.
---
 src/core/hle/service/apt/apt.cpp         |  7 +--
 src/core/hle/service/apt/bcfnt/bcfnt.cpp | 79 +++++++++++++++++-------
 src/core/hle/service/apt/bcfnt/bcfnt.h   | 12 +++-
 3 files changed, 68 insertions(+), 30 deletions(-)

diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index c009e6c981..4f4ee75cda 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -81,13 +81,8 @@ void GetSharedFont(Service::Interface* self) {
 
     // The shared font has to be relocated to the new address before being passed to the application.
     VAddr target_address = Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
-    // The shared font dumped by 3dsutils (https://github.com/citra-emu/3dsutils) uses this address as base,
-    // so we relocate it from there to our real address.
-    // TODO(Subv): This address is wrong if the shared font is dumped from a n3DS,
-    // we need a way to automatically calculate the original address of the font from the file.
-    static const VAddr SHARED_FONT_VADDR = 0x18000000;
     if (!shared_font_relocated) {
-        BCFNT::RelocateSharedFont(shared_font_mem, SHARED_FONT_VADDR, target_address);
+        BCFNT::RelocateSharedFont(shared_font_mem, target_address);
         shared_font_relocated = true;
     }
     cmd_buff[0] = IPC::MakeHeader(0x44, 2, 2);
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
index b0d39d4a5b..57eb39d753 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -9,60 +9,97 @@ namespace Service {
 namespace APT {
 namespace BCFNT {
 
-void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address) {
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address) {
     static const u32 SharedFontStartOffset = 0x80;
-    u8* data = shared_font->GetPointer(SharedFontStartOffset);
+    const u8* cfnt_ptr = shared_font->GetPointer(SharedFontStartOffset);
 
     CFNT cfnt;
-    memcpy(&cfnt, data, sizeof(cfnt));
+    memcpy(&cfnt, cfnt_ptr, sizeof(cfnt));
 
-    // Advance past the header
-    data = shared_font->GetPointer(SharedFontStartOffset + cfnt.header_size);
+    u32 assumed_cmap_offset = 0;
+    u32 assumed_cwdh_offset = 0;
+    u32 assumed_tglp_offset = 0;
+    u32 first_cmap_offset = 0;
+    u32 first_cwdh_offset = 0;
+    u32 first_tglp_offset = 0;
 
+    // First discover the location of sections so that the rebase offset can be auto-detected
+    u32 current_offset = SharedFontStartOffset + cfnt.header_size;
     for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
+        const u8* data = shared_font->GetPointer(current_offset);
 
-        u32 section_size = 0;
-        if (memcmp(data, "FINF", 4) == 0) {
+        SectionHeader section_header;
+        memcpy(&section_header, data, sizeof(section_header));
+
+        if (first_cmap_offset == 0 && memcmp(section_header.magic, "CMAP", 4) == 0) {
+            first_cmap_offset = current_offset;
+        } else if (first_cwdh_offset == 0 && memcmp(section_header.magic, "CWDH", 4) == 0) {
+            first_cwdh_offset = current_offset;
+        } else if (first_tglp_offset == 0 && memcmp(section_header.magic, "TGLP", 4) == 0) {
+            first_tglp_offset = current_offset;
+        } else if (memcmp(section_header.magic, "FINF", 4) == 0) {
+            BCFNT::FINF finf;
+            memcpy(&finf, data, sizeof(finf));
+
+            assumed_cmap_offset = finf.cmap_offset - sizeof(SectionHeader);
+            assumed_cwdh_offset = finf.cwdh_offset - sizeof(SectionHeader);
+            assumed_tglp_offset = finf.tglp_offset - sizeof(SectionHeader);
+        }
+
+        current_offset += section_header.section_size;
+    }
+
+    u32 previous_base = assumed_cmap_offset - first_cmap_offset;
+    ASSERT(previous_base == assumed_cwdh_offset - first_cwdh_offset);
+    ASSERT(previous_base == assumed_tglp_offset - first_tglp_offset);
+
+    u32 offset = new_address - previous_base;
+
+    // Reset pointer back to start of sections and do the actual rebase
+    current_offset = SharedFontStartOffset + cfnt.header_size;
+    for (unsigned block = 0; block < cfnt.num_blocks; ++block) {
+        u8* data = shared_font->GetPointer(current_offset);
+
+        SectionHeader section_header;
+        memcpy(&section_header, data, sizeof(section_header));
+
+        if (memcmp(section_header.magic, "FINF", 4) == 0) {
             BCFNT::FINF finf;
             memcpy(&finf, data, sizeof(finf));
-            section_size = finf.section_size;
 
             // Relocate the offsets in the FINF section
-            finf.cmap_offset += new_address - previous_address;
-            finf.cwdh_offset += new_address - previous_address;
-            finf.tglp_offset += new_address - previous_address;
+            finf.cmap_offset += offset;
+            finf.cwdh_offset += offset;
+            finf.tglp_offset += offset;
 
             memcpy(data, &finf, sizeof(finf));
-        } else if (memcmp(data, "CMAP", 4) == 0) {
+        } else if (memcmp(section_header.magic, "CMAP", 4) == 0) {
             BCFNT::CMAP cmap;
             memcpy(&cmap, data, sizeof(cmap));
-            section_size = cmap.section_size;
 
             // Relocate the offsets in the CMAP section
-            cmap.next_cmap_offset += new_address - previous_address;
+            cmap.next_cmap_offset += offset;
 
             memcpy(data, &cmap, sizeof(cmap));
-        } else if (memcmp(data, "CWDH", 4) == 0) {
+        } else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
             BCFNT::CWDH cwdh;
             memcpy(&cwdh, data, sizeof(cwdh));
-            section_size = cwdh.section_size;
 
             // Relocate the offsets in the CWDH section
-            cwdh.next_cwdh_offset += new_address - previous_address;
+            cwdh.next_cwdh_offset += offset;
 
             memcpy(data, &cwdh, sizeof(cwdh));
-        } else if (memcmp(data, "TGLP", 4) == 0) {
+        } else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
             BCFNT::TGLP tglp;
             memcpy(&tglp, data, sizeof(tglp));
-            section_size = tglp.section_size;
 
             // Relocate the offsets in the TGLP section
-            tglp.sheet_data_offset += new_address - previous_address;
+            tglp.sheet_data_offset += offset;
 
             memcpy(data, &tglp, sizeof(tglp));
         }
 
-        data += section_size;
+        current_offset += section_header.section_size;
     }
 }
 
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.h b/src/core/hle/service/apt/bcfnt/bcfnt.h
index 388c6bea0e..8936dcf63a 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.h
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.h
@@ -22,6 +22,11 @@ struct CFNT {
     u32_le num_blocks;
 };
 
+struct SectionHeader {
+    u8 magic[4];
+    u32_le section_size;
+};
+
 struct FINF {
     u8 magic[4];
     u32_le section_size;
@@ -75,12 +80,13 @@ struct CWDH {
 };
 
 /**
- * Relocates the internal addresses of the BCFNT Shared Font to the new base.
+ * Relocates the internal addresses of the BCFNT Shared Font to the new base. The current base will
+ * be auto-detected based on the file headers.
+ *
  * @param shared_font SharedMemory object that contains the Shared Font
- * @param previous_address Previous address at which the offsets in the structure were based.
  * @param new_address New base for the offsets in the structure.
  */
-void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr previous_address, VAddr new_address);
+void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAddr new_address);
 
 } // namespace BCFNT
 } // namespace APT