From 408dbe75a0058688f601ba8e2142104475a46587 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Sun, 13 Aug 2023 18:08:22 +0300 Subject: [PATCH] Implement 3DS region auto-detect --- include/loader/ncch.hpp | 6 ++++ include/memory.hpp | 4 +++ src/core/loader/elf.cpp | 2 ++ src/core/loader/ncch.cpp | 60 ++++++++++++++++++++++++++++++++++++++++ src/core/loader/ncsd.cpp | 3 ++ src/core/memory.cpp | 9 ++++++ 6 files changed, 84 insertions(+) diff --git a/include/loader/ncch.hpp b/include/loader/ncch.hpp index 95856e8c..68eb4da5 100644 --- a/include/loader/ncch.hpp +++ b/include/loader/ncch.hpp @@ -5,6 +5,7 @@ #include "io_file.hpp" #include "helpers.hpp" #include "crypto/aes_engine.hpp" +#include "services/region_codes.hpp" struct NCCH { struct EncryptionInfo { @@ -60,6 +61,8 @@ struct NCCH { std::vector codeFile; // Contains of the cart's save data std::vector saveData; + // The cart region. Only the CXI's region matters to us. Necessary to get past region locking + std::optional region = std::nullopt; // Returns true on success, false on failure // Partition index/offset/size must have been set before this @@ -71,6 +74,9 @@ struct NCCH { bool hasCode() { return codeFile.size() != 0; } bool hasSaveData() { return saveData.size() != 0; } + // Parse SMDH for region info and such. Returns false on failure, true on success + bool parseSMDH(const std::vector& smdh); + std::pair getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); std::pair getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); diff --git a/include/memory.hpp b/include/memory.hpp index 89d191b3..2ae4cb28 100644 --- a/include/memory.hpp +++ b/include/memory.hpp @@ -9,6 +9,7 @@ #include "helpers.hpp" #include "handles.hpp" #include "loader/ncsd.hpp" +#include "services/region_codes.hpp" #include "services/shared_font.hpp" namespace PhysicalAddrs { @@ -151,6 +152,8 @@ private: // Values taken from 3DBrew and Citra static constexpr FirmwareInfo firm{.unk = 0, .revision = 0, .minor = 0x34, .major = 2, .syscoreVer = 2, .sdkVer = 0x0000F297}; + // Adjusted upon loading a ROM based on the ROM header. Used by CFG::SecureInfoGetArea to get past region locks + Regions region = Regions::USA; public: u16 kernelVersion = 0; @@ -261,4 +264,5 @@ public: void setVRAM(u8* pointer) { vram = pointer; } bool allocateMainThreadStack(u32 size); + Regions getConsoleRegion(); }; diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 62802953..cb854e07 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -63,5 +63,7 @@ std::optional Memory::loadELF(std::ifstream& file) { allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x); } + // ELF can't specify a region, make it default to USA + region = Regions::USA; return static_cast(reader.get_entry()); } \ No newline at end of file diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 9e07f228..26134cd0 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -209,10 +209,25 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn codeFile.resize(fileSize); readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize); } + } else if (std::strcmp(name, "icon") == 0) { + // Parse icon file to extract region info and more in the future (logo, etc) + std::vector tmp; + tmp.resize(fileSize); + readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize); + + if (!parseSMDH(tmp)) { + printf("Failed to parse SMDH!\n"); + } } } } + // If no region has been detected for CXI, set the region to USA by default + if (!region.has_value() && partitionIndex == 0) { + printf("No region detected for CXI, defaulting to USA\n"); + region = Regions::USA; + } + if (hasRomFS()) { printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size); } @@ -221,6 +236,51 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn return true; } +bool NCCH::parseSMDH(const std::vector& smdh) { + if (smdh.size() < 0x36C0) { + printf("The cartridge .icon file is too small, considered invalid. Must be 0x36C0 bytes minimum\n"); + return false; + } + + if (char(smdh[0]) != 'S' || char(smdh[1]) != 'M' || char(smdh[2]) != 'D' || char(smdh[3]) != 'H') { + printf("Invalid SMDH magic!\n"); + return false; + } + + // Bitmask showing which regions are allowed. + // https://www.3dbrew.org/wiki/SMDH#Region_Lockout + const u32 regionMasks = *(u32*)&smdh[0x2018]; + // Detect when games are region free (ie all regions are allowed) for future use + [[maybe_unused]] const bool isRegionFree = (regionMasks & 0x7f) == 0x7f; + + // See which countries are allowed + const bool japan = (regionMasks & 0x1) != 0; + const bool northAmerica = (regionMasks & 0x2) != 0; + const bool europe = (regionMasks & 0x4) != 0; + const bool australia = (regionMasks & 0x8) != 0; + const bool china = (regionMasks & 0x10) != 0; + const bool korea = (regionMasks & 0x20) != 0; + const bool taiwan = (regionMasks & 0x40) != 0; + + // Based on the allowed regions, set the autodetected 3DS region. We currently prefer English-speaking regions for practical purposes. + // But this should be configurable later. + if (northAmerica) { + region = Regions::USA; + } else if (europe) { + region = Regions::Europe; + } else if (australia) { + region = Regions::Australia; + } else if (japan) { + region = Regions::Japan; + } else if (korea) { + region = Regions::Korea; + } else if (china) { + region = Regions::China; + } else if (taiwan) { + region = Regions::Taiwan; + } +} + std::pair NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) { Crypto::AESKey result; diff --git a/src/core/loader/ncsd.cpp b/src/core/loader/ncsd.cpp index 2655cf72..99930e5d 100644 --- a/src/core/loader/ncsd.cpp +++ b/src/core/loader/ncsd.cpp @@ -11,6 +11,9 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) { printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size); printf("Stack size: %08X\n", cxi.stackSize); + // Set autodetected 3DS region to one of the values allowed by the CXI's SMDH + region = cxi.region.value(); + if (!isAligned(cxi.stackSize)) { Helpers::warn("CXI has a suspicious stack size of %08X which is not a multiple of 4KB", cxi.stackSize); } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index d37f2eea..d359d637 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -59,6 +59,9 @@ void Memory::reset() { readTable[i + initialPage] = pointer; writeTable[i + initialPage] = pointer; } + + // Later adjusted based on ROM header when possible + region = Regions::USA; } bool Memory::allocateMainThreadStack(u32 size) { @@ -472,3 +475,9 @@ u64 Memory::timeSince3DSEpoch() { milliseconds ms = duration_cast(seconds(rawTime + timezoneDifference + offset)); return ms.count(); } + +Regions Memory::getConsoleRegion() { + // TODO: Let the user force the console region as they want + // For now we pick one based on the ROM header + return region; +}