mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-06-06 11:01:38 +12:00
Merge branch 'master' into dynapica
This commit is contained in:
commit
8b703dd147
20 changed files with 1155 additions and 545 deletions
83
src/core/crypto/aes_engine.cpp
Normal file
83
src/core/crypto/aes_engine.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "crypto/aes_engine.hpp"
|
||||
#include "helpers.hpp"
|
||||
|
||||
namespace Crypto {
|
||||
void AESEngine::loadKeys(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::in);
|
||||
|
||||
if (file.fail()) {
|
||||
Helpers::warn("Keys: Couldn't read key file: %s", path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
while (!file.eof()) {
|
||||
std::string line;
|
||||
std::getline(file, line);
|
||||
|
||||
// Skip obvious invalid lines
|
||||
if (line.empty() || line.starts_with("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto parts = Helpers::split(line, '=');
|
||||
if (parts.size() != 2) {
|
||||
Helpers::warn("Keys: Failed to parse %s", line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& name = parts[0];
|
||||
const std::string& rawKeyHex = parts[1];
|
||||
|
||||
std::size_t slotId;
|
||||
char keyType;
|
||||
|
||||
bool is_generator = name == "generator";
|
||||
if (!is_generator && std::sscanf(name.c_str(), "slot0x%zXKey%c", &slotId, &keyType) != 2) {
|
||||
Helpers::warn("Keys: Ignoring unknown key %s", name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto key = createKeyFromHex(rawKeyHex);
|
||||
|
||||
if (!key.has_value()) {
|
||||
Helpers::warn("Keys: Failed to parse raw key %s", rawKeyHex.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_generator) {
|
||||
m_generator = key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slotId >= AesKeySlotCount) {
|
||||
Helpers::warn("Keys: Invalid key slot id %u", slotId);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (keyType) {
|
||||
case 'X':
|
||||
setKeyX(slotId, key.value());
|
||||
break;
|
||||
case 'Y':
|
||||
setKeyY(slotId, key.value());
|
||||
break;
|
||||
case 'N':
|
||||
setNormalKey(slotId, key.value());
|
||||
break;
|
||||
default:
|
||||
Helpers::warn("Keys: Invalid key type %c", keyType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// As the generator key can be set at any time, force update all normal keys.
|
||||
for (std::size_t i = 0; i < AesKeySlotCount; i++) {
|
||||
updateNormalKey(i);
|
||||
}
|
||||
|
||||
keysLoaded = true;
|
||||
}
|
||||
};
|
|
@ -133,6 +133,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
auto cxi = mem.getCXI();
|
||||
IOFile& ioFile = mem.CXIFile;
|
||||
|
||||
NCCH::FSInfo fsInfo;
|
||||
|
||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||
switch (type) {
|
||||
case PathType::RomFS: {
|
||||
|
@ -142,9 +144,8 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
Helpers::panic("Tried to read from NCCH with too big of an offset");
|
||||
}
|
||||
|
||||
if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) {
|
||||
Helpers::panic("Failed to seek while reading from RomFS");
|
||||
}
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -153,7 +154,7 @@ std::optional<u32> NCCHArchive::readFile(FileSession* file, u64 offset, u32 size
|
|||
}
|
||||
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
auto [success, bytesRead] = ioFile.readBytes(&data[0], size);
|
||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from NCCH archive");
|
||||
|
|
|
@ -71,6 +71,8 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
auto cxi = mem.getCXI();
|
||||
IOFile& ioFile = mem.CXIFile;
|
||||
|
||||
NCCH::FSInfo fsInfo;
|
||||
|
||||
// Seek to file offset depending on if we're reading from RomFS, ExeFS, etc
|
||||
switch (type) {
|
||||
case PathType::RomFS: {
|
||||
|
@ -80,9 +82,8 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
}
|
||||
|
||||
if (!ioFile.seek(cxi->fileOffset + romFSOffset + offset + 0x1000)) {
|
||||
Helpers::panic("Failed to seek while reading from RomFS");
|
||||
}
|
||||
fsInfo = cxi->romFS;
|
||||
offset += 0x1000;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -93,9 +94,7 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
Helpers::panic("Tried to read from SelfNCCH with too big of an offset");
|
||||
}
|
||||
|
||||
if (!ioFile.seek(cxi->fileOffset + exeFSOffset + offset)) { // TODO: Not sure if this needs the + 0x1000
|
||||
Helpers::panic("Failed to seek while reading from ExeFS");
|
||||
}
|
||||
fsInfo = cxi->exeFS;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -104,7 +103,7 @@ std::optional<u32> SelfNCCHArchive::readFile(FileSession* file, u64 offset, u32
|
|||
}
|
||||
|
||||
std::unique_ptr<u8[]> data(new u8[size]);
|
||||
auto [success, bytesRead] = ioFile.readBytes(&data[0], size);
|
||||
auto [success, bytesRead] = cxi->readFromFile(ioFile, fsInfo, &data[0], offset, size);
|
||||
|
||||
if (!success) {
|
||||
Helpers::panic("Failed to read from SelfNCCH archive");
|
||||
|
|
|
@ -1,142 +1,311 @@
|
|||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include "loader/lz77.hpp"
|
||||
#include "loader/ncch.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
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");
|
||||
#include <iostream>
|
||||
|
||||
bool NCCH::loadFromHeader(Crypto::AESEngine &aesEngine, IOFile& file, const FSInfo &info) {
|
||||
// 0x200 bytes for the NCCH header
|
||||
constexpr u64 headerSize = 0x200;
|
||||
u8 header[headerSize];
|
||||
|
||||
auto [success, bytes] = readFromFile(file, info, header, 0, headerSize);
|
||||
if (!success || bytes != headerSize) {
|
||||
printf("Failed to read NCCH header\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header[0x100] != 'N' || header[0x101] != 'C' || header[0x102] != 'C' || header[0x103] != 'H') {
|
||||
printf("Invalid header on NCCH\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
codeFile.clear();
|
||||
saveData.clear();
|
||||
codeFile.clear();
|
||||
saveData.clear();
|
||||
|
||||
size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break
|
||||
exheaderSize = *(u32*)&header[0x180];
|
||||
size = u64(*(u32*)&header[0x104]) * mediaUnit; // TODO: Maybe don't type pun because big endian will break
|
||||
exheaderSize = *(u32*)&header[0x180];
|
||||
|
||||
const u64 programID = *(u64*)&header[0x118];
|
||||
const u64 programID = *(u64*)&header[0x118];
|
||||
|
||||
// Read NCCH flags
|
||||
isNew3DS = header[0x188 + 4] == 2;
|
||||
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;
|
||||
// Read NCCH flags
|
||||
secondaryKeySlot = header[0x188 + 3];
|
||||
isNew3DS = header[0x188 + 4] == 2;
|
||||
fixedCryptoKey = (header[0x188 + 7] & 0x1) == 0x1;
|
||||
mountRomFS = (header[0x188 + 7] & 0x2) != 0x2;
|
||||
encrypted = (header[0x188 + 7] & 0x4) != 0x4;
|
||||
seedCrypto = (header[0x188 + 7] & 0x20) == 0x20;
|
||||
|
||||
romFS.offset = u64(*(u32*)&header[0x1B0]) * mediaUnit;
|
||||
romFS.size = u64(*(u32*)&header[0x1B4]) * mediaUnit;
|
||||
romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit;
|
||||
// Read exheader, ExeFS and RomFS info
|
||||
exheaderInfo.offset = info.offset + 0x200;
|
||||
exheaderInfo.size = exheaderSize;
|
||||
exheaderInfo.hashRegionSize = 0;
|
||||
|
||||
if (fixedCryptoKey) {
|
||||
Helpers::panic("Fixed crypto keys for NCCH");
|
||||
}
|
||||
exeFS.offset = info.offset + u64(*(u32*)&header[0x1A0]) * mediaUnit;
|
||||
exeFS.size = u64(*(u32*)&header[0x1A4]) * mediaUnit;
|
||||
exeFS.hashRegionSize = u64(*(u32*)&header[0x1A8]) * mediaUnit;
|
||||
|
||||
if (exheaderSize != 0) {
|
||||
const u8* exheader = &header[0x200]; // Extended NCCH header
|
||||
const u64 jumpID = *(u64*)&exheader[0x1C0 + 0x8];
|
||||
romFS.offset = info.offset + u64(*(u32*)&header[0x1B0]) * mediaUnit;
|
||||
romFS.size = u64(*(u32*)&header[0x1B4]) * mediaUnit;
|
||||
romFS.hashRegionSize = u64(*(u32*)&header[0x1B8]) * mediaUnit;
|
||||
|
||||
// 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 encrypted but not actually encrypted\n");
|
||||
encrypted = false;
|
||||
} else if (encrypted) {
|
||||
Helpers::panic("Encrypted NCSD file");
|
||||
}
|
||||
if (encrypted) {
|
||||
if (!aesEngine.haveKeys()) {
|
||||
Helpers::panic(
|
||||
"Loaded an encrypted ROM but AES keys don't seem to have been provided correctly! Navigate to the emulator's\n"
|
||||
"app data folder and make sure you have a sysdata directory with a file called aes_keys.txt which contains your keys!"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 saveDataSize = *(u64*)&exheader[0x1C0 + 0x0]; // Size of save data in bytes
|
||||
saveData.resize(saveDataSize, 0xff);
|
||||
Crypto::AESKey primaryKeyY;
|
||||
Crypto::AESKey secondaryKeyY;
|
||||
std::memcpy(primaryKeyY.data(), header, primaryKeyY.size());
|
||||
|
||||
compressCode = (exheader[0xD] & 1) != 0;
|
||||
stackSize = *(u32*)&exheader[0x1C];
|
||||
bssSize = *(u32*)&exheader[0x3C];
|
||||
if (!seedCrypto) {
|
||||
secondaryKeyY = primaryKeyY;
|
||||
} else {
|
||||
Helpers::panic("Seed crypto is not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
text.extract(&exheader[0x10]);
|
||||
rodata.extract(&exheader[0x20]);
|
||||
data.extract(&exheader[0x30]);
|
||||
}
|
||||
auto primaryResult = getPrimaryKey(aesEngine, primaryKeyY);
|
||||
|
||||
printf("Stack size: %08X\nBSS size: %08X\n", stackSize, bssSize);
|
||||
if (!primaryResult.first) {
|
||||
Helpers::panic("getPrimaryKey failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read ExeFS
|
||||
if (hasExeFS()) {
|
||||
u64 exeFSOffset = fileOffset + exeFS.offset; // Offset of ExeFS in the file = exeFS offset + ncch offset
|
||||
printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset, exeFS.size, exeFSOffset);
|
||||
constexpr size_t exeFSHeaderSize = 0x200;
|
||||
Crypto::AESKey primaryKey = primaryResult.second;
|
||||
|
||||
u8 exeFSHeader[exeFSHeaderSize];
|
||||
auto secondaryResult = getSecondaryKey(aesEngine, secondaryKeyY);
|
||||
|
||||
file.seek(exeFSOffset);
|
||||
auto [success, bytes] = file.readBytes(exeFSHeader, exeFSHeaderSize);
|
||||
if (!success || bytes != exeFSHeaderSize) {
|
||||
printf("Failed to parse ExeFS header\n");
|
||||
return false;
|
||||
}
|
||||
if (!secondaryResult.first) {
|
||||
Helpers::panic("getSecondaryKey failed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ExeFS format allows up to 10 files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
u8* fileInfo = &exeFSHeader[i * 16];
|
||||
Crypto::AESKey secondaryKey = secondaryResult.second;
|
||||
|
||||
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
|
||||
EncryptionInfo encryptionInfoTmp;
|
||||
encryptionInfoTmp.normalKey = primaryKey;
|
||||
encryptionInfoTmp.initialCounter.fill(0);
|
||||
|
||||
u32 fileOffset = *(u32*)&fileInfo[0x8];
|
||||
u32 fileSize = *(u32*)&fileInfo[0xC];
|
||||
for (std::size_t i = 1; i <= sizeof(std::uint64_t) - 1; i++) {
|
||||
encryptionInfoTmp.initialCounter[i] = header[0x108 + sizeof(std::uint64_t) - 1 - i];
|
||||
}
|
||||
encryptionInfoTmp.initialCounter[8] = 1;
|
||||
exheaderInfo.encryptionInfo = encryptionInfoTmp;
|
||||
|
||||
if (fileSize != 0) {
|
||||
printf("File %d. Name: %s, Size: %08X, Offset: %08X\n", i, name, fileSize, fileOffset);
|
||||
}
|
||||
encryptionInfoTmp.initialCounter[8] = 2;
|
||||
exeFS.encryptionInfo = encryptionInfoTmp;
|
||||
|
||||
if (std::strcmp(name, ".code") == 0) {
|
||||
if (hasCode()) {
|
||||
Helpers::panic("Second code file in a single NCCH partition. What should this do?\n");
|
||||
}
|
||||
encryptionInfoTmp.normalKey = secondaryKey;
|
||||
encryptionInfoTmp.initialCounter[8] = 3;
|
||||
romFS.encryptionInfo = encryptionInfoTmp;
|
||||
}
|
||||
|
||||
if (compressCode) {
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
if (exheaderSize != 0) {
|
||||
std::unique_ptr<u8[]> exheader(new u8[exheaderSize]);
|
||||
|
||||
// A file offset of 0 means our file is located right after the ExeFS header
|
||||
// So in the ROM, files are located at (file offset + exeFS offset + exeFS header size)
|
||||
file.seek(exeFSOffset + exeFSHeaderSize + fileOffset);
|
||||
file.readBytes(tmp.data(), fileSize);
|
||||
|
||||
// Decompress .code file from the tmp vector to the "code" vector
|
||||
if (!CartLZ77::decompress(codeFile, tmp)) {
|
||||
printf("Failed to decompress .code file\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
codeFile.resize(fileSize);
|
||||
file.seek(exeFSOffset + exeFSHeaderSize + fileOffset);
|
||||
file.readBytes(codeFile.data(), fileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto [success, bytes] = readFromFile(file, info, &exheader[0], 0x200, exheaderSize);
|
||||
if (!success || bytes != exheaderSize) {
|
||||
printf("Failed to read Extended NCCH header\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasRomFS()) {
|
||||
printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size);
|
||||
}
|
||||
const u64 jumpID = *(u64*)&exheader[0x1C0 + 0x8];
|
||||
|
||||
if (stackSize != 0 && stackSize != VirtualAddrs::DefaultStackSize) {
|
||||
Helpers::warn("Requested stack size is %08X bytes. Temporarily emulated as 0x4000 until adjustable sizes are added\n", stackSize);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// If it's truly encrypted, we need to read section again.
|
||||
if (encrypted) {
|
||||
auto [success, bytes] = readFromFile(file, exheaderInfo, &exheader[0], 0, exheaderSize);
|
||||
if (!success || bytes != exheaderSize) {
|
||||
printf("Failed to read Extended NCCH header\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (encrypted) {
|
||||
if (hasExeFS())
|
||||
Helpers::panic("Encrypted NCCH partition with ExeFS");
|
||||
else
|
||||
printf("Encrypted NCCH partition. Hopefully not required because it doesn't have an ExeFS. Skipped\n");
|
||||
}
|
||||
const u64 saveDataSize = *(u64*)&exheader[0x1C0 + 0x0]; // Size of save data in bytes
|
||||
saveData.resize(saveDataSize, 0xff);
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
compressCode = (exheader[0xD] & 1) != 0;
|
||||
stackSize = *(u32*)&exheader[0x1C];
|
||||
bssSize = *(u32*)&exheader[0x3C];
|
||||
|
||||
text.extract(&exheader[0x10]);
|
||||
rodata.extract(&exheader[0x20]);
|
||||
data.extract(&exheader[0x30]);
|
||||
}
|
||||
|
||||
printf("Stack size: %08X\nBSS size: %08X\n", stackSize, bssSize);
|
||||
|
||||
// Read ExeFS
|
||||
if (hasExeFS()) {
|
||||
u64 exeFSOffset = fileOffset + exeFS.offset; // Offset of ExeFS in the file = exeFS offset + ncch offset
|
||||
printf("ExeFS offset: %08llX, size: %08llX (Offset in file = %08llX)\n", exeFS.offset, exeFS.size, exeFSOffset);
|
||||
constexpr size_t exeFSHeaderSize = 0x200;
|
||||
|
||||
u8 exeFSHeader[exeFSHeaderSize];
|
||||
|
||||
auto [success, bytes] = readFromFile(file, exeFS, exeFSHeader, 0, exeFSHeaderSize);
|
||||
if (!success || bytes != exeFSHeaderSize) {
|
||||
printf("Failed to parse ExeFS header\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// ExeFS format allows up to 10 files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
u8* fileInfo = &exeFSHeader[i * 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", i, name, fileSize, fileOffset);
|
||||
}
|
||||
|
||||
if (std::strcmp(name, ".code") == 0) {
|
||||
if (hasCode()) {
|
||||
Helpers::panic("Second code file in a single NCCH partition. What should this do?\n");
|
||||
}
|
||||
|
||||
if (compressCode) {
|
||||
std::vector<u8> tmp;
|
||||
tmp.resize(fileSize);
|
||||
|
||||
// A file offset of 0 means our file is located right after the ExeFS header
|
||||
// So in the ROM, files are located at (file offset + exeFS offset + exeFS header size)
|
||||
readFromFile(file, exeFS, tmp.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
|
||||
// Decompress .code file from the tmp vector to the "code" vector
|
||||
if (!CartLZ77::decompress(codeFile, tmp)) {
|
||||
printf("Failed to decompress .code file\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
codeFile.resize(fileSize);
|
||||
readFromFile(file, exeFS, codeFile.data(), fileOffset + exeFSHeaderSize, fileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRomFS()) {
|
||||
printf("RomFS offset: %08llX, size: %08llX\n", romFS.offset, romFS.size);
|
||||
}
|
||||
|
||||
if (stackSize != 0 && stackSize != VirtualAddrs::DefaultStackSize) {
|
||||
Helpers::warn("Requested stack size is %08X bytes. Temporarily emulated as 0x4000 until adjustable sizes are added\n", stackSize);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<bool, Crypto::AESKey> NCCH::getPrimaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {
|
||||
Crypto::AESKey result;
|
||||
|
||||
if (encrypted) {
|
||||
if (fixedCryptoKey) {
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
aesEngine.setKeyY(Crypto::KeySlotId::NCCHKey0, keyY);
|
||||
|
||||
if (!aesEngine.hasNormalKey(Crypto::KeySlotId::NCCHKey0)) {
|
||||
return {false, result};
|
||||
}
|
||||
|
||||
result = aesEngine.getNormalKey(Crypto::KeySlotId::NCCHKey0);
|
||||
}
|
||||
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
std::pair<bool, Crypto::AESKey> NCCH::getSecondaryKey(Crypto::AESEngine &aesEngine, const Crypto::AESKey &keyY) {
|
||||
Crypto::AESKey result;
|
||||
|
||||
if (encrypted) {
|
||||
|
||||
if (fixedCryptoKey) {
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
Crypto::KeySlotId keySlotId;
|
||||
|
||||
switch (secondaryKeySlot) {
|
||||
case 0:
|
||||
keySlotId = Crypto::KeySlotId::NCCHKey0;
|
||||
break;
|
||||
case 1:
|
||||
keySlotId = Crypto::KeySlotId::NCCHKey1;
|
||||
break;
|
||||
case 10:
|
||||
keySlotId = Crypto::KeySlotId::NCCHKey2;
|
||||
break;
|
||||
case 11:
|
||||
keySlotId = Crypto::KeySlotId::NCCHKey3;
|
||||
break;
|
||||
default:
|
||||
return {false, result};
|
||||
}
|
||||
|
||||
if (!aesEngine.hasKeyX(keySlotId)) {
|
||||
return {false, result};
|
||||
}
|
||||
|
||||
aesEngine.setKeyY(keySlotId, keyY);
|
||||
|
||||
if (!aesEngine.hasNormalKey(keySlotId)) {
|
||||
return {false, result};
|
||||
}
|
||||
|
||||
result = aesEngine.getNormalKey(keySlotId);
|
||||
}
|
||||
|
||||
return {true, result};
|
||||
}
|
||||
|
||||
std::pair<bool, std::size_t> NCCH::readFromFile(IOFile& file, const FSInfo &info, u8 *dst, std::size_t offset, std::size_t size) {
|
||||
if (size == 0) {
|
||||
return { true, 0 };
|
||||
}
|
||||
|
||||
std::size_t readMaxSize = std::min(size, static_cast<std::size_t>(info.size) - offset);
|
||||
|
||||
file.seek(info.offset + offset);
|
||||
auto [success, bytes] = file.readBytes(dst, readMaxSize);
|
||||
|
||||
if (!success) {
|
||||
return { success, bytes};
|
||||
}
|
||||
|
||||
if (success && info.encryptionInfo.has_value()) {
|
||||
auto& encryptionInfo = info.encryptionInfo.value();
|
||||
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(encryptionInfo.normalKey.data(), encryptionInfo.normalKey.size(), encryptionInfo.initialCounter.data());
|
||||
|
||||
if (offset > 0) {
|
||||
d.Seek(offset);
|
||||
}
|
||||
|
||||
CryptoPP::byte* data = reinterpret_cast<CryptoPP::byte*>(dst);
|
||||
d.ProcessData(data, data, bytes);
|
||||
}
|
||||
|
||||
return { success, bytes};
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
#include "loader/ncsd.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
std::optional<NCSD> Memory::loadNCSD(const std::filesystem::path& path) {
|
||||
std::optional<NCSD> Memory::loadNCSD(Crypto::AESEngine &aesEngine, const std::filesystem::path& path) {
|
||||
NCSD ncsd;
|
||||
if (!ncsd.file.open(path, "rb"))
|
||||
return std::nullopt;
|
||||
|
@ -51,18 +51,12 @@ std::optional<NCSD> Memory::loadNCSD(const std::filesystem::path& path) {
|
|||
ncch.fileOffset = partition.offset;
|
||||
|
||||
if (partition.length != 0) { // Initialize the NCCH of each partition
|
||||
ncsd.file.seek(partition.offset);
|
||||
NCCH::FSInfo ncchFsInfo;
|
||||
|
||||
// 0x200 bytes for the NCCH header and another 0x800 for the exheader
|
||||
constexpr u64 headerSize = 0x200 + 0x800;
|
||||
u8 ncchHeader[headerSize];
|
||||
std::tie(success, bytes) = ncsd.file.readBytes(ncchHeader, headerSize);
|
||||
if (!success || bytes != headerSize) {
|
||||
printf("Failed to read NCCH header\n");
|
||||
return std::nullopt;
|
||||
}
|
||||
ncchFsInfo.offset = partition.offset;
|
||||
ncchFsInfo.size = partition.length;
|
||||
|
||||
if (!ncch.loadFromHeader(ncchHeader, ncsd.file)) {
|
||||
if (!ncch.loadFromHeader(aesEngine, ncsd.file, ncchFsInfo)) {
|
||||
printf("Invalid NCCH partition\n");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
|
423
src/emulator.cpp
423
src/emulator.cpp
|
@ -1,193 +1,328 @@
|
|||
#include "emulator.hpp"
|
||||
|
||||
void Emulator::reset() {
|
||||
cpu.reset();
|
||||
gpu.reset();
|
||||
memory.reset();
|
||||
// Kernel must be reset last because it depends on CPU/Memory state
|
||||
kernel.reset();
|
||||
Emulator::Emulator() : kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory), memory(cpu.getTicksRef()) {
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
|
||||
Helpers::panic("Failed to initialize SDL2");
|
||||
}
|
||||
|
||||
// Reloading r13 and r15 needs to happen after everything has been reset
|
||||
// Otherwise resetting the kernel or cpu might nuke them
|
||||
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
|
||||
if (romType == ROMType::ELF) { // Reload ELF if we're using one
|
||||
loadELF(loadedELF);
|
||||
}
|
||||
// Make SDL use consistent positional button mapping
|
||||
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
|
||||
Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
// Request OpenGL 4.1 Core (Max available on MacOS)
|
||||
// MacOS gets mad if we don't explicitly demand a core profile
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
|
||||
window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL);
|
||||
|
||||
if (window == nullptr) {
|
||||
Helpers::panic("Window creation failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
glContext = SDL_GL_CreateContext(window);
|
||||
if (glContext == nullptr) {
|
||||
Helpers::panic("OpenGL context creation failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (!gladLoadGL(reinterpret_cast<GLADloadfunc>(SDL_GL_GetProcAddress))) {
|
||||
Helpers::panic("OpenGL init failed: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) {
|
||||
gameController = SDL_GameControllerOpen(0);
|
||||
|
||||
if (gameController != nullptr) {
|
||||
SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController);
|
||||
gameControllerID = SDL_JoystickInstanceID(stick);
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void Emulator::reset() {
|
||||
cpu.reset();
|
||||
gpu.reset();
|
||||
memory.reset();
|
||||
// Kernel must be reset last because it depends on CPU/Memory state
|
||||
kernel.reset();
|
||||
|
||||
// Reloading r13 and r15 needs to happen after everything has been reset
|
||||
// Otherwise resetting the kernel or cpu might nuke them
|
||||
cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP
|
||||
if (romType == ROMType::ELF) { // Reload ELF if we're using one
|
||||
loadELF(loadedELF);
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::step() {}
|
||||
|
||||
void Emulator::render() {
|
||||
|
||||
}
|
||||
void Emulator::render() {}
|
||||
|
||||
void Emulator::run() {
|
||||
while (running) {
|
||||
runFrame(); // Run 1 frame of instructions
|
||||
gpu.display(); // Display graphics
|
||||
|
||||
ServiceManager& srv = kernel.getServiceManager();
|
||||
ServiceManager& srv = kernel.getServiceManager();
|
||||
|
||||
// Send VBlank interrupts
|
||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
||||
// Send VBlank interrupts
|
||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank0);
|
||||
srv.sendGPUInterrupt(GPUInterrupt::VBlank1);
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
namespace Keys = HID::Keys;
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
namespace Keys = HID::Keys;
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
printf("Bye :(\n");
|
||||
running = false;
|
||||
return;
|
||||
case SDL_KEYDOWN:
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_l: srv.pressKey(Keys::A); break;
|
||||
case SDLK_k: srv.pressKey(Keys::B); break;
|
||||
case SDLK_o: srv.pressKey(Keys::X); break;
|
||||
case SDLK_i: srv.pressKey(Keys::Y); break;
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
printf("Bye :(\n");
|
||||
running = false;
|
||||
return;
|
||||
|
||||
case SDLK_q: srv.pressKey(Keys::L); break;
|
||||
case SDLK_p: srv.pressKey(Keys::R); break;
|
||||
case SDL_KEYDOWN:
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_l: srv.pressKey(Keys::A); break;
|
||||
case SDLK_k: srv.pressKey(Keys::B); break;
|
||||
case SDLK_o: srv.pressKey(Keys::X); break;
|
||||
case SDLK_i: srv.pressKey(Keys::Y); break;
|
||||
|
||||
case SDLK_RIGHT: srv.pressKey(Keys::Right); break;
|
||||
case SDLK_LEFT: srv.pressKey(Keys::Left); break;
|
||||
case SDLK_UP: srv.pressKey(Keys::Up); break;
|
||||
case SDLK_DOWN: srv.pressKey(Keys::Down); break;
|
||||
case SDLK_q: srv.pressKey(Keys::L); break;
|
||||
case SDLK_p: srv.pressKey(Keys::R); break;
|
||||
|
||||
case SDLK_w: srv.setCirclepadY(0x9C); break;
|
||||
case SDLK_a: srv.setCirclepadX(-0x9C); break;
|
||||
case SDLK_s: srv.setCirclepadY(-0x9C); break;
|
||||
case SDLK_d: srv.setCirclepadX(0x9C); break;
|
||||
case SDLK_RIGHT: srv.pressKey(Keys::Right); break;
|
||||
case SDLK_LEFT: srv.pressKey(Keys::Left); break;
|
||||
case SDLK_UP: srv.pressKey(Keys::Up); break;
|
||||
case SDLK_DOWN: srv.pressKey(Keys::Down); break;
|
||||
|
||||
case SDLK_RETURN: srv.pressKey(Keys::Start); break;
|
||||
case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break;
|
||||
}
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_l: srv.releaseKey(Keys::A); break;
|
||||
case SDLK_k: srv.releaseKey(Keys::B); break;
|
||||
case SDLK_o: srv.releaseKey(Keys::X); break;
|
||||
case SDLK_i: srv.releaseKey(Keys::Y); break;
|
||||
case SDLK_w:
|
||||
srv.setCirclepadY(0x9C);
|
||||
keyboardAnalogY = true;
|
||||
break;
|
||||
|
||||
case SDLK_q: srv.releaseKey(Keys::L); break;
|
||||
case SDLK_p: srv.releaseKey(Keys::R); break;
|
||||
case SDLK_a:
|
||||
srv.setCirclepadX(-0x9C);
|
||||
keyboardAnalogX = true;
|
||||
break;
|
||||
|
||||
case SDLK_RIGHT: srv.releaseKey(Keys::Right); break;
|
||||
case SDLK_LEFT: srv.releaseKey(Keys::Left); break;
|
||||
case SDLK_UP: srv.releaseKey(Keys::Up); break;
|
||||
case SDLK_DOWN: srv.releaseKey(Keys::Down); break;
|
||||
case SDLK_s:
|
||||
srv.setCirclepadY(-0x9C);
|
||||
keyboardAnalogY = true;
|
||||
break;
|
||||
|
||||
// Err this is probably not ideal
|
||||
case SDLK_w: srv.setCirclepadY(0); break;
|
||||
case SDLK_a: srv.setCirclepadX(0); break;
|
||||
case SDLK_s: srv.setCirclepadY(0); break;
|
||||
case SDLK_d: srv.setCirclepadX(0); break;
|
||||
case SDLK_d:
|
||||
srv.setCirclepadX(0x9C);
|
||||
keyboardAnalogX = true;
|
||||
break;
|
||||
|
||||
case SDLK_RETURN: srv.releaseKey(Keys::Start); break;
|
||||
case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break;
|
||||
}
|
||||
break;
|
||||
case SDLK_RETURN: srv.pressKey(Keys::Start); break;
|
||||
case SDLK_BACKSPACE: srv.pressKey(Keys::Select); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN: {
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
const s32 x = event.button.x;
|
||||
const s32 y = event.button.y;
|
||||
case SDL_KEYUP:
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_l: srv.releaseKey(Keys::A); break;
|
||||
case SDLK_k: srv.releaseKey(Keys::B); break;
|
||||
case SDLK_o: srv.releaseKey(Keys::X); break;
|
||||
case SDLK_i: srv.releaseKey(Keys::Y); break;
|
||||
|
||||
// Check if touch falls in the touch screen area
|
||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||
// Convert to 3DS coordinates
|
||||
u16 x_converted = static_cast<u16>(x) - 40;
|
||||
u16 y_converted = static_cast<u16>(y) - 240;
|
||||
case SDLK_q: srv.releaseKey(Keys::L); break;
|
||||
case SDLK_p: srv.releaseKey(Keys::R); break;
|
||||
|
||||
srv.setTouchScreenPress(x_converted, y_converted);
|
||||
}
|
||||
else {
|
||||
srv.releaseTouchScreen();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDLK_RIGHT: srv.releaseKey(Keys::Right); break;
|
||||
case SDLK_LEFT: srv.releaseKey(Keys::Left); break;
|
||||
case SDLK_UP: srv.releaseKey(Keys::Up); break;
|
||||
case SDLK_DOWN: srv.releaseKey(Keys::Down); break;
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
srv.releaseTouchScreen();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Err this is probably not ideal
|
||||
case SDLK_w:
|
||||
case SDLK_s:
|
||||
srv.setCirclepadY(0);
|
||||
keyboardAnalogY = false;
|
||||
break;
|
||||
|
||||
// Update inputs in the HID module
|
||||
srv.updateInputs(cpu.getTicks());
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
case SDLK_a:
|
||||
case SDLK_d:
|
||||
srv.setCirclepadX(0);
|
||||
keyboardAnalogX = false;
|
||||
break;
|
||||
|
||||
case SDLK_RETURN: srv.releaseKey(Keys::Start); break;
|
||||
case SDLK_BACKSPACE: srv.releaseKey(Keys::Select); break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN: {
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
const s32 x = event.button.x;
|
||||
const s32 y = event.button.y;
|
||||
|
||||
// Check if touch falls in the touch screen area
|
||||
if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) {
|
||||
// Convert to 3DS coordinates
|
||||
u16 x_converted = static_cast<u16>(x) - 40;
|
||||
u16 y_converted = static_cast<u16>(y) - 240;
|
||||
|
||||
srv.setTouchScreenPress(x_converted, y_converted);
|
||||
} else {
|
||||
srv.releaseTouchScreen();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||
srv.releaseTouchScreen();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEADDED:
|
||||
if (gameController == nullptr) {
|
||||
gameController = SDL_GameControllerOpen(event.cdevice.which);
|
||||
gameControllerID = event.cdevice.which;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERDEVICEREMOVED:
|
||||
if (event.cdevice.which == gameControllerID) {
|
||||
SDL_GameControllerClose(gameController);
|
||||
gameController = nullptr;
|
||||
gameControllerID = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
case SDL_CONTROLLERBUTTONDOWN: {
|
||||
u32 key = 0;
|
||||
|
||||
switch (event.cbutton.button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break;
|
||||
case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break;
|
||||
case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break;
|
||||
case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break;
|
||||
}
|
||||
|
||||
if (key != 0) {
|
||||
if (event.cbutton.state == SDL_PRESSED) {
|
||||
srv.pressKey(key);
|
||||
} else {
|
||||
srv.releaseKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gameController != nullptr) {
|
||||
const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX);
|
||||
const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY);
|
||||
constexpr s16 deadzone = 3276;
|
||||
constexpr s16 maxValue = 0x9C;
|
||||
constexpr s16 div = 0x8000 / maxValue;
|
||||
|
||||
// Avoid overriding the keyboard's circlepad input
|
||||
if (abs(stickX) < deadzone && !keyboardAnalogX) {
|
||||
srv.setCirclepadX(0);
|
||||
} else {
|
||||
srv.setCirclepadX(stickX / div);
|
||||
}
|
||||
|
||||
if (abs(stickY) < deadzone && !keyboardAnalogY) {
|
||||
srv.setCirclepadY(0);
|
||||
} else {
|
||||
srv.setCirclepadY(-(stickY / div));
|
||||
}
|
||||
}
|
||||
|
||||
// Update inputs in the HID module
|
||||
srv.updateInputs(cpu.getTicks());
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::runFrame() {
|
||||
cpu.runFrame();
|
||||
}
|
||||
void Emulator::runFrame() { cpu.runFrame(); }
|
||||
|
||||
bool Emulator::loadROM(const std::filesystem::path& path) {
|
||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
||||
char* appData = SDL_GetPrefPath(nullptr, "Alber");
|
||||
const std::filesystem::path dataPath = std::filesystem::path(appData) / path.filename().stem();
|
||||
IOFile::setAppDataDir(dataPath);
|
||||
SDL_free(appData);
|
||||
// Get path for saving files (AppData on Windows, /home/user/.local/share/ApplcationName on Linux, etc)
|
||||
// Inside that path, we be use a game-specific folder as well. Eg if we were loading a ROM called PenguinDemo.3ds, the savedata would be in
|
||||
// %APPDATA%/Alber/PenguinDemo/SaveData on Windows, and so on. We do this because games save data in their own filesystem on the cart
|
||||
char* appData = SDL_GetPrefPath(nullptr, "Alber");
|
||||
const std::filesystem::path appDataPath = std::filesystem::path(appData);
|
||||
const std::filesystem::path dataPath = appDataPath / path.filename().stem();
|
||||
const std::filesystem::path aesKeysPath = appDataPath / "sysdata" / "aes_keys.txt";
|
||||
IOFile::setAppDataDir(dataPath);
|
||||
SDL_free(appData);
|
||||
|
||||
kernel.initializeFS();
|
||||
auto extension = path.extension();
|
||||
|
||||
if (extension == ".elf" || extension == ".axf")
|
||||
return loadELF(path);
|
||||
else if (extension == ".3ds")
|
||||
return loadNCSD(path);
|
||||
else {
|
||||
printf("Unknown file type\n");
|
||||
return false;
|
||||
}
|
||||
// Open the text file containing our AES keys if it exists. We use the std::filesystem::exists overload that takes an error code param to
|
||||
// avoid the call throwing exceptions
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(aesKeysPath, ec) && !ec) {
|
||||
aesEngine.loadKeys(aesKeysPath);
|
||||
}
|
||||
|
||||
kernel.initializeFS();
|
||||
auto extension = path.extension();
|
||||
|
||||
if (extension == ".elf" || extension == ".axf")
|
||||
return loadELF(path);
|
||||
else if (extension == ".3ds")
|
||||
return loadNCSD(path);
|
||||
else {
|
||||
printf("Unknown file type\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Emulator::loadNCSD(const std::filesystem::path& path) {
|
||||
romType = ROMType::NCSD;
|
||||
std::optional<NCSD> opt = memory.loadNCSD(path);
|
||||
romType = ROMType::NCSD;
|
||||
std::optional<NCSD> opt = memory.loadNCSD(aesEngine, path);
|
||||
|
||||
if (!opt.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (!opt.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loadedNCSD = opt.value();
|
||||
cpu.setReg(15, loadedNCSD.entrypoint);
|
||||
loadedNCSD = opt.value();
|
||||
cpu.setReg(15, loadedNCSD.entrypoint);
|
||||
|
||||
if (loadedNCSD.entrypoint & 1) {
|
||||
Helpers::panic("Misaligned NCSD entrypoint; should this start the CPU in Thumb mode?");
|
||||
}
|
||||
if (loadedNCSD.entrypoint & 1) {
|
||||
Helpers::panic("Misaligned NCSD entrypoint; should this start the CPU in Thumb mode?");
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Emulator::loadELF(const std::filesystem::path& path) {
|
||||
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
||||
romType = ROMType::ELF;
|
||||
loadedELF.open(path, std::ios_base::binary); // Open ROM in binary mode
|
||||
romType = ROMType::ELF;
|
||||
|
||||
return loadELF(loadedELF);
|
||||
return loadELF(loadedELF);
|
||||
}
|
||||
|
||||
bool Emulator::loadELF(std::ifstream& file) {
|
||||
// Rewind ifstream
|
||||
loadedELF.clear();
|
||||
loadedELF.seekg(0);
|
||||
// Rewind ifstream
|
||||
loadedELF.clear();
|
||||
loadedELF.seekg(0);
|
||||
|
||||
std::optional<u32> entrypoint = memory.loadELF(loadedELF);
|
||||
if (!entrypoint.has_value())
|
||||
return false;
|
||||
std::optional<u32> entrypoint = memory.loadELF(loadedELF);
|
||||
if (!entrypoint.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cpu.setReg(15, entrypoint.value()); // Set initial PC
|
||||
if (entrypoint.value() & 1) {
|
||||
Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
cpu.setReg(15, entrypoint.value()); // Set initial PC
|
||||
if (entrypoint.value() & 1) {
|
||||
Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
108
src/io_file.cpp
Normal file
108
src/io_file.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#include "io_file.hpp"
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// 64 bit offsets for MSVC
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
#define fileno _fileno
|
||||
|
||||
#pragma warning(disable : 4996)
|
||||
#endif
|
||||
|
||||
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#include <io.h> // For _chsize_s
|
||||
#else
|
||||
#include <unistd.h> // For ftruncate
|
||||
#endif
|
||||
|
||||
IOFile::IOFile(const std::filesystem::path& path, const char* permissions) { open(path, permissions); }
|
||||
|
||||
bool IOFile::open(const std::filesystem::path& path, const char* permissions) {
|
||||
const auto str = path.string(); // For some reason converting paths directly with c_str() doesn't work
|
||||
return open(str.c_str(), permissions);
|
||||
}
|
||||
|
||||
bool IOFile::open(const char* filename, const char* permissions) {
|
||||
handle = std::fopen(filename, permissions);
|
||||
return isOpen();
|
||||
}
|
||||
|
||||
void IOFile::close() {
|
||||
if (isOpen()) {
|
||||
fclose(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, std::size_t> IOFile::read(void* data, std::size_t length, std::size_t dataSize) {
|
||||
if (!isOpen()) {
|
||||
return {false, std::numeric_limits<std::size_t>::max()};
|
||||
}
|
||||
|
||||
if (length == 0) return {true, 0};
|
||||
return {true, std::fread(data, dataSize, length, handle)};
|
||||
}
|
||||
|
||||
std::pair<bool, std::size_t> IOFile::write(const void* data, std::size_t length, std::size_t dataSize) {
|
||||
if (!isOpen()) {
|
||||
return {false, std::numeric_limits<std::size_t>::max()};
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
return {true, 0};
|
||||
} else {
|
||||
return {true, std::fwrite(data, dataSize, length, handle)};
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<bool, std::size_t> IOFile::readBytes(void* data, std::size_t count) { return read(data, count, sizeof(std::uint8_t)); }
|
||||
std::pair<bool, std::size_t> IOFile::writeBytes(const void* data, std::size_t count) { return write(data, count, sizeof(std::uint8_t)); }
|
||||
|
||||
std::optional<std::uint64_t> IOFile::size() {
|
||||
if (!isOpen()) return {};
|
||||
|
||||
std::uint64_t pos = ftello(handle);
|
||||
if (fseeko(handle, 0, SEEK_END) != 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::uint64_t size = ftello(handle);
|
||||
if ((size != pos) && (fseeko(handle, pos, SEEK_SET) != 0)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool IOFile::seek(std::int64_t offset, int origin) {
|
||||
if (!isOpen() || fseeko(handle, offset, origin) != 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IOFile::rewind() { return seek(0, SEEK_SET); }
|
||||
FILE* IOFile::getHandle() { return handle; }
|
||||
|
||||
void IOFile::setAppDataDir(const std::filesystem::path& dir) {
|
||||
if (dir == "") Helpers::panic("Failed to set app data directory");
|
||||
appData = dir;
|
||||
}
|
||||
|
||||
bool IOFile::setSize(std::uint64_t size) {
|
||||
if (!isOpen()) return false;
|
||||
bool success;
|
||||
|
||||
#ifdef WIN32
|
||||
success = _chsize_s(_fileno(handle), size) == 0;
|
||||
#else
|
||||
success = ftruncate(fileno(handle), size) == 0;
|
||||
#endif
|
||||
fflush(handle);
|
||||
return success;
|
||||
}
|
|
@ -5,7 +5,7 @@ int main (int argc, char *argv[]) {
|
|||
|
||||
emu.initGraphicsContext();
|
||||
|
||||
auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "OoT Demo.3ds");
|
||||
auto romPath = std::filesystem::current_path() / (argc > 1 ? argv[1] : "teapot.elf");
|
||||
if (!emu.loadROM(romPath)) {
|
||||
// For some reason just .c_str() doesn't show the proper path
|
||||
Helpers::panic("Failed to load ROM file: %s", romPath.string().c_str());
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue