diff --git a/include/loader/ncch.hpp b/include/loader/ncch.hpp index f0f93736..186abd8f 100644 --- a/include/loader/ncch.hpp +++ b/include/loader/ncch.hpp @@ -1,8 +1,18 @@ #pragma once #include +#include "io_file.hpp" #include "helpers.hpp" struct NCCH { + struct FSInfo { // Info on the ExeFS/RomFS + u64 offset = 0; + u64 size = 0; + u64 hashRegionSize = 0; + }; + + u64 partitionIndex = 0; + u64 fileOffset = 0; + bool isNew3DS = false; bool initialized = false; bool compressExeFS = false; @@ -16,10 +26,17 @@ struct NCCH { u32 bssSize = 0; u32 exheaderSize = 0; + FSInfo exeFS; + FSInfo romFS; + // Header: 0x200 + 0x800 byte NCCH header + exheadr // Returns true on success, false on failure - bool loadFromHeader(u8* header); + // Partition index/offset/size must have been set before this + bool loadFromHeader(u8* header, IOFile& file); + bool hasExtendedHeader() { return exheaderSize != 0; } + bool hasExeFS() { return exeFS.size != 0; } + bool hasRomFS() { return romFS.size != 0; } private: std::array primaryKey = {}; // For exheader, ExeFS header and icons diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 55623bfa..c6c26134 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -2,7 +2,7 @@ #include "loader/ncch.hpp" #include "memory.hpp" -bool NCCH::loadFromHeader(u8* header) { +bool NCCH::loadFromHeader(u8* header, IOFile& file) { if (header[0x100] != 'N' || header[0x101] != 'C' || header[0x102] != 'C' || header[0x103] != 'H') { printf("Invalid header on NCCH\n"); return false; @@ -18,8 +18,15 @@ bool NCCH::loadFromHeader(u8* header) { fixedCryptoKey = (header[0x188 + 7] & 0x1) == 0x1; mountRomFS = (header[0x188 + 7] & 0x2) != 0x2; encrypted = (header[0x188 + 7] & 0x4) != 0x4; + + // Read ExeFS and RomFS info + exeFS.offset = u64(*(u32*)&header[0x1A0]) * mediaUnit; + exeFS.size = u64(*(u32*)&header[0x1A4]) * mediaUnit; + exeFS.hashRegionSize = u64(*(u32*)&header[0x1A8]) * mediaUnit; - bool encryptedExheader = encrypted; + romFS.offset = u64(*(u32*)&header[0x1B0]) * mediaUnit; + romFS.size = u64(*(u32*)&header[0x1B4]) * mediaUnit; + romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit; if (fixedCryptoKey) { Helpers::panic("Fixed crypto keys for NCCH"); @@ -29,12 +36,13 @@ bool NCCH::loadFromHeader(u8* header) { const u8* exheader = &header[0x200]; // Extended NCCH header const u64 jumpID = *(u64*)&exheader[0x1C0 + 0x8]; - // TODO: How does this even work - if (u32(programID) == u32(jumpID) && encryptedExheader) { - printf("Exheader is supposedly ecrypted but not actually encrypted\n"); - encryptedExheader = false; - } else if (encryptedExheader) { - Helpers::panic("Encrypted exheader"); + // It seems like some decryption tools will decrypt the file, without actually setting the NoCrypto flag in the NCCH header + // This is a nice and easy hack to see if a file is pretending to be encrypted, taken from 3DMoo and Citra + if (u32(programID) == u32(jumpID) && encrypted) { + printf("NCSD is supposedly ecrypted but not actually encrypted\n"); + encrypted = false; + } else if (encrypted) { + Helpers::panic("Encrypted NCSD file"); } compressExeFS = (exheader[0xD] & 1) != 0; @@ -44,6 +52,42 @@ bool NCCH::loadFromHeader(u8* header) { printf("Stack size: %08X\nBSS size: %08X\n", stackSize, bssSize); + // Read ExeFS + if (hasExeFS()) { + printf("ExeFS offset: %08llX, size: %08llX\n", exeFS.offset, exeFS.size); + auto exeFSOffset = fileOffset + exeFS.offset; + constexpr size_t exeFSHeaderSize = 0x200; + + u8 exeFSHeader[exeFSHeaderSize]; + + file.seek(exeFSOffset); + auto [success, bytes] = file.readBytes(exeFSHeader, exeFSHeaderSize); + if (!success || bytes != exeFSHeaderSize) { + printf("Failed to parse ExeFS header\n"); + return false; + } + + // ExeFS format allows up to 10 files + for (int file = 0; file < 10; file++) { + u8* fileInfo = &exeFSHeader[file * 16]; + + char name[9]; + std::memcpy(name, fileInfo, 8); // Get file name as a string + name[8] = '\0'; // Add null terminator to it just in case there's none + + u32 fileOffset = *(u32*)&fileInfo[0x8]; + u32 fileSize = *(u32*)&fileInfo[0xC]; + + if (fileSize != 0) { + printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", file, name, fileSize, fileOffset); + } + } + } + + if (hasRomFS()) { + printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size); + } + if (stackSize != VirtualAddrs::DefaultStackSize) { Helpers::panic("Stack size != 0x4000"); } diff --git a/src/core/loader/ncsd.cpp b/src/core/loader/ncsd.cpp index a0d3bb35..c3f3b761 100644 --- a/src/core/loader/ncsd.cpp +++ b/src/core/loader/ncsd.cpp @@ -42,9 +42,13 @@ std::optional Memory::loadNCSD(const std::filesystem::path& path) { for (int i = 0; i < 8; i++) { auto& partition = ncsd.partitions[i]; + NCCH& ncch = partition.ncch; partition.offset = u64(partitionData[i * 2]) * NCSD::mediaUnit; partition.length = u64(partitionData[i * 2 + 1]) * NCSD::mediaUnit; + ncch.partitionIndex = i; + ncch.fileOffset = partition.offset; + if (partition.length != 0) { // Initialize the NCCH of each partition ncsd.file.seek(partition.offset); @@ -57,7 +61,7 @@ std::optional Memory::loadNCSD(const std::filesystem::path& path) { return std::nullopt; } - if (!partition.ncch.loadFromHeader(ncchHeader)) { + if (!ncch.loadFromHeader(ncchHeader, ncsd.file)) { printf("Invalid NCCH partition\n"); return std::nullopt; }