diff options
Diffstat (limited to 'src/core/loader')
-rw-r--r-- | src/core/loader/elf.cpp | 370 | ||||
-rw-r--r-- | src/core/loader/elf.h | 32 | ||||
-rw-r--r-- | src/core/loader/loader.cpp | 77 | ||||
-rw-r--r-- | src/core/loader/loader.h | 121 | ||||
-rw-r--r-- | src/core/loader/ncch.cpp | 312 | ||||
-rw-r--r-- | src/core/loader/ncch.h | 227 |
6 files changed, 1139 insertions, 0 deletions
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp new file mode 100644 index 0000000000..76c9d6d549 --- /dev/null +++ b/src/core/loader/elf.cpp @@ -0,0 +1,370 @@ +// Copyright 2013 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <string> +#include <memory> + +#include "common/common.h" +#include "common/file_util.h" +#include "common/symbols.h" + +#include "core/mem_map.h" +#include "core/loader/elf.h" +#include "core/hle/kernel/kernel.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ELF Header Constants + +// File type +enum ElfType { + ET_NONE = 0, + ET_REL = 1, + ET_EXEC = 2, + ET_DYN = 3, + ET_CORE = 4, + ET_LOPROC = 0xFF00, + ET_HIPROC = 0xFFFF, +}; + +// Machine/Architecture +enum ElfMachine { + EM_NONE = 0, + EM_M32 = 1, + EM_SPARC = 2, + EM_386 = 3, + EM_68K = 4, + EM_88K = 5, + EM_860 = 7, + EM_MIPS = 8 +}; + +// File version +#define EV_NONE 0 +#define EV_CURRENT 1 + +// Identification index +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_PAD 7 +#define EI_NIDENT 16 + +// Magic number +#define ELFMAG0 0x7F +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +// Sections constants + +// Section types +#define SHT_NULL 0 +#define SHT_PROGBITS 1 +#define SHT_SYMTAB 2 +#define SHT_STRTAB 3 +#define SHT_RELA 4 +#define SHT_HASH 5 +#define SHT_DYNAMIC 6 +#define SHT_NOTE 7 +#define SHT_NOBITS 8 +#define SHT_REL 9 +#define SHT_SHLIB 10 +#define SHT_DYNSYM 11 +#define SHT_LOPROC 0x70000000 +#define SHT_HIPROC 0x7FFFFFFF +#define SHT_LOUSER 0x80000000 +#define SHT_HIUSER 0xFFFFFFFF + +// Section flags +enum ElfSectionFlags +{ + SHF_WRITE = 0x1, + SHF_ALLOC = 0x2, + SHF_EXECINSTR = 0x4, + SHF_MASKPROC = 0xF0000000, +}; + +// Segment types +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_SHLIB 5 +#define PT_PHDR 6 +#define PT_LOPROC 0x70000000 +#define PT_HIPROC 0x7FFFFFFF + +typedef unsigned int Elf32_Addr; +typedef unsigned short Elf32_Half; +typedef unsigned int Elf32_Off; +typedef signed int Elf32_Sword; +typedef unsigned int Elf32_Word; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ELF file header + +struct Elf32_Ehdr { + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +}; + +// Section header +struct Elf32_Shdr { + Elf32_Word sh_name; + Elf32_Word sh_type; + Elf32_Word sh_flags; + Elf32_Addr sh_addr; + Elf32_Off sh_offset; + Elf32_Word sh_size; + Elf32_Word sh_link; + Elf32_Word sh_info; + Elf32_Word sh_addralign; + Elf32_Word sh_entsize; +}; + +// Segment header +struct Elf32_Phdr { + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +}; + +// Symbol table entry +struct Elf32_Sym { + Elf32_Word st_name; + Elf32_Addr st_value; + Elf32_Word st_size; + unsigned char st_info; + unsigned char st_other; + Elf32_Half st_shndx; +}; + +// Relocation entries +struct Elf32_Rel { + Elf32_Addr r_offset; + Elf32_Word r_info; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ElfReader class + +typedef int SectionID; + +class ElfReader { +private: + char *base; + u32 *base32; + + Elf32_Ehdr *header; + Elf32_Phdr *segments; + Elf32_Shdr *sections; + + u32 *sectionAddrs; + bool relocate; + u32 entryPoint; + +public: + ElfReader(void *ptr); + ~ElfReader() { } + + u32 Read32(int off) const { return base32[off >> 2]; } + + // Quick accessors + ElfType GetType() const { return (ElfType)(header->e_type); } + ElfMachine GetMachine() const { return (ElfMachine)(header->e_machine); } + u32 GetEntryPoint() const { return entryPoint; } + u32 GetFlags() const { return (u32)(header->e_flags); } + bool LoadInto(u32 vaddr); + bool LoadSymbols(); + + int GetNumSegments() const { return (int)(header->e_phnum); } + int GetNumSections() const { return (int)(header->e_shnum); } + const u8 *GetPtr(int offset) const { return (u8*)base + offset; } + const char *GetSectionName(int section) const; + const u8 *GetSectionDataPtr(int section) const { + if (section < 0 || section >= header->e_shnum) + return nullptr; + if (sections[section].sh_type != SHT_NOBITS) + return GetPtr(sections[section].sh_offset); + else + return nullptr; + } + bool IsCodeSection(int section) const { + return sections[section].sh_type == SHT_PROGBITS; + } + const u8 *GetSegmentPtr(int segment) { + return GetPtr(segments[segment].p_offset); + } + u32 GetSectionAddr(SectionID section) const { return sectionAddrs[section]; } + int GetSectionSize(SectionID section) const { return sections[section].sh_size; } + SectionID GetSectionByName(const char *name, int firstSection = 0) const; //-1 for not found + + bool DidRelocate() { + return relocate; + } +}; + +ElfReader::ElfReader(void *ptr) { + base = (char*)ptr; + base32 = (u32 *)ptr; + header = (Elf32_Ehdr*)ptr; + + segments = (Elf32_Phdr *)(base + header->e_phoff); + sections = (Elf32_Shdr *)(base + header->e_shoff); + + entryPoint = header->e_entry; + + LoadSymbols(); +} + +const char *ElfReader::GetSectionName(int section) const { + if (sections[section].sh_type == SHT_NULL) + return nullptr; + + int name_offset = sections[section].sh_name; + char *ptr = (char*)GetSectionDataPtr(header->e_shstrndx); + + if (ptr) + return ptr + name_offset; + + return nullptr; +} + +bool ElfReader::LoadInto(u32 vaddr) { + DEBUG_LOG(MASTER_LOG, "String section: %i", header->e_shstrndx); + + // Should we relocate? + relocate = (header->e_type != ET_EXEC); + + if (relocate) { + DEBUG_LOG(MASTER_LOG, "Relocatable module"); + entryPoint += vaddr; + } else { + DEBUG_LOG(MASTER_LOG, "Prerelocated executable"); + } + INFO_LOG(MASTER_LOG, "%i segments:", header->e_phnum); + + // First pass : Get the bits into RAM + u32 segment_addr[32]; + u32 base_addr = relocate ? vaddr : 0; + + 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, + 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], + p->p_memsz); + } + } + INFO_LOG(MASTER_LOG, "Done loading."); + return true; +} + +SectionID ElfReader::GetSectionByName(const char *name, int firstSection) const { + for (int i = firstSection; i < header->e_shnum; i++) { + const char *secname = GetSectionName(i); + + if (secname != nullptr && strcmp(name, secname) == 0) + return i; + } + return -1; +} + +bool ElfReader::LoadSymbols() { + bool hasSymbols = false; + SectionID sec = GetSectionByName(".symtab"); + if (sec != -1) { + int stringSection = sections[sec].sh_link; + const char *stringBase = (const char *)GetSectionDataPtr(stringSection); + + //We have a symbol table! + Elf32_Sym *symtab = (Elf32_Sym *)(GetSectionDataPtr(sec)); + int numSymbols = sections[sec].sh_size / sizeof(Elf32_Sym); + for (int sym = 0; sym < numSymbols; sym++) { + int size = symtab[sym].st_size; + if (size == 0) + continue; + + int type = symtab[sym].st_info & 0xF; + + const char *name = stringBase + symtab[sym].st_name; + + Symbols::Add(symtab[sym].st_value, name, size, type); + + hasSymbols = true; + } + } + + return hasSymbols; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Loader namespace + +namespace Loader { + +/// AppLoader_ELF constructor +AppLoader_ELF::AppLoader_ELF(const std::string& filename) : is_loaded(false) { + this->filename = filename; +} + +/// AppLoader_NCCH destructor +AppLoader_ELF::~AppLoader_ELF() { +} + +/** + * Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) + * @param error_string Pointer to string to put error message if an error has occurred + * @todo Move NCSD parsing out of here and create a separate function for loading these + * @return True on success, otherwise false + */ +ResultStatus AppLoader_ELF::Load() { + INFO_LOG(LOADER, "Loading ELF file %s...", filename.c_str()); + + if (is_loaded) + return ResultStatus::ErrorAlreadyLoaded; + + File::IOFile file(filename, "rb"); + + if (file.IsOpen()) { + u32 size = (u32)file.GetSize(); + std::unique_ptr<u8[]> buffer(new u8[size]); + file.ReadBytes(&buffer[0], size); + + ElfReader elf_reader(&buffer[0]); + elf_reader.LoadInto(0x00100000); + Kernel::LoadExec(elf_reader.GetEntryPoint()); + } else { + return ResultStatus::Error; + } + return ResultStatus::Success; +} + +} // namespace Loader diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h new file mode 100644 index 0000000000..d3cbf414d4 --- /dev/null +++ b/src/core/loader/elf.h @@ -0,0 +1,32 @@ +// Copyright 2013 Dolphin Emulator Project / Citra Emulator Project +// Licensed under GPLv2 +// 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 ELF/AXF file +class AppLoader_ELF : public AppLoader { +public: + AppLoader_ELF(const std::string& filename); + ~AppLoader_ELF(); + + /** + * Load the bootable file + * @return ResultStatus result of function + */ + ResultStatus Load(); + +private: + std::string filename; + bool is_loaded; +}; + +} // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp new file mode 100644 index 0000000000..96cb81de03 --- /dev/null +++ b/src/core/loader/loader.cpp @@ -0,0 +1,77 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <memory> + +#include "core/loader/loader.h" +#include "core/loader/elf.h" +#include "core/loader/ncch.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Loader { + +/** + * Identifies the type of a bootable file + * @param filename String filename of bootable file + * @todo (ShizZy) this function sucks... make it actually check file contents etc. + * @return FileType of file + */ +FileType IdentifyFile(const std::string &filename) { + if (filename.size() == 0) { + ERROR_LOG(LOADER, "invalid filename %s", filename.c_str()); + return FileType::Error; + } + std::string extension = filename.size() >= 5 ? filename.substr(filename.size() - 4) : ""; + + if (!strcasecmp(extension.c_str(), ".elf")) { + return FileType::ELF; // TODO(bunnei): Do some filetype checking :p + } + else if (!strcasecmp(extension.c_str(), ".axf")) { + return FileType::ELF; // TODO(bunnei): Do some filetype checking :p + } + else if (!strcasecmp(extension.c_str(), ".cxi")) { + return FileType::CXI; // TODO(bunnei): Do some filetype checking :p + } + else if (!strcasecmp(extension.c_str(), ".cci")) { + return FileType::CCI; // TODO(bunnei): Do some filetype checking :p + } + return FileType::Unknown; +} + +/** + * Identifies and loads a bootable file + * @param filename String filename of bootable file + * @return ResultStatus result of function + */ +ResultStatus LoadFile(const std::string& filename) { + INFO_LOG(LOADER, "Loading file %s...", filename.c_str()); + + switch (IdentifyFile(filename)) { + + // Standard ELF file format... + case FileType::ELF: { + return AppLoader_ELF(filename).Load(); + } + + // NCCH/NCSD container formats... + case FileType::CXI: + case FileType::CCI: { + return AppLoader_NCCH(filename).Load(); + } + + // Error occurred durring IdentifyFile... + case FileType::Error: + + // IdentifyFile could know identify file type... + case FileType::Unknown: + + default: + return ResultStatus::ErrorInvalidFormat; + } + + return ResultStatus::Error; +} + +} // namespace Loader diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h new file mode 100644 index 0000000000..95f16fcb17 --- /dev/null +++ b/src/core/loader/loader.h @@ -0,0 +1,121 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <vector> + +#include "common/common.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Loader namespace + +namespace Loader { + +/// File types supported by CTR +enum class FileType { + Error, + Unknown, + CCI, + CXI, + CIA, + ELF, +}; + +/// Return type for functions in Loader namespace +enum class ResultStatus { + Success, + Error, + ErrorInvalidFormat, + ErrorNotImplemented, + ErrorNotLoaded, + ErrorNotUsed, + ErrorAlreadyLoaded, +}; + +/// Interface for loading an application +class AppLoader : NonCopyable { +public: + AppLoader() { } + virtual ~AppLoader() { } + + /** + * Load the application + * @return ResultStatus result of function + */ + virtual ResultStatus Load() = 0; + + /** + * Get the code (typically .code section) of the application + * @param error ResultStatus result of function + * @return Reference to code buffer + */ + virtual const std::vector<u8>& ReadCode(ResultStatus& error) const { + error = ResultStatus::ErrorNotImplemented; + return code; + } + + /** + * Get the icon (typically icon section) of the application + * @param error ResultStatus result of function + * @return Reference to icon buffer + */ + virtual const std::vector<u8>& ReadIcon(ResultStatus& error) const { + error = ResultStatus::ErrorNotImplemented; + return icon; + } + + /** + * Get the banner (typically banner section) of the application + * @param error ResultStatus result of function + * @return Reference to banner buffer + */ + virtual const std::vector<u8>& ReadBanner(ResultStatus& error) const { + error = ResultStatus::ErrorNotImplemented; + return banner; + } + + /** + * Get the logo (typically logo section) of the application + * @param error ResultStatus result of function + * @return Reference to logo buffer + */ + virtual const std::vector<u8>& ReadLogo(ResultStatus& error) const { + error = ResultStatus::ErrorNotImplemented; + return logo; + } + + /** + * Get the RomFs archive of the application + * @param error ResultStatus result of function + * @return Reference to RomFs archive buffer + */ + virtual const std::vector<u8>& ReadRomFS(ResultStatus& error) const { + error = ResultStatus::ErrorNotImplemented; + return romfs; + } + +protected: + std::vector<u8> code; ///< ExeFS .code section + std::vector<u8> icon; ///< ExeFS .icon section + std::vector<u8> banner; ///< ExeFS .banner section + std::vector<u8> logo; ///< ExeFS .logo section + std::vector<u8> romfs; ///< RomFs archive +}; + +/** + * Identifies the type of a bootable file + * @param filename String filename of bootable file + * @return FileType of file + */ +FileType IdentifyFile(const std::string &filename); + +/** + * Identifies and loads a bootable file + * @param filename String filename of bootable file + * @return ResultStatus result of function + */ +ResultStatus LoadFile(const std::string& filename); + +} // namespace diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp new file mode 100644 index 0000000000..60505bdfad --- /dev/null +++ b/src/core/loader/ncch.cpp @@ -0,0 +1,312 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <memory> + +#include "common/file_util.h" + +#include "core/loader/ncch.h" +#include "core/hle/kernel/kernel.h" +#include "core/mem_map.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Loader namespace + +namespace Loader { + +static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs +static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) + +/** + * Get the decompressed size of an LZSS compressed ExeFS file + * @param buffer Buffer of compressed file + * @param size Size of compressed buffer + * @return Size of decompressed buffer + */ +u32 LZSS_GetDecompressedSize(u8* buffer, u32 size) { + u32 offset_size = *(u32*)(buffer + size - 4); + return offset_size + size; +} + +/** + * Decompress ExeFS file (compressed with LZSS) + * @param compressed Compressed buffer + * @param compressed_size Size of compressed buffer + * @param decompressed Decompressed buffer + * @param decompressed_size Size of decompressed buffer + * @return True on success, otherwise false + */ +bool LZSS_Decompress(u8* compressed, u32 compressed_size, u8* decompressed, u32 decompressed_size) { + u8* footer = compressed + compressed_size - 8; + u32 buffer_top_and_bottom = *(u32*)footer; + u32 i, j; + u32 out = decompressed_size; + u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); + u8 control; + u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); + + memset(decompressed, 0, decompressed_size); + memcpy(decompressed, compressed, compressed_size); + + while(index > stop_index) { + control = compressed[--index]; + + for(i = 0; i < 8; i++) { + if(index <= stop_index) + break; + if(index <= 0) + break; + if(out <= 0) + break; + + if(control & 0x80) { + // Check if compression is out of bounds + if(index < 2) { + return false; + } + index -= 2; + + u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); + u32 segment_size = ((segment_offset >> 12) & 15) + 3; + segment_offset &= 0x0FFF; + segment_offset += 2; + + // Check if compression is out of bounds + if(out < segment_size) { + return false; + } + for(j = 0; j < segment_size; j++) { + u8 data; + // Check if compression is out of bounds + if(out + segment_offset >= decompressed_size) { + return false; + } + data = decompressed[out + segment_offset]; + decompressed[--out] = data; + } + } else { + // Check if compression is out of bounds + if(out < 1) { + return false; + } + decompressed[--out] = compressed[--index]; + } + control <<= 1; + } + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// AppLoader_NCCH class + +/// AppLoader_NCCH constructor +AppLoader_NCCH::AppLoader_NCCH(const std::string& filename) { + this->filename = filename; + is_loaded = false; + is_compressed = false; + entry_point = 0; + ncch_offset = 0; + exefs_offset = 0; +} + +/// AppLoader_NCCH destructor +AppLoader_NCCH::~AppLoader_NCCH() { + if (file.IsOpen()) + file.Close(); +} + +/** + * Loads .code section into memory for booting + * @return ResultStatus result of function + */ +ResultStatus AppLoader_NCCH::LoadExec() { + if (!is_loaded) + return ResultStatus::ErrorNotLoaded; + + ResultStatus res; + code = ReadCode(res); + + if (ResultStatus::Success == res) { + Memory::WriteBlock(entry_point, &code[0], code.size()); + Kernel::LoadExec(entry_point); + } + return res; +} + +/** + * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) + * @param name Name of section to read out of NCCH file + * @param buffer Vector to read data into + * @param error ResultStatus result of function + * @return Reference to buffer of data that was read + */ +const std::vector<u8>& AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& buffer, + ResultStatus& error) { + // Iterate through the ExeFs archive until we find the .code file... + 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); + + s64 section_offset = (exefs_header.section[i].offset + exefs_offset + + sizeof(ExeFs_Header) + ncch_offset); + file.Seek(section_offset, 0); + + // Section is compressed... + if (i == 0 && is_compressed) { + // Read compressed .code section... + std::unique_ptr<u8[]> temp_buffer(new u8[exefs_header.section[i].size]); + file.ReadBytes(&temp_buffer[0], exefs_header.section[i].size); + + // Decompress .code section... + u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], + exefs_header.section[i].size); + buffer.resize(decompressed_size); + if (!LZSS_Decompress(&temp_buffer[0], exefs_header.section[i].size, &buffer[0], + decompressed_size)) { + error = ResultStatus::ErrorInvalidFormat; + return buffer; + } + // Section is uncompressed... + } else { + buffer.resize(exefs_header.section[i].size); + file.ReadBytes(&buffer[0], exefs_header.section[i].size); + } + error = ResultStatus::Success; + return buffer; + } + } + error = ResultStatus::ErrorNotUsed; + return buffer; +} + +/** + * Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) + * @param error_string Pointer to string to put error message if an error has occurred + * @todo Move NCSD parsing out of here and create a separate function for loading these + * @return True on success, otherwise false + */ +ResultStatus AppLoader_NCCH::Load() { + INFO_LOG(LOADER, "Loading NCCH file %s...", filename.c_str()); + + if (is_loaded) + return ResultStatus::ErrorAlreadyLoaded; + + file = File::IOFile(filename, "rb"); + + if (file.IsOpen()) { + file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); + + // 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!"); + ncch_offset = 0x4000; + file.Seek(ncch_offset, 0); + file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); + } + + // Verify we are loading the correct file type... + if (0 != memcmp(&ncch_header.magic, "NCCH", 4)) + return ResultStatus::ErrorInvalidFormat; + + // Read ExHeader... + + file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)); + + 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); + + // 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); + + file.Seek(exefs_offset + ncch_offset, 0); + file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)); + + is_loaded = true; // Set state to loaded + + LoadExec(); // Load the executable into memory for booting + + return ResultStatus::Success; + } + return ResultStatus::Error; +} + +/** + * Get the code (typically .code section) of the application + * @param error ResultStatus result of function + * @return Reference to code buffer + */ +const std::vector<u8>& AppLoader_NCCH::ReadCode(ResultStatus& error) { + return LoadSectionExeFS(".code", code, error); +} + +/** + * Get the icon (typically icon section) of the application + * @param error ResultStatus result of function + * @return Reference to icon buffer + */ +const std::vector<u8>& AppLoader_NCCH::ReadIcon(ResultStatus& error) { + return LoadSectionExeFS("icon", icon, error); +} + +/** + * Get the banner (typically banner section) of the application + * @param error ResultStatus result of function + * @return Reference to banner buffer + */ +const std::vector<u8>& AppLoader_NCCH::ReadBanner(ResultStatus& error) { + return LoadSectionExeFS("banner", banner, error); +} + +/** + * Get the logo (typically logo section) of the application + * @param error ResultStatus result of function + * @return Reference to logo buffer + */ +const std::vector<u8>& AppLoader_NCCH::ReadLogo(ResultStatus& error) { + return LoadSectionExeFS("logo", logo, error); +} + +/** + * Get the RomFs archive of the application + * @param error ResultStatus result of function + * @return Reference to RomFs archive buffer + */ +const std::vector<u8>& AppLoader_NCCH::ReadRomFS(ResultStatus& error) { + // Check if the NCCH has a RomFS... + if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) { + 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); + + romfs.resize(romfs_size); + + file.Seek(romfs_offset, 0); + file.ReadBytes(&romfs[0], romfs_size); + + error = ResultStatus::Success; + return romfs; + } else { + NOTICE_LOG(LOADER, "RomFS unused"); + } + error = ResultStatus::ErrorNotUsed; + return romfs; +} + +} // namespace Loader diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h new file mode 100644 index 0000000000..bf65425a48 --- /dev/null +++ b/src/core/loader/ncch.h @@ -0,0 +1,227 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common.h" +#include "common/file_util.h" + +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// NCCH header (Note: "NCCH" appears to be a publically unknown acronym) + +struct NCCH_Header { + u8 signature[0x100]; + char magic[4]; + u32 content_size; + u8 partition_id[8]; + u16 maker_code; + u16 version; + u8 reserved_0[4]; + u8 program_id[8]; + u8 temp_flag; + u8 reserved_1[0x2f]; + u8 product_code[0x10]; + u8 extended_header_hash[0x20]; + u32 extended_header_size; + u8 reserved_2[4]; + u8 flags[8]; + u32 plain_region_offset; + u32 plain_region_size; + u8 reserved_3[8]; + u32 exefs_offset; + u32 exefs_size; + u32 exefs_hash_region_size; + u8 reserved_4[4]; + u32 romfs_offset; + u32 romfs_size; + u32 romfs_hash_region_size; + u8 reserved_5[4]; + u8 exefs_super_block_hash[0x20]; + u8 romfs_super_block_hash[0x20]; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExeFS (executable file system) headers + +typedef struct { + char name[8]; + u32 offset; + u32 size; +} ExeFs_SectionHeader; + +typedef struct { + ExeFs_SectionHeader section[8]; + u8 reserved[0x80]; + u8 hashes[8][0x20]; +} ExeFs_Header; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExHeader (executable file system header) headers + +struct ExHeader_SystemInfoFlags{ + u8 reserved[5]; + u8 flag; + u8 remaster_version[2]; +}; + +struct ExHeader_CodeSegmentInfo{ + u32 address; + u32 num_max_pages; + u32 code_size; +}; + +struct ExHeader_CodeSetInfo { + u8 name[8]; + ExHeader_SystemInfoFlags flags; + ExHeader_CodeSegmentInfo text; + u8 stacksize[4]; + ExHeader_CodeSegmentInfo ro; + u8 reserved[4]; + ExHeader_CodeSegmentInfo data; + u8 bsssize[4]; +}; + +struct ExHeader_DependencyList{ + u8 program_id[0x30][8]; +}; + +struct ExHeader_SystemInfo{ + u32 save_data_size; + u8 reserved[4]; + u8 jump_id[8]; + u8 reserved_2[0x30]; +}; + +struct ExHeader_StorageInfo{ + u8 ext_save_data_id[8]; + u8 system_save_data_id[8]; + u8 reserved[8]; + u8 access_info[7]; + u8 other_attributes; +}; + +struct ExHeader_ARM11_SystemLocalCaps{ + u8 program_id[8]; + u8 flags[8]; + u8 resource_limit_descriptor[0x10][2]; + ExHeader_StorageInfo storage_info; + u8 service_access_control[0x20][8]; + u8 reserved[0x1f]; + u8 resource_limit_category; +}; + +struct ExHeader_ARM11_KernelCaps{ + u8 descriptors[28][4]; + u8 reserved[0x10]; +}; + +struct ExHeader_ARM9_AccessControl{ + u8 descriptors[15]; + u8 descversion; +}; + +struct ExHeader_Header{ + ExHeader_CodeSetInfo codeset_info; + ExHeader_DependencyList dependency_list; + ExHeader_SystemInfo system_info; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + struct { + u8 signature[0x100]; + u8 ncch_public_key_modulus[0x100]; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + } access_desc; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Loader namespace + +namespace Loader { + +/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) +class AppLoader_NCCH : public AppLoader { +public: + AppLoader_NCCH(const std::string& filename); + ~AppLoader_NCCH(); + + /** + * Load the application + * @return ResultStatus result of function + */ + ResultStatus Load(); + + /** + * Get the code (typically .code section) of the application + * @param error ResultStatus result of function + * @return Reference to code buffer + */ + const std::vector<u8>& ReadCode(ResultStatus& error); + + /** + * Get the icon (typically icon section) of the application + * @param error ResultStatus result of function + * @return Reference to icon buffer + */ + const std::vector<u8>& ReadIcon(ResultStatus& error); + + /** + * Get the banner (typically banner section) of the application + * @param error ResultStatus result of function + * @return Reference to banner buffer + */ + const std::vector<u8>& ReadBanner(ResultStatus& error); + + /** + * Get the logo (typically logo section) of the application + * @param error ResultStatus result of function + * @return Reference to logo buffer + */ + const std::vector<u8>& ReadLogo(ResultStatus& error); + + /** + * Get the RomFs archive of the application + * @param error ResultStatus result of function + * @return Reference to RomFs archive buffer + */ + const std::vector<u8>& ReadRomFS(ResultStatus& error); + +private: + + /** + * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) + * @param name Name of section to read out of NCCH file + * @param buffer Vector to read data into + * @param error ResultStatus result of function + * @return Reference to buffer of data that was read + */ + const std::vector<u8>& LoadSectionExeFS(const char* name, std::vector<u8>& buffer, + ResultStatus& error); + + /** + * Loads .code section into memory for booting + * @return ResultStatus result of function + */ + ResultStatus LoadExec(); + + File::IOFile file; + std::string filename; + + bool is_loaded; + bool is_compressed; + + u32 entry_point; + u32 ncch_offset; // Offset to NCCH header, can be 0 or after NCSD header + u32 exefs_offset; + + NCCH_Header ncch_header; + ExeFs_Header exefs_header; + ExHeader_Header exheader_header; +}; + +} // namespace Loader |