Merge pull request #179 from wheremyfoodat/timerz

Autodetect 3DS region and properly report it in CFG::SecureInfoGetRegion
This commit is contained in:
wheremyfoodat 2023-08-13 18:47:32 +03:00 committed by GitHub
commit 19278c4561
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 58 deletions

View file

@ -2,77 +2,84 @@
#include <array> #include <array>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include "io_file.hpp"
#include "helpers.hpp"
#include "crypto/aes_engine.hpp" #include "crypto/aes_engine.hpp"
#include "helpers.hpp"
#include "io_file.hpp"
#include "services/region_codes.hpp"
struct NCCH { struct NCCH {
struct EncryptionInfo { struct EncryptionInfo {
Crypto::AESKey normalKey; Crypto::AESKey normalKey;
Crypto::AESKey initialCounter; Crypto::AESKey initialCounter;
}; };
struct FSInfo { // Info on the ExeFS/RomFS struct FSInfo { // Info on the ExeFS/RomFS
u64 offset = 0; u64 offset = 0;
u64 size = 0; u64 size = 0;
u64 hashRegionSize = 0; u64 hashRegionSize = 0;
std::optional<EncryptionInfo> encryptionInfo; std::optional<EncryptionInfo> encryptionInfo;
}; };
// Descriptions for .text, .data and .rodata sections // Descriptions for .text, .data and .rodata sections
struct CodeSetInfo { struct CodeSetInfo {
u32 address = 0; u32 address = 0;
u32 pageCount = 0; u32 pageCount = 0;
u32 size = 0; u32 size = 0;
// Extract the code set info from the relevant header data // Extract the code set info from the relevant header data
void extract(const u8* headerEntry) { void extract(const u8 *headerEntry) {
address = *(u32*)&headerEntry[0]; address = *(u32 *)&headerEntry[0];
pageCount = *(u32*)&headerEntry[4]; pageCount = *(u32 *)&headerEntry[4];
size = *(u32*)&headerEntry[8]; size = *(u32 *)&headerEntry[8];
} }
}; };
u64 partitionIndex = 0; u64 partitionIndex = 0;
u64 fileOffset = 0; u64 fileOffset = 0;
bool isNew3DS = false; bool isNew3DS = false;
bool initialized = false; bool initialized = false;
bool compressCode = false; // Shows whether the .code file in the ExeFS is compressed bool compressCode = false; // Shows whether the .code file in the ExeFS is compressed
bool mountRomFS = false; bool mountRomFS = false;
bool encrypted = false; bool encrypted = false;
bool fixedCryptoKey = false; bool fixedCryptoKey = false;
bool seedCrypto = false; bool seedCrypto = false;
u8 secondaryKeySlot = 0; u8 secondaryKeySlot = 0;
static constexpr u64 mediaUnit = 0x200; static constexpr u64 mediaUnit = 0x200;
u64 size = 0; // Size of NCCH converted to bytes u64 size = 0; // Size of NCCH converted to bytes
u32 stackSize = 0; u32 stackSize = 0;
u32 bssSize = 0; u32 bssSize = 0;
u32 exheaderSize = 0; u32 exheaderSize = 0;
FSInfo exheaderInfo; FSInfo exheaderInfo;
FSInfo exeFS; FSInfo exeFS;
FSInfo romFS; FSInfo romFS;
CodeSetInfo text, data, rodata; CodeSetInfo text, data, rodata;
// Contents of the .code file in the ExeFS // Contents of the .code file in the ExeFS
std::vector<u8> codeFile; std::vector<u8> codeFile;
// Contains of the cart's save data // Contains of the cart's save data
std::vector<u8> saveData; std::vector<u8> saveData;
// The cart region. Only the CXI's region matters to us. Necessary to get past region locking
std::optional<Regions> region = std::nullopt;
// Returns true on success, false on failure // Returns true on success, false on failure
// Partition index/offset/size must have been set before this // Partition index/offset/size must have been set before this
bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info); bool loadFromHeader(Crypto::AESEngine &aesEngine, IOFile &file, const FSInfo &info);
bool hasExtendedHeader() { return exheaderSize != 0; } bool hasExtendedHeader() { return exheaderSize != 0; }
bool hasExeFS() { return exeFS.size != 0; } bool hasExeFS() { return exeFS.size != 0; }
bool hasRomFS() { return romFS.size != 0; } bool hasRomFS() { return romFS.size != 0; }
bool hasCode() { return codeFile.size() != 0; } bool hasCode() { return codeFile.size() != 0; }
bool hasSaveData() { return saveData.size() != 0; } bool hasSaveData() { return saveData.size() != 0; }
std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); // Parse SMDH for region info and such. Returns false on failure, true on success
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY); bool parseSMDH(const std::vector<u8> &smdh);
std::pair<bool, std::size_t> readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size); std::pair<bool, Crypto::AESKey> getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
std::pair<bool, Crypto::AESKey> getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY);
std::pair<bool, std::size_t> readFromFile(IOFile &file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size);
}; };

