mirror of
https://github.com/LNH-team/pico-loader.git
synced 2026-01-10 08:29:29 +01:00
1127 lines
35 KiB
C++
1127 lines
35 KiB
C++
#include "common.h"
|
|
#include <string.h>
|
|
#include <libtwl/rtos/rtosIrq.h>
|
|
#include <libtwl/sys/swi.h>
|
|
#include <libtwl/mem/memTwlWram.h>
|
|
#include <libtwl/dma/dmaTwl.h>
|
|
#include <libtwl/i2c/i2cMcu.h>
|
|
#include <libtwl/gfx/gfxStatus.h>
|
|
#include <libtwl/spi/spiFlash.h>
|
|
#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())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
SetupTwlConfig();
|
|
TwlAes().SetupAes(&_romHeader);
|
|
}
|
|
|
|
RemapWram();
|
|
}
|
|
|
|
if (bootMode != BootMode::Multiboot)
|
|
{
|
|
if (!TryLoadArm9())
|
|
{
|
|
ErrorDisplay().PrintError("Failed to load arm9.");
|
|
return;
|
|
}
|
|
|
|
if (Environment::IsDsiMode() && _romHeader.IsTwlRom())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
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();
|
|
}
|
|
|
|
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.IsTwlRom())
|
|
{
|
|
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::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.IsTwlRom())
|
|
{
|
|
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<u8[]>(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.IsTwlRom())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
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.IsTwlRom())
|
|
{
|
|
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<u8>(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<u8>(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<FIL>();
|
|
auto keyTable = std::make_unique_for_overwrite<Blowfish::KeyTable>();
|
|
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<Blowfish>(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<UserLanguage>(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;
|
|
}
|