diff options
Diffstat (limited to 'src/NDSCart.cpp')
-rw-r--r-- | src/NDSCart.cpp | 939 |
1 files changed, 939 insertions, 0 deletions
diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp new file mode 100644 index 0000000..416da26 --- /dev/null +++ b/src/NDSCart.cpp @@ -0,0 +1,939 @@ +/* + Copyright 2016-2017 StapleButter + + This file is part of melonDS. + + melonDS is free software: you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + melonDS is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with melonDS. If not, see http://www.gnu.org/licenses/. +*/ + +#include <stdio.h> +#include <string.h> +#include "NDS.h" +#include "NDSCart.h" + + +namespace NDSCart_SRAM +{ + +u8* SRAM; +u32 SRAMLength; + +char SRAMPath[256]; + +void (*WriteFunc)(u8 val, bool islast); + +u32 Discover_MemoryType; +u32 Discover_Likeliness; +u8* Discover_Buffer; +u32 Discover_DataPos; + +u32 Hold; +u8 CurCmd; +u32 DataPos; +u8 Data; + +u8 StatusReg; +u32 Addr; + + +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); +void Write_Discover(u8 val, bool islast); + + +bool Init() +{ + SRAM = NULL; + Discover_Buffer = NULL; + return true; +} + +void DeInit() +{ + if (SRAM) delete[] SRAM; + if (Discover_Buffer) delete[] Discover_Buffer; +} + +void Reset() +{ +} + +void LoadSave(char* path) +{ + if (SRAM) delete[] SRAM; + if (Discover_Buffer) delete[] Discover_Buffer; + + Discover_Buffer = NULL; + + strncpy(SRAMPath, path, 255); + SRAMPath[255] = '\0'; + + FILE* f = fopen(path, "rb"); + if (f) + { + fseek(f, 0, SEEK_END); + SRAMLength = (u32)ftell(f); + SRAM = new u8[SRAMLength]; + + fseek(f, 0, SEEK_SET); + fread(SRAM, SRAMLength, 1, f); + + fclose(f); + + switch (SRAMLength) + { + case 512: WriteFunc = Write_EEPROMTiny; break; + case 8192: + case 65536: WriteFunc = Write_EEPROM; break; + case 256*1024: + case 512*1024: + case 1024*1024: + case 8192*1024: WriteFunc = Write_Flash; break; + default: + printf("!! BAD SAVE LENGTH %d\n", SRAMLength); + WriteFunc = Write_Null; + break; + } + } + else + { + SRAMLength = 0; + WriteFunc = Write_Discover; + Discover_MemoryType = 2; + Discover_Likeliness = 0; + + Discover_DataPos = 0; + Discover_Buffer = new u8[256*1024]; + memset(Discover_Buffer, 0, 256*1024); + } + + Hold = 0; + CurCmd = 0; + Data = 0; + StatusReg = 0x00; +} + +u8 Read() +{ + return Data; +} + +void SetMemoryType() +{ + switch (Discover_MemoryType) + { + case 1: + printf("Save memory type: EEPROM 4k\n"); + WriteFunc = Write_EEPROMTiny; + SRAMLength = 512; + break; + + case 2: + printf("Save memory type: EEPROM 64k\n"); + WriteFunc = Write_EEPROM; + SRAMLength = 8192; + break; + + case 3: + printf("Save memory type: EEPROM 512k\n"); + WriteFunc = Write_EEPROM; + SRAMLength = 65536; + break; + + case 4: + printf("Save memory type: Flash. Hope the size is 256K.\n"); + WriteFunc = Write_Flash; + SRAMLength = 256*1024; + break; + + case 5: + printf("Save memory type: ...something else\n"); + WriteFunc = Write_Null; + SRAMLength = 0; + break; + } + + if (!SRAMLength) + return; + + SRAM = new u8[SRAMLength]; + + // replay writes that occured during discovery + u8 prev_cmd = CurCmd; + u32 pos = 0; + while (pos < 256*1024) + { + u32 len = *(u32*)&Discover_Buffer[pos]; + pos += 4; + if (len == 0) break; + + CurCmd = Discover_Buffer[pos++]; + DataPos = 0; + Addr = 0; + Data = 0; + for (u32 i = 1; i < len; i++) + { + WriteFunc(Discover_Buffer[pos++], (i==(len-1))); + DataPos++; + } + } + + CurCmd = prev_cmd; + + delete[] Discover_Buffer; +} + +void Write_Discover(u8 val, bool islast) +{ + // attempt at autodetecting the type of save memory. + // we basically hope the game will be nice and clear whole pages of memory. + + if (CurCmd == 0x03 || CurCmd == 0x0B) + { + if (Discover_Likeliness) + { + // apply. and pray. + SetMemoryType(); + + DataPos = 0; + Addr = 0; + Data = 0; + return WriteFunc(val, islast); + } + else + { + Data = 0; + return; + } + } + + if (CurCmd == 0x02 || CurCmd == 0x0A) + { + if (DataPos == 0) + Discover_Buffer[Discover_DataPos + 4] = CurCmd; + + Discover_Buffer[Discover_DataPos + 5 + DataPos] = val; + + if (islast) + { + u32 len = DataPos+1; + + *(u32*)&Discover_Buffer[Discover_DataPos] = len+1; + Discover_DataPos += 5+len; + + if (Discover_Likeliness <= len) + { + Discover_Likeliness = len; + + if (len > 3+256) // bigger Flash, FRAM, whatever + { + Discover_MemoryType = 5; + } + else if (len > 2+128) // Flash + { + Discover_MemoryType = 4; + } + else if (len > 2+32) // EEPROM 512k + { + Discover_MemoryType = 3; + } + else if (len > 1+16 || (len != 1+16 && CurCmd != 0x0A)) // EEPROM 64k + { + Discover_MemoryType = 2; + } + else // EEPROM 4k + { + Discover_MemoryType = 1; + } + } + + printf("discover: type=%d likeliness=%d\n", Discover_MemoryType, Discover_Likeliness); + } + } +} + +void Write_Null(u8 val, bool islast) {} + +void Write_EEPROMTiny(u8 val, bool islast) +{ + // TODO +} + +void Write_EEPROM(u8 val, bool islast) +{ + switch (CurCmd) + { + case 0x02: + if (DataPos < 2) + { + Addr <<= 8; + Addr |= val; + Data = 0; + } + else + { + SRAM[Addr & (SRAMLength-1)] = val; + Addr++; + } + break; + + case 0x03: + if (DataPos < 2) + { + Addr <<= 8; + Addr |= val; + Data = 0; + } + else + { + Data = SRAM[Addr & (SRAMLength-1)]; + Addr++; + } + break; + + case 0x9F: + Data = 0xFF; + break; + + default: + if (DataPos==0) + printf("unknown EEPROM save command %02X\n", CurCmd); + break; + } +} + +void Write_Flash(u8 val, bool islast) +{ + switch (CurCmd) + { + case 0x03: + if (DataPos < 3) + { + Addr <<= 8; + Addr |= val; + Data = 0; + } + else + { + // CHECKME: does Flash also wraparound when the address is out of bounds? + if (Addr >= SRAMLength) + Data = 0; + else + Data = SRAM[Addr]; + + Addr++; + } + break; + + case 0x0A: + if (DataPos < 3) + { + Addr <<= 8; + Addr |= val; + Data = 0; + } + else + { + if (Addr < SRAMLength) + SRAM[Addr] = val; + + Addr++; + } + break; + + case 0x9F: + Data = 0xFF; + break; + + default: + if (DataPos==0) + printf("unknown Flash save command %02X\n", CurCmd); + break; + } +} + +void Write(u8 val, u32 hold) +{ + bool islast = false; + + if (!hold) + { + if (Hold) islast = true; + Hold = 0; + } + + if (hold && (!Hold)) + { + CurCmd = val; + Hold = 1; + Data = 0; + DataPos = 0; + Addr = 0; + //printf("save SPI command %02X\n", CurCmd); + return; + } + + switch (CurCmd) + { + case 0x02: + case 0x03: + case 0x0A: + case 0x0B: + case 0x9F: + WriteFunc(val, islast); + DataPos++; + break; + + case 0x04: // write disable + StatusReg &= ~(1<<1); + Data = 0; + break; + + case 0x05: // read status reg + Data = StatusReg; + break; + + case 0x06: // write enable + StatusReg |= (1<<1); + Data = 0; + break; + + default: + if (DataPos==0) + printf("unknown save SPI command %02X\n", CurCmd); + break; + } + + if (islast && (CurCmd == 0x02 || CurCmd == 0x0A)) + { + FILE* f = fopen(SRAMPath, "wb"); + if (f) + { + fwrite(SRAM, SRAMLength, 1, f); + fclose(f); + } + } +} + +} + + +namespace NDSCart +{ + +u16 SPICnt; +u32 ROMCnt; + +u8 ROMCommand[8]; +u32 ROMDataOut; + +u8 DataOut[0x4000]; +u32 DataOutPos; +u32 DataOutLen; + +bool CartInserted; +u8* CartROM; +u32 CartROMSize; +u32 CartID; +bool CartIsHomebrew; + +u32 CmdEncMode; +u32 DataEncMode; + +u32 Key1_KeyBuf[0x412]; + +u64 Key2_X; +u64 Key2_Y; + + +u32 ByteSwap(u32 val) +{ + return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24); +} + +void Key1_Encrypt(u32* data) +{ + 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 Key1_Decrypt(u32* data) +{ + 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 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(u32 idcode, u32 level, u32 mod) +{ + 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; + } +} + + +bool Init() +{ + if (!NDSCart_SRAM::Init()) return false; + + return true; +} + +void DeInit() +{ + NDSCart_SRAM::DeInit(); +} + +void Reset() +{ + SPICnt = 0; + ROMCnt = 0; + + memset(ROMCommand, 0, 8); + ROMDataOut = 0; + + Key2_X = 0; + Key2_Y = 0; + + memset(DataOut, 0, 0x4000); + DataOutPos = 0; + DataOutLen = 0; + + CartInserted = false; + CartROM = NULL; + CartROMSize = 0; + CartID = 0; + CartIsHomebrew = false; + + CmdEncMode = 0; + DataEncMode = 0; + + NDSCart_SRAM::Reset(); +} + + +bool LoadROM(char* path) +{ + // TODO: streaming mode? for really big ROMs or systems with limited RAM + // for now we're lazy + + FILE* f = fopen(path, "rb"); + if (!f) + { + printf("Failed to open ROM file %s\n", path); + return false; + } + + fseek(f, 0, SEEK_END); + u32 len = (u32)ftell(f); + + CartROMSize = 0x200; + while (CartROMSize < len) + CartROMSize <<= 1; + + u32 gamecode; + fseek(f, 0x0C, SEEK_SET); + fread(&gamecode, 4, 1, f); + + CartROM = new u8[CartROMSize]; + memset(CartROM, 0, CartROMSize); + fseek(f, 0, SEEK_SET); + fread(CartROM, 1, len, f); + + fclose(f); + //CartROM = f; + + // temp. TODO: later make this user selectable + // calling this sets up shit for booting from the cart directly. + // normal behavior is booting from the BIOS. + NDS::SetupDirectBoot(); + + CartInserted = true; + + // generate a ROM ID + // note: most games don't check the actual value + // it just has to stay the same throughout gameplay + CartID = 0x00001FC2; + + u32 arm9base = *(u32*)&CartROM[0x20]; + if (arm9base < 0x8000) + { + if (arm9base >= 0x4000) + { + // reencrypt secure area if needed + if (*(u32*)&CartROM[arm9base] == 0xE7FFDEFF) + { + printf("Re-encrypting cart secure area\n"); + + strncpy((char*)&CartROM[arm9base], "encryObj", 8); + + Key1_InitKeycode(gamecode, 3, 2); + for (u32 i = 0; i < 0x800; i += 8) + Key1_Encrypt((u32*)&CartROM[arm9base + i]); + + Key1_InitKeycode(gamecode, 2, 2); + Key1_Encrypt((u32*)&CartROM[arm9base]); + } + } + else + CartIsHomebrew = true; + } + + // encryption + Key1_InitKeycode(gamecode, 2, 2); + + + // save + char savepath[256]; + strncpy(savepath, path, 255); + savepath[255] = '\0'; + strncpy(savepath + strlen(path) - 3, "sav", 3); + printf("Save file: %s\n", savepath); + NDSCart_SRAM::LoadSave(savepath); + + return true; +} + +void ReadROM(u32 addr, u32 len, u32 offset) +{ + if (!CartInserted) return; + + if (addr >= CartROMSize) return; + if ((addr+len) > CartROMSize) + len = CartROMSize - addr; + + memcpy(DataOut+offset, CartROM+addr, len); +} + +void ReadROM_B7(u32 addr, u32 len, u32 offset) +{ + addr &= (CartROMSize-1); + if (!CartIsHomebrew) + { + if (addr < 0x8000) + addr = 0x8000 + (addr & 0x1FF); + } + + memcpy(DataOut+offset, CartROM+addr, len); +} + + +void EndTransfer() +{ + ROMCnt &= ~(1<<23); + ROMCnt &= ~(1<<31); + + if (SPICnt & (1<<14)) + NDS::SetIRQ((NDS::ExMemCnt[0]>>11)&0x1, NDS::IRQ_CartSendDone); +} + +void ROMPrepareData(u32 param) +{ + if (DataOutPos >= DataOutLen) + ROMDataOut = 0; + else + ROMDataOut = *(u32*)&DataOut[DataOutPos]; + + DataOutPos += 4; + + ROMCnt |= (1<<23); + NDS::CheckDMAs(0, 0x06); + NDS::CheckDMAs(1, 0x12); + + //if (DataOutPos < DataOutLen) + // NDS::ScheduleEvent((ROMCnt & (1<<27)) ? 8:5, ROMPrepareData, 0); +} + +void WriteROMCnt(u32 val) +{ + ROMCnt = val & 0xFF7F7FFF; + + if (!(SPICnt & (1<<15))) return; + + if (val & (1<<15)) + { + u32 snum = (NDS::ExMemCnt[0]>>8)&0x8; + u64 seed0 = *(u32*)&NDS::ROMSeed0[snum] | ((u64)NDS::ROMSeed0[snum+4] << 32); + u64 seed1 = *(u32*)&NDS::ROMSeed1[snum] | ((u64)NDS::ROMSeed1[snum+4] << 32); + + Key2_X = 0; + Key2_Y = 0; + for (u32 i = 0; i < 39; i++) + { + if (seed0 & (1ULL << i)) Key2_X |= (1ULL << (38-i)); + if (seed1 & (1ULL << i)) Key2_Y |= (1ULL << (38-i)); + } + + printf("seed0: %02X%08X\n", (u32)(seed0>>32), (u32)seed0); + printf("seed1: %02X%08X\n", (u32)(seed1>>32), (u32)seed1); + printf("key2 X: %02X%08X\n", (u32)(Key2_X>>32), (u32)Key2_X); + printf("key2 Y: %02X%08X\n", (u32)(Key2_Y>>32), (u32)Key2_Y); + } + + if (!(ROMCnt & (1<<31))) return; + + u32 datasize = (ROMCnt >> 24) & 0x7; + if (datasize == 7) + datasize = 4; + else if (datasize > 0) + datasize = 0x100 << datasize; + + DataOutPos = 0; + DataOutLen = 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) + { + *(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]; + } + + /*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], + datasize);*/ + + switch (cmd[0]) + { + case 0x9F: + memset(DataOut, 0xFF, DataOutLen); + break; + + case 0x00: + memset(DataOut, 0, DataOutLen); + if (DataOutLen > 0x1000) + { + ReadROM(0, 0x1000, 0); + for (u32 pos = 0x1000; pos < DataOutLen; pos += 0x1000) + memcpy(DataOut+pos, DataOut, 0x1000); + } + else + ReadROM(0, DataOutLen, 0); + break; + + case 0x90: + case 0xB8: + for (u32 pos = 0; pos < DataOutLen; pos += 4) + *(u32*)&DataOut[pos] = CartID; + break; + + case 0x3C: + CmdEncMode = 1; + break; + + case 0xB7: + { + u32 addr = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4]; + memset(DataOut, 0, DataOutLen); + + if (((addr + DataOutLen - 1) >> 12) != (addr >> 12)) + { + u32 len1 = 0x1000 - (addr & 0xFFF); + ReadROM_B7(addr, len1, 0); + ReadROM_B7(addr+len1, DataOutLen-len1, len1); + } + else + ReadROM_B7(addr, DataOutLen, 0); + } + break; + + default: + switch (cmd[0] & 0xF0) + { + case 0x40: + DataEncMode = 2; + break; + + case 0x10: + for (u32 pos = 0; pos < DataOutLen; pos += 4) + *(u32*)&DataOut[pos] = CartID; + break; + + case 0x20: + { + u32 addr = (cmd[2] & 0xF0) << 8; + ReadROM(addr, 0x1000, 0); + } + break; + + case 0xA0: + CmdEncMode = 2; + break; + } + break; + } + + //ROMCnt &= ~(1<<23); + ROMCnt |= (1<<23); + + if (datasize == 0) + EndTransfer(); + else + { + NDS::CheckDMAs(0, 0x05); + NDS::CheckDMAs(1, 0x12); + } + //NDS::ScheduleEvent((ROMCnt & (1<<27)) ? 8:5, ROMPrepareData, 0); +} + +u32 ReadROMData() +{ + /*if (ROMCnt & (1<<23)) + { + ROMCnt &= ~(1<<23); + if (DataOutPos >= DataOutLen) + EndTransfer(); + } + + return ROMDataOut;*/ + u32 ret; + if (DataOutPos >= DataOutLen) + ret = 0; + else + ret = *(u32*)&DataOut[DataOutPos]; + + DataOutPos += 4; + + if (DataOutPos == DataOutLen) + EndTransfer(); + + return ret; +} + +void DMA(u32 addr) +{ + void (*writefn)(u32,u32) = (NDS::ExMemCnt[0] & (1<<11)) ? NDS::ARM7Write32 : NDS::ARM9Write32; + for (u32 i = 0; i < DataOutLen; i+=4) + { + writefn(addr+i, *(u32*)&DataOut[i]); + } + + EndTransfer(); +} + + +void WriteSPICnt(u16 val) +{ + SPICnt = (SPICnt & 0x0080) | (val & 0xE043); +} + +u8 ReadSPIData() +{ + if (!(SPICnt & (1<<15))) return 0; + if (!(SPICnt & (1<<13))) return 0; + + return NDSCart_SRAM::Read(); +} + +void WriteSPIData(u8 val) +{ + if (!(SPICnt & (1<<15))) return; + if (!(SPICnt & (1<<13))) return; + + // TODO: take delays into account + + NDSCart_SRAM::Write(val, SPICnt&(1<<6)); +} + +} |