aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/loader
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/loader')
-rw-r--r--src/core/loader/3dsx.cpp234
-rw-r--r--src/core/loader/3dsx.h32
-rw-r--r--src/core/loader/elf.cpp20
-rw-r--r--src/core/loader/elf.h4
-rw-r--r--src/core/loader/loader.cpp24
-rw-r--r--src/core/loader/loader.h3
-rw-r--r--src/core/loader/ncch.cpp40
-rw-r--r--src/core/loader/ncch.h8
8 files changed, 326 insertions, 39 deletions
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
new file mode 100644
index 00000000..4d072871
--- /dev/null
+++ b/src/core/loader/3dsx.cpp
@@ -0,0 +1,234 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <vector>
+
+#include "core/file_sys/archive_romfs.h"
+#include "core/loader/elf.h"
+#include "core/loader/ncch.h"
+#include "core/hle/service/fs/archive.h"
+#include "core/mem_map.h"
+
+#include "3dsx.h"
+
+
+namespace Loader {
+
+
+/**
+ * File layout:
+ * - File header
+ * - Code, rodata and data relocation table headers
+ * - Code segment
+ * - Rodata segment
+ * - Loadable (non-BSS) part of the data segment
+ * - Code relocation table
+ * - Rodata relocation table
+ * - Data relocation table
+ *
+ * Memory layout before relocations are applied:
+ * [0..codeSegSize) -> code segment
+ * [codeSegSize..rodataSegSize) -> rodata segment
+ * [rodataSegSize..dataSegSize) -> data segment
+ *
+ * Memory layout after relocations are applied: well, however the loader sets it up :)
+ * The entrypoint is always the start of the code segment.
+ * The BSS section must be cleared manually by the application.
+ */
+enum THREEDSX_Error {
+ ERROR_NONE = 0,
+ ERROR_READ = 1,
+ ERROR_FILE = 2,
+ ERROR_ALLOC = 3
+};
+static const u32 RELOCBUFSIZE = 512;
+
+// File header
+static const u32 THREEDSX_MAGIC = 0x58534433; // '3DSX'
+#pragma pack(1)
+struct THREEDSX_Header
+{
+ u32 magic;
+ u16 header_size, reloc_hdr_size;
+ u32 format_ver;
+ u32 flags;
+
+ // Sizes of the code, rodata and data segments +
+ // size of the BSS section (uninitialized latter half of the data segment)
+ u32 code_seg_size, rodata_seg_size, data_seg_size, bss_size;
+};
+
+// Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts.
+struct THREEDSX_RelocHdr
+{
+ // # of absolute relocations (that is, fix address to post-relocation memory layout)
+ u32 cross_segment_absolute;
+ // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be patched)
+ u32 cross_segment_relative;
+ // more?
+
+ // Relocations are written in this order:
+ // - Absolute relocations
+ // - Relative relocations
+};
+
+// Relocation entry: from the current pointer, skip X words and patch Y words
+struct THREEDSX_Reloc
+{
+ u16 skip, patch;
+};
+#pragma pack()
+
+struct THREEloadinfo
+{
+ u8* seg_ptrs[3]; // code, rodata & data
+ u32 seg_addrs[3];
+ u32 seg_sizes[3];
+};
+
+class THREEDSXReader {
+public:
+ static int Load3DSXFile(const std::string& filename, u32 base_addr);
+};
+
+static u32 TranslateAddr(u32 addr, THREEloadinfo *loadinfo, u32* offsets)
+{
+ if (addr < offsets[0])
+ return loadinfo->seg_addrs[0] + addr;
+ if (addr < offsets[1])
+ return loadinfo->seg_addrs[1] + addr - offsets[0];
+ return loadinfo->seg_addrs[2] + addr - offsets[1];
+}
+
+int THREEDSXReader::Load3DSXFile(const std::string& filename, u32 base_addr)
+{
+ FileUtil::IOFile file(filename, "rb");
+ if (!file.IsOpen()) {
+ return ERROR_FILE;
+ }
+ THREEDSX_Header hdr;
+ if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr))
+ return ERROR_READ;
+
+ THREEloadinfo loadinfo;
+ //loadinfo segments must be a multiple of 0x1000
+ loadinfo.seg_sizes[0] = (hdr.code_seg_size + 0xFFF) &~0xFFF;
+ loadinfo.seg_sizes[1] = (hdr.rodata_seg_size + 0xFFF) &~0xFFF;
+ loadinfo.seg_sizes[2] = (hdr.data_seg_size + 0xFFF) &~0xFFF;
+ u32 offsets[2] = { loadinfo.seg_sizes[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] };
+ u32 data_load_size = (hdr.data_seg_size - hdr.bss_size + 0xFFF) &~0xFFF;
+ u32 bss_load_size = loadinfo.seg_sizes[2] - data_load_size;
+ u32 n_reloc_tables = hdr.reloc_hdr_size / 4;
+ std::vector<u8> all_mem(loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + loadinfo.seg_sizes[2] + 3 * n_reloc_tables);
+
+ loadinfo.seg_addrs[0] = base_addr;
+ loadinfo.seg_addrs[1] = loadinfo.seg_addrs[0] + loadinfo.seg_sizes[0];
+ loadinfo.seg_addrs[2] = loadinfo.seg_addrs[1] + loadinfo.seg_sizes[1];
+ loadinfo.seg_ptrs[0] = &all_mem[0];
+ loadinfo.seg_ptrs[1] = loadinfo.seg_ptrs[0] + loadinfo.seg_sizes[0];
+ loadinfo.seg_ptrs[2] = loadinfo.seg_ptrs[1] + loadinfo.seg_sizes[1];
+
+ // Skip header for future compatibility
+ file.Seek(hdr.header_size, SEEK_SET);
+
+ // Read the relocation headers
+ u32* relocs = (u32*)(loadinfo.seg_ptrs[2] + hdr.data_seg_size);
+
+ for (u32 current_segment = 0; current_segment < 3; current_segment++) {
+ if (file.ReadBytes(&relocs[current_segment*n_reloc_tables], n_reloc_tables * 4) != n_reloc_tables * 4)
+ return ERROR_READ;
+ }
+
+ // Read the segments
+ if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size)
+ return ERROR_READ;
+ if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size)
+ return ERROR_READ;
+ if (file.ReadBytes(loadinfo.seg_ptrs[2], hdr.data_seg_size - hdr.bss_size) != hdr.data_seg_size - hdr.bss_size)
+ return ERROR_READ;
+
+ // BSS clear
+ memset((char*)loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size);
+
+ // Relocate the segments
+ for (u32 current_segment = 0; current_segment < 3; current_segment++) {
+ for (u32 current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables; current_segment_reloc_table++) {
+ u32 n_relocs = relocs[current_segment*n_reloc_tables + current_segment_reloc_table];
+ if (current_segment_reloc_table >= 2) {
+ // We are not using this table - ignore it because we don't know what it dose
+ file.Seek(n_relocs*sizeof(THREEDSX_Reloc), SEEK_CUR);
+ continue;
+ }
+ static THREEDSX_Reloc reloc_table[RELOCBUFSIZE];
+
+ u32* pos = (u32*)loadinfo.seg_ptrs[current_segment];
+ u32* end_pos = pos + (loadinfo.seg_sizes[current_segment] / 4);
+
+ while (n_relocs) {
+ u32 remaining = std::min(RELOCBUFSIZE, n_relocs);
+ n_relocs -= remaining;
+
+ if (file.ReadBytes(reloc_table, remaining*sizeof(THREEDSX_Reloc)) != remaining*sizeof(THREEDSX_Reloc))
+ return ERROR_READ;
+
+ for (u32 current_inprogress = 0; current_inprogress < remaining && pos < end_pos; current_inprogress++) {
+ LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)\n",
+ current_segment_reloc_table, (u32)reloc_table[current_inprogress].skip, (u32)reloc_table[current_inprogress].patch);
+ pos += reloc_table[current_inprogress].skip;
+ s32 num_patches = reloc_table[current_inprogress].patch;
+ while (0 < num_patches && pos < end_pos) {
+ u32 in_addr = (char*)pos - (char*)&all_mem[0];
+ u32 addr = TranslateAddr(*pos, &loadinfo, offsets);
+ LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)\n",
+ base_addr + in_addr, addr, current_segment_reloc_table, *pos);
+ switch (current_segment_reloc_table) {
+ case 0: *pos = (addr); break;
+ case 1: *pos = (addr - in_addr); break;
+ default: break; //this should never happen
+ }
+ pos++;
+ num_patches--;
+ }
+ }
+ }
+ }
+ }
+
+ // Write the data
+ memcpy(Memory::GetPointer(base_addr), &all_mem[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + loadinfo.seg_sizes[2]);
+
+ LOG_DEBUG(Loader, "CODE: %u pages\n", loadinfo.seg_sizes[0] / 0x1000);
+ LOG_DEBUG(Loader, "RODATA: %u pages\n", loadinfo.seg_sizes[1] / 0x1000);
+ LOG_DEBUG(Loader, "DATA: %u pages\n", data_load_size / 0x1000);
+ LOG_DEBUG(Loader, "BSS: %u pages\n", bss_load_size / 0x1000);
+
+ return ERROR_NONE;
+}
+
+ /// AppLoader_DSX constructor
+ AppLoader_THREEDSX::AppLoader_THREEDSX(const std::string& filename) : filename(filename) {
+ }
+
+ /// AppLoader_DSX destructor
+ AppLoader_THREEDSX::~AppLoader_THREEDSX() {
+ }
+
+ /**
+ * Loads a 3DSX file
+ * @return Success on success, otherwise Error
+ */
+ ResultStatus AppLoader_THREEDSX::Load() {
+ LOG_INFO(Loader, "Loading 3DSX file %s...", filename.c_str());
+ FileUtil::IOFile file(filename, "rb");
+ if (file.IsOpen()) {
+ THREEDSXReader::Load3DSXFile(filename, 0x00100000);
+ Kernel::LoadExec(0x00100000);
+ } else {
+ return ResultStatus::Error;
+ }
+ return ResultStatus::Success;
+ }
+
+} // namespace Loader
diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h
new file mode 100644
index 00000000..da883666
--- /dev/null
+++ b/src/core/loader/3dsx.h
@@ -0,0 +1,32 @@
+// Copyright 2014 Dolphin Emulator Project / Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/loader/loader.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Loader namespace
+
+namespace Loader {
+
+/// Loads an 3DSX file
+class AppLoader_THREEDSX final : public AppLoader {
+public:
+ AppLoader_THREEDSX(const std::string& filename);
+ ~AppLoader_THREEDSX() override;
+
+ /**
+ * Load the bootable file
+ * @return ResultStatus result of function
+ */
+ ResultStatus Load() override;
+
+private:
+ std::string filename;
+ bool is_loaded;
+};
+
+} // namespace Loader
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 63d2496e..35433501 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <string>
@@ -254,18 +254,18 @@ const char *ElfReader::GetSectionName(int section) const {
}
bool ElfReader::LoadInto(u32 vaddr) {
- DEBUG_LOG(MASTER_LOG, "String section: %i", header->e_shstrndx);
+ LOG_DEBUG(Loader, "String section: %i", header->e_shstrndx);
// Should we relocate?
relocate = (header->e_type != ET_EXEC);
if (relocate) {
- DEBUG_LOG(MASTER_LOG, "Relocatable module");
+ LOG_DEBUG(Loader, "Relocatable module");
entryPoint += vaddr;
} else {
- DEBUG_LOG(MASTER_LOG, "Prerelocated executable");
+ LOG_DEBUG(Loader, "Prerelocated executable");
}
- INFO_LOG(MASTER_LOG, "%i segments:", header->e_phnum);
+ LOG_DEBUG(Loader, "%i segments:", header->e_phnum);
// First pass : Get the bits into RAM
u32 segment_addr[32];
@@ -273,17 +273,17 @@ bool ElfReader::LoadInto(u32 vaddr) {
for (int i = 0; i < header->e_phnum; i++) {
Elf32_Phdr *p = segments + i;
- INFO_LOG(MASTER_LOG, "Type: %i Vaddr: %08x Filesz: %i Memsz: %i ", p->p_type, p->p_vaddr,
+ LOG_DEBUG(Loader, "Type: %i Vaddr: %08x Filesz: %i Memsz: %i ", p->p_type, p->p_vaddr,
p->p_filesz, p->p_memsz);
if (p->p_type == PT_LOAD) {
segment_addr[i] = base_addr + p->p_vaddr;
memcpy(Memory::GetPointer(segment_addr[i]), GetSegmentPtr(i), p->p_filesz);
- INFO_LOG(MASTER_LOG, "Loadable Segment Copied to %08x, size %08x", segment_addr[i],
+ LOG_DEBUG(Loader, "Loadable Segment Copied to %08x, size %08x", segment_addr[i],
p->p_memsz);
}
}
- INFO_LOG(MASTER_LOG, "Done loading.");
+ LOG_DEBUG(Loader, "Done loading.");
return true;
}
@@ -346,7 +346,7 @@ AppLoader_ELF::~AppLoader_ELF() {
* @return True on success, otherwise false
*/
ResultStatus AppLoader_ELF::Load() {
- INFO_LOG(LOADER, "Loading ELF file %s...", filename.c_str());
+ LOG_INFO(Loader, "Loading ELF file %s...", filename.c_str());
if (is_loaded)
return ResultStatus::ErrorAlreadyLoaded;
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index 5ae88439..c221cce6 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -1,5 +1,5 @@
-// Copyright 2013 Dolphin Emulator Project / Citra Emulator Project
-// Licensed under GPLv2
+// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index a268e021..87580cb2 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -1,13 +1,16 @@
// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <memory>
+#include <string>
+
+#include "common/make_unique.h"
#include "core/file_sys/archive_romfs.h"
+#include "core/loader/3dsx.h"
#include "core/loader/elf.h"
#include "core/loader/ncch.h"
-#include "core/hle/kernel/archive.h"
+#include "core/hle/service/fs/archive.h"
#include "core/mem_map.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -22,7 +25,7 @@ namespace Loader {
*/
FileType IdentifyFile(const std::string &filename) {
if (filename.size() == 0) {
- ERROR_LOG(LOADER, "invalid filename %s", filename.c_str());
+ LOG_ERROR(Loader, "invalid filename %s", filename.c_str());
return FileType::Error;
}
@@ -42,6 +45,8 @@ FileType IdentifyFile(const std::string &filename) {
return FileType::CCI;
} else if (extension == ".bin") {
return FileType::BIN;
+ } else if (extension == ".3dsx") {
+ return FileType::THREEDSX;
}
return FileType::Unknown;
}
@@ -52,10 +57,14 @@ FileType IdentifyFile(const std::string &filename) {
* @return ResultStatus result of function
*/
ResultStatus LoadFile(const std::string& filename) {
- INFO_LOG(LOADER, "Loading file %s...", filename.c_str());
+ LOG_INFO(Loader, "Loading file %s...", filename.c_str());
switch (IdentifyFile(filename)) {
+ //3DSX file format...
+ case FileType::THREEDSX:
+ return AppLoader_THREEDSX(filename).Load();
+
// Standard ELF file format...
case FileType::ELF:
return AppLoader_ELF(filename).Load();
@@ -67,7 +76,8 @@ ResultStatus LoadFile(const std::string& filename) {
// Load application and RomFS
if (ResultStatus::Success == app_loader.Load()) {
- Kernel::CreateArchive(new FileSys::Archive_RomFS(app_loader), "RomFS");
+ Kernel::g_program_id = app_loader.GetProgramId();
+ Service::FS::CreateArchive(Common::make_unique<FileSys::Archive_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
return ResultStatus::Success;
}
break;
@@ -76,7 +86,7 @@ ResultStatus LoadFile(const std::string& filename) {
// Raw BIN file format...
case FileType::BIN:
{
- INFO_LOG(LOADER, "Loading BIN file %s...", filename.c_str());
+ LOG_INFO(Loader, "Loading BIN file %s...", filename.c_str());
FileUtil::IOFile file(filename, "rb");
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 68f84300..ec5534d4 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -1,5 +1,5 @@
// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
@@ -22,6 +22,7 @@ enum class FileType {
CIA,
ELF,
BIN,
+ THREEDSX, //3DSX
};
/// Return type for functions in Loader namespace
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 343bb752..0dc21699 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -1,5 +1,5 @@
// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
@@ -140,13 +140,13 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>&
// Iterate through the ExeFs archive until we find the .code file...
FileUtil::IOFile file(filename, "rb");
if (file.IsOpen()) {
+ LOG_DEBUG(Loader, "%d sections:", kMaxSections);
for (int i = 0; i < kMaxSections; i++) {
// Load the specified section...
if (strcmp((const char*)exefs_header.section[i].name, name) == 0) {
- INFO_LOG(LOADER, "ExeFS section %d:", i);
- INFO_LOG(LOADER, " name: %s", exefs_header.section[i].name);
- INFO_LOG(LOADER, " offset: 0x%08X", exefs_header.section[i].offset);
- INFO_LOG(LOADER, " size: 0x%08X", exefs_header.section[i].size);
+ LOG_DEBUG(Loader, "%d - offset: 0x%08X, size: 0x%08X, name: %s", i,
+ exefs_header.section[i].offset, exefs_header.section[i].size,
+ exefs_header.section[i].name);
s64 section_offset = (exefs_header.section[i].offset + exefs_offset +
sizeof(ExeFs_Header)+ncch_offset);
@@ -181,7 +181,7 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>&
}
}
} else {
- ERROR_LOG(LOADER, "Unable to read file %s!", filename.c_str());
+ LOG_ERROR(Loader, "Unable to read file %s!", filename.c_str());
return ResultStatus::Error;
}
return ResultStatus::ErrorNotUsed;
@@ -194,7 +194,7 @@ ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>&
* @return True on success, otherwise false
*/
ResultStatus AppLoader_NCCH::Load() {
- INFO_LOG(LOADER, "Loading NCCH file %s...", filename.c_str());
+ LOG_INFO(Loader, "Loading NCCH file %s...", filename.c_str());
if (is_loaded)
return ResultStatus::ErrorAlreadyLoaded;
@@ -205,7 +205,7 @@ ResultStatus AppLoader_NCCH::Load() {
// Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
if (0 == memcmp(&ncch_header.magic, "NCSD", 4)) {
- WARN_LOG(LOADER, "Only loading the first (bootable) NCCH within the NCSD file!");
+ LOG_WARNING(Loader, "Only loading the first (bootable) NCCH within the NCSD file!");
ncch_offset = 0x4000;
file.Seek(ncch_offset, 0);
file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
@@ -222,17 +222,17 @@ ResultStatus AppLoader_NCCH::Load() {
is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1;
entry_point = exheader_header.codeset_info.text.address;
- INFO_LOG(LOADER, "Name: %s", exheader_header.codeset_info.name);
- INFO_LOG(LOADER, "Code compressed: %s", is_compressed ? "yes" : "no");
- INFO_LOG(LOADER, "Entry point: 0x%08X", entry_point);
+ LOG_INFO(Loader, "Name: %s", exheader_header.codeset_info.name);
+ LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no");
+ LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point);
// Read ExeFS...
exefs_offset = ncch_header.exefs_offset * kBlockSize;
u32 exefs_size = ncch_header.exefs_size * kBlockSize;
- INFO_LOG(LOADER, "ExeFS offset: 0x%08X", exefs_offset);
- INFO_LOG(LOADER, "ExeFS size: 0x%08X", exefs_size);
+ LOG_DEBUG(Loader, "ExeFS offset: 0x%08X", exefs_offset);
+ LOG_DEBUG(Loader, "ExeFS size: 0x%08X", exefs_size);
file.Seek(exefs_offset + ncch_offset, 0);
file.ReadBytes(&exefs_header, sizeof(ExeFs_Header));
@@ -243,7 +243,7 @@ ResultStatus AppLoader_NCCH::Load() {
return ResultStatus::Success;
} else {
- ERROR_LOG(LOADER, "Unable to read file %s!", filename.c_str());
+ LOG_ERROR(Loader, "Unable to read file %s!", filename.c_str());
}
return ResultStatus::Error;
}
@@ -297,8 +297,8 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::vector<u8>& buffer) const {
u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000;
u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000;
- INFO_LOG(LOADER, "RomFS offset: 0x%08X", romfs_offset);
- INFO_LOG(LOADER, "RomFS size: 0x%08X", romfs_size);
+ LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset);
+ LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size);
buffer.resize(romfs_size);
@@ -307,12 +307,16 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::vector<u8>& buffer) const {
return ResultStatus::Success;
}
- NOTICE_LOG(LOADER, "RomFS unused");
+ LOG_DEBUG(Loader, "NCCH has no RomFS");
return ResultStatus::ErrorNotUsed;
} else {
- ERROR_LOG(LOADER, "Unable to read file %s!", filename.c_str());
+ LOG_ERROR(Loader, "Unable to read file %s!", filename.c_str());
}
return ResultStatus::Error;
}
+u64 AppLoader_NCCH::GetProgramId() const {
+ return *reinterpret_cast<u64 const*>(&ncch_header.program_id[0]);
+}
+
} // namespace Loader
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h
index 03116add..fd925897 100644
--- a/src/core/loader/ncch.h
+++ b/src/core/loader/ncch.h
@@ -1,5 +1,5 @@
// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
+// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
@@ -191,6 +191,12 @@ public:
*/
ResultStatus ReadRomFS(std::vector<u8>& buffer) const override;
+ /*
+ * Gets the program id from the NCCH header
+ * @return u64 Program id
+ */
+ u64 GetProgramId() const;
+
private:
/**