aboutsummaryrefslogtreecommitdiff
path: root/src/NDSCart.cpp
diff options
context:
space:
mode:
authorArisotura <thetotalworm@gmail.com>2021-04-25 00:48:02 +0200
committerGitHub <noreply@github.com>2021-04-25 00:48:02 +0200
commit1846a712659ed31357e8ae795055dace0bdd951d (patch)
treea11c1bf2c0b61ee787dac81eec6d7f35fbcace63 /src/NDSCart.cpp
parentede6e832d84b9d4b1117ee480122480a7c540509 (diff)
Cart refactor (#1073)
complete cart-interface refactor, will make this code a lot easier to deal with
Diffstat (limited to 'src/NDSCart.cpp')
-rw-r--r--src/NDSCart.cpp1762
1 files changed, 1060 insertions, 702 deletions
diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp
index 56a454a..b40529b 100644
--- a/src/NDSCart.cpp
+++ b/src/NDSCart.cpp
@@ -29,53 +29,349 @@
#include "melonDLDI.h"
#include "NDSCart_SRAMManager.h"
-namespace NDSCart_SRAM
+
+namespace NDSCart
{
-u8* SRAM;
-u32 SRAMLength;
+// SRAM TODO: emulate write delays???
-char SRAMPath[1024];
-bool SRAMFileDirty;
+u16 SPICnt;
+u32 ROMCnt;
+
+u8 SPIData;
+u32 SPIDataPos;
+bool SPIHold;
+
+u8 ROMCommand[8];
+u32 ROMData;
-void (*WriteFunc)(u8 val, bool islast);
+u8 TransferData[0x4000];
+u32 TransferPos;
+u32 TransferLen;
+u32 TransferDir;
+u8 TransferCmd[8];
-u32 Hold;
-u8 CurCmd;
-u32 DataPos;
-u8 Data;
+bool CartInserted;
+u8* CartROM;
+u32 CartROMSize;
+u32 CartID;
+bool CartIsHomebrew;
+bool CartIsDSi;
-u8 StatusReg;
-u32 Addr;
+CartCommon* Cart;
+u32 Key1_KeyBuf[0x412];
-void Write_Null(u8 val, bool islast);
-void Write_EEPROMTiny(u8 val, bool islast);
-void Write_EEPROM(u8 val, bool islast);
-void Write_Flash(u8 val, bool islast);
+u64 Key2_X;
+u64 Key2_Y;
-bool Init()
+u32 ByteSwap(u32 val)
{
- SRAM = NULL;
- return true;
+ return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
}
-void DeInit()
+void Key1_Encrypt(u32* data)
{
- if (SRAM) delete[] SRAM;
+ u32 y = data[0];
+ u32 x = data[1];
+ u32 z;
+
+ for (u32 i = 0x0; i <= 0xF; i++)
+ {
+ z = Key1_KeyBuf[i] ^ x;
+ x = Key1_KeyBuf[0x012 + (z >> 24) ];
+ x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
+ x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
+ x += Key1_KeyBuf[0x312 + (z & 0xFF)];
+ x ^= y;
+ y = z;
+ }
+
+ data[0] = x ^ Key1_KeyBuf[0x10];
+ data[1] = y ^ Key1_KeyBuf[0x11];
}
-void Reset()
+void Key1_Decrypt(u32* data)
{
- if (SRAM) delete[] SRAM;
- SRAM = NULL;
+ u32 y = data[0];
+ u32 x = data[1];
+ u32 z;
+
+ for (u32 i = 0x11; i >= 0x2; i--)
+ {
+ z = Key1_KeyBuf[i] ^ x;
+ x = Key1_KeyBuf[0x012 + (z >> 24) ];
+ x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
+ x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
+ x += Key1_KeyBuf[0x312 + (z & 0xFF)];
+ x ^= y;
+ y = z;
+ }
+
+ data[0] = x ^ Key1_KeyBuf[0x1];
+ data[1] = y ^ Key1_KeyBuf[0x0];
}
-void DoSavestate(Savestate* file)
+void Key1_ApplyKeycode(u32* keycode, u32 mod)
+{
+ Key1_Encrypt(&keycode[1]);
+ Key1_Encrypt(&keycode[0]);
+
+ u32 temp[2] = {0,0};
+
+ for (u32 i = 0; i <= 0x11; i++)
+ {
+ Key1_KeyBuf[i] ^= ByteSwap(keycode[i % mod]);
+ }
+ for (u32 i = 0; i <= 0x410; i+=2)
+ {
+ Key1_Encrypt(temp);
+ Key1_KeyBuf[i ] = temp[1];
+ Key1_KeyBuf[i+1] = temp[0];
+ }
+}
+
+void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod)
+{
+ // TODO: source the key data from different possible places
+ if (dsi && NDS::ConsoleType==1)
+ memcpy(Key1_KeyBuf, &DSi::ARM7iBIOS[0xC6D0], 0x1048); // hax
+ else
+ memcpy(Key1_KeyBuf, &NDS::ARM7BIOS[0x30], 0x1048); // hax
+
+ u32 keycode[3] = {idcode, idcode>>1, idcode<<1};
+ if (level >= 1) Key1_ApplyKeycode(keycode, mod);
+ if (level >= 2) Key1_ApplyKeycode(keycode, mod);
+ if (level >= 3)
+ {
+ keycode[1] <<= 1;
+ keycode[2] >>= 1;
+ Key1_ApplyKeycode(keycode, mod);
+ }
+}
+
+
+void Key2_Encrypt(u8* data, u32 len)
+{
+ for (u32 i = 0; i < len; i++)
+ {
+ Key2_X = (((Key2_X >> 5) ^
+ (Key2_X >> 17) ^
+ (Key2_X >> 18) ^
+ (Key2_X >> 31)) & 0xFF)
+ + (Key2_X << 8);
+ Key2_Y = (((Key2_Y >> 5) ^
+ (Key2_Y >> 23) ^
+ (Key2_Y >> 18) ^
+ (Key2_Y >> 31)) & 0xFF)
+ + (Key2_Y << 8);
+
+ Key2_X &= 0x0000007FFFFFFFFFULL;
+ Key2_Y &= 0x0000007FFFFFFFFFULL;
+ }
+}
+
+
+void ApplyModcrypt(u32 addr, u32 len, u8* iv)
+{return;
+ u8 key[16];
+
+ DSi_AES::GetModcryptKey(&CartROM[0], key);
+ DSi_AES::ApplyModcrypt(&CartROM[addr], len, key, iv);
+}
+
+
+CartCommon::CartCommon(u8* rom, u32 len, u32 chipid)
+{
+ ROM = rom;
+ ROMLength = len;
+ ChipID = chipid;
+
+ u8 unitcode = ROM[0x12];
+ IsDSi = (unitcode & 0x02) != 0;
+}
+
+CartCommon::~CartCommon()
+{
+}
+
+void CartCommon::Reset()
+{
+ CmdEncMode = 0;
+ DataEncMode = 0;
+}
+
+void CartCommon::SetupDirectBoot()
+{
+ CmdEncMode = 2;
+ DataEncMode = 2;
+}
+
+void CartCommon::DoSavestate(Savestate* file)
{
file->Section("NDCS");
+ file->Var32(&CmdEncMode);
+ file->Var32(&DataEncMode);
+}
+
+void CartCommon::LoadSave(const char* path, u32 type)
+{
+}
+
+void CartCommon::RelocateSave(const char* path, bool write)
+{
+}
+
+int CartCommon::ImportSRAM(const u8* data, u32 length)
+{
+ return 0;
+}
+
+void CartCommon::FlushSRAMFile()
+{
+}
+
+int CartCommon::ROMCommandStart(u8* cmd, u8* data, u32 len)
+{
+ switch (cmd[0])
+ {
+ case 0x9F:
+ memset(data, 0xFF, len);
+ return 0;
+
+ case 0x00:
+ memset(data, 0, len);
+ if (len > 0x1000)
+ {
+ ReadROM(0, 0x1000, data, 0);
+ for (u32 pos = 0x1000; pos < len; pos += 0x1000)
+ memcpy(data+pos, data, 0x1000);
+ }
+ else
+ ReadROM(0, len, data, 0);
+ return 0;
+
+ case 0x90:
+ case 0xB8:
+ for (u32 pos = 0; pos < len; pos += 4)
+ *(u32*)&data[pos] = ChipID;
+ return 0;
+
+ case 0x3C:
+ CmdEncMode = 1;
+ Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2);
+ return 0;
+
+ case 0x3D:
+ if (IsDSi)
+ {
+ CmdEncMode = 11;
+ Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2);
+ }
+ return 0;
+
+ default:
+ if (CmdEncMode == 1 || CmdEncMode == 11)
+ {
+ // decrypt the KEY1 command as needed
+ // (KEY2 commands do not need decrypted because KEY2 is handled entirely by hardware,
+ // but KEY1 is not, so DS software is responsible for encrypting KEY1 commands)
+ u8 cmddec[8];
+ *(u32*)&cmddec[0] = ByteSwap(*(u32*)&cmd[4]);
+ *(u32*)&cmddec[4] = ByteSwap(*(u32*)&cmd[0]);
+ Key1_Decrypt((u32*)cmddec);
+ u32 tmp = ByteSwap(*(u32*)&cmddec[4]);
+ *(u32*)&cmddec[4] = ByteSwap(*(u32*)&cmddec[0]);
+ *(u32*)&cmddec[0] = tmp;
+
+ // TODO eventually: verify all the command parameters and shit
+
+ switch (cmddec[0] & 0xF0)
+ {
+ case 0x40:
+ DataEncMode = 2;
+ return 0;
+
+ case 0x10:
+ for (u32 pos = 0; pos < len; pos += 4)
+ *(u32*)&data[pos] = ChipID;
+ return 0;
+
+ case 0x20:
+ {
+ u32 addr = (cmddec[2] & 0xF0) << 8;
+ if (CmdEncMode == 11)
+ {
+ // the DSi region starts with 0x3000 unreadable bytes
+ // similarly to how the DS region starts at 0x1000 with 0x3000 unreadable bytes
+ // these contain data for KEY1 crypto
+ u32 dsiregion = *(u16*)&ROM[0x92] << 19;
+ addr -= 0x1000;
+ addr += dsiregion;
+ }
+ ReadROM(addr, 0x1000, data, 0);
+ }
+ return 0;
+
+ case 0xA0:
+ CmdEncMode = 2;
+ return 0;
+ }
+ }
+ return 0;
+ }
+}
+
+void CartCommon::ROMCommandFinish(u8* cmd, u8* data, u32 len)
+{
+}
+
+u8 CartCommon::SPIWrite(u8 val, u32 pos, bool last)
+{
+ return 0xFF;
+}
+
+void CartCommon::SetIRQ()
+{
+ NDS::SetIRQ(0, NDS::IRQ_CartIREQMC);
+ NDS::SetIRQ(1, NDS::IRQ_CartIREQMC);
+}
+
+void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset)
+{
+ if (addr >= ROMLength) return;
+ if ((addr+len) > ROMLength)
+ len = ROMLength - addr;
+
+ memcpy(data+offset, ROM+addr, len);
+}
+
+
+CartRetail::CartRetail(u8* rom, u32 len, u32 chipid) : CartCommon(rom, len, chipid)
+{
+ SRAM = nullptr;
+}
+
+CartRetail::~CartRetail()
+{
+ if (SRAM) delete[] SRAM;
+}
+
+void CartRetail::Reset()
+{
+ CartCommon::Reset();
+
+ SRAMCmd = 0;
+ SRAMAddr = 0;
+ SRAMStatus = 0;
+}
+
+void CartRetail::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
+
// we reload the SRAM contents.
// it should be the same file (as it should be the same ROM, duh)
// but the contents may change
@@ -104,79 +400,70 @@ void DoSavestate(Savestate* file)
// SPI status shito
- file->Var32(&Hold);
- file->Var8(&CurCmd);
- file->Var32(&DataPos);
- file->Var8(&Data);
-
- file->Var8(&StatusReg);
- file->Var32(&Addr);
+ file->Var8(&SRAMCmd);
+ file->Var32(&SRAMAddr);
+ file->Var8(&SRAMStatus);
// SRAMManager might now have an old buffer (or one from the future or alternate timeline!)
if (!file->Saving)
+ {
+ SRAMFileDirty = false;
NDSCart_SRAMManager::RequestFlush();
+ }
}
-void LoadSave(const char* path, u32 type)
+void CartRetail::LoadSave(const char* path, u32 type)
{
if (SRAM) delete[] SRAM;
strncpy(SRAMPath, path, 1023);
SRAMPath[1023] = '\0';
- FILE* f = Platform::OpenFile(path, "rb");
- if (f)
+ if (type > 9) type = 0;
+ int sramlen[] =
+ {
+ 0,
+ 512,
+ 8192, 65536, 128*1024,
+ 256*1024, 512*1024, 1024*1024,
+ 8192*1024, 16384*1024
+ };
+ SRAMLength = sramlen[type];
+
+ if (SRAMLength)
{
- fseek(f, 0, SEEK_END);
- SRAMLength = (u32)ftell(f);
SRAM = new u8[SRAMLength];
+ memset(SRAM, 0xFF, SRAMLength);
+ }
+ FILE* f = Platform::OpenFile(path, "rb");
+ if (f)
+ {
fseek(f, 0, SEEK_SET);
- fread(SRAM, SRAMLength, 1, f);
+ fread(SRAM, 1, SRAMLength, f);
fclose(f);
}
- else
- {
- if (type > 9) type = 0;
- int sramlen[] = {0, 512, 8192, 65536, 128*1024, 256*1024, 512*1024, 1024*1024, 8192*1024, 32768*1024};
- SRAMLength = sramlen[type];
-
- if (SRAMLength)
- {
- SRAM = new u8[SRAMLength];
- memset(SRAM, 0xFF, SRAMLength);
- }
- }
SRAMFileDirty = false;
NDSCart_SRAMManager::Setup(path, SRAM, SRAMLength);
- switch (SRAMLength)
+ switch (type)
{
- case 512: WriteFunc = Write_EEPROMTiny; break;
- case 8192:
- case 65536:
- case 128*1024: WriteFunc = Write_EEPROM; break;
- case 256*1024:
- case 512*1024:
- case 1024*1024:
- case 8192*1024: WriteFunc = Write_Flash; break;
- case 32768*1024: WriteFunc = Write_Null; break; // NAND FLASH, handled differently
- default:
- printf("!! BAD SAVE LENGTH %d\n", SRAMLength);
- case 0:
- WriteFunc = Write_Null;
- break;
+ case 1: SRAMType = 1; break; // EEPROM, small
+ case 2:
+ case 3:
+ case 4: SRAMType = 2; break; // EEPROM, regular
+ case 5:
+ case 6:
+ case 7: SRAMType = 3; break; // FLASH
+ case 8:
+ case 9: SRAMType = 4; break; // NAND
+ default: SRAMType = 0; break; // ...whatever else
}
-
- Hold = 0;
- CurCmd = 0;
- Data = 0;
- StatusReg = 0x00;
}
-void RelocateSave(const char* path, bool write)
+void CartRetail::RelocateSave(const char* path, bool write)
{
if (!write)
{
@@ -198,506 +485,733 @@ void RelocateSave(const char* path, bool write)
fclose(f);
}
-u8 Read()
+int CartRetail::ImportSRAM(const u8* data, u32 length)
{
- return Data;
+ memcpy(SRAM, data, std::min(length, SRAMLength));
+ FILE* f = Platform::OpenFile(SRAMPath, "wb");
+ if (f)
+ {
+ fwrite(SRAM, SRAMLength, 1, f);
+ fclose(f);
+ }
+
+ return length - SRAMLength;
}
-void Write_Null(u8 val, bool islast) {}
+void CartRetail::FlushSRAMFile()
+{
+ if (!SRAMFileDirty) return;
-void Write_EEPROMTiny(u8 val, bool islast)
+ SRAMFileDirty = false;
+ NDSCart_SRAMManager::RequestFlush();
+}
+
+int CartRetail::ROMCommandStart(u8* cmd, u8* data, u32 len)
{
- switch (CurCmd)
+ switch (cmd[0])
{
- case 0x02:
- case 0x0A:
- if (DataPos < 1)
+ case 0xB7:
{
- Addr = val;
- Data = 0;
+ u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
+ memset(data, 0, len);
+
+ if (((addr + len - 1) >> 12) != (addr >> 12))
+ {
+ u32 len1 = 0x1000 - (addr & 0xFFF);
+ ReadROM_B7(addr, len1, data, 0);
+ ReadROM_B7(addr+len1, len-len1, data, len1);
+ }
+ else
+ ReadROM_B7(addr, len, data, 0);
+ }
+ return 0;
+
+ default:
+ return CartCommon::ROMCommandStart(cmd, data, len);
+ }
+}
+
+u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last)
+{
+ if (SRAMType == 0) return 0;
+
+ if (pos == 0)
+ {
+ // handle generic commands with no parameters
+ switch (val)
+ {
+ case 0x04: // write disable
+ SRAMStatus &= ~(1<<1);
+ break;
+ case 0x06: // write enable
+ SRAMStatus |= (1<<1);
+ break;
+
+ default:
+ SRAMCmd = val;
+ SRAMAddr = 0;
+ }
+
+ return 0;
+ }
+
+ switch (SRAMType)
+ {
+ case 1: return SRAMWrite_EEPROMTiny(val, pos, last);
+ case 2: return SRAMWrite_EEPROM(val, pos, last);
+ case 3: return SRAMWrite_FLASH(val, pos, last);
+ default: return 0;
+ }
+}
+
+void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset)
+{
+ addr &= (ROMLength-1);
+
+ if (addr < 0x8000)
+ addr = 0x8000 + (addr & 0x1FF);
+
+ // TODO: protect DSi secure area
+ // also protect DSi region if not unlocked
+ // and other security shenanigans
+
+ memcpy(data+offset, ROM+addr, len);
+}
+
+u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last)
+{
+ switch (SRAMCmd)
+ {
+ case 0x01: // write status register
+ // TODO: WP bits should be nonvolatile!
+ if (pos == 1)
+ SRAMStatus = (SRAMStatus & 0x01) | (val & 0x0C);
+ return 0;
+
+ case 0x05: // read status register
+ return SRAMStatus | 0xF0;
+
+ case 0x02: // write low
+ case 0x0A: // write high
+ if (pos < 2)
+ {
+ SRAMAddr = val;
}
else
{
- SRAM[(Addr + ((CurCmd==0x0A)?0x100:0)) & 0x1FF] = val;
- Addr++;
+ // TODO: implement WP bits!
+ if (SRAMStatus & (1<<1))
+ {
+ SRAM[(SRAMAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF] = val;
+ SRAMFileDirty |= last;
+ }
+ SRAMAddr++;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
- case 0x03:
- case 0x0B:
- if (DataPos < 1)
+ case 0x03: // read low
+ case 0x0B: // read high
+ if (pos < 2)
{
- Addr = val;
- Data = 0;
+ SRAMAddr = val;
+ return 0;
}
else
{
- Data = SRAM[(Addr + ((CurCmd==0x0B)?0x100:0)) & 0x1FF];
- Addr++;
+ u8 ret = SRAM[(SRAMAddr + ((SRAMCmd==0x0B)?0x100:0)) & 0x1FF];
+ SRAMAddr++;
+ return ret;
}
- break;
- case 0x9F:
- Data = 0xFF;
- break;
+ case 0x9F: // read JEDEC ID
+ return 0xFF;
default:
- if (DataPos==0)
- printf("unknown tiny EEPROM save command %02X\n", CurCmd);
- break;
+ if (pos == 1)
+ printf("unknown tiny EEPROM save command %02X\n", SRAMCmd);
+ return 0;
}
}
-void Write_EEPROM(u8 val, bool islast)
+u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last)
{
u32 addrsize = 2;
if (SRAMLength > 65536) addrsize++;
- switch (CurCmd)
+ switch (SRAMCmd)
{
- case 0x02:
- if (DataPos < addrsize)
+ case 0x01: // write status register
+ // TODO: WP bits should be nonvolatile!
+ if (pos == 1)
+ SRAMStatus = (SRAMStatus & 0x01) | (val & 0x0C);
+ return 0;
+
+ case 0x05: // read status register
+ return SRAMStatus;
+
+ case 0x02: // write
+ if (pos <= addrsize)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
}
else
{
- SRAM[Addr & (SRAMLength-1)] = val;
- Addr++;
+ // TODO: implement WP bits
+ if (SRAMStatus & (1<<1))
+ {
+ SRAM[SRAMAddr & (SRAMLength-1)] = val;
+ SRAMFileDirty |= last;
+ }
+ SRAMAddr++;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
- case 0x03:
- if (DataPos < addrsize)
+ case 0x03: // read
+ if (pos <= addrsize)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
+ return 0;
}
else
{
- Data = SRAM[Addr & (SRAMLength-1)];
- Addr++;
+ // TODO: size limit!!
+ u8 ret = SRAM[SRAMAddr & (SRAMLength-1)];
+ SRAMAddr++;
+ return ret;
}
- break;
- case 0x9F:
- Data = 0xFF;
- break;
+ case 0x9F: // read JEDEC ID
+ // TODO: GBAtek implies it's not always all FF (FRAM)
+ return 0xFF;
default:
- if (DataPos==0)
- printf("unknown EEPROM save command %02X\n", CurCmd);
- break;
+ if (pos == 1)
+ printf("unknown EEPROM save command %02X\n", SRAMCmd);
+ return 0;
}
}
-void Write_Flash(u8 val, bool islast)
+u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
{
- switch (CurCmd)
+ switch (SRAMCmd)
{
- case 0x02:
- if (DataPos < 3)
+ case 0x05: // read status register
+ return SRAMStatus;
+
+ case 0x02: // page program
+ if (pos <= 3)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
}
else
{
- SRAM[Addr & (SRAMLength-1)] = 0;
- Addr++;
+ if (SRAMStatus & (1<<1))
+ {
+ // CHECKME: should it be &=~val ??
+ SRAM[SRAMAddr & (SRAMLength-1)] = 0;
+ SRAMFileDirty |= last;
+ }
+ SRAMAddr++;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
- case 0x03:
- if (DataPos < 3)
+ case 0x03: // read
+ if (pos <= 3)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
+ return 0;
}
else
{
- Data = SRAM[Addr & (SRAMLength-1)];
- Addr++;
+ u8 ret = SRAM[SRAMAddr & (SRAMLength-1)];
+ SRAMAddr++;
+ return ret;
}
- break;
- case 0x0A:
- if (DataPos < 3)
+ case 0x0A: // page write
+ if (pos <= 3)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
}
else
{
- SRAM[Addr & (SRAMLength-1)] = val;
- Addr++;
+ if (SRAMStatus & (1<<1))
+ {
+ SRAM[SRAMAddr & (SRAMLength-1)] = val;
+ SRAMFileDirty |= last;
+ }
+ SRAMAddr++;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
- case 0x9F:
- Data = 0xFF;
- break;
+ case 0x9F: // read JEDEC IC
+ // GBAtek says it should be 0xFF. verify?
+ return 0xFF;
- case 0xD8:
- if (DataPos < 3)
+ case 0xD8: // sector erase
+ if (pos <= 3)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
}
- if (DataPos == 2)
+ if ((pos == 3) && (SRAMStatus & (1<<1)))
{
for (u32 i = 0; i < 0x10000; i++)
{
- SRAM[Addr & (SRAMLength-1)] = 0;
- Addr++;
+ SRAM[SRAMAddr & (SRAMLength-1)] = 0;
+ SRAMAddr++;
}
+ SRAMFileDirty = true;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
- case 0xDB:
- if (DataPos < 3)
+ case 0xDB: // page erase
+ if (pos <= 3)
{
- Addr <<= 8;
- Addr |= val;
- Data = 0;
+ SRAMAddr <<= 8;
+ SRAMAddr |= val;
}
- if (DataPos == 2)
+ if ((pos == 3) && (SRAMStatus & (1<<1)))
{
for (u32 i = 0; i < 0x100; i++)
{
- SRAM[Addr & (SRAMLength-1)] = 0;
- Addr++;
+ SRAM[SRAMAddr & (SRAMLength-1)] = 0;
+ SRAMAddr++;
}
+ SRAMFileDirty = true;
}
- break;
+ if (last) SRAMStatus &= ~(1<<1);
+ return 0;
default:
- if (DataPos==0)
- printf("unknown Flash save command %02X\n", CurCmd);
- break;
+ if (pos == 1)
+ printf("unknown FLASH save command %02X\n", SRAMCmd);
+ return 0;
}
}
-void Write(u8 val, u32 hold)
+
+CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid) : CartRetail(rom, len, chipid)
{
- bool islast = false;
+}
- if (!hold)
- {
- if (Hold) islast = true;
- else CurCmd = val;
- Hold = 0;
- }
+CartRetailNAND::~CartRetailNAND()
+{
+}
- if (hold && (!Hold))
- {
- CurCmd = val;
- Hold = 1;
- Data = 0;
- DataPos = 0;
- Addr = 0;
- //printf("save SPI command %02X\n", CurCmd);
- return;
- }
+void CartRetailNAND::Reset()
+{
+ CartRetail::Reset();
- switch (CurCmd)
- {
- case 0x00:
- // Pokémon carts have an IR transceiver thing, and send this
- // to bypass it and access SRAM.
- // TODO: design better
- CurCmd = val;
- break;
- case 0x08:
- // see above
- // TODO: work out how the IR thing works. emulate it.
- Data = 0xAA;
- break;
+ SRAMAddr = 0;
+ SRAMStatus = 0x20;
+ SRAMWindow = 0;
- case 0x02:
- case 0x03:
- case 0x0A:
- case 0x0B:
- case 0x9F:
- case 0xD8:
- case 0xDB:
- WriteFunc(val, islast);
- DataPos++;
- break;
+ // ROM header 94/96 = SRAM addr start / 0x20000
+ SRAMBase = *(u16*)&ROM[0x96] << 17;
- case 0x04: // write disable
- StatusReg &= ~(1<<1);
- Data = 0;
- break;
+ memset(SRAMWriteBuffer, 0, 0x800);
+}
- case 0x05: // read status reg
- Data = StatusReg;
- break;
+void CartRetailNAND::DoSavestate(Savestate* file)
+{
+ CartRetail::DoSavestate(file);
- case 0x06: // write enable
- StatusReg |= (1<<1);
- Data = 0;
- break;
+ file->Var32(&SRAMBase);
+ file->Var32(&SRAMWindow);
- default:
- if (DataPos==0)
- printf("unknown save SPI command %02X %02X %d\n", CurCmd, val, islast);
- break;
- }
+ file->VarArray(SRAMWriteBuffer, 0x800);
+ file->Var32(&SRAMWritePos);
- SRAMFileDirty |= islast && (CurCmd == 0x02 || CurCmd == 0x0A) && (SRAMLength > 0);
+ if (!file->Saving)
+ BuildSRAMID();
}
-void FlushSRAMFile()
+void CartRetailNAND::LoadSave(const char* path, u32 type)
{
- if (!SRAMFileDirty) return;
-
- SRAMFileDirty = false;
- NDSCart_SRAMManager::RequestFlush();
+ CartRetail::LoadSave(path, type);
+ BuildSRAMID();
}
+int CartRetailNAND::ImportSRAM(const u8* data, u32 length)
+{
+ CartRetail::ImportSRAM(data, length);
+ BuildSRAMID();
}
-
-namespace NDSCart
+int CartRetailNAND::ROMCommandStart(u8* cmd, u8* data, u32 len)
{
+ switch (cmd[0])
+ {
+ case 0x81: // write data
+ if ((SRAMStatus & (1<<4)) && SRAMWindow >= SRAMBase && SRAMWindow < (SRAMBase+SRAMLength))
+ {
+ u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
-u16 SPICnt;
-u32 ROMCnt;
+ if (addr >= SRAMWindow && addr < (SRAMWindow+0x20000))
+ {
+ // the command is issued 4 times, each with the same address
+ // seems they use the one from the first command (CHECKME)
+ if (!SRAMAddr)
+ SRAMAddr = addr;
+ }
+ }
+ else
+ SRAMAddr = 0;
+ return 1;
-u8 ROMCommand[8];
-u32 ROMData;
+ case 0x82: // commit write
+ if (SRAMAddr && SRAMWritePos)
+ {
+ if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000))
+ {
+ memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800);
+ SRAMFileDirty = true;
+ }
-u8 TransferData[0x4000];
-u32 TransferPos;
-u32 TransferLen;
-u32 TransferDir;
-u8 TransferCmd[8];
+ SRAMAddr = 0;
+ SRAMWritePos = 0;
+ }
+ SRAMStatus &= ~(1<<4);
+ return 0;
-bool CartInserted;
-u8* CartROM;
-u32 CartROMSize;
-u32 CartID;
-bool CartIsHomebrew;
-bool CartIsDSi;
+ case 0x84: // discard write buffer
+ SRAMAddr = 0;
+ SRAMWritePos = 0;
+ return 0;
-FILE* CartSD;
+ case 0x85: // write enable
+ if (SRAMWindow)
+ {
+ SRAMStatus |= (1<<4);
+ SRAMWritePos = 0;
+ }
+ return 0;
-u32 CmdEncMode;
-u32 DataEncMode;
+ case 0x8B: // revert to ROM read mode
+ SRAMWindow = 0;
+ return 0;
-u32 Key1_KeyBuf[0x412];
+ case 0x94: // return ID data
+ {
+ // TODO: check what the data really is. probably the NAND chip's ID.
+ // also, might be different between different games or even between different carts.
+ // this was taken from a Jam with the Band cart.
+ u8 iddata[0x30] =
+ {
+ 0xEC, 0xF1, 0x00, 0x95, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
-u64 Key2_X;
-u64 Key2_Y;
+ if (SRAMLength) memcpy(&iddata[0x18], &SRAM[SRAMLength - 0x800], 16);
+
+ memset(data, 0, len);
+ memcpy(data, iddata, std::min(len, 0x30u));
+ }
+ return 0;
+
+ case 0xB2: // set window for accessing SRAM
+ {
+ u32 addr = (cmd[1]<<24) | ((cmd[2]&0xFE)<<16);
+ // window is 0x20000 bytes, address is aligned to that boundary
+ // NAND remains stuck 'busy' forever if this is less than the starting SRAM address
+ // TODO.
+ if (addr < SRAMBase) printf("NAND: !! BAD ADDR %08X < %08X\n", addr, SRAMBase);
+ if (addr >= (SRAMBase+SRAMLength)) printf("NAND: !! BAD ADDR %08X > %08X\n", addr, SRAMBase+SRAMLength);
-void ROMCommand_Retail(u8* cmd);
-void ROMCommand_RetailNAND(u8* cmd);
-void ROMCommand_Homebrew(u8* cmd);
+ SRAMWindow = addr;
+ }
+ return 0;
-void (*ROMCommandHandler)(u8* cmd);
+ case 0xB7:
+ {
+ u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
+ if (SRAMWindow == 0)
+ {
+ // regular ROM mode
+ memset(data, 0, len);
-u32 ByteSwap(u32 val)
-{
- return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
+ if (((addr + len - 1) >> 12) != (addr >> 12))
+ {
+ u32 len1 = 0x1000 - (addr & 0xFFF);
+ ReadROM_B7(addr, len1, data, 0);
+ ReadROM_B7(addr+len1, len-len1, data, len1);
+ }
+ else
+ ReadROM_B7(addr, len, data, 0);
+ }
+ else
+ {
+ // SRAM mode
+ memset(data, 0xFF, len);
+
+ if (SRAMWindow >= SRAMBase && SRAMWindow < (SRAMBase+SRAMLength) &&
+ addr >= SRAMWindow && addr < (SRAMWindow+0x20000))
+ {
+ memcpy(data, &SRAM[addr - SRAMBase], len);
+ }
+ }
+ }
+ return 0;
+
+ case 0xD6: // read NAND status
+ {
+ // status bits
+ // bit5: ready
+ // bit4: write enable
+
+ for (u32 i = 0; i < len; i+=4)
+ *(u32*)&data[i] = SRAMStatus * 0x01010101;
+ }
+ return 0;
+
+ default:
+ return CartRetail::ROMCommandStart(cmd, data, len);
+ }
}
-void Key1_Encrypt(u32* data)
+void CartRetailNAND::ROMCommandFinish(u8* cmd, u8* data, u32 len)
{
- u32 y = data[0];
- u32 x = data[1];
- u32 z;
-
- for (u32 i = 0x0; i <= 0xF; i++)
+ switch (cmd[0])
{
- z = Key1_KeyBuf[i] ^ x;
- x = Key1_KeyBuf[0x012 + (z >> 24) ];
- x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
- x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
- x += Key1_KeyBuf[0x312 + (z & 0xFF)];
- x ^= y;
- y = z;
+ case 0x81: // write data
+ if (SRAMAddr)
+ {
+ if ((SRAMWritePos + len) > 0x800)
+ len = 0x800 - SRAMWritePos;
+
+ memcpy(&SRAMWriteBuffer[SRAMWritePos], data, len);
+ SRAMWritePos += len;
+ }
+ return;
+
+ default:
+ return CartCommon::ROMCommandFinish(cmd, data, len);
}
+}
- data[0] = x ^ Key1_KeyBuf[0x10];
- data[1] = y ^ Key1_KeyBuf[0x11];
+u8 CartRetailNAND::SPIWrite(u8 val, u32 pos, bool last)
+{
+ return 0xFF;
}
-void Key1_Decrypt(u32* data)
+void CartRetailNAND::BuildSRAMID()
{
- u32 y = data[0];
- u32 x = data[1];
- u32 z;
+ // the last 128K of the SRAM are read-only.
+ // most of it is FF, except for the NAND ID at the beginning
+ // of the last 0x800 bytes.
- for (u32 i = 0x11; i >= 0x2; i--)
+ if (SRAMLength > 0x20000)
{
- z = Key1_KeyBuf[i] ^ x;
- x = Key1_KeyBuf[0x012 + (z >> 24) ];
- x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
- x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
- x += Key1_KeyBuf[0x312 + (z & 0xFF)];
- x ^= y;
- y = z;
+ memset(&SRAM[SRAMLength - 0x20000], 0xFF, 0x20000);
+
+ // TODO: check what the data is all about!
+ // this was pulled from a Jam with the Band cart. may be different on other carts.
+ // WarioWare DIY may have different data or not have this at all.
+ // the ID data is also found in the response to command 94, and JwtB checks it.
+ // WarioWare doesn't seem to care.
+ // there is also more data here, but JwtB doesn't seem to care.
+ u8 iddata[0x10] = {0xEC, 0x00, 0x9E, 0xA1, 0x51, 0x65, 0x34, 0x35, 0x30, 0x35, 0x30, 0x31, 0x19, 0x19, 0x02, 0x0A};
+ memcpy(&SRAM[SRAMLength - 0x800], iddata, 16);
}
+}
- data[0] = x ^ Key1_KeyBuf[0x1];
- data[1] = y ^ Key1_KeyBuf[0x0];
+
+CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion) : CartRetail(rom, len, chipid)
+{
+ IRVersion = irversion;
}
-void Key1_ApplyKeycode(u32* keycode, u32 mod)
+CartRetailIR::~CartRetailIR()
{
- Key1_Encrypt(&keycode[1]);
- Key1_Encrypt(&keycode[0]);
+}
- u32 temp[2] = {0,0};
+void CartRetailIR::Reset()
+{
+ CartRetail::Reset();
- for (u32 i = 0; i <= 0x11; i++)
- {
- Key1_KeyBuf[i] ^= ByteSwap(keycode[i % mod]);
- }
- for (u32 i = 0; i <= 0x410; i+=2)
- {
- Key1_Encrypt(temp);
- Key1_KeyBuf[i ] = temp[1];
- Key1_KeyBuf[i+1] = temp[0];
- }
+ IRCmd = 0;
}
-void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod)
+void CartRetailIR::DoSavestate(Savestate* file)
{
- // TODO: source the key data from different possible places
- if (dsi && NDS::ConsoleType==1)
- memcpy(Key1_KeyBuf, &DSi::ARM7iBIOS[0xC6D0], 0x1048); // hax
- else
- memcpy(Key1_KeyBuf, &NDS::ARM7BIOS[0x30], 0x1048); // hax
+ CartRetail::DoSavestate(file);
- u32 keycode[3] = {idcode, idcode>>1, idcode<<1};
- if (level >= 1) Key1_ApplyKeycode(keycode, mod);
- if (level >= 2) Key1_ApplyKeycode(keycode, mod);
- if (level >= 3)
+ file->Var8(&IRCmd);
+}
+
+u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last)
+{
+ if (pos == 0)
{
- keycode[1] <<= 1;
- keycode[2] >>= 1;
- Key1_ApplyKeycode(keycode, mod);
+ IRCmd = val;
+ return 0;
}
-}
+ // TODO: emulate actual IR comm
-void Key2_Encrypt(u8* data, u32 len)
-{
- for (u32 i = 0; i < len; i++)
+ switch (IRCmd)
{
- Key2_X = (((Key2_X >> 5) ^
- (Key2_X >> 17) ^
- (Key2_X >> 18) ^
- (Key2_X >> 31)) & 0xFF)
- + (Key2_X << 8);
- Key2_Y = (((Key2_Y >> 5) ^
- (Key2_Y >> 23) ^
- (Key2_Y >> 18) ^
- (Key2_Y >> 31)) & 0xFF)
- + (Key2_Y << 8);
+ case 0x00: // pass-through
+ return CartRetail::SPIWrite(val, pos-1, last);
- Key2_X &= 0x0000007FFFFFFFFFULL;
- Key2_Y &= 0x0000007FFFFFFFFFULL;
+ case 0x08: // ID
+ return 0xAA;
}
}
-void ApplyModcrypt(u32 addr, u32 len, u8* iv)
-{return;
- u8 key[16];
+CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid) : CartRetail(rom, len, chipid)
+{
+ printf("POKETYPE CART\n");
+}
- DSi_AES::GetModcryptKey(&CartROM[0], key);
- DSi_AES::ApplyModcrypt(&CartROM[addr], len, key, iv);
+CartRetailBT::~CartRetailBT()
+{
}
+void CartRetailBT::Reset()
+{
+ CartRetail::Reset();
+}
-bool Init()
+void CartRetailBT::DoSavestate(Savestate* file)
{
- if (!NDSCart_SRAM::Init()) return false;
+ CartRetail::DoSavestate(file);
+}
- CartROM = NULL;
+u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last)
+{
+ printf("POKETYPE SPI: %02X %d %d - %08X\n", val, pos, last, NDS::GetPC(0));
- CartSD = NULL;
+ /*if (pos == 0)
+ {
+ // TODO do something with it??
+ if(val==0xFF)SetIRQ();
+ }
+ if(pos==7)SetIRQ();*/
- return true;
+ return 0;
}
-void DeInit()
-{
- if (CartROM) delete[] CartROM;
-
- if (CartSD) fclose(CartSD);
- NDSCart_SRAM::DeInit();
+CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid) : CartCommon(rom, len, chipid)
+{
+ if (Config::DLDIEnable)
+ {
+ ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI));
+ SDFile = Platform::OpenLocalFile(Config::DLDISDPath, "r+b");
+ }
+ else
+ SDFile = nullptr;
}
-void Reset()
+CartHomebrew::~CartHomebrew()
{
- CartInserted = false;
- if (CartROM) delete[] CartROM;
- CartROM = NULL;
- CartROMSize = 0;
- CartID = 0;
- CartIsHomebrew = false;
- CartIsDSi = false;
+ if (SDFile) fclose(SDFile);
+}
- if (CartSD) fclose(CartSD);
- CartSD = NULL;
+void CartHomebrew::Reset()
+{
+ CartCommon::Reset();
- ROMCommandHandler = NULL;
+ if (SDFile) fclose(SDFile);
- NDSCart_SRAM::Reset();
+ if (Config::DLDIEnable)
+ SDFile = Platform::OpenLocalFile(Config::DLDISDPath, "r+b");
+ else
+ SDFile = nullptr;
+}
- ResetCart();
+void CartHomebrew::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
}
-void DoSavestate(Savestate* file)
+int CartHomebrew::ROMCommandStart(u8* cmd, u8* data, u32 len)
{
- file->Section("NDSC");
+ switch (cmd[0])
+ {
+ case 0xB7:
+ {
+ u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
+ memset(data, 0, len);
- file->Var16(&SPICnt);
- file->Var32(&ROMCnt);
+ if (((addr + len - 1) >> 12) != (addr >> 12))
+ {
+ u32 len1 = 0x1000 - (addr & 0xFFF);
+ ReadROM_B7(addr, len1, data, 0);
+ ReadROM_B7(addr+len1, len-len1, data, len1);
+ }
+ else
+ ReadROM_B7(addr, len, data, 0);
+ }
+ return 0;
- file->VarArray(ROMCommand, 8);
- file->Var32(&ROMData);
+ case 0xC0: // SD read
+ {
+ u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
+ u64 addr = sector * 0x200ULL;
- file->VarArray(TransferData, 0x4000);
- file->Var32(&TransferPos);
- file->Var32(&TransferLen);
- file->Var32(&TransferDir);
- file->VarArray(TransferCmd, 8);
+ if (SDFile)
+ {
+ fseek(SDFile, addr, SEEK_SET);
+ fread(data, len, 1, SDFile);
+ }
+ }
+ return 0;
- // cart inserted/len/ROM/etc should be already populated
- // savestate should be loaded after the right game is loaded
- // (TODO: system to verify that indeed the right ROM is loaded)
- // (what to CRC? whole ROM? code binaries? latter would be more convenient for ie. romhaxing)
+ case 0xC1: // SD write
+ return 1;
- file->Var32(&CmdEncMode);
- file->Var32(&DataEncMode);
+ default:
+ return CartCommon::ROMCommandStart(cmd, data, len);
+ }
+}
- // TODO: check KEY1 shit??
+void CartHomebrew::ROMCommandFinish(u8* cmd, u8* data, u32 len)
+{
+ // TODO: delayed SD writing? like we have for SRAM
- NDSCart_SRAM::DoSavestate(file);
-}
+ switch (cmd[0])
+ {
+ case 0xC1:
+ {
+ u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
+ u64 addr = sector * 0x200ULL;
+ if (SDFile)
+ {
+ fseek(SDFile, addr, SEEK_SET);
+ fwrite(data, len, 1, SDFile);
+ }
+ }
+ break;
-void ApplyDLDIPatch(const u8* patch, u32 len)
+ default:
+ return CartCommon::ROMCommandFinish(cmd, data, len);
+ }
+}
+
+void CartHomebrew::ApplyDLDIPatch(const u8* patch, u32 patchlen)
{
- u32 offset = *(u32*)&CartROM[0x20];
- u32 size = *(u32*)&CartROM[0x2C];
+ u32 offset = *(u32*)&ROM[0x20];
+ u32 size = *(u32*)&ROM[0x2C];
- u8* binary = &CartROM[offset];
+ u8* binary = &ROM[offset];
u32 dldioffset = 0;
for (u32 i = 0; i < size; i++)
@@ -723,14 +1237,12 @@ void ApplyDLDIPatch(const u8* patch, u32 len)
*(u32*)&patch[8] != 0x006D6873)
{
printf("bad DLDI patch\n");
- delete[] patch;
return;
}
if (patch[0x0D] > binary[dldioffset+0x0F])
{
printf("DLDI driver ain't gonna fit, sorry\n");
- delete[] patch;
return;
}
@@ -747,7 +1259,7 @@ void ApplyDLDIPatch(const u8* patch, u32 len)
u32 patchsize = 1 << patch[0x0D];
u32 patchend = patchbase + patchsize;
- memcpy(&binary[dldioffset], patch, len);
+ memcpy(&binary[dldioffset], patch, patchlen);
*(u32*)&binary[dldioffset+0x40] += delta;
*(u32*)&binary[dldioffset+0x44] += delta;
@@ -814,6 +1326,75 @@ void ApplyDLDIPatch(const u8* patch, u32 len)
printf("applied DLDI patch\n");
}
+void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset)
+{
+ // TODO: how strict should this be for homebrew?
+
+ addr &= (ROMLength-1);
+
+ memcpy(data+offset, ROM+addr, len);
+}
+
+
+
+bool Init()
+{
+ CartROM = nullptr;
+ Cart = nullptr;
+
+ return true;
+}
+
+void DeInit()
+{
+ if (CartROM) delete[] CartROM;
+ if (Cart) delete Cart;
+}
+
+void Reset()
+{
+ CartInserted = false;
+ if (CartROM) delete[] CartROM;
+ CartROM = nullptr;
+ CartROMSize = 0;
+ CartID = 0;
+ CartIsHomebrew = false;
+ CartIsDSi = false;
+
+ if (Cart) delete Cart;
+ Cart = nullptr;
+
+ ResetCart();
+}
+
+void DoSavestate(Savestate* file)
+{
+ file->Section("NDSC");
+
+ file->Var16(&SPICnt);
+ file->Var32(&ROMCnt);
+
+ file->Var8(&SPIData);
+ file->Var32(&SPIDataPos);
+ file->Bool32(&SPIHold);
+
+ file->VarArray(ROMCommand, 8);
+ file->Var32(&ROMData);
+
+ file->VarArray(TransferData, 0x4000);
+ file->Var32(&TransferPos);
+ file->Var32(&TransferLen);
+ file->Var32(&TransferDir);
+ file->VarArray(TransferCmd, 8);
+
+ // cart inserted/len/ROM/etc should be already populated
+ // savestate should be loaded after the right game is loaded
+ // (TODO: system to verify that indeed the right ROM is loaded)
+ // (what to CRC? whole ROM? code binaries? latter would be more convenient for ie. romhaxing)
+
+ if (Cart) Cart->DoSavestate(file);
+}
+
bool ReadROMParams(u32 gamecode, ROMListEntry* params)
{
@@ -927,12 +1508,18 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct)
else
CartID |= (0x100 - (CartROMSize >> 28)) << 8;
- if (romparams.SaveMemType == 8)
+ if (romparams.SaveMemType == 8 || romparams.SaveMemType == 9)
CartID |= 0x08000000; // NAND flag
if (CartIsDSi)
CartID |= 0x40000000;
+ // cart ID for Jam with the Band
+ // TODO: this kind of ID triggers different KEY1 phase
+ // (repeats commands a bunch of times)
+ //CartID = 0x88017FEC;
+ //CartID = 0x80007FC2; // pokémon typing adventure
+
printf("Cart ID: %08X\n", CartID);
u32 arm9base = *(u32*)&CartROM[0x20];
@@ -961,41 +1548,46 @@ bool LoadROMCommon(u32 filelength, const char *sram, bool direct)
if ((arm9base < 0x4000) || (gamecode == 0x23232323))
{
CartIsHomebrew = true;
- if (Config::DLDIEnable)
- ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI));
}
- if (direct)
+ CartInserted = true;
+
+ u32 irversion = 0;
+ if ((gamecode & 0xFF) == 'I')
{
- // TODO: in the case of an already-encrypted secure area, direct boot
- // needs it decrypted
- NDS::SetupDirectBoot();
- CmdEncMode = 2;
+ if (((gamecode >> 8) & 0xFF) < 'P')
+ irversion = 1; // Active Health / Walk with Me
+ else
+ irversion = 2; // Pokémon HG/SS, B/W, B2/W2
}
- CartInserted = true;
-
- // TODO: support more fancy cart types (homebrew?, flashcarts, etc)
if (CartIsHomebrew)
- ROMCommandHandler = ROMCommand_Homebrew;
+ Cart = new CartHomebrew(CartROM, CartROMSize, CartID);
else if (CartID & 0x08000000)
- ROMCommandHandler = ROMCommand_RetailNAND;
+ Cart = new CartRetailNAND(CartROM, CartROMSize, CartID);
+ else if (irversion != 0)
+ Cart = new CartRetailIR(CartROM, CartROMSize, CartID, irversion);
+ else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx
+ Cart = new CartRetailBT(CartROM, CartROMSize, CartID);
else
- ROMCommandHandler = ROMCommand_Retail;
+ Cart = new CartRetail(CartROM, CartROMSize, CartID);
+
+ if (Cart)
+ {
+ Cart->Reset();
+ if (direct)
+ {
+ NDS::SetupDirectBoot();
+ Cart->SetupDirectBoot();
+ }
+ }
// encryption
Key1_InitKeycode(false, gamecode, 2, 2);
// save
printf("Save file: %s\n", sram);
- NDSCart_SRAM::LoadSave(sram, romparams.SaveMemType);
-
- if (CartIsHomebrew && Config::DLDIEnable)
- {
- CartSD = Platform::OpenLocalFile(Config::DLDISDPath, "r+b");
- }
- else
- CartSD = NULL;
+ if (Cart) Cart->LoadSave(sram, romparams.SaveMemType);
return true;
}
@@ -1049,26 +1641,18 @@ bool LoadROM(const u8* romdata, u32 filelength, const char *sram, bool direct)
void RelocateSave(const char* path, bool write)
{
- // herp derp
- NDSCart_SRAM::RelocateSave(path, write);
+ if (Cart) Cart->RelocateSave(path, write);
}
void FlushSRAMFile()
{
- NDSCart_SRAM::FlushSRAMFile();
+ if (Cart) Cart->FlushSRAMFile();
}
int ImportSRAM(const u8* data, u32 length)
{
- memcpy(NDSCart_SRAM::SRAM, data, std::min(length, NDSCart_SRAM::SRAMLength));
- FILE* f = Platform::OpenFile(NDSCart_SRAM::SRAMPath, "wb");
- if (f)
- {
- fwrite(NDSCart_SRAM::SRAM, NDSCart_SRAM::SRAMLength, 1, f);
- fclose(f);
- }
-
- return length - NDSCart_SRAM::SRAMLength;
+ if (Cart) return Cart->ImportSRAM(data, length);
+ return 0;
}
void ResetCart()
@@ -1078,6 +1662,10 @@ void ResetCart()
SPICnt = 0;
ROMCnt = 0;
+ SPIData = 0;
+ SPIDataPos = 0;
+ SPIHold = false;
+
memset(ROMCommand, 0, 8);
ROMData = 0;
@@ -1091,33 +1679,7 @@ void ResetCart()
memset(TransferCmd, 0, 8);
TransferCmd[0] = 0xFF;
- CmdEncMode = 0;
- DataEncMode = 0;
-}
-
-void ReadROM(u32 addr, u32 len, u32 offset)
-{
- if (!CartInserted) return;
-
- if (addr >= CartROMSize) return;
- if ((addr+len) > CartROMSize)
- len = CartROMSize - addr;
-
- memcpy(TransferData+offset, CartROM+addr, len);
-}
-
-void ReadROM_B7(u32 addr, u32 len, u32 offset)
-{
- if (!CartInserted) return;
-
- addr &= (CartROMSize-1);
- if (!CartIsHomebrew)
- {
- if (addr < 0x8000)
- addr = 0x8000 + (addr & 0x1FF);
- }
-
- memcpy(TransferData+offset, CartROM+addr, len);
+ if (Cart) Cart->Reset();
}
@@ -1126,29 +1688,10 @@ void ROMEndTransfer(u32 param)
ROMCnt &= ~(1<<31);
if (SPICnt & (1<<14))
- NDS::SetIRQ((NDS::ExMemCnt[0]>>11)&0x1, NDS::IRQ_CartSendDone);
-
- if (TransferDir == 1)
- {
- // finish a write
+ NDS::SetIRQ((NDS::ExMemCnt[0]>>11)&0x1, NDS::IRQ_CartXferDone);
- u8* cmd = TransferCmd;
- switch (cmd[0])
- {
- case 0xC1:
- {
- u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- u64 addr = sector * 0x200ULL;
-
- if (CartSD)
- {
- fseek(CartSD, addr, SEEK_SET);
- fwrite(TransferData, TransferLen, 1, CartSD);
- }
- }
- break;
- }
- }
+ if (Cart)
+ Cart->ROMCommandFinish(TransferCmd, TransferData, TransferLen);
}
void ROMPrepareData(u32 param)
@@ -1171,141 +1714,14 @@ void ROMPrepareData(u32 param)
NDS::CheckDMAs(0, 0x05);
}
-
-void ROMCommand_Retail(u8* cmd)
-{
- switch (cmd[0])
- {
- case 0xB7:
- {
- u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- memset(TransferData, 0, TransferLen);
-
- if (((addr + TransferLen - 1) >> 12) != (addr >> 12))
- {
- u32 len1 = 0x1000 - (addr & 0xFFF);
- ReadROM_B7(addr, len1, 0);
- ReadROM_B7(addr+len1, TransferLen-len1, len1);
- }
- else
- ReadROM_B7(addr, TransferLen, 0);
- }
- break;
-
- default:
- printf("unknown retail cart command %02X\n", cmd[0]);
- break;
- }
-}
-
-void ROMCommand_RetailNAND(u8* cmd)
-{
- switch (cmd[0])
- {
- case 0x94: // NAND init
- {
- // initial value: should have bit7 clear
- NDSCart_SRAM::StatusReg = 0;
-
- // Jam with the Band stores words 6-9 of this at 0x02131BB0
- // it doesn't seem to use those anywhere later
- for (u32 pos = 0; pos < TransferLen; pos += 4)
- *(u32*)&TransferData[pos] = 0;
- }
- break;
-
- case 0xB2: // set savemem addr
- {
- NDSCart_SRAM::StatusReg |= 0x20;
- }
- break;
-
- case 0xB7:
- {
- u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- memset(TransferData, 0, TransferLen);
-
- if (((addr + TransferLen - 1) >> 12) != (addr >> 12))
- {
- u32 len1 = 0x1000 - (addr & 0xFFF);
- ReadROM_B7(addr, len1, 0);
- ReadROM_B7(addr+len1, TransferLen-len1, len1);
- }
- else
- ReadROM_B7(addr, TransferLen, 0);
- }
- break;
-
- case 0xD6: // NAND status
- {
- // status reg bits:
- // * bit7: busy? error?
- // * bit5: accessing savemem
-
- for (u32 pos = 0; pos < TransferLen; pos += 4)
- *(u32*)&TransferData[pos] = NDSCart_SRAM::StatusReg * 0x01010101;
- }
- break;
-
- default:
- printf("unknown NAND command %02X %04Xn", cmd[0], TransferLen);
- break;
- }
-}
-
-void ROMCommand_Homebrew(u8* cmd)
-{
- switch (cmd[0])
- {
- case 0xB7:
- {
- u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- memset(TransferData, 0, TransferLen);
-
- if (((addr + TransferLen - 1) >> 12) != (addr >> 12))
- {
- u32 len1 = 0x1000 - (addr & 0xFFF);
- ReadROM_B7(addr, len1, 0);
- ReadROM_B7(addr+len1, TransferLen-len1, len1);
- }
- else
- ReadROM_B7(addr, TransferLen, 0);
- }
- break;
-
- case 0xC0: // SD read
- {
- u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- u64 addr = sector * 0x200ULL;
-
- if (CartSD)
- {
- fseek(CartSD, addr, SEEK_SET);
- fread(TransferData, TransferLen, 1, CartSD);
- }
- }
- break;
-
- case 0xC1: // SD write
- {
- TransferDir = 1;
- memcpy(TransferCmd, cmd, 8);
- }
- break;
-
- default:
- printf("unknown homebrew cart command %02X\n", cmd[0]);
- break;
- }
-}
-
-
void WriteROMCnt(u32 val)
{
ROMCnt = (val & 0xFF7F7FFF) | (ROMCnt & 0x00800000);
if (!(SPICnt & (1<<15))) return;
+ // all this junk would only really be useful if melonDS was interfaced to
+ // a DS cart reader
if (val & (1<<15))
{
u32 snum = (NDS::ExMemCnt[0]>>8)&0x8;
@@ -1337,110 +1753,24 @@ void WriteROMCnt(u32 val)
TransferPos = 0;
TransferLen = datasize;
- // handle KEY1 encryption as needed.
- // KEY2 encryption is implemented in hardware and doesn't need to be handled.
- u8 cmd[8];
- if (CmdEncMode == 1 || CmdEncMode == 11)
- {
- *(u32*)&cmd[0] = ByteSwap(*(u32*)&ROMCommand[4]);
- *(u32*)&cmd[4] = ByteSwap(*(u32*)&ROMCommand[0]);
- Key1_Decrypt((u32*)cmd);
- u32 tmp = ByteSwap(*(u32*)&cmd[4]);
- *(u32*)&cmd[4] = ByteSwap(*(u32*)&cmd[0]);
- *(u32*)&cmd[0] = tmp;
- }
- else
- {
- *(u32*)&cmd[0] = *(u32*)&ROMCommand[0];
- *(u32*)&cmd[4] = *(u32*)&ROMCommand[4];
- }
+ *(u32*)&TransferCmd[0] = *(u32*)&ROMCommand[0];
+ *(u32*)&TransferCmd[4] = *(u32*)&ROMCommand[4];
/*printf("ROM COMMAND %04X %08X %02X%02X%02X%02X%02X%02X%02X%02X SIZE %04X\n",
SPICnt, ROMCnt,
- cmd[0], cmd[1], cmd[2], cmd[3],
- cmd[4], cmd[5], cmd[6], cmd[7],
+ TransferCmd[0], TransferCmd[1], TransferCmd[2], TransferCmd[3],
+ TransferCmd[4], TransferCmd[5], TransferCmd[6], TransferCmd[7],
datasize);*/
// default is read
// commands that do writes will change this
TransferDir = 0;
- switch (cmd[0])
- {
- case 0x9F:
- memset(TransferData, 0xFF, TransferLen);
- break;
-
- case 0x00:
- memset(TransferData, 0, TransferLen);
- if (TransferLen > 0x1000)
- {
- ReadROM(0, 0x1000, 0);
- for (u32 pos = 0x1000; pos < TransferLen; pos += 0x1000)
- memcpy(TransferData+pos, TransferData, 0x1000);
- }
- else
- ReadROM(0, TransferLen, 0);
- break;
-
- case 0x90:
- case 0xB8:
- for (u32 pos = 0; pos < TransferLen; pos += 4)
- *(u32*)&TransferData[pos] = CartID;
- break;
-
- case 0x3C:
- if (CartInserted)
- {
- CmdEncMode = 1;
- Key1_InitKeycode(false, *(u32*)&CartROM[0xC], 2, 2);
- }
- break;
-
- case 0x3D:
- if (CartInserted && CartIsDSi)
- {
- CmdEncMode = 11;
- Key1_InitKeycode(true, *(u32*)&CartROM[0xC], 1, 2);
- }
- break;
-
- default:
- if (CmdEncMode == 1 || CmdEncMode == 11)
- {
- switch (cmd[0] & 0xF0)
- {
- case 0x40:
- DataEncMode = 2;
- break;
-
- case 0x10:
- for (u32 pos = 0; pos < TransferLen; pos += 4)
- *(u32*)&TransferData[pos] = CartID;
- break;
-
- case 0x20:
- {
- u32 addr = (cmd[2] & 0xF0) << 8;
- if (CmdEncMode == 11)
- {
- u32 arm9i_base = *(u32*)&CartROM[0x1C0];
- addr -= 0x4000;
- addr += arm9i_base;
- }
- ReadROM(addr, 0x1000, 0);
- }
- break;
-
- case 0xA0:
- CmdEncMode = 2;
- break;
- }
- }
- else if (ROMCommandHandler)
- ROMCommandHandler(cmd);
- break;
- }
+ // TODO: how should we detect that the transfer should be a write?
+ // you're supposed to set bit30 of ROMCNT for a write, but it's also
+ // possible to do reads just fine when that bit is set
+ if (Cart)
+ TransferDir = Cart->ROMCommandStart(TransferCmd, TransferData, TransferLen);
ROMCnt &= ~(1<<23);
@@ -1454,6 +1784,7 @@ void WriteROMCnt(u32 val)
u32 cmddelay = 8;
// delays are only applied when the WR bit is cleared
+ // CHECKME: do the delays apply at the end (instead of start) when WR is set?
if (!(ROMCnt & (1<<30)))
{
cmddelay += (ROMCnt & 0x1FFF);
@@ -1517,6 +1848,12 @@ void WriteROMData(u32 val)
void WriteSPICnt(u16 val)
{
+ if ((SPICnt & 0x2040) == 0x2040 && (val & 0x2000) == 0x0000)
+ {
+ // forcefully reset SPI hold
+ SPIHold = false;
+ }
+
SPICnt = (SPICnt & 0x0080) | (val & 0xE043);
if (SPICnt & (1<<7))
printf("!! CHANGING AUXSPICNT DURING TRANSFER: %04X\n", val);
@@ -1533,7 +1870,7 @@ u8 ReadSPIData()
if (!(SPICnt & (1<<13))) return 0;
if (SPICnt & (1<<7)) return 0; // checkme
- return NDSCart_SRAM::Read();
+ return SPIData;
}
void WriteSPIData(u8 val)
@@ -1544,7 +1881,28 @@ void WriteSPIData(u8 val)
if (SPICnt & (1<<7)) printf("!! WRITING AUXSPIDATA DURING PENDING TRANSFER\n");
SPICnt |= (1<<7);
- NDSCart_SRAM::Write(val, SPICnt&(1<<6));
+
+ bool hold = SPICnt&(1<<6);
+ bool islast = false;
+ if (!hold)
+ {
+ if (SPIHold) SPIDataPos++;
+ else SPIDataPos = 0;
+ islast = true;
+ SPIHold = false;
+ }
+ else if (hold && (!SPIHold))
+ {
+ SPIHold = true;
+ SPIDataPos = 0;
+ }
+ else
+ {
+ SPIDataPos++;
+ }
+
+ if (Cart) SPIData = Cart->SPIWrite(val, SPIDataPos, islast);
+ else SPIData = 0;
// SPI transfers one bit per cycle -> 8 cycles per byte
u32 delay = 8 * (8 << (SPICnt & 0x3));