View file

@ -9,6 +9,7 @@
#include "helpers.hpp" #include "helpers.hpp"
#include "handles.hpp" #include "handles.hpp"
#include "loader/ncsd.hpp" #include "loader/ncsd.hpp"
#include "services/region_codes.hpp"
#include "services/shared_font.hpp" #include "services/shared_font.hpp"
namespace PhysicalAddrs { namespace PhysicalAddrs {
@ -151,6 +152,8 @@ private:
// Values taken from 3DBrew and Citra // Values taken from 3DBrew and Citra
static constexpr FirmwareInfo firm{.unk = 0, .revision = 0, .minor = 0x34, .major = 2, .syscoreVer = 2, .sdkVer = 0x0000F297}; 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: public:
u16 kernelVersion = 0; u16 kernelVersion = 0;
@ -261,4 +264,5 @@ public:
void setVRAM(u8* pointer) { vram = pointer; } void setVRAM(u8* pointer) { vram = pointer; }
bool allocateMainThreadStack(u32 size); bool allocateMainThreadStack(u32 size);
Regions getConsoleRegion();
}; };

View file

@ -63,5 +63,7 @@ std::optional<u32> Memory::loadELF(std::ifstream& file) {
allocateMemory(vaddr, fcramAddr, memorySize, true, r, w, x); 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<u32>(reader.get_entry()); return static_cast<u32>(reader.get_entry());
} }

View file

@ -209,10 +209,25 @@ bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSIn
codeFile.resize(fileSize); codeFile.resize(fileSize);
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, 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<u8> 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()) { if (hasRomFS()) {
printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size); 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; return true;
} }
bool NCCH::parseSMDH(const std::vector<u8>& 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<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) { std::pair<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {
Crypto::AESKey result; Crypto::AESKey result;

View file

@ -11,6 +11,12 @@ bool Memory::mapCXI(NCSD& ncsd, NCCH& cxi) {
printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size); printf("Data address = %08X, size = %08X\n", cxi.data.address, cxi.data.size);
printf("Stack size: %08X\n", cxi.stackSize); printf("Stack size: %08X\n", cxi.stackSize);
static constexpr std::array<const char*, 7> regionNames = {"Japan", "North America", "Europe", "Australia", "China", "Korea", "Taiwan" };
// Set autodetected 3DS region to one of the values allowed by the CXI's SMDH
region = cxi.region.value();
printf("Console region autodetected to: %s\n", regionNames[static_cast<size_t>(region)]);
if (!isAligned(cxi.stackSize)) { if (!isAligned(cxi.stackSize)) {
Helpers::warn("CXI has a suspicious stack size of %08X which is not a multiple of 4KB", cxi.stackSize); Helpers::warn("CXI has a suspicious stack size of %08X which is not a multiple of 4KB", cxi.stackSize);
} }

View file

@ -59,6 +59,9 @@ void Memory::reset() {
readTable[i + initialPage] = pointer; readTable[i + initialPage] = pointer;
writeTable[i + initialPage] = pointer; writeTable[i + initialPage] = pointer;
} }
// Later adjusted based on ROM header when possible
region = Regions::USA;
} }
bool Memory::allocateMainThreadStack(u32 size) { bool Memory::allocateMainThreadStack(u32 size) {
@ -472,3 +475,9 @@ u64 Memory::timeSince3DSEpoch() {
milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset)); milliseconds ms = duration_cast<milliseconds>(seconds(rawTime + timezoneDifference + offset));
return ms.count(); 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;
}