From e8f641a52de33b33e0d6c73ee7ca07f06dcd8aeb Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 23 Jul 2018 12:33:24 -0400
Subject: [PATCH] NRO Assets and NACP file format

Cleanup

Review fixes
---
 src/core/CMakeLists.txt                |  2 +
 src/core/file_sys/control_metadata.cpp | 42 +++++++++++++
 src/core/file_sys/control_metadata.h   | 81 ++++++++++++++++++++++++++
 src/core/loader/nro.cpp                | 79 ++++++++++++++++++++++++-
 src/core/loader/nro.h                  | 12 ++++
 5 files changed, 215 insertions(+), 1 deletion(-)
 create mode 100644 src/core/file_sys/control_metadata.cpp
 create mode 100644 src/core/file_sys/control_metadata.h

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 27a5de7fd0..fd75854b05 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -12,6 +12,8 @@ add_library(core STATIC
     core_timing.h
     file_sys/content_archive.cpp
     file_sys/content_archive.h
+    file_sys/control_metadata.cpp
+    file_sys/control_metadata.h
     file_sys/directory.h
     file_sys/errors.h
     file_sys/mode.h
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
new file mode 100644
index 0000000000..3ddc9f162d
--- /dev/null
+++ b/src/core/file_sys/control_metadata.cpp
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/string_util.h"
+#include "common/swap.h"
+#include "core/file_sys/control_metadata.h"
+
+namespace FileSys {
+
+std::string LanguageEntry::GetApplicationName() const {
+    return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
+}
+
+std::string LanguageEntry::GetDeveloperName() const {
+    return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
+}
+
+NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) {
+    file->ReadObject(raw.get());
+}
+
+const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
+    return raw->language_entries.at(static_cast<u8>(language));
+}
+
+std::string NACP::GetApplicationName(Language language) const {
+    return GetLanguageEntry(language).GetApplicationName();
+}
+
+std::string NACP::GetDeveloperName(Language language) const {
+    return GetLanguageEntry(language).GetDeveloperName();
+}
+
+u64 NACP::GetTitleId() const {
+    return raw->title_id;
+}
+
+std::string NACP::GetVersionString() const {
+    return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
new file mode 100644
index 0000000000..cc3b745f7e
--- /dev/null
+++ b/src/core/file_sys/control_metadata.h
@@ -0,0 +1,81 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <string>
+#include "common/common_funcs.h"
+#include "core/file_sys/vfs.h"
+
+namespace FileSys {
+
+// A localized entry containing strings within the NACP.
+// One for each language of type Language.
+struct LanguageEntry {
+    std::array<char, 0x200> application_name;
+    std::array<char, 0x100> developer_name;
+
+    std::string GetApplicationName() const;
+    std::string GetDeveloperName() const;
+};
+static_assert(sizeof(LanguageEntry) == 0x300, "LanguageEntry has incorrect size.");
+
+// The raw file format of a NACP file.
+struct RawNACP {
+    std::array<LanguageEntry, 16> language_entries;
+    INSERT_PADDING_BYTES(0x38);
+    u64_le title_id;
+    INSERT_PADDING_BYTES(0x20);
+    std::array<char, 0x10> version_string;
+    u64_le dlc_base_title_id;
+    u64_le title_id_2;
+    INSERT_PADDING_BYTES(0x28);
+    u64_le product_code;
+    u64_le title_id_3;
+    std::array<u64_le, 0x7> title_id_array;
+    INSERT_PADDING_BYTES(0x8);
+    u64_le title_id_update;
+    std::array<u8, 0x40> bcat_passphrase;
+    INSERT_PADDING_BYTES(0xEC0);
+};
+static_assert(sizeof(RawNACP) == 0x4000, "RawNACP has incorrect size.");
+
+// A language on the NX. These are for names and icons.
+enum class Language : u8 {
+    AmericanEnglish = 0,
+    BritishEnglish = 1,
+    Japanese = 2,
+    French = 3,
+    German = 4,
+    LatinAmericanSpanish = 5,
+    Spanish = 6,
+    Italian = 7,
+    Dutch = 8,
+    CanadianFrench = 9,
+    Portugese = 10,
+    Russian = 11,
+    Korean = 12,
+    Taiwanese = 13,
+    Chinese = 14,
+};
+
+// A class representing the format used by NX metadata files, typically named Control.nacp.
+// These store application name, dev name, title id, and other miscellaneous data.
+class NACP {
+public:
+    explicit NACP(VirtualFile file);
+    const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const;
+    std::string GetApplicationName(Language language = Language::AmericanEnglish) const;
+    std::string GetDeveloperName(Language language = Language::AmericanEnglish) const;
+    u64 GetTitleId() const;
+    std::string GetVersionString() const;
+
+private:
+    VirtualFile file;
+    std::unique_ptr<RawNACP> raw;
+};
+
+} // namespace FileSys
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index c020399f27..44158655cb 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -10,6 +10,8 @@
 #include "common/logging/log.h"
 #include "common/swap.h"
 #include "core/core.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/vfs_offset.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/resource_limit.h"
@@ -49,7 +51,55 @@ struct ModHeader {
 };
 static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
 
-AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
+struct AssetSection {
+    u64_le offset;
+    u64_le size;
+};
+static_assert(sizeof(AssetSection) == 0x10, "AssetSection has incorrect size.");
+
+struct AssetHeader {
+    u32_le magic;
+    u32_le format_version;
+    AssetSection icon;
+    AssetSection nacp;
+    AssetSection romfs;
+};
+static_assert(sizeof(AssetHeader) == 0x38, "AssetHeader has incorrect size.");
+
+AppLoader_NRO::AppLoader_NRO(FileSys::VirtualFile file) : AppLoader(file) {
+    NroHeader nro_header{};
+    if (file->ReadObject(&nro_header) != sizeof(NroHeader))
+        return;
+
+    if (file->GetSize() >= nro_header.file_size + sizeof(AssetHeader)) {
+        u64 offset = nro_header.file_size;
+        AssetHeader asset_header{};
+        if (file->ReadObject(&asset_header, offset) != sizeof(AssetHeader))
+            return;
+
+        if (asset_header.format_version != 0)
+            LOG_WARNING(Loader,
+                        "NRO Asset Header has format {}, currently supported format is 0. If "
+                        "strange glitches occur with metadata, check NRO assets.",
+                        asset_header.format_version);
+        if (asset_header.magic != Common::MakeMagic('A', 'S', 'E', 'T'))
+            return;
+
+        if (asset_header.nacp.size > 0) {
+            nacp = std::make_unique<FileSys::NACP>(std::make_shared<FileSys::OffsetVfsFile>(
+                file, asset_header.nacp.size, offset + asset_header.nacp.offset, "Control.nacp"));
+        }
+
+        if (asset_header.romfs.size > 0) {
+            romfs = std::make_shared<FileSys::OffsetVfsFile>(
+                file, asset_header.romfs.size, offset + asset_header.romfs.offset, "game.romfs");
+        }
+
+        if (asset_header.icon.size > 0) {
+            icon_data = file->ReadBytes(asset_header.icon.size, offset + asset_header.icon.offset);
+        }
+    }
+}
 
 FileType AppLoader_NRO::IdentifyType(const FileSys::VirtualFile& file) {
     // Read NSO header
@@ -136,4 +186,31 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     return ResultStatus::Success;
 }
 
+ResultStatus AppLoader_NRO::ReadIcon(std::vector<u8>& buffer) {
+    if (icon_data.empty())
+        return ResultStatus::ErrorNotUsed;
+    buffer = icon_data;
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NRO::ReadProgramId(u64& out_program_id) {
+    if (nacp == nullptr)
+        return ResultStatus::ErrorNotUsed;
+    out_program_id = nacp->GetTitleId();
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NRO::ReadRomFS(FileSys::VirtualFile& dir) {
+    if (romfs == nullptr)
+        return ResultStatus::ErrorNotUsed;
+    dir = romfs;
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
+    if (nacp == nullptr)
+        return ResultStatus::ErrorNotUsed;
+    title = nacp->GetApplicationName();
+    return ResultStatus::Success;
+}
 } // namespace Loader
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 2c03d06bb1..5f3fc40d26 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -6,12 +6,15 @@
 
 #include <string>
 #include "common/common_types.h"
+#include "core/file_sys/control_metadata.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/loader/linker.h"
 #include "core/loader/loader.h"
 
 namespace Loader {
 
+struct AssetHeader;
+
 /// Loads an NRO file
 class AppLoader_NRO final : public AppLoader, Linker {
 public:
@@ -30,8 +33,17 @@ public:
 
     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
 
+    ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+    ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+    ResultStatus ReadTitle(std::string& title) override;
+
 private:
     bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
+
+    std::vector<u8> icon_data;
+    std::unique_ptr<FileSys::NACP> nacp;
+    FileSys::VirtualFile romfs;
 };
 
 } // namespace Loader