Initial commit

This commit is contained in:
Gericom
2025-11-22 11:08:28 +01:00
commit 9cf3ffbfcf
358 changed files with 58350 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "ApListFactory.h"
ApList* ApListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open ap list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(ApListEntry);
auto entries = std::make_unique_for_overwrite<ApListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(ApListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(ApListEntry))
{
LOG_FATAL("Failed to read ap list file\n");
return nullptr;
}
f_close(&file);
return new ApList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "ApList.h"
/// @brief Factory for creating \see ApList instances.
class ApListFactory
{
public:
/// @brief Creates an \see ApList instance from the file at the given \p path.
/// @param path The ap list file path.
/// @return A pointer to the constructed \see ApList instance, or \c nullptr if construction failed.
ApList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,151 @@
#include "common.h"
#include <libtwl/dma/dmaNitro.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/sio/sio.h>
#include <libtwl/sound/sound.h>
#include <libtwl/sound/soundCapture.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/sys/sysPower.h>
#include <libtwl/timer/timer.h>
#include "SdmmcDefinitions.h"
#include "core/Environment.h"
#include "Arm7IoRegisterClearer.h"
void Arm7IoRegisterClearer::ClearIoRegisters() const
{
REG_IME = 0;
REG_IE = 0;
if (Environment::IsDsiMode())
{
ClearTwlIoRegisters();
}
ClearNtrIoRegisters();
}
void Arm7IoRegisterClearer::ClearNtrIoRegisters() const
{
REG_DISPSTAT = 0;
REG_TM0CNT_H = 0; // timer 0
REG_TM0CNT_L = 0;
REG_TM1CNT_H = 0; // timer 1
REG_TM1CNT_L = 0;
REG_TM2CNT_H = 0; // timer 2
REG_TM2CNT_L = 0;
REG_TM3CNT_H = 0; // timer 3
REG_TM3CNT_L = 0;
REG_DMA0CNT = 0; // dma 0
REG_DMA0SAD = 0;
REG_DMA0DAD = 0;
REG_DMA1CNT = 0; // dma 1
REG_DMA1SAD = 0;
REG_DMA1DAD = 0;
REG_DMA2CNT = 0; // dma 2
REG_DMA2SAD = 0;
REG_DMA2DAD = 0;
REG_DMA3CNT = 0; // dma 3
REG_DMA3SAD = 0;
REG_DMA3DAD = 0;
REG_RCNT0_L = 0;
for (int i = 0; i < 16; i++)
{
REG_SOUNDxCNT(i) = 0;
REG_SOUNDxSAD(i) = 0;
REG_SOUNDxTMR(i) = 0;
REG_SOUNDxPNT(i) = 0;
REG_SOUNDxLEN(i) = 0;
}
REG_SOUNDCNT = 0;
REG_SNDCAP0CNT = 0;
REG_SNDCAP1CNT = 0;
REG_SNDCAP0DAD = 0;
REG_SNDCAP0LEN = 0;
REG_SNDCAP1DAD = 0;
REG_SNDCAP1LEN = 0;
sys_setWifiPower(false);
sys_setSoundPower(true);
}
void Arm7IoRegisterClearer::ClearTwlIoRegisters() const
{
REG_IE2 = 0;
REG_NDMA0SAD = 0;
REG_NDMA0DAD = 0;
REG_NDMA0TCNT = 0;
REG_NDMA0WCNT = 0;
REG_NDMA0BCNT = 0;
REG_NDMA0FDATA = 0;
REG_NDMA0CNT = 0;
REG_NDMA1SAD = 0;
REG_NDMA1DAD = 0;
REG_NDMA1TCNT = 0;
REG_NDMA1WCNT = 0;
REG_NDMA1BCNT = 0;
REG_NDMA1FDATA = 0;
REG_NDMA1CNT = 0;
REG_NDMA2SAD = 0;
REG_NDMA2DAD = 0;
REG_NDMA2TCNT = 0;
REG_NDMA2WCNT = 0;
REG_NDMA2BCNT = 0;
REG_NDMA2FDATA = 0;
REG_NDMA2CNT = 0;
REG_NDMA3SAD = 0;
REG_NDMA3DAD = 0;
REG_NDMA3TCNT = 0;
REG_NDMA3WCNT = 0;
REG_NDMA3BCNT = 0;
REG_NDMA3FDATA = 0;
REG_NDMA3CNT = 0;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN32) = 512;
*(vu16*)(SDMMC_BASE + REG_SDBLKCOUNT32) = 1;
*(vu16*)(SDMMC_BASE + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(SDMMC_BASE + REG_SDRESET) |= 1u;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS0) = 0;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS1) = 0;
*(vu16*)(SDMMC_BASE + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(SDMMC_BASE + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDCLKCTL) = 0x20;
*(vu16*)(SDMMC_BASE + REG_SDOPT) = 0x40EE;
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN) = 512;
*(vu16*)(SDMMC_BASE + REG_SDSTOP) = 0;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(0x04004A00 + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN32) = 512;
*(vu16*)(0x04004A00 + REG_SDBLKCOUNT32) = 1;
*(vu16*)(0x04004A00 + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(0x04004A00 + REG_SDRESET) |= 1u;
*(vu16*)(0x04004A00 + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(0x04004A00 + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(0x04004A00 + REG_SDSTATUS0) = 0;
*(vu16*)(0x04004A00 + REG_SDSTATUS1) = 0;
*(vu16*)(0x04004A00 + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(0x04004A00 + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDCLKCTL) = 0x20;
*(vu16*)(0x04004A00 + REG_SDOPT) = 0x40EE;
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN) = 512;
*(vu16*)(0x04004A00 + REG_SDSTOP) = 0;
}

View File

@@ -0,0 +1,13 @@
#pragma once
/// @brief Class for clearing the arm9 IO registers.
class Arm7IoRegisterClearer
{
public:
/// @brief Clears the arm7 IO registers.
void ClearIoRegisters() const;
private:
void ClearNtrIoRegisters() const;
void ClearTwlIoRegisters() const;
};

View File

@@ -0,0 +1,113 @@
#include "common.h"
#include <bit>
#include "Blowfish.h"
void Blowfish::Encrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Encrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Encrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 0; i < 16; i++)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[16]) | ((u64)(y ^ _keyTable.pTable[17]) << 32);
}
void Blowfish::Decrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Decrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Decrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 17; i >= 2; i--)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[1]) | ((u64)(y ^ _keyTable.pTable[0]) << 32);
}
void Blowfish::TransformTable(u32 idCode, int level, int modulo)
{
u32 keyCode[3] = { idCode, idCode >> 1, idCode << 1 };
if (level >= 1)
{
ApplyKeyCode(&keyCode[0], modulo);
}
if (level >= 2)
{
ApplyKeyCode(&keyCode[0], modulo);
}
keyCode[1] <<= 1;
keyCode[2] >>= 1;
if (level >= 3)
{
ApplyKeyCode(&keyCode[0], modulo);
}
}
void Blowfish::ApplyKeyCode(u32* keyCode, int modulo)
{
Encrypt(&keyCode[1], &keyCode[1], 8);
Encrypt(&keyCode[0], &keyCode[0], 8);
const u32 reversedKeyCode[3] =
{
std::byteswap(keyCode[0]),
std::byteswap(keyCode[1]),
std::byteswap(keyCode[2])
};
int keyCodeIndex = 0;
for (u32 i = 0; i < BLOWFISH_PTABLE_ENTRY_COUNT; i++)
{
_keyTable.pTable[i] ^= reversedKeyCode[keyCodeIndex];
if (++keyCodeIndex == (modulo >> 2))
{
keyCodeIndex = 0;
}
}
u64 scratch = 0;
u64* keyTable = (u64*)&_keyTable;
for (u32 i = 0; i < (sizeof(KeyTable) / 8); i++)
{
scratch = Encrypt(scratch);
keyTable[i] = (scratch >> 32) | ((scratch & 0xFFFFFFFFu) << 32);
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <string.h>
#define BLOWFISH_PTABLE_ENTRY_COUNT 18
#define BLOWFISH_SBOX_COUNT 4
#define BLOWFISH_SBOX_ENTRY_COUNT 256
/// @brief Class for blowfish encryption and decryption.
class Blowfish
{
public:
/// @brief Struct representing a blowfish key table.
struct KeyTable
{
u32 pTable[BLOWFISH_PTABLE_ENTRY_COUNT];
u32 sBoxes[BLOWFISH_SBOX_COUNT][BLOWFISH_SBOX_ENTRY_COUNT];
};
static_assert(sizeof(KeyTable) == 0x1048, "Invalid size of Blowfish::KeyTable.");
/// @brief Constructs an instance of \see Blowfish using the given \p keyTable.
/// @param keyTable The key table to use. A copy will be made into the constructed class.
explicit Blowfish(const KeyTable* keyTable)
: Blowfish(keyTable->pTable, keyTable->sBoxes) { }
/// @brief Constructs an instance of \see Blowfish using the given \p pTable and \p sBoxes.
/// @param pTable The p table to use. A copy will be made into the constructed class.
/// @param sBoxes The s boxes to use. A copy will be made into the constructed class.
Blowfish(const void* pTable, const void* sBoxes)
{
memcpy(_keyTable.pTable, pTable, sizeof(_keyTable.pTable));
memcpy(_keyTable.sBoxes, sBoxes, sizeof(_keyTable.sBoxes));
}
/// @brief Encrypts the given \p length from \p src to \p dst.
/// @param src The source buffer.
/// @param dst The encrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Encrypt(const void* src, void* dst, u32 length) const;
/// @brief Encrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to encrypt.
/// @return The encrypted result.
u64 Encrypt(u64 value) const;
/// @brief Drcrypts the given \p length from \p src to \p dst.
/// @param src The encrypted source buffer.
/// @param dst The decrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Decrypt(const void* src, void* dst, u32 length) const;
/// @brief Decrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to decrypt.
/// @return The decrypted result.
u64 Decrypt(u64 value) const;
/// @brief Transforms the key table by the given \p idCode, \p level and \p modulo.
/// @param idCode The id code to use.
/// @param level The transform level to use.
/// @param modulo The modulo to use.
void TransformTable(u32 idCode, int level, int modulo);
private:
void ApplyKeyCode(u32* keyCode, int modulo);
KeyTable _keyTable;
};

View File

@@ -0,0 +1,14 @@
#pragma once
/// @brief The Pico Loader boot mode.
enum class BootMode
{
/// @brief Boot a retail or homebrew rom.
Normal,
/// @brief Reboot a retail rom that used OS_ResetSystem.
SdkResetSystem,
/// @brief Boot a multiboot rom that is already loaded into memory.
Multiboot
};

View File

@@ -0,0 +1,107 @@
#include "common.h"
#include <string.h>
#include "SaveList.h"
#include "SaveListFactory.h"
#include "fileInfo.h"
#include "CardSaveArranger.h"
#define SAVE_LIST_PATH "/_pico/savelist.bin"
#define DEFAULT_SAVE_SIZE (512 * 1024)
#define SAVE_FILL_VALUE 0xFF
bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
{
SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH);
u32 saveSize = DEFAULT_SAVE_SIZE;
if (saveList)
{
const auto saveListEntry = saveList->FindEntry(gameCode);
if (!saveListEntry)
{
LOG_WARNING("Game code %c%c%c%c not found in save list\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24);
}
else
{
saveSize = saveListEntry->GetSaveSize();
saveListEntry->Dump();
}
delete saveList;
}
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
LOG_DEBUG("Save file: %s\n", savePath);
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), initialSize) != FR_OK)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
auto ffBuffer = std::make_unique<u8[]>(512);
memset(ffBuffer.get(), SAVE_FILL_VALUE, 512);
u32 offset = initialSize;
// Align to 512 bytes
if ((offset & 511) != 0)
{
u32 remainingTo512 = 512 - (offset & 511);
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), remainingTo512, &bytesWritten) != FR_OK ||
bytesWritten != remainingTo512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += remainingTo512;
}
// Write in 512-byte blocks
while (offset < saveSize)
{
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += 512;
}
}
DWORD* clusterTab = (DWORD*)SHARED_SAVE_FILE_INFO->clusterMap;
clusterTab[0] = sizeof(SHARED_SAVE_FILE_INFO->clusterMap) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make save cluster table. Result: %d\n", seekResult);
return false;
}
SHARED_SAVE_FILE_INFO->clusterShift = __builtin_ctz(file->obj.fs->csize);
SHARED_SAVE_FILE_INFO->database = file->obj.fs->database;
SHARED_SAVE_FILE_INFO->clusterMask = file->obj.fs->csize - 1;
LOG_DEBUG("Made save cluster table\n");
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close save file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
/// @brief Class for setting up the save file for retail card roms.
class CardSaveArranger
{
public:
/// @brief Sets up the save file at \p savePath for a retail card rom with the given \p gameCode.
/// @param gameCode The game code of the retail card rom.
/// @param savePath The desired save file path.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupCardSave(u32 gameCode, const TCHAR* savePath) const;
};

