diff options
Diffstat (limited to 'src/NDSCart.cpp')
-rw-r--r-- | src/NDSCart.cpp | 318 |
1 files changed, 175 insertions, 143 deletions
diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 12f7ad1..cda0bf4 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -16,7 +16,6 @@ with melonDS. If not, see http://www.gnu.org/licenses/. */ -#include <stdio.h> #include <string.h> #include "NDS.h" #include "DSi.h" @@ -27,6 +26,7 @@ #include "Platform.h" #include "ROMList.h" #include "melonDLDI.h" +#include "xxhash/xxhash.h" using Platform::Log; using Platform::LogLevel; @@ -52,15 +52,7 @@ u32 TransferLen; u32 TransferDir; u8 TransferCmd[8]; -bool CartInserted; -u8* CartROM; -u32 CartROMSize; -u32 CartID; - -NDSHeader Header; -NDSBanner Banner; - -CartCommon* Cart; +std::unique_ptr<CartCommon> Cart; u32 Key1_KeyBuf[0x412]; @@ -134,32 +126,36 @@ void Key1_ApplyKeycode(u32* keycode, u32 mod) } } -void Key1_LoadKeyBuf(bool dsi) +void Key1_LoadKeyBuf(bool dsi, bool externalBios, u8 *bios, u32 biosLength) { - // it is possible that this gets called before the BIOSes are loaded - // so we will read from the BIOS files directly - - if (Platform::GetConfigBool(Platform::ExternalBIOSEnable)) + if (externalBios) { - std::string path = Platform::GetConfigString(dsi ? Platform::DSi_BIOS7Path : Platform::BIOS7Path); - FILE* f = Platform::OpenLocalFile(path, "rb"); - if (f) + u32 expected_bios_length = dsi ? 0x10000 : 0x4000; + if (biosLength != expected_bios_length) + { + Platform::Log(LogLevel::Error, "NDSCart: Expected an ARM7 BIOS of %u bytes, got %u bytes\n", expected_bios_length, biosLength); + } + else if (bios == nullptr) { - fseek(f, dsi ? 0xC6D0 : 0x0030, SEEK_SET); - fread(Key1_KeyBuf, 0x1048, 1, f); - fclose(f); + Platform::Log(LogLevel::Error, "NDSCart: Expected an ARM7 BIOS of %u bytes, got nullptr\n", expected_bios_length); + } + else + { + memcpy(Key1_KeyBuf, bios + (dsi ? 0xC6D0 : 0x0030), 0x1048); + Platform::Log(LogLevel::Debug, "NDSCart: Initialized Key1_KeyBuf from memory\n"); } } else { // well memset(Key1_KeyBuf, 0, 0x1048); + Platform::Log(LogLevel::Debug, "NDSCart: Initialized Key1_KeyBuf to zero\n"); } } -void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod) +void Key1_InitKeycode(bool dsi, u32 idcode, u32 level, u32 mod, u8 *bios, u32 biosLength) { - Key1_LoadKeyBuf(dsi); + Key1_LoadKeyBuf(dsi, Platform::GetConfigBool(Platform::ExternalBIOSEnable), bios, biosLength); u32 keycode[3] = {idcode, idcode>>1, idcode<<1}; if (level >= 1) Key1_ApplyKeycode(keycode, mod); @@ -194,32 +190,35 @@ void Key2_Encrypt(u8* data, u32 len) } -CartCommon::CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump) +CartCommon::CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) { ROM = rom; ROMLength = len; ChipID = chipid; + ROMParams = romparams; - u8 unitcode = ROM[0x12]; - IsDSi = (unitcode & 0x02) != 0 && !badDSiDump; - DSiBase = *(u16*)&ROM[0x92] << 19; + memcpy(&Header, rom, sizeof(Header)); + IsDSi = Header.IsDSi() && !badDSiDump; + DSiBase = Header.DSiRegionStart << 19; } CartCommon::~CartCommon() { + delete[] ROM; } -u32 CartCommon::Checksum() +u32 CartCommon::Checksum() const { + const NDSHeader& header = GetHeader(); u32 crc = CRC32(ROM, 0x40); - crc = CRC32(&ROM[Header.ARM9ROMOffset], Header.ARM9Size, crc); - crc = CRC32(&ROM[Header.ARM7ROMOffset], Header.ARM7Size, crc); + crc = CRC32(&ROM[header.ARM9ROMOffset], header.ARM9Size, crc); + crc = CRC32(&ROM[header.ARM7ROMOffset], header.ARM7Size, crc); if (IsDSi) { - crc = CRC32(&ROM[Header.DSiARM9iROMOffset], Header.DSiARM9iSize, crc); - crc = CRC32(&ROM[Header.DSiARM7iROMOffset], Header.DSiARM7iSize, crc); + crc = CRC32(&ROM[header.DSiARM9iROMOffset], header.DSiARM9iSize, crc); + crc = CRC32(&ROM[header.DSiARM7iROMOffset], header.DSiARM7iSize, crc); } return crc; @@ -285,7 +284,7 @@ int CartCommon::ROMCommandStart(u8* cmd, u8* data, u32 len) case 0x3C: CmdEncMode = 1; - Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2); + Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS)); DSiMode = false; return 0; @@ -293,7 +292,7 @@ int CartCommon::ROMCommandStart(u8* cmd, u8* data, u32 len) if (IsDSi) { CmdEncMode = 1; - Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2); + Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, DSi::ARM7iBIOS, sizeof(DSi::ARM7iBIOS)); DSiMode = true; } return 0; @@ -402,8 +401,19 @@ void CartCommon::ReadROM(u32 addr, u32 len, u8* data, u32 offset) memcpy(data+offset, ROM+addr, len); } +const NDSBanner* CartCommon::Banner() const +{ + const NDSHeader& header = GetHeader(); + size_t bannersize = header.IsDSi() ? 0x23C0 : 0xA40; + if (header.BannerOffset >= 0x200 && header.BannerOffset < (ROMLength - bannersize)) + { + return reinterpret_cast<const NDSBanner*>(ROM + header.BannerOffset); + } + + return nullptr; +} -CartRetail::CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump) : CartCommon(rom, len, chipid, badDSiDump) +CartRetail::CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) : CartCommon(rom, len, chipid, badDSiDump, romparams) { SRAM = nullptr; } @@ -865,7 +875,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last) } -CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid) : CartRetail(rom, len, chipid, false) +CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) { } @@ -1092,7 +1102,7 @@ void CartRetailNAND::BuildSRAMID() } -CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump) : CartRetail(rom, len, chipid, badDSiDump) +CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams) : CartRetail(rom, len, chipid, badDSiDump, romparams) { IRVersion = irversion; } @@ -1138,7 +1148,7 @@ u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last) } -CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid) : CartRetail(rom, len, chipid, false) +CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) { Log(LogLevel::Info,"POKETYPE CART\n"); } @@ -1172,7 +1182,7 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last) } -CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid) : CartCommon(rom, len, chipid, false) +CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartCommon(rom, len, chipid, false, romparams) { SD = nullptr; } @@ -1225,7 +1235,7 @@ void CartHomebrew::SetupDirectBoot(const std::string& romname) { // add the ROM to the SD volume - if (!SD->InjectFile(romname, CartROM, CartROMSize)) + if (!SD->InjectFile(romname, ROM, ROMLength)) return; // setup argv command line @@ -1237,9 +1247,10 @@ void CartHomebrew::SetupDirectBoot(const std::string& romname) strncat(argv, romname.c_str(), 511); argvlen = strlen(argv); + const NDSHeader& header = GetHeader(); void (*writefn)(u32,u32) = (NDS::ConsoleType==1) ? DSi::ARM9Write32 : NDS::ARM9Write32; - u32 argvbase = Header.ARM9RAMAddress + Header.ARM9Size; + u32 argvbase = header.ARM9RAMAddress + header.ARM9Size; argvbase = (argvbase + 0xF) & ~0xF; for (u32 i = 0; i <= argvlen; i+=4) @@ -1457,8 +1468,6 @@ void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset) bool Init() { - CartInserted = false; - CartROM = nullptr; Cart = nullptr; return true; @@ -1466,8 +1475,7 @@ bool Init() void DeInit() { - if (CartROM) delete[] CartROM; - if (Cart) delete Cart; + Cart = nullptr; } void Reset() @@ -1530,14 +1538,12 @@ void DoSavestate(Savestate* file) bool ReadROMParams(u32 gamecode, ROMListEntry* params) { - u32 len = sizeof(ROMList) / sizeof(ROMListEntry); - u32 offset = 0; - u32 chk_size = len >> 1; + u32 chk_size = ROMListSize >> 1; for (;;) { u32 key = 0; - ROMListEntry* curentry = &ROMList[offset + chk_size]; + const ROMListEntry* curentry = &ROMList[offset + chk_size]; key = curentry->GameCode; if (key == gamecode) @@ -1562,7 +1568,7 @@ bool ReadROMParams(u32 gamecode, ROMListEntry* params) chk_size >>= 1; } - if (offset >= len) + if (offset >= ROMListSize) { return false; } @@ -1572,21 +1578,24 @@ bool ReadROMParams(u32 gamecode, ROMListEntry* params) void DecryptSecureArea(u8* out) { - u32 gamecode = (u32)Header.GameCode[3] << 24 | - (u32)Header.GameCode[2] << 16 | - (u32)Header.GameCode[1] << 8 | - (u32)Header.GameCode[0]; - u32 arm9base = Header.ARM9ROMOffset; + const NDSHeader& header = Cart->GetHeader(); + const u8* cartrom = Cart->GetROM(); + + u32 gamecode = header.GameCodeAsU32(); + u32 arm9base = header.ARM9ROMOffset; - memcpy(out, &CartROM[arm9base], 0x800); + memcpy(out, &cartrom[arm9base], 0x800); - Key1_InitKeycode(false, gamecode, 2, 2); + Key1_InitKeycode(false, gamecode, 2, 2, NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS)); Key1_Decrypt((u32*)&out[0]); - Key1_InitKeycode(false, gamecode, 3, 2); + Key1_InitKeycode(false, gamecode, 3, 2, NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS)); for (u32 i = 0; i < 0x800; i += 8) Key1_Decrypt((u32*)&out[i]); + XXH64_hash_t hash = XXH64(out, 0x800, 0); + Log(LogLevel::Debug, "Secure area post-decryption xxh64 hash: %zx\n", hash); + if (!strncmp((const char*)out, "encryObj", 8)) { Log(LogLevel::Info, "Secure area decryption OK\n"); @@ -1601,38 +1610,47 @@ void DecryptSecureArea(u8* out) } } -bool LoadROM(const u8* romdata, u32 romlen) +std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen) { - if (CartInserted) - EjectCart(); + if (romdata == nullptr) + { + Log(LogLevel::Error, "NDSCart: romdata is null\n"); + return nullptr; + } - memset(&Header, 0, sizeof(Header)); - memset(&Banner, 0, sizeof(Banner)); + if (romlen == 0) + { + Log(LogLevel::Error, "NDSCart: romlen is zero\n"); + return nullptr; + } - CartROMSize = 0x200; - while (CartROMSize < romlen) - CartROMSize <<= 1; + u32 cartromsize = 0x200; + while (cartromsize < romlen) + cartromsize <<= 1; // ROM size must be a power of 2 + u8* cartrom = nullptr; try { - CartROM = new u8[CartROMSize]; + cartrom = new u8[cartromsize]; } catch (const std::bad_alloc& e) { - Log(LogLevel::Error, "NDSCart: failed to allocate memory for ROM (%d bytes)\n", CartROMSize); - return false; + Log(LogLevel::Error, "NDSCart: failed to allocate memory for ROM (%d bytes)\n", cartromsize); + + return nullptr; } - memset(CartROM, 0, CartROMSize); - memcpy(CartROM, romdata, romlen); + // copy romdata into cartrom then zero out the remaining space + memcpy(cartrom, romdata, romlen); + memset(cartrom + romlen, 0, cartromsize - romlen); - memcpy(&Header, CartROM, sizeof(Header)); + NDSHeader header {}; + memcpy(&header, cartrom, sizeof(header)); - u8 unitcode = Header.UnitCode; - bool dsi = (unitcode & 0x02) != 0; + bool dsi = header.IsDSi(); bool badDSiDump = false; - u32 dsiRegion = *(u32*)&CartROM[0x1B0]; + u32 dsiRegion = header.DSiRegionMask; if (dsi && dsiRegion == 0) { Log(LogLevel::Info, "DS header indicates DSi, but region is zero. Going in bad dump mode.\n"); @@ -1640,85 +1658,49 @@ bool LoadROM(const u8* romdata, u32 romlen) dsi = false; } - size_t bannersize = dsi ? 0x23C0 : 0xA40; - if (Header.BannerOffset >= 0x200 && Header.BannerOffset < (CartROMSize - bannersize)) - { - memcpy(&Banner, CartROM + Header.BannerOffset, bannersize); - } - - Log(LogLevel::Info, "Game code: %.4s\n", Header.GameCode); - - u32 gamecode = (u32)Header.GameCode[3] << 24 | - (u32)Header.GameCode[2] << 16 | - (u32)Header.GameCode[1] << 8 | - (u32)Header.GameCode[0]; + u32 gamecode = header.GameCodeAsU32(); - u32 arm9base = Header.ARM9ROMOffset; - bool homebrew = (arm9base < 0x4000) || (gamecode == 0x23232323); + u32 arm9base = header.ARM9ROMOffset; + bool homebrew = header.IsHomebrew(); - ROMListEntry romparams; + ROMListEntry romparams {}; if (!ReadROMParams(gamecode, &romparams)) { // set defaults - Log(LogLevel::Warn, "ROM entry not found\n"); + Log(LogLevel::Warn, "ROM entry not found for gamecode %d\n", gamecode); romparams.GameCode = gamecode; - romparams.ROMSize = CartROMSize; + romparams.ROMSize = cartromsize; if (homebrew) romparams.SaveMemType = 0; // no saveRAM for homebrew else romparams.SaveMemType = 2; // assume EEPROM 64k (TODO FIXME) } - else - Log(LogLevel::Info, "ROM entry: %08X %08X\n", romparams.ROMSize, romparams.SaveMemType); if (romparams.ROMSize != romlen) - Log(LogLevel::Warn, "!! bad ROM size %d (expected %d) rounded to %d\n", romlen, romparams.ROMSize, CartROMSize); + Log(LogLevel::Warn, "!! bad ROM size %d (expected %d) rounded to %d\n", romlen, romparams.ROMSize, cartromsize); // generate a ROM ID // note: most games don't check the actual value // it just has to stay the same throughout gameplay - CartID = 0x000000C2; + u32 cartid = 0x000000C2; - if (CartROMSize >= 1024*1024 && CartROMSize <= 128*1024*1024) - CartID |= ((CartROMSize >> 20) - 1) << 8; + if (cartromsize >= 1024 * 1024 && cartromsize <= 128 * 1024 * 1024) + cartid |= ((cartromsize >> 20) - 1) << 8; else - CartID |= (0x100 - (CartROMSize >> 28)) << 8; + cartid |= (0x100 - (cartromsize >> 28)) << 8; if (romparams.SaveMemType >= 8 && romparams.SaveMemType <= 10) - CartID |= 0x08000000; // NAND flag + cartid |= 0x08000000; // NAND flag if (dsi) - CartID |= 0x40000000; + 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 - - Log(LogLevel::Info, "Cart ID: %08X\n", CartID); - - if (arm9base >= 0x4000 && arm9base < 0x8000) - { - // reencrypt secure area if needed - if (*(u32*)&CartROM[arm9base] == 0xE7FFDEFF && *(u32*)&CartROM[arm9base+0x10] != 0xE7FFDEFF) - { - Log(LogLevel::Debug, "Re-encrypting cart secure area\n"); - - strncpy((char*)&CartROM[arm9base], "encryObj", 8); - - Key1_InitKeycode(false, gamecode, 3, 2); - for (u32 i = 0; i < 0x800; i += 8) - Key1_Encrypt((u32*)&CartROM[arm9base + i]); - - Key1_InitKeycode(false, gamecode, 2, 2); - Key1_Encrypt((u32*)&CartROM[arm9base]); - } - } - - CartInserted = true; - DSi::SetCartInserted(true); + //cartid = 0x88017FEC; + //cartid = 0x80007FC2; // pokémon typing adventure u32 irversion = 0; if ((gamecode & 0xFF) == 'I') @@ -1729,26 +1711,83 @@ bool LoadROM(const u8* romdata, u32 romlen) irversion = 2; // Pokémon HG/SS, B/W, B2/W2 } + std::unique_ptr<CartCommon> cart; if (homebrew) - Cart = new CartHomebrew(CartROM, CartROMSize, CartID); - else if (CartID & 0x08000000) - Cart = new CartRetailNAND(CartROM, CartROMSize, CartID); + cart = std::make_unique<CartHomebrew>(cartrom, cartromsize, cartid, romparams); + else if (cartid & 0x08000000) + cart = std::make_unique<CartRetailNAND>(cartrom, cartromsize, cartid, romparams); else if (irversion != 0) - Cart = new CartRetailIR(CartROM, CartROMSize, CartID, irversion, badDSiDump); + cart = std::make_unique<CartRetailIR>(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - Cart = new CartRetailBT(CartROM, CartROMSize, CartID); + cart = std::make_unique<CartRetailBT>(cartrom, cartromsize, cartid, romparams); else - Cart = new CartRetail(CartROM, CartROMSize, CartID, badDSiDump); + cart = std::make_unique<CartRetail>(cartrom, cartromsize, cartid, badDSiDump, romparams); + + if (romparams.SaveMemType > 0) + cart->SetupSave(romparams.SaveMemType); + + return cart; +} + +// Why a move function? Because the Cart object is polymorphic, +// and cloning polymorphic objects without knowing the underlying type is annoying. +bool InsertROM(std::unique_ptr<CartCommon>&& cart) +{ + if (!cart) { + Log(LogLevel::Error, "Failed to insert invalid cart; existing cart (if any) was not ejected.\n"); + return false; + } if (Cart) - Cart->Reset(); + EjectCart(); + + Cart = std::move(cart); - if (Cart && romparams.SaveMemType > 0) - Cart->SetupSave(romparams.SaveMemType); + Cart->Reset(); + + const NDSHeader& header = Cart->GetHeader(); + const ROMListEntry romparams = Cart->GetROMParams(); + const u8* cartrom = Cart->GetROM(); + if (header.ARM9ROMOffset >= 0x4000 && header.ARM9ROMOffset < 0x8000) + { + // reencrypt secure area if needed + if (*(u32*)&cartrom[header.ARM9ROMOffset] == 0xE7FFDEFF && *(u32*)&cartrom[header.ARM9ROMOffset + 0x10] != 0xE7FFDEFF) + { + Log(LogLevel::Debug, "Re-encrypting cart secure area\n"); + + strncpy((char*)&cartrom[header.ARM9ROMOffset], "encryObj", 8); + + Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS)); + for (u32 i = 0; i < 0x800; i += 8) + Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset + i]); + + Key1_InitKeycode(false, romparams.GameCode, 2, 2, NDS::ARM7BIOS, sizeof(NDS::ARM7BIOS)); + Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset]); + + Log(LogLevel::Debug, "Re-encrypted cart secure area\n"); + } + else + { + Log(LogLevel::Debug, "No need to re-encrypt cart secure area\n"); + } + } + + Log(LogLevel::Info, "Inserted cart with game code: %.4s\n", header.GameCode); + Log(LogLevel::Info, "Inserted cart with ID: %08X\n", Cart->ID()); + Log(LogLevel::Info, "ROM entry: %08X %08X\n", romparams.ROMSize, romparams.SaveMemType); + + DSi::SetCartInserted(true); return true; } +bool LoadROM(const u8* romdata, u32 romlen) +{ + std::unique_ptr<CartCommon> cart = ParseROM(romdata, romlen); + + return InsertROM(std::move(cart)); +} + void LoadSave(const u8* savedata, u32 savelen) { if (Cart) @@ -1773,21 +1812,14 @@ u32 GetSaveMemoryLength() void EjectCart() { - if (!CartInserted) return; + if (!Cart) return; // ejecting the cart triggers the gamecard IRQ NDS::SetIRQ(0, NDS::IRQ_CartIREQMC); NDS::SetIRQ(1, NDS::IRQ_CartIREQMC); - if (Cart) delete Cart; Cart = nullptr; - CartInserted = false; - if (CartROM) delete[] CartROM; - CartROM = nullptr; - CartROMSize = 0; - CartID = 0; - DSi::SetCartInserted(false); // CHECKME: does an eject imply anything for the ROM/SPI transfer registers? |