aboutsummaryrefslogtreecommitdiff
path: root/src/NDSCart.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/NDSCart.cpp')
-rw-r--r--src/NDSCart.cpp318
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?