View File

@@ -0,0 +1,356 @@
#include "common.h"
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/sound/twlI2s.h>
#include <libtwl/sound/sound.h>
#include <libtwl/spi/spiCodec.h>
#include <libtwl/spi/spiPmic.h>
#include <libtwl/sys/twlScfg.h>
#include "ipc.h"
#include "gameCode.h"
#include "DSMode.h"
void DSMode::SwitchToDSMode(u32 gameCode) const
{
SwitchToDSTouchAndSoundMode(gameCode);
mcu_writeReg(MCU_REG_MODE, 0);
*(vu16*)0x04004C04 |= (1 << 8); // ntr wifi
REG_SCFG_A9ROM = SCFG_A9ROM_DISABLE_SECURE | SCFG_A9ROM_NITRO;
REG_SCFG_A7ROM = SCFG_A7ROM_DISABLE_SECURE | SCFG_A7ROM_NITRO | SCFG_A7ROM_DISABLE_FUSE;
REG_SCFG_EXT = 0x12A03000u;
sendToArm9(IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE);
receiveFromArm9();
LOG_DEBUG("Switched to ds mode\n");
}
void DSMode::SwitchToDSTouchAndSoundMode(u32 gameCode) const
{
REG_I2SCNT = I2SCNT_MIX_RATIO_DSP_0_NITRO_8 | I2SCNT_FREQUENCY_32728_HZ;
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 21);
}
REG_I2SCNT |= I2SCNT_ENABLE;
SwitchCodecToDSMode(gameCode);
REG_SOUNDCNT = SOUNDCNT_MASTER_ENABLE | SOUNDCNT_MASTER_VOLUME(0x7F);
LOG_DEBUG("Switched to ds touch and sound mode\n");
}
void DSMode::SwitchCodecToDSMode(u32 gameCode) const
{
// 0xAC: special setting (when found special gamecode)
// 0xA7: normal setting (for any other gamecodes)
u8 volLevel = ShouldUseVolumeFix(gameCode) ? 0xAC : 0xA7;
// Touchscreen
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_HPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_SPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_MICBIAS);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x0C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x10);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x00);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x14);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0x00);
codec_readRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x02);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
codec_writeRegister(CODEC_REG_PAGE0_RESET, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_GPI1_GPI2_PIN_CONTROL, 0x66);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x81);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0xA1);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 0x15);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x83);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x83);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK);
codec_writeRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK, 0x08);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_LEFT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_DAC_RIGHT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, 0x2B);
codec_writeRegister(CODEC_REG_PAGE1_DELTA_SIGMA_MONO_ADC_CHANNEL_FINE_GAIN_INPUT_SELECTION_FOR_P_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_ADC_INPUT_SELECTION_FOR_M_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_INPUT_CM_SETTINGS, 0x60);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x02);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x10);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x40);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0xD4);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x44);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0xA7);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0xA7);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x03);
}
codec_setPage(CODEC_PAGE_3);
{
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_2, 0x00);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
codec_readRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
// Set remaining values
codec_writeRegister(0x03, 0x44);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_LSB, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MINIDSP_INTERPOLATION, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_ADC_AOSR_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MINIDSP_DECIMATION, 0x04);
codec_writeRegister(CODEC_REG_PAGE0_CLKOUT_M_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_BCLK_N_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_FLAG_REGISTER, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_GPIO1_IN_OUT_PIN_CONTROL, 0x34);
codec_writeRegister(CODEC_REG_PAGE0_GPIO2_IN_OUT_PIN_CONTROL, 0x32);
codec_writeRegister(CODEC_REG_PAGE0_SDOUT_OUT_PIN_CONTROL, 0x12);
codec_writeRegister(CODEC_REG_PAGE0_SDIN_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_MISO_OUT_PIN_CONTROL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_SCLK_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_DAC_INSTRUCTION_SET, 0x19);
codec_writeRegister(CODEC_REG_PAGE0_ADC_INSTRUCTION_SET, 0x05);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_1, 0x0F);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_2, 0x38);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MID, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_LSB, 0xEE);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_MSB, 0x10);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_LSB, 0xD8);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_MSB, 0x7E);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_LSB, 0xE3);
codec_writeRegister(CODEC_REG_PAGE0_AGC_MAXIMUM_GAIN, 0x7F);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0xD2);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_GAIN, 0x2C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x70);
codec_writeRegister(CODEC_REG_PAGE1_HP_DRIVER_CONTROL, 0x20);
}
// Finish up!
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_255);
{
codec_writeRegister(CODEC_REG_PAGE255_BACKWARDS_COMPATIBILITY_MODE, CODEC_PAGE255_BACKWARDS_COMPATIBILITY_MODE_ON);
}
pmic_setAmplifierEnable(true);
}
bool DSMode::ShouldUseVolumeFix(u32 gameCode) const
{
switch (gameCode & 0xFFFFFF)
{
case GAMECODE_NO_REGION("A3T"):
case GAMECODE_NO_REGION("A4U"):
case GAMECODE_NO_REGION("A5H"):
case GAMECODE_NO_REGION("A5I"):
case GAMECODE_NO_REGION("A8N"):
case GAMECODE_NO_REGION("ABJ"):
case GAMECODE_NO_REGION("ABN"):
case GAMECODE_NO_REGION("ABX"):
case GAMECODE_NO_REGION("ACC"):
case GAMECODE_NO_REGION("ACL"):
case GAMECODE_NO_REGION("ACZ"):
case GAMECODE_NO_REGION("ADA"):
case GAMECODE_NO_REGION("AHD"):
case GAMECODE_NO_REGION("AJU"):
case GAMECODE_NO_REGION("AKA"):
case GAMECODE_NO_REGION("AKE"):
case GAMECODE_NO_REGION("ALH"):
case GAMECODE_NO_REGION("AMH"):
case GAMECODE_NO_REGION("AN9"):
case GAMECODE_NO_REGION("ANR"):
case GAMECODE_NO_REGION("APA"):
case GAMECODE_NO_REGION("APY"):
case GAMECODE_NO_REGION("ART"):
case GAMECODE_NO_REGION("AV2"):
case GAMECODE_NO_REGION("AV3"):
case GAMECODE_NO_REGION("AV4"):
case GAMECODE_NO_REGION("AV5"):
case GAMECODE_NO_REGION("AV6"):
case GAMECODE_NO_REGION("AVI"):
case GAMECODE_NO_REGION("AVT"):
case GAMECODE_NO_REGION("AWH"):
case GAMECODE_NO_REGION("AWY"):
case GAMECODE_NO_REGION("AXB"):
case GAMECODE_NO_REGION("AXJ"):
case GAMECODE_NO_REGION("AY7"):
case GAMECODE_NO_REGION("AYK"):
case GAMECODE_NO_REGION("AZW"):
case GAMECODE_NO_REGION("CPU"):
case GAMECODE_NO_REGION("YB2"):
case GAMECODE_NO_REGION("YB3"):
case GAMECODE_NO_REGION("YBO"):
case GAMECODE_NO_REGION("YCH"):
case GAMECODE_NO_REGION("YCQ"):
case GAMECODE_NO_REGION("YFE"):
case GAMECODE_NO_REGION("YFS"):
case GAMECODE_NO_REGION("YG8"):
case GAMECODE_NO_REGION("YGD"):
case GAMECODE_NO_REGION("YKR"):
case GAMECODE_NO_REGION("YNZ"):
case GAMECODE_NO_REGION("YO9"):
case GAMECODE_NO_REGION("YON"):
case GAMECODE_NO_REGION("YRM"):
case GAMECODE_NO_REGION("YT3"):
case GAMECODE_NO_REGION("YW2"):
case GAMECODE_NO_REGION("YYK"):
{
return true;
}
default:
{
return false;
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
/// @brief Class handling switching from DSi to DS mode.
class DSMode
{
public:
/// @brief Switches to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch to DS mode for.
void SwitchToDSMode(u32 gameCode) const;
/// @brief Switches CODEC to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch CODEC to DS mode for.
void SwitchToDSTouchAndSoundMode(u32 gameCode) const;
private:
void SwitchCodecToDSMode(u32 gameCode) const;
bool ShouldUseVolumeFix(u32 gameCode) const;
};

View File

@@ -0,0 +1,106 @@
#include "common.h"
#include <string.h>
#include "DldiDriver.h"
void DldiDriver::Relocate(u32 targetAddress)
{
u32 currentAddress = _driver->driverStartAddress;
if (currentAddress == targetAddress)
return;
u32 currentEndAddress = _driver->driverEndAddress;
if (_driver->fixFlags & DLDI_FIX_ALL)
{
LOG_WARNING("Dldi driver uses FIX_ALL flag");
u32* ptr = (u32*)((u8*)_driver + sizeof(dldi_header_t));
u32 size = currentEndAddress - currentAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
else
{
// note that we are not going to do those fixes when we did
// FIX_ALL already, since it covers them already
if (_driver->fixFlags & DLDI_FIX_GLUE)
{
u32* ptr = (u32*)((u8*)_driver + _driver->glueStartAddress - currentAddress);
u32 size = _driver->glueEndAddress - _driver->glueStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
if (_driver->fixFlags & DLDI_FIX_GOT)
{
u32* ptr = (u32*)((u8*)_driver + _driver->gotStartAddress - currentAddress);
u32 size = _driver->gotEndAddress - _driver->gotStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
}
_driver->driverStartAddress = targetAddress;
_driver->driverEndAddress = _driver->driverEndAddress + targetAddress - currentAddress;
_driver->glueStartAddress = _driver->glueStartAddress + targetAddress - currentAddress;
_driver->glueEndAddress = _driver->glueEndAddress + targetAddress - currentAddress;
_driver->gotStartAddress = _driver->gotStartAddress + targetAddress - currentAddress;
_driver->gotEndAddress = _driver->gotEndAddress + targetAddress - currentAddress;
_driver->bssStartAddress = _driver->bssStartAddress + targetAddress - currentAddress;
_driver->bssEndAddress = _driver->bssEndAddress + targetAddress - currentAddress;
_driver->startupFuncAddress = _driver->startupFuncAddress + targetAddress - currentAddress;
_driver->isInsertedFuncAddress = _driver->isInsertedFuncAddress + targetAddress - currentAddress;
_driver->readSectorsFuncAddress = _driver->readSectorsFuncAddress + targetAddress - currentAddress;
_driver->writeSectorsFuncAddress = _driver->writeSectorsFuncAddress + targetAddress - currentAddress;
_driver->clearStatusFuncAddress = _driver->clearStatusFuncAddress + targetAddress - currentAddress;
_driver->shutdownFuncAddress = _driver->shutdownFuncAddress + targetAddress - currentAddress;
}
void DldiDriver::PrepareForUse()
{
if (_driver->fixFlags & DLDI_FIX_BSS)
{
memset((u8*)_driver + _driver->bssStartAddress - _driver->driverStartAddress, 0,
_driver->bssEndAddress - _driver->bssStartAddress);
}
}
bool DldiDriver::PatchTo(dldi_header_t* stub)
{
if (_driver->dldiMagic != DLDI_MAGIC)
return false;
u32 stubSize = stub->stubSize;
if (stubSize < _driver->driverSize)
{
LOG_ERROR("Dldi stub of size %d is not large enough for driver of size %d\n",
stubSize, _driver->driverSize);
return false;
}
u32 targetAddress = stub->driverStartAddress;
u32 driverSize = 1 << _driver->driverSize;
memcpy(stub, _driver, driverSize);
stub->stubSize = stubSize;
auto newDriver = DldiDriver(stub);
newDriver.Relocate(targetAddress);
newDriver.PrepareForUse();
return true;
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include "dldiHeader.h"
/// @brief Class representing a DLDI driver.
class DldiDriver
{
dldi_header_t* _driver;
public:
explicit DldiDriver(dldi_header_t* driver)
: _driver(driver) { }
/// @brief Returns whether the driver has been relocated to its current ram address.
/// @return True if the driver is relocated to its current ram address, or false otherwise.
bool IsRelocated() const
{
return _driver->driverStartAddress == (u32)_driver;
}
/// @brief Relocates the driver to its current ram address.
void Relocate()
{
Relocate((u32)_driver);
}
/// @brief Relocates the driver to the given targetAddress.
/// @param targetAddress The address to relocate the driver to.
void Relocate(u32 targetAddress);
/// @brief Clears the bss area of the driver if needed, making it ready for use.
/// This method should only be used after placing the driver in a memory region
/// with enough space and relocating it.
void PrepareForUse();
/// @brief Patches this dldi driver into the given stub.
/// @param stub The stub to patch the dldi driver into.
/// @return \c true if patching was successful, or \c false otherwise.
bool PatchTo(dldi_header_t* stub);
/// @brief Calls the startup function of the DLDI driver and returns the result.
/// @return \c true if startup was successful, or \c false otherwise.
bool Startup()
{
return ((dldi_startup_func_t)_driver->startupFuncAddress)();
}
/// @brief Calls the is inserted function of the DLDI driver and returns the result.
/// @return \c true if the storage medium is inserted, or \c false otherwise.
bool IsInserted()
{
return ((dldi_inserted_func_t)_driver->isInsertedFuncAddress)();
}
/// @brief Reads sectors using this DLDI driver.
/// @param sector The sector to start reading at.
/// @param count The number of sectors to read.
/// @param dst The destination buffer.
/// @return \c true if reading was successful, or \c false otherwise.
bool ReadSectors(u32 sector, u32 count, void* dst)
{
return ((dldi_read_func_t)_driver->readSectorsFuncAddress)(sector, count, dst);
}
/// @brief Writes sectors using this DLDI driver.
/// @param sector The sector to start writing at.
/// @param count The number of sectors to write.
/// @param src The source buffer.
/// @return \c true if writing was successful, or \c false otherwise.
bool WriteSectors(u32 sector, u32 count, const void* src)
{
return ((dldi_write_func_t)_driver->writeSectorsFuncAddress)(sector, count, src);
}
/// @brief Calls the clear status function of the DLDI driver and returns the result.
/// @return \c true if clear status was successful, or \c false otherwise.
bool ClearStatus()
{
return ((dldi_clearstatus_func_t)_driver->clearStatusFuncAddress)();
}
/// @brief Calls the shutdown function of the DLDI driver and returns the result.
/// @return \c true if shutdown was successful, or \c false otherwise.
bool Shutdown()
{
return ((dldi_shutdown_func_t)_driver->shutdownFuncAddress)();
}
};

View File

@@ -0,0 +1,39 @@
#pragma once
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC 0
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_NAND 1
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_PHYSICAL (0 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FILE (1 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER (2 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_FIRST (0 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_SECOND (1 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_ENCRYPTED (1 << 7)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ (1 << 1)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE (1 << 2)
/// @brief Struct representing a DSi device list entry.
struct dsi_devicelist_entry_t
{
char driveLetter;
u8 flags;
u8 accessRights;
u8 padding;
char deviceName[16];
char path[64];
};
static_assert(sizeof(dsi_devicelist_entry_t) == 0x54, "Invalid dsi_devicelist_entry_t size.");
/// @brief Struct representing a DSi device list.
struct dsi_devicelist_t
{
dsi_devicelist_entry_t deviceList[11];
u8 padding[0x24];
char appFileName[64];
};
static_assert(sizeof(dsi_devicelist_t) == 0x400, "Invalid dsi_devicelist_t size.");

View File

@@ -0,0 +1,235 @@
#include "common.h"
#include <string.h>
#include <memory>
#include "DsiWareSaveArranger.h"
bool DsiWareSaveArranger::SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const
{
char path[256];
strcpy(path, romPath);
if (!CreateDeviceListPath(path, result.romFilePath))
{
return false;
}
if (romHeader.twlPrivateSavSize != 0)
{
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'r';
extension[3] = 'v';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPrivateSavSize) ||
!CreateDeviceListPath(path, result.privateSavePath))
{
return false;
}
}
if (romHeader.twlPublicSavSize != 0)
{
strcpy(path, romPath);
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'u';
extension[3] = 'b';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPublicSavSize) ||
!CreateDeviceListPath(path, result.publicSavePath))
{
return false;
}
}
return true;
}
bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const
{
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), 0) != FR_OK)
{
LOG_FATAL("Failed to create private save file\n");
return false;
}
auto fatHeader = CreateFatHeader(saveSize);
UINT bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), sizeof(fat_header_t), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(fat_header_t))
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
memset(fatHeader.get(), 0, 512);
while (f_tell(file.get()) < saveSize)
{
bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
}
}
f_close(file.get());
return true;
}
std::unique_ptr<DsiWareSaveArranger::fat_header_t> DsiWareSaveArranger::CreateFatHeader(u32 saveSize) const
{
// based on https://github.com/Epicpkmn11/NTM/blob/master/arm9/src/sav.c
const u32 maxSectors = saveSize >> 9;
u32 sectorCount = 1;
u32 secPerTrk = 1;
u32 numHeads = 1;
u32 sectorCountNext = 0;
while (sectorCountNext <= maxSectors)
{
sectorCountNext = secPerTrk * (numHeads + 1) * (numHeads + 1);
if (sectorCountNext <= maxSectors)
{
numHeads++;
sectorCount = sectorCountNext;
secPerTrk++;
sectorCountNext = secPerTrk * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
sectorCount = sectorCountNext;
}
}
}
sectorCountNext = (secPerTrk + 1) * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
secPerTrk++;
sectorCount = sectorCountNext;
}
u32 sectorsPerCluster;
u32 totalClusters;
if (sectorCount > 8192)
{
sectorsPerCluster = 8;
totalClusters = (sectorCount + 7) >> 3;
}
else if (sectorCount > 1024)
{
sectorsPerCluster = 4;
totalClusters = (sectorCount + 3) >> 2;
}
else
{
sectorsPerCluster = 1;
totalClusters = sectorCount;
}
u32 fatSizeInBytes = ((totalClusters + 1) >> 1) * 3; // 2 sectors -> 3 byte
auto fatHeader = std::make_unique<fat_header_t>();
fatHeader->jumpInstruction[0] = 0xE9;
fatHeader->jumpInstruction[1] = 0;
fatHeader->jumpInstruction[2] = 0;
memcpy(fatHeader->oemName, "MSWIN4.1", 8);
fatHeader->bytesPerSector = 512;
fatHeader->sectorsPerCluster = sectorsPerCluster;
fatHeader->reservedSectors = 1;
fatHeader->numberOfFats = 2;
fatHeader->rootEntries = saveSize < 0x8C000 ? 32 : 512;
fatHeader->totalSectorsSmall = sectorCount;
fatHeader->mediaType = 0xF8; // "hard drive"
fatHeader->fatSectorCount = (fatSizeInBytes + 511) >> 9;
fatHeader->sectorsPerTrack = secPerTrk;
fatHeader->numberOfHeads = numHeads;
fatHeader->hiddenSectors = 0;
fatHeader->totalSectorsLarge = 0;
fatHeader->diskNumber = 5;
fatHeader->BS_Reserved1 = 0;
fatHeader->bootSignature = 0x29;
fatHeader->volumeSerialNumber = 0x12345678;
memcpy(fatHeader->volumeLabel, "VOLUMELABEL", 11);
memcpy(fatHeader->fileSystemType, "FAT12 ", 8);
memset(fatHeader->bootCode, 0, sizeof(fatHeader->bootCode));
fatHeader->endOfSectorMarker = 0xAA55;
return fatHeader;
}
bool DsiWareSaveArranger::CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const
{
auto fileInfo = std::make_unique<FILINFO>();
strcpy(deviceListPath, "nand:/");
char* shortPath = deviceListPath + 6;
char* currentPathSegment = strchr(savePath, '/');
do
{
currentPathSegment = strchr(currentPathSegment + 1, '/');
if (currentPathSegment)
{
*currentPathSegment = 0;
}
if (f_stat(savePath, fileInfo.get()) != FR_OK)
{
LOG_DEBUG("Failed to create short path\n");
return false;
}
LOG_DEBUG("%s\n", savePath);
// Use fname when altname is empty to handle short filenames.
const char* nameToUse = fileInfo->altname[0]
? fileInfo->altname
: fileInfo->fname;
u32 length = strlen(nameToUse);
if (shortPath + length - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
strcpy(shortPath, nameToUse);
shortPath += length;
if (currentPathSegment)
{
if (shortPath + 1 - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
*shortPath++ = '/';
*currentPathSegment = '/';
}
} while (currentPathSegment);
LOG_DEBUG("%s\n", deviceListPath);
return true;
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include <memory>
#include "fat/ff.h"
#include "ndsHeader.h"
/// @brief Struct holding the result of setting up DSiWare save data.
struct DsiWareSaveResult
{
/// @brief The short filename path of the private save file.
char privateSavePath[64];
/// @brief The short filename path of the public save file.
char publicSavePath[64];
/// @brief The short filename path of rom file.
char romFilePath[64];
};
/// @brief Class for setting up the save files for DSiWare roms.
class DsiWareSaveArranger
{
public:
/// @brief Sets up the save files for a DSiWare rom with the given \p romPath and \p romHeader.
/// @param romPath The path to the DSiWare rom to setup the save files for.
/// @param romHeader The header of the DSiWare rom.
/// @param result Struct in which the resulting paths are returned.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const;
private:
#pragma pack(push, 1)
/// @brief Struct representing a FAT header.
struct fat_header_t
{
u8 jumpInstruction[3];
u8 oemName[8];
u16 bytesPerSector;
u8 sectorsPerCluster;
u16 reservedSectors;
u8 numberOfFats;
u16 rootEntries;
u16 totalSectorsSmall;
u8 mediaType;
u16 fatSectorCount;
u16 sectorsPerTrack;
u16 numberOfHeads;
u32 hiddenSectors;
u32 totalSectorsLarge;
u8 diskNumber;
u8 BS_Reserved1;
u8 bootSignature;
u32 volumeSerialNumber;
u8 volumeLabel[11];
u8 fileSystemType[8];
u8 bootCode[448];
u16 endOfSectorMarker;
};
#pragma pack(pop)
static_assert(sizeof(fat_header_t) == 512, "Invalid size of fat_header_t.");
bool SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const;
std::unique_ptr<fat_header_t> CreateFatHeader(u32 saveSize) const;
bool CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const;
};

View File

@@ -0,0 +1,39 @@
#pragma once
#include "common.h"
template <typename T, u32 PatternLength>
class InverseKmpMatcher
{
const T* _pattern;
u8 _prefixFunc[PatternLength];
public:
consteval InverseKmpMatcher(const T(&pattern)[PatternLength])
: _pattern(pattern)
{
_prefixFunc[0] = 0;
int k = 0;
for (u32 q = 1; q < PatternLength; q++)
{
while (k > 0 && pattern[k] != pattern[q])
k = _prefixFunc[k - 1];
if (pattern[k] == pattern[q])
k++;
_prefixFunc[q] = k;
}
}
int FindFirstOccurance(const T* data, u32 length) const
{
int q = 0;
for (u32 i = 0; i < length; i++)
{
while (q > 0 && ~_pattern[q] != data[i])
q = _prefixFunc[q - 1];
if (~_pattern[q] == data[i])
q++;
if (q == PatternLength)
return i - q + 1;
}
return -1;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
#pragma once
#include "common.h"
#include "ndsHeader.h"
#include "DsiWareSaveArranger.h"
#include "BootMode.h"
struct dsi_devicelist_entry_t;
/// @brief Class for loading DS(i) roms.
class NdsLoader
{
public:
/// @brief Sets the \p romPath.
/// @param romPath The rom path.
void SetRomPath(const TCHAR* romPath)
{
_romPath = romPath;
}
/// @brief Sets the \p savePath to use.
/// @param savePath The save path to use.
void SetSavePath(const TCHAR* savePath)
{
_savePath = savePath;
}
/// @brief Sets the argv arguments to pass to the rom.
/// @param arguments The argv arguments.
/// @param argumentsLength The length of the argv arguments.
void SetArguments(const char* arguments, u32 argumentsLength)
{
_arguments = arguments;
_argumentsLength = argumentsLength;
}
/// @brief Loads the rom according to the specified \p bootMode.
/// @param bootMode The boot mode.
void Load(BootMode bootMode);
private:
FIL _romFile;
const TCHAR* _romPath;
const TCHAR* _savePath;
u32 _argumentsLength = 0;
const char* _arguments = nullptr;
nds_header_twl_t _romHeader;
DsiWareSaveResult _dsiwareSaveResult;
bool IsCloneBootRom(u32 romOffset);
void ApplyArm7Patches();
void SetupSharedMemory(u32 cardId, u32 agbMem, u32 resetParam, u32 romOffset, u32 bootType);
void LoadFirmwareUserSettings();
bool ShouldAttemptDldiPatch();
void ClearMainMemory();
void CreateRomClusterTable();
bool TryLoadRomHeader(u32 romOffset);
void HandleCardSave();
void HandleAntiPiracy();
void RemapWram();
bool TryLoadArm9();
bool TryLoadArm9i();
bool TryDecryptArm9i();
bool TryDecryptArm7i();
bool TryLoadArm7();
bool TryLoadArm7i();
void HandleDldiPatching();
void StartRom(BootMode bootMode);
void SetupTwlConfig();
void SetDeviceListEntry(dsi_devicelist_entry_t& deviceListEntry,
char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights);
void SetupDsiDeviceList();
void InsertArgv();
bool TrySetupDsiWareSave();
bool TryDecryptSecureArea();
};

View File

@@ -0,0 +1,60 @@
#include "common.h"
#include <memory>
#include "PicoLoaderArranger.h"
#define PICO_LOADER_9_PATH "/_pico/picoLoader9.bin"
#define PICO_LOADER_7_PATH "/_pico/picoLoader7.bin"
bool PicoLoaderArranger::SetupPicoLoaderInfo(loader_info_t* info) const
{
auto file = std::make_unique<FIL>();
if (f_open(file.get(), PICO_LOADER_9_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader9.bin\n");
return false;
}
DWORD* clusterTab = (DWORD*)info->clusterMap9;
clusterTab[0] = sizeof(info->clusterMap9) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader9 cluster table. Result: %d\n", seekResult);
return false;
}
info->clusterShift = __builtin_ctz(file->obj.fs->csize);
info->picoLoaderBootDrive = gLoaderHeader.bootDrive;
info->database = file->obj.fs->database;
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader9 file\n");
return false;
}
if (f_open(file.get(), PICO_LOADER_7_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader7.bin\n");
return false;
}
clusterTab = (DWORD*)info->clusterMap7;
clusterTab[0] = sizeof(info->clusterMap7) / sizeof(u32);
file->cltbl = clusterTab;
seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader7 cluster table. Result: %d\n", seekResult);
return false;
}
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader7 file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "LoaderInfo.h"
/// @brief Class for setting up \see loader_info_t.
class PicoLoaderArranger
{
public:
/// @brief Sets up the given \p info for being able to reload Pico Loader at a later time.
/// @param info The \see loader_info_t struct to fill.
/// @return \c true when setting up was successful, or \c false otherwise.
bool SetupPicoLoaderInfo(loader_info_t* info) const;
};

View File

@@ -0,0 +1,22 @@
#include "common.h"
#include <algorithm>
#include "SaveList.h"
const SaveListEntry* SaveList::FindEntry(u32 gameCode)
{
if (_count != 0)
{
const auto gameEntry = std::lower_bound(_entries.get(), _entries.get() + _count, gameCode,
[] (const SaveListEntry& entry, u32 value)
{
return entry.GetGameCode() < value;
});
if (gameEntry != _entries.get() + _count && gameEntry->GetGameCode() == gameCode)
{
return gameEntry;
}
}
return nullptr;
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <memory>
#include "common.h"
#include "CardSaveType.h"
/// @brief Class representing a save list entry.
class SaveListEntry
{
u32 gameCode;
u8 saveType; // see CardSaveType
u8 saveSize; // 0 or 1 << x
u8 reserved[2]; // for possible future use
public:
u32 GetGameCode() const { return gameCode; }
CardSaveType GetSaveType() const { return static_cast<CardSaveType>(saveType); }
u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); }
void Dump() const
{
const char* saveType;
switch (GetSaveType())
{
case CardSaveType::None:
{
saveType = "none";
break;
}
case CardSaveType::Eeprom:
{
saveType = "eeprom";
break;
}
case CardSaveType::Flash:
{
saveType = "flash";
break;
}
case CardSaveType::Nand:
{
saveType = "nand";
break;
}
default:
{
saveType = "unknown";
break;
}
}
LOG_DEBUG("%c%c%c%c - %s - 0x%X\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24,
saveType,
GetSaveSize());
}
};
static_assert(sizeof(SaveListEntry) == 8, "Invalid sizeof(SaveListEntry)");
/// @brief Class representing a save list.
class SaveList
{
public:
SaveList(std::unique_ptr<const SaveListEntry[]> entries, u32 count)
: _entries(std::move(entries)), _count(count) { }
/// @brief Attempts to find the save list entry for the given \p gameCode.
/// @param gameCode The game code to search for.
/// @return A pointer to the \see SaveListEntry when found, or \c nullptr otherwise.
const SaveListEntry* FindEntry(u32 gameCode);
private:
std::unique_ptr<const SaveListEntry[]> _entries;
u32 _count;
};

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "SaveListFactory.h"
SaveList* SaveListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open save list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(SaveListEntry);
auto entries = std::make_unique_for_overwrite<SaveListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(SaveListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(SaveListEntry))
{
LOG_FATAL("Failed to read save list file\n");
return nullptr;
}
f_close(&file);
return new SaveList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "SaveList.h"
/// @brief Factory for creating \see SaveList instances.
class SaveListFactory
{
public:
/// @brief Creates a \see SaveList instance from the file at the given \p path.
/// @param path The save list file path.
/// @return A pointer to the constructed \see SaveList instance, or \c nullptr if construction failed.
SaveList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,104 @@
#pragma once
#include <nds/ndstypes.h>
#define DATA32_SUPPORT
#define SDMMC_BASE 0x04004800
#define REG_SDCMD 0x00
#define REG_SDPORTSEL 0x02
#define REG_SDCMDARG 0x04
#define REG_SDCMDARG0 0x04
#define REG_SDCMDARG1 0x06
#define REG_SDSTOP 0x08
#define REG_SDRESP 0x0c
#define REG_SDBLKCOUNT 0x0a
#define REG_SDRESP0 0x0c
#define REG_SDRESP1 0x0e
#define REG_SDRESP2 0x10
#define REG_SDRESP3 0x12
#define REG_SDRESP4 0x14
#define REG_SDRESP5 0x16
#define REG_SDRESP6 0x18
#define REG_SDRESP7 0x1a
#define REG_SDSTATUS0 0x1c
#define REG_SDSTATUS1 0x1e
#define REG_SDIRMASK0 0x20
#define REG_SDIRMASK1 0x22
#define REG_SDCLKCTL 0x24
#define REG_SDBLKLEN 0x26
#define REG_SDOPT 0x28
#define REG_SDFIFO 0x30
#define REG_SDDATACTL 0xd8
#define REG_SDRESET 0xe0
#define REG_SDPROTECTED 0xf6 //bit 0 determines if sd is protected or not?
#define REG_SDDATACTL32 0x100
#define REG_SDBLKLEN32 0x104
#define REG_SDBLKCOUNT32 0x108
#define REG_SDFIFO32 0x10C
#define REG_CLK_AND_WAIT_CTL 0x138
#define REG_RESET_SDIO 0x1e0
//The below defines are from linux kernel drivers/mmc tmio_mmc.h.
/* Definitions for values the CTRL_STATUS register can take. */
#define TMIO_STAT0_CMDRESPEND 0x0001
#define TMIO_STAT0_DATAEND 0x0004
#define TMIO_STAT0_CARD_REMOVE 0x0008
#define TMIO_STAT0_CARD_INSERT 0x0010
#define TMIO_STAT0_SIGSTATE 0x0020
#define TMIO_STAT0_WRPROTECT 0x0080
#define TMIO_STAT0_CARD_REMOVE_A 0x0100
#define TMIO_STAT0_CARD_INSERT_A 0x0200
#define TMIO_STAT0_SIGSTATE_A 0x0400
#define TMIO_STAT1_CMD_IDX_ERR 0x0001
#define TMIO_STAT1_CRCFAIL 0x0002
#define TMIO_STAT1_STOPBIT_ERR 0x0004
#define TMIO_STAT1_DATATIMEOUT 0x0008
#define TMIO_STAT1_RXOVERFLOW 0x0010
#define TMIO_STAT1_TXUNDERRUN 0x0020
#define TMIO_STAT1_CMDTIMEOUT 0x0040
#define TMIO_STAT1_RXRDY 0x0100
#define TMIO_STAT1_TXRQ 0x0200
#define TMIO_STAT1_ILL_FUNC 0x2000
#define TMIO_STAT1_CMD_BUSY 0x4000
#define TMIO_STAT1_ILL_ACCESS 0x8000
#define SDMC_NORMAL 0x00000000
#define SDMC_ERR_COMMAND 0x00000001
#define SDMC_ERR_CRC 0x00000002
#define SDMC_ERR_END 0x00000004
#define SDMC_ERR_TIMEOUT 0x00000008
#define SDMC_ERR_FIFO_OVF 0x00000010
#define SDMC_ERR_FIFO_UDF 0x00000020
#define SDMC_ERR_WP 0x00000040
#define SDMC_ERR_ABORT 0x00000080
#define SDMC_ERR_FPGA_TIMEOUT 0x00000100
#define SDMC_ERR_PARAM 0x00000200
#define SDMC_ERR_R1_STATUS 0x00000800
#define SDMC_ERR_NUM_WR_SECTORS 0x00001000
#define SDMC_ERR_RESET 0x00002000
#define SDMC_ERR_ILA 0x00004000
#define SDMC_ERR_INFO_DETECT 0x00008000
#define SDMC_STAT_ERR_UNKNOWN 0x00080000
#define SDMC_STAT_ERR_CC 0x00100000
#define SDMC_STAT_ERR_ECC_FAILED 0x00200000
#define SDMC_STAT_ERR_CRC 0x00800000
#define SDMC_STAT_ERR_OTHER 0xf9c70008
#define TMIO_MASK_ALL 0x837f031d
#define TMIO_MASK_GW (TMIO_STAT1_ILL_ACCESS | TMIO_STAT1_CMDTIMEOUT | TMIO_STAT1_TXUNDERRUN | TMIO_STAT1_RXOVERFLOW | \
TMIO_STAT1_DATATIMEOUT | TMIO_STAT1_STOPBIT_ERR | TMIO_STAT1_CRCFAIL | TMIO_STAT1_CMD_IDX_ERR)
#define TMIO_MASK_READOP (TMIO_STAT1_RXRDY | TMIO_STAT1_DATAEND)
#define TMIO_MASK_WRITEOP (TMIO_STAT1_TXRQ | TMIO_STAT1_DATAEND)

View File

@@ -0,0 +1,153 @@
#include "common.h"
#include <libtwl/sys/twlScfg.h>
#include <libtwl/sys/twlFuse.h>
#include <libtwl/dma/dmaTwl.h>
#include "TwlAes.h"
#define KEY_SLOT_MODULE 0
#define KEY_SLOT_NAND 3
#define MODULE_KEYX_NINT 0x746E694E // Nint
#define MODULE_KEYX_ENDO 0x6F646E65 // endo
#define NAND_KEYX_DSI_XOR_LO 0x24EE6906
#define NAND_KEYX_DSI_XOR_HI 0xE65B601D
#define NAND_KEYX_3DS_NINT 0x544E494E // NINT
#define NAND_KEYX_3DS_ENDO 0x4F444E45 // ENDO
#define NAND_KEYY_WORD_3 0xE1A00005 // mov r0, r5
#define DMA_CHANNEL_AES_OUT 0
#define DMA_CHANNEL_AES_IN 1
void TwlAes::SetupAes(const nds_header_twl_t* romHeader) const
{
// ensure AES is enabled
REG_SCFG_EXT |= SCFG_EXT_AES;
REG_SCFG_CLK |= SCFG_CLK_AES;
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
SetupModuleKeyXY(romHeader);
SetupNandKeyX();
aes_waitKeyBusy();
(&REG_AES_SEED0)[KEY_SLOT_NAND * 3].words[3] = NAND_KEYY_WORD_3;
}
void TwlAes::DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const
{
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
aes_setKeySlot(0);
aes_waitKeyBusy();
u32 offset = 0;
while (length > 0)
{
REG_AES_CNT = 0;
aes_reset();
u32 blockLength = std::min<u32>(length, 0xFFFF0u);
auto ctr = *iv;
((u64*)&ctr)[1] += __builtin_add_overflow(((u64*)&ctr)[0], offset >> 4, &((u64*)&ctr)[0]);
aes_setInitializationVector(&ctr);
aes_setPayloadBlockCount(blockLength >> 4);
LOG_DEBUG("%p\n", (u8*)data + offset);
dma_twl_config_t inputDmaConfig
{
.src = (u8*)data + offset,
.dst = (void*)&REG_AES_IFIFO,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_DST_MODE_FIXED | NDMACNT_SRC_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_IN | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_IN, &inputDmaConfig);
dma_twl_config_t outputDmaConfig
{
.src = (const void*)&REG_AES_OFIFO,
.dst = (u8*)data + offset,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_SRC_MODE_FIXED | NDMACNT_DST_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_OUT | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_OUT, &outputDmaConfig);
aes_start(
AES_CNT_INPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_OUTPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_MODE_CTR);
dma_twlWait(DMA_CHANNEL_AES_OUT);
aes_waitBusy();
offset += blockLength;
length -= blockLength;
}
}
void TwlAes::SetupModuleKeyXY(const nds_header_twl_t* romHeader) const
{
if ((romHeader->ntrHeader.twlFlags & (1 << 2)) || (romHeader->twlFlags2 & (1 << 7)))
{
// debug
aes_setKey(KEY_SLOT_MODULE, (const aes_u128_t*)romHeader);
}
else
{
// retail
aes_u128_t keyX { .words =
{
MODULE_KEYX_NINT,
MODULE_KEYX_ENDO,
romHeader->ntrHeader.gameCode,
__builtin_bswap32(romHeader->ntrHeader.gameCode)
}};
aes_setKeyXY(KEY_SLOT_MODULE, &keyX, (const aes_u128_t*)romHeader->arm9iSha1Hmac);
}
}
void TwlAes::SetupNandKeyX() const
{
if ((REG_SCFG_A7ROM & SCFG_A7ROM_DISABLE_FUSE) || (REG_FUSE_VERIFY & FUSE_VERIFY_ERROR))
{
// No access to the console id register. We'll assume nand key x is already setup.
return;
}
u32 consoleIdLo = REG_FUSE_ID0;
u32 consoleIdHi = REG_FUSE_ID1;
aes_u128_t keyX;
keyX.words[0] = consoleIdLo;
if (consoleIdLo & 0x80000000)
{
// 3DS
keyX.words[1] = NAND_KEYX_3DS_NINT;
keyX.words[2] = NAND_KEYX_3DS_ENDO;
}
else
{
// DSi
keyX.words[1] = consoleIdLo ^ NAND_KEYX_DSI_XOR_LO;
keyX.words[2] = consoleIdHi ^ NAND_KEYX_DSI_XOR_HI;
}
keyX.words[3] = consoleIdHi;
aes_setKeyX(KEY_SLOT_NAND, &keyX);
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <libtwl/aes/aes.h>
#include "ndsHeader.h"
/// @brief Class handling AES crypto for DSi roms.
class TwlAes
{
public:
/// @brief Sets up AES for the DSi rom with the given \p romHeader.
/// @param romHeader The header of the DSi rom to setup AES for.
void SetupAes(const nds_header_twl_t* romHeader) const;
/// @brief Performs modcrypt decryption.
/// @param data The data to decrypt.
/// @param length The length of the data to decrypt.
/// @param iv The AES initialization vector.
void DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const;
private:
void SetupModuleKeyXY(const nds_header_twl_t* romHeader) const;
void SetupNandKeyX() const;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "common.h"
#define DLDI_MAGIC 0xBF8DA5ED
#define DLDI_DRIVER_MAGIC_NONE 0x49444C44
#define DLDI_FIX_ALL (1 << 0)
#define DLDI_FIX_GLUE (1 << 1)
#define DLDI_FIX_GOT (1 << 2)
#define DLDI_FIX_BSS (1 << 3)
#define DLDI_FEATURE_CANREAD (1 << 0)
#define DLDI_FEATURE_CANWRITE (1 << 1)
#define DLDI_FEATURE_SLOT_GBA (1 << 4)
#define DLDI_FEATURE_SLOT_NDS (1 << 5)
typedef bool (*dldi_startup_func_t)(void);
typedef bool (*dldi_inserted_func_t)(void);
typedef bool (*dldi_read_func_t)(u32 sector, u32 count, void* dst);
typedef bool (*dldi_write_func_t)(u32 sector, u32 count, const void* src);
typedef bool (*dldi_clearstatus_func_t)(void);
typedef bool (*dldi_shutdown_func_t)(void);
/// @brief Struct representing a DLDI header.
struct dldi_header_t
{
u32 dldiMagic;
u8 dldiString[8];
u8 dldiVersion;
u8 driverSize;
u8 fixFlags;
u8 stubSize;
u8 driverName[0x30];
u32 driverStartAddress;
u32 driverEndAddress;
u32 glueStartAddress;
u32 glueEndAddress;
u32 gotStartAddress;
u32 gotEndAddress;
u32 bssStartAddress;
u32 bssEndAddress;
u32 driverMagic;
u32 featureFlags;
u32 startupFuncAddress;
u32 isInsertedFuncAddress;
u32 readSectorsFuncAddress;
u32 writeSectorsFuncAddress;
u32 clearStatusFuncAddress;
u32 shutdownFuncAddress;
};
static_assert(sizeof(dldi_header_t) == 0x80, "Size of dldi_header_t incorrect");