#include "common.h" #include #include #include #include #include #include #include #include #include "core/Environment.h" #include "clearFast.h" #include "InverseKmpMatcher.h" #include "fat/dldi.h" #include "fileInfo.h" #include "CardSaveArranger.h" #include "gameCode.h" #include "ApList.h" #include "ApListFactory.h" #include "TwlConfig.h" #include "argv.h" #include "DsiDeviceList.h" #include "Blowfish.h" #include "PicoLoaderArranger.h" #include "ipc.h" #include "errorDisplay/ErrorDisplay.h" #include "TwlAes.h" #include "DSMode.h" #include "Arm7IoRegisterClearer.h" #include "NdsLoader.h" #define AP_LIST_PATH "/_pico/aplist.bin" #define BIOS_NDS7_PATH "/_pico/biosnds7.rom" typedef void (*entrypoint_t)(void); static const InverseKmpMatcher sDldiStubMatcher { (const u32[]) { ~0xBF8DA5EDu, ~0x69684320u, ~0x6D6873u } }; void NdsLoader::Load(BootMode bootMode) { if (bootMode == BootMode::Normal) { LOG_DEBUG("Loading %s\n", _romPath); } u32 cardId = *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[0]; u32 agbMem = *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.isDebuggerData[0x1C]; u32 resetParam = bootMode == BootMode::SdkResetSystem ? TWL_SHARED_MEMORY->ntrSharedMem.resetParam : 0; u32 bootType = TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo.bootType; u32 romOffset = bootMode == BootMode::SdkResetSystem ? TWL_SHARED_MEMORY->ntrSharedMem.romOffset : 0; if (romOffset < 0x8000) { romOffset = 0; } if (bootMode != BootMode::Multiboot) { u8* gap0Buffer = nullptr; u8* gap200Buffer = nullptr; u8* pokemonBuffer = nullptr; u8* multibootBuffer = nullptr; if (bootMode == BootMode::SdkResetSystem) { gap0Buffer = new u8[sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap0)]; memcpy(gap0Buffer, TWL_SHARED_MEMORY->ntrSharedMem.gap0, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap0)); gap200Buffer = new u8[sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap200)]; memcpy(gap200Buffer, TWL_SHARED_MEMORY->ntrSharedMem.gap200, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap200)); multibootBuffer = new u8[sizeof(TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo)]; memcpy(multibootBuffer, &TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo)); if (*(vu32*)0x02FFF00C == GAMECODE("ADAJ")) { pokemonBuffer = new u8[0x160]; memcpy(pokemonBuffer, (void*)0x02FFF000, 0x160); } } ClearMainMemory(); if (Environment::IsIsNitroEmulator()) { memset(&TWL_SHARED_MEMORY->ntrSharedMem, 0, sizeof(shared_memory_ntr_t)); memset((void*)0x02FFF000, 0, 0x160); *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.isDebuggerData[0x1C] = agbMem; } if (bootMode == BootMode::SdkResetSystem) { memcpy(TWL_SHARED_MEMORY->ntrSharedMem.gap0, gap0Buffer, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap0)); delete[] gap0Buffer; memcpy(TWL_SHARED_MEMORY->ntrSharedMem.gap200, gap200Buffer, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.gap200)); delete[] gap200Buffer; memcpy(&TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo, multibootBuffer, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo)); delete[] multibootBuffer; if (pokemonBuffer) { memcpy((void*)0x02FFF000, pokemonBuffer, 0x160); delete[] pokemonBuffer; } } } if (!PicoLoaderArranger().SetupPicoLoaderInfo((loader_info_t*)TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader)) { ErrorDisplay().PrintError("Failed to setup Pico Loader info."); return; } sendToArm9(IPC_COMMAND_ARM9_INITIALIZE_LOADER_INFO); receiveFromArm9(); switch (bootMode) { case BootMode::Multiboot: { // already in memory break; } case BootMode::SdkResetSystem: { _romFile.obj.fs = &gFatFs; _romFile.obj.id = gFatFs.id; _romFile.obj.sclust = SHARED_ROM_FILE_INFO->clusterMap[2]; _romFile.obj.attr = 0; _romFile.obj.objsize = SHARED_ROM_FILE_INFO->fileSize; _romFile.dir_sect = 0; _romFile.dir_ptr = gFatFs.win; _romFile.cltbl = (DWORD*)SHARED_ROM_FILE_INFO->clusterMap; _romFile.flag = FA_OPEN_EXISTING | FA_READ; _romFile.err = 0; _romFile.sect = 0; _romFile.fptr = 0; break; } default: { if (f_open(&_romFile, _romPath, FA_OPEN_EXISTING | FA_READ) != FR_OK) { LOG_FATAL("Failed to open rom file\n"); ErrorDisplay().PrintError("Failed to open the rom file.\n%s", _romPath); return; } CreateRomClusterTable(); break; } } if (bootMode == BootMode::Multiboot) { memcpy(&_romHeader, TWL_SHARED_MEMORY->ntrSharedMem.romHeader, sizeof(TWL_SHARED_MEMORY->ntrSharedMem.romHeader)); _romFile.dir_sect = 0; _romFile.dir_ptr = gFatFs.win; } else { if (!TryLoadRomHeader(romOffset)) { ErrorDisplay().PrintError("Failed to load rom header."); return; } } bool isHomebrew = (_romHeader.makerCode[0] == 0 && _romHeader.makerCode[1] == 0) || (_romHeader.arm9AutoLoadDoneHookAddress == 0 && _romHeader.arm7AutoLoadDoneHookAddress == 0) || _romHeader.arm7LoadAddress >= 0x03000000; if (isHomebrew) { LOG_DEBUG("Homebrew\n"); } else { LOG_DEBUG("Sdk rom\n"); bool isCloneBootRom = bootMode != BootMode::Multiboot && IsCloneBootRom(romOffset); sendToArm9(IPC_COMMAND_ARM9_SET_ROM_FILE_INFO); sendToArm9(_romFile.dir_sect); sendToArm9((u32)(_romFile.dir_ptr - _romFile.obj.fs->win)); sendToArm9(isCloneBootRom ? 1 : 0); memset(&_dsiwareSaveResult, 0, sizeof(_dsiwareSaveResult)); if (bootMode != BootMode::Multiboot) { if (_romHeader.IsDsiWare()) { // There exist DS mode DSiWare applications, which don't have a save file. if (_romHeader.SupportsDsiMode()) { if (bootMode == BootMode::SdkResetSystem) { // todo: for DSiWare we should store the rom path LOG_FATAL("Soft reset from DSiWare not yet supported\n"); return; } if (!TrySetupDsiWareSave()) { ErrorDisplay().PrintError("Failed to setup DSiWare save."); return; } } } else { if (bootMode != BootMode::SdkResetSystem) { if (!CardSaveArranger().SetupCardSave(&_romHeader, _savePath)) { ErrorDisplay().PrintError("Failed to setup save file."); return; } } HandleAntiPiracy(); } } } if (bootMode == BootMode::Normal) { bootType = _romHeader.IsDsiWare() ? BOOT_TYPE_NAND : BOOT_TYPE_CARD; HandleIQueRegionFreePatching(); } else if (bootMode == BootMode::Multiboot) { bootType = BOOT_TYPE_MULTIBOOT; } SetupSharedMemory(cardId, agbMem, resetParam, romOffset, bootType); if (Environment::IsDsiMode()) { if (_romHeader.SupportsDsiMode()) { SetupTwlConfig(); TwlAes().SetupAes(&_romHeader); } RemapWram(); } if (bootMode != BootMode::Multiboot) { if (!TryLoadArm9()) { ErrorDisplay().PrintError("Failed to load arm9."); return; } if (Environment::IsDsiMode() && _romHeader.SupportsDsiMode()) { if (!TryLoadArm9i()) { ErrorDisplay().PrintError("Failed to load arm9i."); return; } } } if (!isHomebrew) { sendToArm9(IPC_COMMAND_ARM9_APPLY_PATCHES); } if (bootMode != BootMode::Multiboot) { if (!TryLoadArm7()) { ErrorDisplay().PrintError("Failed to load arm7."); return; } if (Environment::IsDsiMode() && _romHeader.SupportsDsiMode()) { if (!TryLoadArm7i()) { ErrorDisplay().PrintError("Failed to load arm7i."); return; } } } if (!isHomebrew) { // wait for arm9 patches to be ready receiveFromArm9(); LOG_DEBUG("Arm9 patches done\n"); ApplyArm7Patches(); LOG_DEBUG("Arm7 patches done\n"); } if (Environment::IsDsiMode() && _romHeader.SupportsDsiMode()) { SetupDsiDeviceList(); // Set twl wram locking (REG_MBK9) settings from rom header REG_MBK9 = _romHeader.mbk9Setting[0] | (_romHeader.mbk9Setting[1] << 8) | (_romHeader.mbk9Setting[2] << 16); u32 scfgExt7 = 0x93FBFB00 | (_romHeader.arm7ScfgExt7 & 0x40407); REG_SCFG_EXT = scfgExt7; REG_SCFG_CLK = 0x187; *(vu32*)0x03FFFFC4 = scfgExt7; *(vu32*)0x03FFFFC8 = 0xF884; // combination of various SCFG bits, including which bootrom is in use } else { // When booted through the DSi menu these are actually set to 0x12A00000 and 0xC0EC // It probably doesn't make a difference compared to the zero's from the DS firmware *(vu32*)0x03FFFFC4 = 0; // If this value is incorrect, SVC_GetPitchTable doesn't work correctly in SDK 5. // When bit 2 is set (arm9i bootrom second half protected) and bit 5 is clear (arm7i bootrom in use) // it enables a bugfix to get pitch table values from the arm7i bios. *(vu32*)0x03FFFFC8 = 0; } if (bootMode != BootMode::Multiboot) { f_close(&_romFile); } Arm7IoRegisterClearer().ClearIoRegisters(); *(vu32*)0x03FFFFF8 = 0; *(vu32*)0x03FFFFFC = 0; if (Environment::IsDsiMode()) { if (_romHeader.SupportsDsiMode()) { if (!(_romHeader.twlFlags2 & 1)) { DSMode().SwitchToDSTouchAndSoundMode(_romHeader.gameCode); } // Set back power button handling to irq mode mcu_writeReg(MCU_REG_MODE, 1); } else { DSMode().SwitchToDSMode(_romHeader.gameCode); } } if (isHomebrew) { InsertArgv(); HandleHomebrewPatching(); } HandleDldiPatching(); StartRom(bootMode); } bool NdsLoader::IsCloneBootRom(u32 romOffset) { bool isCloneBootRom = false; if (_romHeader.ntrRomSize != 0) { UINT bytesRead = 0; u16 signatureHeader; if (f_lseek(&_romFile, romOffset + _romHeader.ntrRomSize) == FR_OK && f_read(&_romFile, &signatureHeader, sizeof(signatureHeader), &bytesRead) == FR_OK && bytesRead == 2 && signatureHeader == 0x6361) { LOG_DEBUG("Cloneboot rom\n"); isCloneBootRom = true; } } return isCloneBootRom; } void NdsLoader::InsertArgv() { u32 argSize = 0; // Find the address to write argv u32 argDst = ((_romHeader.arm9LoadAddress + _romHeader.arm9Size + 3) & ~3) + 4; if (_romHeader.SupportsDsiMode()) { u32 argDstTwl = ((_romHeader.arm9iLoadAddress + _romHeader.arm9iSize + 3) & ~3) + 4; if (argDstTwl > argDst) { argDst = argDstTwl; } } // If the rom path starts with / insert the drive prefix if (_romPath[0] == '/') { const char* drivePrefix; switch (gLoaderHeader.bootDrive) { case PLOAD_BOOT_DRIVE_DLDI: default: { drivePrefix = "fat:"; break; } case PLOAD_BOOT_DRIVE_DSI_SD: { drivePrefix = "sd:"; break; } case PLOAD_BOOT_DRIVE_AGB_SEMIHOSTING: { drivePrefix = "pc2:"; break; } } strcpy((char*)(argDst + argSize), drivePrefix); argSize += strlen(drivePrefix); } // Copy the rom path strcpy((char*)(argDst + argSize), _romPath); argSize += strlen(_romPath) + 1; // Copy any additional arguments if (_argumentsLength > 0) { memcpy((void*)(argDst + argSize), _arguments, _argumentsLength); argSize += _argumentsLength; } HOMEBREW_ARGV->magic = HOMEBREW_ARGV_MAGIC; HOMEBREW_ARGV->commandLine = (char*)argDst; HOMEBREW_ARGV->length = argSize; } void NdsLoader::HandleHomebrewPatching() { if (_launcherPath != nullptr && _launcherPath[0] != 0) { sendToArm9(IPC_COMMAND_ARM9_SETUP_HOMEBREW_BOOTSTUB); sendToArm9(16 * 1024); // required dldi space void* dldiSpace = (void*)receiveFromArm9(); char* launcherPath = (char*)receiveFromArm9(); if (dldiSpace != nullptr) { dldi_copyTo(dldiSpace); } if (launcherPath != nullptr) { strncpy(launcherPath, _launcherPath, 256); launcherPath[255] = 0; } } } void NdsLoader::ApplyArm7Patches() { sendToArm9(IPC_COMMAND_ARM9_APPLY_ARM7_PATCHES); void* patchSpaceStart = (void*)receiveFromArm9(); if (patchSpaceStart) { u32 mbk6 = 0; u32 mbk7 = 0; u32 mbk8 = 0; if (Environment::IsDsiMode() && _romHeader.SupportsDsiMode()) { mbk6 = REG_MBK6; mbk7 = REG_MBK7; mbk8 = REG_MBK8; REG_MBK6 = 0; REG_MBK7 = 0; REG_MBK8 = 0; } const u32 patchSpaceSize = 2 * 1024; auto patchCode = std::make_unique_for_overwrite(patchSpaceSize); void* srcAddress = (void*)(((u32)patchSpaceStart & 0x7FFF) + 0x037F8000); if ((u32)srcAddress + patchSpaceSize > 0x03800000) { u32 part1Size = (u32)0x03800000 - (u32)srcAddress; memcpy(patchCode.get(), srcAddress, part1Size); memcpy(patchCode.get() + part1Size, (void*)0x037F8000, patchSpaceSize - part1Size); } else { memcpy(patchCode.get(), srcAddress, patchSpaceSize); } if (Environment::IsDsiMode() && _romHeader.SupportsDsiMode()) { REG_MBK6 = mbk6; REG_MBK7 = mbk7; REG_MBK8 = mbk8; } memcpy(patchSpaceStart, patchCode.get(), patchSpaceSize); } } void NdsLoader::SetupSharedMemory(u32 cardId, u32 agbMem, u32 resetParam, u32 romOffset, u32 bootType) { memcpy(TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader, &_romHeader, sizeof(nds_header_ntr_t) - 0x10); memcpy(TWL_SHARED_MEMORY->ntrSharedMem.romHeader, &_romHeader, sizeof(nds_header_ntr_t)); *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[0] = cardId; *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[4] = cardId; *(vu16*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[8] = _romHeader.headerCrc; *(vu16*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[0xA] = _romHeader.secureAreaCrc; *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.bootCheckInfo[0x10] = 0x5835; // use correct card id address *(vu32*)&TWL_SHARED_MEMORY->ntrSharedMem.isDebuggerData[0x1C] = agbMem; TWL_SHARED_MEMORY->ntrSharedMem.resetParam = resetParam; TWL_SHARED_MEMORY->ntrSharedMem.romOffset = romOffset; TWL_SHARED_MEMORY->ntrSharedMem.multibootInfo.bootType = bootType; LoadFirmwareUserSettings(); if (!_romHeader.SupportsDsiMode()) { memcpy((void*)0x027FF800, (void*)0x02FFF800, sizeof(shared_memory_ntr_t)); memcpy((void*)0x023FF800, (void*)0x02FFF800, sizeof(shared_memory_ntr_t)); // for Pokemon memcpy((void*)0x027FF000, (void*)0x02FFF000, 0x160); memcpy((void*)0x023FF000, (void*)0x02FFF000, 0x160); } else { strcpy(TWL_SHARED_MEMORY->sysMenuVersionInfoContentId, "00000009"); TWL_SHARED_MEMORY->sysMenuVersionInfoContentLastInitialCode = 'P'; // europe } memcpy(TWL_SHARED_MEMORY->twlCardRomHeader, &_romHeader, sizeof(nds_header_twl_t)); memcpy(TWL_SHARED_MEMORY->twlRomHeader, &_romHeader, sizeof(nds_header_twl_t)); } void NdsLoader::LoadFirmwareUserSettings() { u32 flashSettingsAddress; flash_readBytes(0x20, (u8*)&flashSettingsAddress, 2); flashSettingsAddress *= 8; u8 updateCounter1; flash_readBytes(flashSettingsAddress + 0x70, &updateCounter1, 1); u8 updateCounter2; flash_readBytes(flashSettingsAddress + 0x170, &updateCounter2, 1); if ((updateCounter1 & 0x7F) == ((updateCounter2 + 1) & 0x7F)) { flash_readBytes(flashSettingsAddress, TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData, 0x74); } else { flash_readBytes(flashSettingsAddress + 0x100, TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData, 0x74); } flash_readBytes(0x36, &TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x74], 6); // mac address *(u16*)&TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x7A] = 0x1041; // wifi channels *(u32*)&TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0xE8] = 0x3E; // supported languages *(u32*)&TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0xEC] = 0; // wifi not force disabled TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0xF0] = 2; // region } bool NdsLoader::ShouldAttemptDldiPatch() { // Some games contain a fake dldi header to trick flashcards // See also: https://shutterbug2000.github.io/very-clever/ switch (_romHeader.gameCode) { // Final Fantasy IV case GAMECODE("YF4P"): case GAMECODE("YF4E"): case GAMECODE("YF4J"): // Final Fantasy Crystal Chronicles - Ring of Fates case GAMECODE("AFXE"): case GAMECODE("AFXP"): // Nanashi no Geemu case GAMECODE("YFQJ"): { return false; } } return true; } void NdsLoader::ClearMainMemory() { if (Environment::IsDsiMode()) { dma_twlSetFixedArbitration(); dma_twlFill32(0, 0, (void*)0x02000000, 0x01000000); } else { sendToArm9(IPC_COMMAND_ARM9_CLEAR_MAIN_MEM); receiveFromArm9(); } LOG_DEBUG("Main memory cleared\n"); } void NdsLoader::CreateRomClusterTable() { DWORD* clusterTab = (DWORD*)SHARED_ROM_FILE_INFO->clusterMap; clusterTab[0] = sizeof(SHARED_ROM_FILE_INFO->clusterMap) / sizeof(u32); _romFile.cltbl = clusterTab; FRESULT result = f_lseek(&_romFile, CREATE_LINKMAP); if (result != FR_OK) { LOG_FATAL("Failed to make cluster table. Result: %d\n", result); return; } SHARED_ROM_FILE_INFO->clusterShift = __builtin_ctz(_romFile.obj.fs->csize); SHARED_ROM_FILE_INFO->database = _romFile.obj.fs->database; SHARED_ROM_FILE_INFO->clusterMask = _romFile.obj.fs->csize - 1; SHARED_ROM_FILE_INFO->fileSize = f_size(&_romFile); LOG_DEBUG("Made cluster table\n"); } bool NdsLoader::TryLoadRomHeader(u32 romOffset) { if (f_lseek(&_romFile, romOffset) != FR_OK) { LOG_FATAL("Failed to seek to header\n"); return false; } UINT bytesRead = 0; FRESULT result = f_read(&_romFile, &_romHeader, sizeof(_romHeader), &bytesRead); if (result != FR_OK || bytesRead < 512) { LOG_FATAL("Failed to read rom header. Result: %d, bytesRead: %d\n", result, bytesRead); return false; } LOG_DEBUG("Rom header loaded\n"); return true; } void NdsLoader::HandleAntiPiracy() { auto apList = ApListFactory().CreateFromFile(AP_LIST_PATH); if (apList) { auto entry = apList->FindEntry(_romHeader.gameCode, _romHeader.softwareVersion); if (entry) { entry->Dump(); sendToArm9(IPC_COMMAND_ARM9_SET_AP_INFO); sendToArm9(((u32*)entry)[0]); sendToArm9(((u32*)entry)[1]); sendToArm9(((u32*)entry)[2]); sendToArm9(((u32*)entry)[3]); } else { LOG_DEBUG("No entry found in ap list\n"); } delete apList; } } void NdsLoader::RemapWram() { mem_unlockAllTwlWram(); if (_romHeader.SupportsDsiMode()) { REG_MBK6 = _romHeader.arm7MbkSettings[0]; REG_MBK7 = _romHeader.arm7MbkSettings[1]; REG_MBK8 = _romHeader.arm7MbkSettings[2]; sendToArm9(IPC_COMMAND_ARM9_WRAM_CONFIG); sendToArm9(_romHeader.wramCntSetting); sendToArm9(_romHeader.arm9MbkSettings[0]); sendToArm9(_romHeader.arm9MbkSettings[1]); sendToArm9(_romHeader.arm9MbkSettings[2]); sendToArm9(_romHeader.globalMbkSettings[0]); sendToArm9(_romHeader.globalMbkSettings[1]); sendToArm9(_romHeader.globalMbkSettings[2]); sendToArm9(_romHeader.globalMbkSettings[3]); sendToArm9(_romHeader.globalMbkSettings[4]); receiveFromArm9(); } else { REG_MBK6 = 0; REG_MBK7 = 0; REG_MBK8 = 0; sendToArm9(IPC_COMMAND_ARM9_WRAM_CONFIG); sendToArm9(3); sendToArm9(0); sendToArm9(0); sendToArm9(0); sendToArm9(0); sendToArm9(0); sendToArm9(0); sendToArm9(0); sendToArm9(0); receiveFromArm9(); } LOG_DEBUG("Wram configured\n"); } bool NdsLoader::TryLoadArm9() { if (f_lseek(&_romFile, _romHeader.arm9RomOffset + TWL_SHARED_MEMORY->ntrSharedMem.romOffset) != FR_OK) { LOG_FATAL("Failed to seek to arm9\n"); return false; } UINT bytesRead = 0; FRESULT result = f_read(&_romFile, (void*)_romHeader.arm9LoadAddress, _romHeader.arm9Size, &bytesRead); if (result != FR_OK || bytesRead != _romHeader.arm9Size) { LOG_FATAL("Failed to read arm9. Result: %d, bytesRead: %d\n", result, bytesRead); return false; } if (!TryDecryptSecureArea()) { LOG_WARNING("Failed to decrypt secure area\n"); // Keep going. In case the rom doesn't actually have a(n encrypted) secure area // it will run anyway. This can happen for homebrew for example. } LOG_DEBUG("Arm9 loaded\n"); return true; } bool NdsLoader::TryLoadArm9i() { if (_romHeader.arm9iSize == 0) { return true; } if (f_lseek(&_romFile, _romHeader.arm9iRomOffset) != FR_OK) { LOG_FATAL("Failed to seek to arm9i\n"); return false; } UINT bytesRead = 0; FRESULT result = f_read(&_romFile, (void*)_romHeader.arm9iLoadAddress, _romHeader.arm9iSize, &bytesRead); if (result != FR_OK || bytesRead != _romHeader.arm9iSize) { LOG_FATAL("Failed to read arm9i. Result: %d, bytesRead: %d\n", result, bytesRead); return false; } LOG_DEBUG("Arm9i loaded\n"); return TryDecryptArm9i(); } bool NdsLoader::TryDecryptArm9i() { if (!(_romHeader.twlFlags & (1 << 1))) { return true; } if (_romHeader.modcryptArea1Offset != _romHeader.arm9iRomOffset) { LOG_FATAL("Could not decrypt arm9i. Encrypted area offset is not equal to arm9i offset.\n"); return false; } TwlAes().DecryptModuleAes((void*)_romHeader.arm9iLoadAddress, _romHeader.modcryptArea1Size, (const aes_u128_t*)_romHeader.arm9Sha1Hmac); LOG_DEBUG("Arm9i decrypted\n"); return true; } bool NdsLoader::TryDecryptArm7i() { if (!(_romHeader.twlFlags & (1 << 1)) || _romHeader.modcryptArea2Size == 0) { return true; } if (_romHeader.modcryptArea2Offset != _romHeader.arm7iRomOffset) { LOG_FATAL("Could not decrypt arm7i. Encrypted area offset is not equal to arm7i offset.\n"); return false; } TwlAes().DecryptModuleAes((void*)_romHeader.arm7iLoadAddress, _romHeader.modcryptArea2Size, (const aes_u128_t*)_romHeader.arm7Sha1Hmac); LOG_DEBUG("Arm7i decrypted\n"); return true; } bool NdsLoader::TryLoadArm7() { if (f_lseek(&_romFile, _romHeader.arm7RomOffset + TWL_SHARED_MEMORY->ntrSharedMem.romOffset) != FR_OK) { LOG_FATAL("Failed to seek to arm7\n"); return false; } UINT bytesRead = 0; FRESULT result = f_read(&_romFile, (void*)_romHeader.arm7LoadAddress, _romHeader.arm7Size, &bytesRead); if (result != FR_OK || bytesRead != _romHeader.arm7Size) { LOG_FATAL("Failed to read arm7. Result: %d, bytesRead: %d\n", result, bytesRead); return false; } LOG_DEBUG("Arm7 loaded\n"); return true; } bool NdsLoader::TryLoadArm7i() { if (_romHeader.arm7iSize == 0) { return true; } if (f_lseek(&_romFile, _romHeader.arm7iRomOffset) != FR_OK) { LOG_FATAL("Failed to seek to arm7i\n"); return false; } UINT bytesRead = 0; FRESULT result = f_read(&_romFile, (void*)_romHeader.arm7iLoadAddress, _romHeader.arm7iSize, &bytesRead); if (result != FR_OK || bytesRead != _romHeader.arm7iSize) { LOG_FATAL("Failed to read arm7i. Result: %d, bytesRead: %d\n", result, bytesRead); return false; } LOG_DEBUG("Arm7i loaded\n"); return TryDecryptArm7i(); } void NdsLoader::HandleDldiPatching() { if (!ShouldAttemptDldiPatch()) { return; } int arm9DldiAddr = sDldiStubMatcher.FindFirstOccurance( (const u32*)_romHeader.arm9LoadAddress, _romHeader.arm9Size >> 2) << 2; if (arm9DldiAddr >= 0) { dldi_header_t* arm9Dldi = (dldi_header_t*)(_romHeader.arm9LoadAddress + arm9DldiAddr); LOG_DEBUG("Dldi found in arm9 at address %p\n", arm9Dldi); dldi_patchTo(arm9Dldi); } int arm7DldiAddr = sDldiStubMatcher.FindFirstOccurance( (const u32*)_romHeader.arm7LoadAddress, _romHeader.arm7Size >> 2) << 2; if (arm7DldiAddr >= 0) { dldi_header_t* arm7Dldi = (dldi_header_t*)(_romHeader.arm7LoadAddress + arm7DldiAddr); LOG_DEBUG("Dldi found in arm7 at address %p\n", arm7Dldi); dldi_patchTo(arm7Dldi); } } void NdsLoader::StartRom(BootMode bootMode) { LOG_DEBUG("Booting...\n"); while (gfx_getVCount() != 191); while (gfx_getVCount() == 191); sendToArm9(IPC_COMMAND_ARM9_BOOT); sendToArm9(bootMode == BootMode::SdkResetSystem ? 1 : 0); REG_IF = ~0u; if (Environment::IsDsiMode()) { REG_IF2 = ~0u; } ((entrypoint_t)_romHeader.arm7EntryAddress)(); } void NdsLoader::SetupTwlConfig() { ConsoleRegion romRegion = GetRomRegion(_romHeader.gameCode); // Set language based on rom region, TODO: allow user to override this via some config or so UserLanguage userLang = GetLanguageByRomRegion(romRegion); twl_config_t* twlConfig = (twl_config_t*)0x02000400; *(twl_config_t**)0x02FFFDFC = twlConfig; *(vu8*)0x02FFFDFA = 0x80; *(vu8*)0x02FFFDFB = 1; memset(twlConfig, 0, sizeof(twl_config_t)); twlConfig->configFlags = 0x0100000F; twlConfig->country = 0x4E; twlConfig->language = static_cast(userLang); twlConfig->rtcYear = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x66]; twlConfig->rtcOffset = *(s64*)&TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x68]; twlConfig->eulaAgreeVersion[0] = 1; twlConfig->alarmHour = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x52]; twlConfig->alarmMinute = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x53]; twlConfig->alarmEnable = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x56]; twlConfig->systemMenuUsedTitleSlots = 9; twlConfig->systemMenuFreeTitleSlots = 30; twlConfig->field24 = 3; memcpy(&twlConfig->touchCalibrationX1Adc, &TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x58], 0xC); twlConfig->field3C = 0x0201209C; twlConfig->favoriteColor = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[2]; twlConfig->birthdayMonth = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[3]; twlConfig->birthdayDay = TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[4]; memcpy(twlConfig->nickname, &TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[6], 0x16); memcpy(twlConfig->message, &TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x1C], 0x36); u8 wifiType; flash_readBytes(0x1FD, &wifiType, 1); *(vu8*)0x020005E0 = wifiType; if (wifiType == 2 || wifiType == 3) { *(vu32*)0x020005E4 = 0x520000; *(vu32*)0x020005E8 = 0x520000; *(vu32*)0x020005EC = 0x020000; } else { *(vu32*)0x020005E4 = 0x500400; *(vu32*)0x020005E8 = 0x500000; *(vu32*)0x020005EC = 0x02E000; } *(vu16*)0x020005E2 = swi_getCrc16(0xFFFF, (void*)0x020005E4, 0xC); // Set bitmask for supported languages *(vu32*)0x02FFFD68 = GetSupportedLanguagesByRegion(romRegion); *(vu32*)0x02FFFD6C = 0; *(vu8*)0x02FFFD70 = static_cast(romRegion); // region } void NdsLoader::SetDeviceListEntry(dsi_devicelist_entry_t& deviceListEntry, char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights) { deviceListEntry.driveLetter = driveLetter; deviceListEntry.flags = flags; deviceListEntry.accessRights = accessRights; strcpy(deviceListEntry.deviceName, deviceName); strcpy(deviceListEntry.path, path); } void NdsLoader::SetupDsiDeviceList() { if (_romHeader.arm7DeviceListAddress == 0) { return; } auto deviceList = (dsi_devicelist_t*)_romHeader.arm7DeviceListAddress; memset(deviceList, 0, sizeof(dsi_devicelist_t)); int listEntry = 0; SetDeviceListEntry(deviceList->deviceList[listEntry++], 'A', "nand", "/", DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_PHYSICAL, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); SetDeviceListEntry(deviceList->deviceList[listEntry++], 'D', "shared1", "nand:/shared1", DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); SetDeviceListEntry(deviceList->deviceList[listEntry++], 'F', "photo", "nand:/photo", DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); if (_romHeader.twlPrivateSavSize != 0) { SetDeviceListEntry(deviceList->deviceList[listEntry++], 'G', "dataPrv", _dsiwareSaveResult.privateSavePath, DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FILE, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); } if (_romHeader.twlPublicSavSize != 0) { SetDeviceListEntry(deviceList->deviceList[listEntry++], 'H', "dataPub", _dsiwareSaveResult.publicSavePath, DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FILE, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); } SetDeviceListEntry(deviceList->deviceList[listEntry++], 'I', "sdmc", "nand:/.", DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC | DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER, DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ | DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE); strcpy(deviceList->appFileName, _dsiwareSaveResult.romFilePath); } bool NdsLoader::TrySetupDsiWareSave() { return DsiWareSaveArranger().SetupDsiWareSave(_romPath, _romHeader, _dsiwareSaveResult); } bool NdsLoader::TryDecryptSecureArea() { if (_romHeader.arm9RomOffset < 0x4000 || _romHeader.arm9RomOffset >= 0x8000) { return true; } if (((u32*)_romHeader.arm9LoadAddress)[0] == 0xE7FFDEFF && ((u32*)_romHeader.arm9LoadAddress)[1] == 0xE7FFDEFF) { return true; } u16 secureAreaCrc = swi_getCrc16(0xFFFF, (const void*)_romHeader.arm9LoadAddress, 0x8000 - _romHeader.arm9RomOffset); if (secureAreaCrc != _romHeader.secureAreaCrc) { return true; } auto bios7File = std::make_unique(); auto keyTable = std::make_unique_for_overwrite(); UINT bytesRead = 0; if (f_open(bios7File.get(), BIOS_NDS7_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK || f_lseek(bios7File.get(), 0x30) != FR_OK || f_read(bios7File.get(), keyTable.get(), sizeof(Blowfish::KeyTable), &bytesRead) != FR_OK || bytesRead != sizeof(Blowfish::KeyTable)) { return false; } auto blowfish = std::make_unique(keyTable.get()); blowfish->TransformTable(_romHeader.gameCode, 3, 8); blowfish->Decrypt( (const void*)_romHeader.arm9LoadAddress, (void*)_romHeader.arm9LoadAddress, 0x4800 - _romHeader.arm9RomOffset); ((u32*)_romHeader.arm9LoadAddress)[0] = 0xE7FFDEFF; ((u32*)_romHeader.arm9LoadAddress)[1] = 0xE7FFDEFF; LOG_DEBUG("Decrypted secure area\n"); return true; } void NdsLoader::HandleIQueRegionFreePatching() { if ((_romHeader.flags & 0x80) == 0x80) { _romHeader.flags &= ~0x80; _romHeader.headerCrc = swi_getCrc16(0xFFFF, (void*)&_romHeader, 0x15E); } } ConsoleRegion NdsLoader::GetRomRegion(u32 gameCode) { u8 gameRegionCode = (gameCode >> 24) & 0xFF; if (gameRegionCode != 'A' && gameRegionCode != 'O') { // Determine region by TID if (gameRegionCode == 'J') { return ConsoleRegion::Japan; } else if (gameRegionCode == 'E' || gameRegionCode == 'T') { return ConsoleRegion::America; } else if (gameRegionCode == 'P' || gameRegionCode == 'V') { return ConsoleRegion::Europe; } else if (gameRegionCode == 'U') { return ConsoleRegion::Australia; } else if (gameRegionCode == 'C') { return ConsoleRegion::China; } else if (gameRegionCode== 'K') { return ConsoleRegion::Korea; } } return ConsoleRegion::Europe; // Default to EUR } UserLanguage NdsLoader::GetLanguageByRomRegion(ConsoleRegion romRegion) { UserLanguage userLang = static_cast(TWL_SHARED_MEMORY->ntrSharedMem.firmwareUserData[0x64] & 0x07); if (romRegion == ConsoleRegion::Japan) { return UserLanguage::Japanese; } else if (romRegion == ConsoleRegion::America && (userLang != UserLanguage::English || userLang != UserLanguage::French || userLang != UserLanguage::Spanish)) { return UserLanguage::English; } else if (romRegion == ConsoleRegion::Europe && userLang < UserLanguage::English && userLang > UserLanguage::Spanish) { return UserLanguage::English; } else if (romRegion == ConsoleRegion::China) { return UserLanguage::Chinese; } else if (romRegion == ConsoleRegion::Korea) { return UserLanguage::Korean; } return userLang; } u32 NdsLoader::GetSupportedLanguagesByRegion(ConsoleRegion region) { switch (region) { case ConsoleRegion::Japan: { return 0x01; } case ConsoleRegion::America: { return 0x26; } case ConsoleRegion::Europe: { return 0x3E; } case ConsoleRegion::Australia: { return 0x02; } case ConsoleRegion::China: { return 0x40; } case ConsoleRegion::Korea: { return 0x80; } } return 0x3E; }