aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Args.h100
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/DSi.cpp84
-rw-r--r--src/DSi.h22
-rw-r--r--src/DSi_AES.cpp2
-rw-r--r--src/DSi_NAND.cpp3
-rw-r--r--src/DSi_NWifi.cpp6
-rw-r--r--src/DSi_SD.cpp202
-rw-r--r--src/DSi_SD.h79
-rw-r--r--src/FATStorage.cpp90
-rw-r--r--src/FATStorage.h30
-rw-r--r--src/FreeBIOS.cpp4
-rw-r--r--src/FreeBIOS.h7
-rw-r--r--src/GBACart.cpp1796
-rw-r--r--src/GBACart.h546
-rw-r--r--src/NDS.cpp84
-rw-r--r--src/NDS.h63
-rw-r--r--src/NDSCart.cpp377
-rw-r--r--src/NDSCart.h230
-rw-r--r--src/Platform.h17
-rw-r--r--src/SPI.cpp94
-rw-r--r--src/SPI.h18
-rw-r--r--src/Utils.cpp66
-rw-r--r--src/Utils.h43
-rw-r--r--src/Wifi.cpp6
-rw-r--r--src/frontend/qt_sdl/ArchiveUtil.cpp7
-rw-r--r--src/frontend/qt_sdl/ArchiveUtil.h2
-rw-r--r--src/frontend/qt_sdl/Platform.cpp28
-rw-r--r--src/frontend/qt_sdl/ROMManager.cpp647
-rw-r--r--src/frontend/qt_sdl/ROMManager.h19
-rw-r--r--src/frontend/qt_sdl/main.cpp166
-rw-r--r--src/frontend/qt_sdl/main.h21
32 files changed, 2505 insertions, 2356 deletions
diff --git a/src/Args.h b/src/Args.h
new file mode 100644
index 0000000..bfa1b13
--- /dev/null
+++ b/src/Args.h
@@ -0,0 +1,100 @@
+/*
+ Copyright 2016-2023 melonDS team
+
+ 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/.
+*/
+
+#ifndef MELONDS_ARGS_H
+#define MELONDS_ARGS_H
+
+#include <array>
+#include <optional>
+#include <memory>
+
+#include "types.h"
+#include "MemConstants.h"
+#include "DSi_NAND.h"
+#include "FATStorage.h"
+#include "FreeBIOS.h"
+#include "SPI_Firmware.h"
+
+namespace melonDS
+{
+namespace NDSCart { class CartCommon; }
+namespace GBACart { class CartCommon; }
+
+template<size_t N>
+constexpr std::array<u8, N> BrokenBIOS = []() constexpr {
+ std::array<u8, N> broken {};
+
+ for (int i = 0; i < 16; i++)
+ {
+ broken[i*4+0] = 0xE7;
+ broken[i*4+1] = 0xFF;
+ broken[i*4+2] = 0xDE;
+ broken[i*4+3] = 0xFF;
+ }
+
+ return broken;
+}();
+
+/// Arguments to pass into the NDS constructor.
+/// New fields here should have default values if possible.
+struct NDSArgs
+{
+ /// NDS ROM to install.
+ /// Defaults to nullptr, which means no cart.
+ /// Should be populated with the desired save data beforehand,
+ /// including an SD card if applicable.
+ std::unique_ptr<NDSCart::CartCommon> NDSROM = nullptr;
+
+ /// GBA ROM to install.
+ /// Defaults to nullptr, which means no cart.
+ /// Should be populated with the desired save data beforehand.
+ /// Ignored in DSi mode.
+ std::unique_ptr<GBACart::CartCommon> GBAROM = nullptr;
+
+ /// NDS ARM9 BIOS to install.
+ /// Defaults to FreeBIOS, which is not compatible with DSi mode.
+ std::array<u8, ARM9BIOSSize> ARM9BIOS = bios_arm9_bin;
+
+ /// NDS ARM7 BIOS to install.
+ /// Defaults to FreeBIOS, which is not compatible with DSi mode.
+ std::array<u8, ARM7BIOSSize> ARM7BIOS = bios_arm7_bin;
+
+ /// Firmware image to install.
+ /// Defaults to generated NDS firmware.
+ /// Generated firmware is not compatible with DSi mode.
+ melonDS::Firmware Firmware {0};
+};
+
+/// Arguments to pass into the DSi constructor.
+/// New fields here should have default values if possible.
+/// Contains no virtual methods, so there's no vtable.
+struct DSiArgs final : public NDSArgs
+{
+ std::array<u8, DSiBIOSSize> ARM9iBIOS = BrokenBIOS<DSiBIOSSize>;
+ std::array<u8, DSiBIOSSize> ARM7iBIOS = BrokenBIOS<DSiBIOSSize>;
+
+ /// NAND image to install.
+ /// Required, there is no default value.
+ DSi_NAND::NANDImage NANDImage;
+
+ /// SD card to install.
+ /// Defaults to std::nullopt, which means no SD card.
+ std::optional<FATStorage> DSiSDCard;
+};
+}
+#endif //MELONDS_ARGS_H
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9fe93ae..ddb1b3c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -49,6 +49,8 @@ add_library(core STATIC
SPI_Firmware.cpp
SPU.cpp
types.h
+ Utils.cpp
+ Utils.h
version.h
Wifi.cpp
WifiAP.cpp
diff --git a/src/DSi.cpp b/src/DSi.cpp
index f2781e4..5dcd419 100644
--- a/src/DSi.cpp
+++ b/src/DSi.cpp
@@ -19,6 +19,7 @@
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
+#include "Args.h"
#include "NDS.h"
#include "DSi.h"
#include "ARM.h"
@@ -68,8 +69,8 @@ const u32 NDMAModes[] =
0xFF, // wifi / GBA cart slot (TODO)
};
-DSi::DSi() noexcept :
- NDS(1),
+DSi::DSi(DSiArgs&& args) noexcept :
+ NDS(std::move(args), 1),
NDMAs {
DSi_NDMA(0, 0, *this),
DSi_NDMA(0, 1, *this),
@@ -80,9 +81,11 @@ DSi::DSi() noexcept :
DSi_NDMA(1, 2, *this),
DSi_NDMA(1, 3, *this),
},
+ ARM7iBIOS(args.ARM7iBIOS),
+ ARM9iBIOS(args.ARM9iBIOS),
DSP(*this),
- SDMMC(*this, 0),
- SDIO(*this, 1),
+ SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)),
+ SDIO(*this),
I2C(*this),
CamModule(*this),
AES(*this)
@@ -118,9 +121,6 @@ void DSi::Reset()
CamModule.Reset();
DSP.Reset();
- SDMMC.CloseHandles();
- SDIO.CloseHandles();
-
LoadNAND();
SDMMC.Reset();
@@ -162,24 +162,22 @@ void DSi::Stop(Platform::StopReason reason)
CamModule.Stop();
}
-bool DSi::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen)
+void DSi::SetNDSCart(std::unique_ptr<NDSCart::CartCommon>&& cart)
{
- if (NDS::LoadCart(romdata, romlen, savedata, savelen))
- {
- SetCartInserted(true);
- return true;
- }
-
- return false;
+ NDS::SetNDSCart(std::move(cart));
+ SetCartInserted(NDSCartSlot.GetCart() != nullptr);
}
-void DSi::EjectCart()
+std::unique_ptr<NDSCart::CartCommon> DSi::EjectCart()
{
- NDS::EjectCart();
+ auto oldcart = NDS::EjectCart();
SetCartInserted(false);
+
+ return oldcart;
}
+
void DSi::CamInputFrame(int cam, u32* data, int width, int height, bool rgb)
{
switch (cam)
@@ -509,9 +507,9 @@ void DSi::SetupDirectBoot()
ARM9Write32(0x02FFE000+i, tmp);
}
- if (NANDImage && *NANDImage)
+ if (DSi_NAND::NANDImage* image = SDMMC.GetNAND(); image && *image)
{ // If a NAND image is installed, and it's valid...
- if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*NANDImage))
+ if (DSi_NAND::NANDMount nand = DSi_NAND::NANDMount(*image))
{
DSi_NAND::DSiFirmwareSystemSettings userdata {};
nand.ReadUserData(userdata);
@@ -531,7 +529,7 @@ void DSi::SetupDirectBoot()
}
}
- Firmware::WifiBoard nwifiver = SPI.GetFirmware()->GetHeader().WifiBoard;
+ Firmware::WifiBoard nwifiver = SPI.GetFirmware().GetHeader().WifiBoard;
ARM9Write8(0x020005E0, static_cast<u8>(nwifiver));
// TODO: these should be taken from the wifi firmware in NAND
@@ -674,9 +672,6 @@ void DSi::SoftReset()
// the DSP most likely gets reset
DSP.Reset();
- SDMMC.CloseHandles();
- SDIO.CloseHandles();
-
LoadNAND();
SDMMC.Reset();
@@ -709,21 +704,22 @@ void DSi::SoftReset()
bool DSi::LoadNAND()
{
- if (!NANDImage)
+ DSi_NAND::NANDImage* image = SDMMC.GetNAND();
+ if (!(image && *image))
{
Log(LogLevel::Error, "No NAND image loaded\n");
return false;
}
Log(LogLevel::Info, "Loading DSi NAND\n");
- DSi_NAND::NANDMount nandmount(*NANDImage);
+ DSi_NAND::NANDMount nandmount(*SDMMC.GetNAND());
if (!nandmount)
{
Log(LogLevel::Error, "Failed to load DSi NAND\n");
return false;
}
- FileHandle* nand = NANDImage->GetFile();
+ FileHandle* nand = image->GetFile();
// Make sure NWRAM is accessible.
// The Bits are set to the startup values in Reset() and we might
@@ -879,9 +875,9 @@ bool DSi::LoadNAND()
}
}
- const DSi_NAND::DSiKey& emmccid = NANDImage->GetEMMCID();
+ const DSi_NAND::DSiKey& emmccid = image->GetEMMCID();
Log(LogLevel::Debug, "eMMC CID: %08llX%08llX\n", *(const u64*)&emmccid[0], *(const u64*)&emmccid[8]);
- Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", NANDImage->GetConsoleID());
+ Log(LogLevel::Debug, "Console ID: %" PRIx64 "\n", image->GetConsoleID());
if (Platform::GetConfigBool(Platform::DSi_FullBIOSBoot))
{
@@ -1728,12 +1724,12 @@ bool DSi::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region)
return false;
}
- region->Mem = ARM9BIOS;
+ region->Mem = &ARM9BIOS[0];
region->Mask = 0xFFF;
}
else
{
- region->Mem = ARM9iBIOS;
+ region->Mem = &ARM9iBIOS[0];
region->Mask = 0xFFFF;
}
return true;
@@ -2678,14 +2674,14 @@ u8 DSi::ARM7IORead8(u32 addr)
case 0x04004500: return I2C.ReadData();
case 0x04004501: return I2C.ReadCnt();
- case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFF;
- case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 8) & 0xFF;
- case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFF;
- case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 24) & 0xFF;
- case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFF;
- case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 40) & 0xFF;
- case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 48) & 0xFF;
- case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 56;
+ case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFF;
+ case 0x04004D01: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 8) & 0xFF;
+ case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFF;
+ case 0x04004D03: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 24) & 0xFF;
+ case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFF;
+ case 0x04004D05: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 40) & 0xFF;
+ case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 48) & 0xFF;
+ case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 56;
case 0x04004D08: return 0;
case 0x4004700: return DSP.ReadSNDExCnt() & 0xFF;
@@ -2726,10 +2722,10 @@ u16 DSi::ARM7IORead16(u32 addr)
CASE_READ16_32BIT(0x0400405C, MBK[1][7])
CASE_READ16_32BIT(0x04004060, MBK[1][8])
- case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFF;
- case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 16) & 0xFFFF;
- case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (NANDImage->GetConsoleID() >> 32) & 0xFFFF;
- case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 48;
+ case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFF;
+ case 0x04004D02: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 16) & 0xFFFF;
+ case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return (SDMMC.GetNAND()->GetConsoleID() >> 32) & 0xFFFF;
+ case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 48;
case 0x04004D08: return 0;
case 0x4004700: return DSP.ReadSNDExCnt();
@@ -2806,8 +2802,8 @@ u32 DSi::ARM7IORead32(u32 addr)
case 0x04004400: return AES.ReadCnt();
case 0x0400440C: return AES.ReadOutputFIFO();
- case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() & 0xFFFFFFFF;
- case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return NANDImage->GetConsoleID() >> 32;
+ case 0x04004D00: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() & 0xFFFFFFFF;
+ case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 32;
case 0x04004D08: return 0;
case 0x4004700:
diff --git a/src/DSi.h b/src/DSi.h
index a221b5d..acd85c1 100644
--- a/src/DSi.h
+++ b/src/DSi.h
@@ -33,6 +33,7 @@ class DSi_I2CHost;
class DSi_CamModule;
class DSi_AES;
class DSi_DSP;
+class DSiArgs;
namespace DSi_NAND
{
@@ -48,9 +49,8 @@ public:
u16 SCFG_Clock9;
u32 SCFG_EXT[2];
- u8 ARM9iBIOS[0x10000];
- u8 ARM7iBIOS[0x10000];
- std::unique_ptr<DSi_NAND::NANDImage> NANDImage;
+ std::array<u8, DSiBIOSSize> ARM9iBIOS;
+ std::array<u8, DSiBIOSSize> ARM7iBIOS;
DSi_SDHost SDMMC;
DSi_SDHost SDIO;
@@ -130,19 +130,29 @@ public:
void ARM7IOWrite32(u32 addr, u32 val) override;
public:
- DSi() noexcept;
+ DSi(DSiArgs&& args) noexcept;
~DSi() noexcept override;
DSi(const DSi&) = delete;
DSi& operator=(const DSi&) = delete;
DSi(DSi&&) = delete;
DSi& operator=(DSi&&) = delete;
- bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) override;
- void EjectCart() override;
+ void SetNDSCart(std::unique_ptr<NDSCart::CartCommon>&& cart) override;
+ std::unique_ptr<NDSCart::CartCommon> EjectCart() override;
bool NeedsDirectBoot() override
{
// for now, DSi mode requires original BIOS/NAND
return false;
}
+
+ [[nodiscard]] const DSi_NAND::NANDImage& GetNAND() const noexcept { return *SDMMC.GetNAND(); }
+ [[nodiscard]] DSi_NAND::NANDImage& GetNAND() noexcept { return *SDMMC.GetNAND(); }
+ void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { SDMMC.SetNAND(std::move(nand)); }
+ u64 GetConsoleID() const noexcept { return SDMMC.GetNAND()->GetConsoleID(); }
+
+ [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return SDMMC.GetSDCard(); }
+ void SetSDCard(FATStorage&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); }
+ void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); }
+
void CamInputFrame(int cam, u32* data, int width, int height, bool rgb) override;
bool DMAsInMode(u32 cpu, u32 mode) override;
bool DMAsRunning(u32 cpu) override;
diff --git a/src/DSi_AES.cpp b/src/DSi_AES.cpp
index 8e04586..47a613e 100644
--- a/src/DSi_AES.cpp
+++ b/src/DSi_AES.cpp
@@ -78,7 +78,7 @@ void DSi_AES::Reset()
OutputMACDue = false;
// initialize keys
- u64 consoleid = DSi.NANDImage->GetConsoleID();
+ u64 consoleid = DSi.SDMMC.GetNAND()->GetConsoleID();
// slot 0: modcrypt
*(u32*)&KeyX[0][0] = 0x746E694E;
diff --git a/src/DSi_NAND.cpp b/src/DSi_NAND.cpp
index 59f582f..b6b83ab 100644
--- a/src/DSi_NAND.cpp
+++ b/src/DSi_NAND.cpp
@@ -131,6 +131,9 @@ NANDImage& NANDImage::operator=(NANDImage&& other) noexcept
{
if (this != &other)
{
+ if (CurFile)
+ CloseFile(CurFile);
+
CurFile = other.CurFile;
eMMC_CID = other.eMMC_CID;
ConsoleID = other.ConsoleID;
diff --git a/src/DSi_NWifi.cpp b/src/DSi_NWifi.cpp
index 9e006e2..a6177de 100644
--- a/src/DSi_NWifi.cpp
+++ b/src/DSi_NWifi.cpp
@@ -165,13 +165,13 @@ void DSi_NWifi::Reset()
for (int i = 0; i < 9; i++)
Mailbox[i].Clear();
- const Firmware* fw = DSi.SPI.GetFirmware();
+ const Firmware& fw = DSi.SPI.GetFirmware();
- MacAddress mac = fw->GetHeader().MacAddr;
+ MacAddress mac = fw.GetHeader().MacAddr;
Log(LogLevel::Info, "NWifi MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- Firmware::WifiBoard type = fw->GetHeader().WifiBoard;
+ Firmware::WifiBoard type = fw.GetHeader().WifiBoard;
switch (type)
{
case Firmware::WifiBoard::W015: // AR6002
diff --git a/src/DSi_SD.cpp b/src/DSi_SD.cpp
index 4cbf595..ff88def 100644
--- a/src/DSi_SD.cpp
+++ b/src/DSi_SD.cpp
@@ -18,6 +18,7 @@
#include <stdio.h>
#include <string.h>
+#include "Args.h"
#include "DSi.h"
#include "DSi_SD.h"
#include "DSi_NAND.h"
@@ -26,6 +27,10 @@
namespace melonDS
{
+using std::holds_alternative;
+using std::unique_ptr;
+using std::get_if;
+using std::get;
using namespace Platform;
// observed IRQ behavior during transfers
@@ -57,36 +62,38 @@ enum
};
-DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, u32 num) : DSi(dsi)
+DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional<FATStorage>&& sdcard) noexcept : DSi(dsi), Num(0)
{
- Num = num;
+ DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer,
+ Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX));
+ DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer,
+ Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX));
+
+ Ports[0] = sdcard ? std::make_unique<DSi_MMCStorage>(DSi, this, std::move(*sdcard)) : nullptr;
+ sdcard = std::nullopt; // to ensure that sdcard isn't left with a moved-from object
+ Ports[1] = std::make_unique<DSi_MMCStorage>(DSi, this, std::move(nand));
+}
- DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
+// Creates an SDIO host
+DSi_SDHost::DSi_SDHost(melonDS::DSi& dsi) noexcept : DSi(dsi), Num(1)
+{
+ DSi.RegisterEventFunc(Event_DSi_SDIOTransfer ,
Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX));
- DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
+ DSi.RegisterEventFunc(Event_DSi_SDIOTransfer,
Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX));
- Ports[0] = nullptr;
+ Ports[0] = std::make_unique<DSi_NWifi>(DSi, this);
Ports[1] = nullptr;
}
DSi_SDHost::~DSi_SDHost()
{
- if (Ports[0]) delete Ports[0];
- if (Ports[1]) delete Ports[1];
-
DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
Transfer_TX);
DSi.UnregisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer,
Transfer_RX);
-}
-void DSi_SDHost::CloseHandles()
-{
- if (Ports[0]) delete Ports[0];
- if (Ports[1]) delete Ports[1];
- Ports[0] = nullptr;
- Ports[1] = nullptr;
+ // unique_ptr's destructor will clean up the ports
}
void DSi_SDHost::Reset()
@@ -129,48 +136,70 @@ void DSi_SDHost::Reset()
TXReq = false;
- CloseHandles();
+ if (Ports[0]) Ports[0]->Reset();
+ if (Ports[1]) Ports[1]->Reset();
+}
- if (Num == 0)
- {
- DSi_MMCStorage* sd;
- DSi_MMCStorage* mmc;
+FATStorage* DSi_SDHost::GetSDCard() noexcept
+{
+ if (Num != 0) return nullptr;
+ return static_cast<DSi_MMCStorage*>(Ports[0].get())->GetSDCard();
+}
- if (Platform::GetConfigBool(Platform::DSiSD_Enable))
+const FATStorage* DSi_SDHost::GetSDCard() const noexcept
+{
+ if (Num != 0) return nullptr;
+ return static_cast<const DSi_MMCStorage*>(Ports[0].get())->GetSDCard();
+}
+
+DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept
+{
+ if (Num != 0) return nullptr;
+ return static_cast<DSi_MMCStorage*>(Ports[1].get())->GetNAND();
+}
+
+const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept
+{
+ if (Num != 0) return nullptr;
+ return static_cast<const DSi_MMCStorage*>(Ports[1].get())->GetNAND();
+}
+
+void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept
+{
+ if (Num != 0) return;
+
+ static_cast<DSi_MMCStorage*>(Ports[0].get())->SetSDCard(std::move(sdcard));
+}
+
+void DSi_SDHost::SetSDCard(std::optional<FATStorage>&& sdcard) noexcept
+{
+ if (Num != 0) return;
+
+ if (sdcard)
+ {
+ if (!Ports[0])
{
- std::string folderpath;
- if (Platform::GetConfigBool(Platform::DSiSD_FolderSync))
- folderpath = Platform::GetConfigString(Platform::DSiSD_FolderPath);
- else
- folderpath = "";
-
- sd = new DSi_MMCStorage(this,
- false,
- Platform::GetConfigString(Platform::DSiSD_ImagePath),
- (u64)Platform::GetConfigInt(Platform::DSiSD_ImageSize) * 1024 * 1024,
- Platform::GetConfigBool(Platform::DSiSD_ReadOnly),
- folderpath);
- u8 sd_cid[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00};
- sd->SetCID(sd_cid);
+ Ports[0] = std::make_unique<DSi_MMCStorage>(DSi, this, std::move(*sdcard));
}
else
- sd = nullptr;
-
- mmc = new DSi_MMCStorage(this, *DSi.NANDImage);
- mmc->SetCID(DSi.NANDImage->GetEMMCID().data());
-
- Ports[0] = sd;
- Ports[1] = mmc;
+ {
+ static_cast<DSi_MMCStorage*>(Ports[0].get())->SetSDCard(std::move(*sdcard));
+ }
}
else
{
- DSi_NWifi* nwifi = new DSi_NWifi(DSi, this);
-
- Ports[0] = nwifi;
+ Ports[0] = nullptr;
}
- if (Ports[0]) Ports[0]->Reset();
- if (Ports[1]) Ports[1]->Reset();
+ sdcard = std::nullopt;
+ // a moved-from optional isn't empty, it contains a moved-from object
+}
+
+void DSi_SDHost::SetNAND(DSi_NAND::NANDImage&& nand) noexcept
+{
+ if (Num != 0) return;
+
+ static_cast<DSi_MMCStorage*>(Ports[1].get())->SetNAND(std::move(nand));
}
void DSi_SDHost::DoSavestate(Savestate* file)
@@ -261,7 +290,7 @@ void DSi_SDHost::SetCardIRQ()
if (!(CardIRQCtl & (1<<0))) return;
u16 oldflags = CardIRQStatus & ~CardIRQMask;
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
+ DSi_SDDevice* dev = Ports[PortSelect & 0x1].get();
if (dev->IRQ) CardIRQStatus |= (1<<0);
else CardIRQStatus &= ~(1<<0);
@@ -332,7 +361,7 @@ u32 DSi_SDHost::DataRX(u8* data, u32 len)
void DSi_SDHost::FinishTX(u32 param)
{
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
+ DSi_SDDevice* dev = Ports[PortSelect & 0x1].get();
if (BlockCountInternal == 0)
{
@@ -419,7 +448,7 @@ u32 DSi_SDHost::GetTransferrableLen(u32 len)
void DSi_SDHost::CheckRX()
{
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
+ DSi_SDDevice* dev = Ports[PortSelect & 0x1].get();
CheckSwapFIFO();
@@ -459,7 +488,7 @@ void DSi_SDHost::CheckTX()
return;
}
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
+ DSi_SDDevice* dev = Ports[PortSelect & 0x1].get();
if (dev) dev->ContinueTransfer();
}
@@ -550,7 +579,6 @@ u16 DSi_SDHost::ReadFIFO16()
return 0;
}
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
u16 ret = DataFIFO[f].Read();
if (DataFIFO[f].IsEmpty())
@@ -571,7 +599,6 @@ u32 DSi_SDHost::ReadFIFO32()
return 0;
}
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
u32 ret = DataFIFO32.Read();
if (DataFIFO32.IsEmpty())
@@ -593,7 +620,7 @@ void DSi_SDHost::Write(u32 addr, u16 val)
Command = val;
u8 cmd = Command & 0x3F;
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
+ DSi_SDDevice* dev = Ports[PortSelect & 0x1].get();
if (dev)
{
// CHECKME
@@ -707,7 +734,6 @@ void DSi_SDHost::Write(u32 addr, u16 val)
void DSi_SDHost::WriteFIFO16(u16 val)
{
- DSi_SDDevice* dev = Ports[PortSelect & 0x1];
u32 f = CurFIFO;
if (DataFIFO[f].IsFull())
{
@@ -780,34 +806,23 @@ void DSi_SDHost::CheckSwapFIFO()
#define MMC_DESC (Internal?"NAND":"SDcard")
-DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand)
- : DSi_SDDevice(host), Internal(true), NAND(&nand), SD(nullptr)
+DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept
+ : DSi_SDDevice(host), DSi(dsi), Storage(std::move(nand))
{
ReadOnly = false;
+ SetCID(get<DSi_NAND::NANDImage>(Storage).GetEMMCID().data());
}
-DSi_MMCStorage::DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir)
- : DSi_SDDevice(host)
+DSi_MMCStorage::DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept
+ : DSi_SDDevice(host), DSi(dsi), Storage(std::move(sdcard))
{
- Internal = internal;
- NAND = nullptr;
-
- SD = new FATStorage(filename, size, readonly, sourcedir);
- SD->Open();
-
- ReadOnly = readonly;
+ ReadOnly = get<FATStorage>(Storage).IsReadOnly();
+ SetCID(DSiSDCardCID);
}
-DSi_MMCStorage::~DSi_MMCStorage()
-{
- if (SD)
- {
- SD->Close();
- delete SD;
- }
-
- // Do not close the NANDImage, it's not owned by this object
-}
+// The FATStorage or NANDImage is owned by this object;
+// std::variant's destructor will clean it up.
+DSi_MMCStorage::~DSi_MMCStorage() = default;
void DSi_MMCStorage::Reset()
{
@@ -836,7 +851,7 @@ void DSi_MMCStorage::Reset()
void DSi_MMCStorage::DoSavestate(Savestate* file)
{
- file->Section(Internal ? "NAND" : "SDCR");
+ file->Section(holds_alternative<DSi_NAND::NANDImage>(Storage) ? "NAND" : "SDCR");
file->VarArray(CID, 16);
file->VarArray(CSD, 16);
@@ -871,7 +886,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param)
case 1: // SEND_OP_COND
// CHECKME!!
// also TODO: it's different for the SD card
- if (Internal)
+ if (std::holds_alternative<DSi_NAND::NANDImage>(Storage))
{
param &= ~(1<<30);
OCR &= 0xBF000000;
@@ -895,7 +910,7 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param)
return;
case 3: // get/set RCA
- if (Internal)
+ if (holds_alternative<DSi_NAND::NANDImage>(Storage))
{
RCA = param >> 16;
Host->SendResponse(CSR|0x10000, true); // huh??
@@ -930,7 +945,8 @@ void DSi_MMCStorage::SendCMD(u8 cmd, u32 param)
case 12: // stop operation
SetState(0x04);
- if (NAND) FileFlush(NAND->GetFile());
+ if (auto* nand = get_if<DSi_NAND::NANDImage>(&Storage))
+ FileFlush(nand->GetFile());
RWCommand = 0;
Host->SendResponse(CSR, true);
return;
@@ -1011,7 +1027,7 @@ void DSi_MMCStorage::SendACMD(u8 cmd, u32 param)
// DSi boot2 sets this to 0x40100000 (hardcoded)
// then has two codepaths depending on whether bit30 did get set
// is it settable at all on the MMC? probably not.
- if (Internal) param &= ~(1<<30);
+ if (holds_alternative<DSi_NAND::NANDImage>(Storage)) param &= ~(1<<30);
OCR &= 0xBF000000;
OCR |= (param & 0x40FFFFFF);
Host->SendResponse(OCR, true);
@@ -1057,14 +1073,14 @@ u32 DSi_MMCStorage::ReadBlock(u64 addr)
len = Host->GetTransferrableLen(len);
u8 data[0x200];
- if (SD)
+ if (auto* sd = std::get_if<FATStorage>(&Storage))
{
- SD->ReadSectors((u32)(addr >> 9), 1, data);
+ sd->ReadSectors((u32)(addr >> 9), 1, data);
}
- else if (NAND)
+ else if (auto* nand = std::get_if<DSi_NAND::NANDImage>(&Storage))
{
- FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start);
- FileRead(&data[addr & 0x1FF], 1, len, NAND->GetFile());
+ FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start);
+ FileRead(&data[addr & 0x1FF], 1, len, nand->GetFile());
}
return Host->DataRX(&data[addr & 0x1FF], len);
@@ -1078,23 +1094,23 @@ u32 DSi_MMCStorage::WriteBlock(u64 addr)
u8 data[0x200];
if (len < 0x200)
{
- if (SD)
+ if (auto* sd = get_if<FATStorage>(&Storage))
{
- SD->ReadSectors((u32)(addr >> 9), 1, data);
+ sd->ReadSectors((u32)(addr >> 9), 1, data);
}
}
if ((len = Host->DataTX(&data[addr & 0x1FF], len)))
{
if (!ReadOnly)
{
- if (SD)
+ if (auto* sd = get_if<FATStorage>(&Storage))
{
- SD->WriteSectors((u32)(addr >> 9), 1, data);
+ sd->WriteSectors((u32)(addr >> 9), 1, data);
}
- else if (NAND)
+ else if (auto* nand = get_if<DSi_NAND::NANDImage>(&Storage))
{
- FileSeek(NAND->GetFile(), addr, FileSeekOrigin::Start);
- FileWrite(&data[addr & 0x1FF], 1, len, NAND->GetFile());
+ FileSeek(nand->GetFile(), addr, FileSeekOrigin::Start);
+ FileWrite(&data[addr & 0x1FF], 1, len, nand->GetFile());
}
}
}
diff --git a/src/DSi_SD.h b/src/DSi_SD.h
index 17ba8d3..05f8c9d 100644
--- a/src/DSi_SD.h
+++ b/src/DSi_SD.h
@@ -20,28 +20,30 @@
#define DSI_SD_H
#include <cstring>
+#include <variant>
#include "FIFO.h"
#include "FATStorage.h"
+#include "DSi_NAND.h"
#include "Savestate.h"
namespace melonDS
{
-namespace DSi_NAND
-{
- class NANDImage;
-}
-
class DSi_SDDevice;
class DSi;
+using Nothing = std::monostate;
+using DSiStorage = std::variant<std::monostate, FATStorage, DSi_NAND::NANDImage>;
class DSi_SDHost
{
public:
- DSi_SDHost(melonDS::DSi& dsi, u32 num);
+ /// Creates an SDMMC host.
+ DSi_SDHost(melonDS::DSi& dsi, DSi_NAND::NANDImage&& nand, std::optional<FATStorage>&& sdcard = std::nullopt) noexcept;
+
+ /// Creates an SDIO host
+ explicit DSi_SDHost(melonDS::DSi& dsi) noexcept;
~DSi_SDHost();
- void CloseHandles();
void Reset();
void DoSavestate(Savestate* file);
@@ -59,6 +61,15 @@ public:
void SetCardIRQ();
+ [[nodiscard]] FATStorage* GetSDCard() noexcept;
+ [[nodiscard]] const FATStorage* GetSDCard() const noexcept;
+ [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept;
+ [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept;
+
+ void SetSDCard(FATStorage&& sdcard) noexcept;
+ void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept;
+ void SetNAND(DSi_NAND::NANDImage&& nand) noexcept;
+
u16 Read(u32 addr);
void Write(u32 addr, u16 val);
u16 ReadFIFO16();
@@ -96,7 +107,7 @@ private:
u32 Param;
u16 ResponseBuffer[8];
- DSi_SDDevice* Ports[2];
+ std::array<std::unique_ptr<DSi_SDDevice>, 2> Ports {};
u32 CurFIFO; // FIFO accessible for read/write
FIFO<u16, 0x100> DataFIFO[2];
@@ -134,25 +145,53 @@ protected:
class DSi_MMCStorage : public DSi_SDDevice
{
public:
- DSi_MMCStorage(DSi_SDHost* host, DSi_NAND::NANDImage& nand);
- DSi_MMCStorage(DSi_SDHost* host, bool internal, const std::string& filename, u64 size, bool readonly, const std::string& sourcedir);
- ~DSi_MMCStorage();
-
- void Reset();
-
- void DoSavestate(Savestate* file);
+ DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, DSi_NAND::NANDImage&& nand) noexcept;
+ DSi_MMCStorage(melonDS::DSi& dsi, DSi_SDHost* host, FATStorage&& sdcard) noexcept;
+ ~DSi_MMCStorage() override;
+
+ [[nodiscard]] FATStorage* GetSDCard() noexcept { return std::get_if<FATStorage>(&Storage); }
+ [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return std::get_if<FATStorage>(&Storage); }
+ [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept { return std::get_if<DSi_NAND::NANDImage>(&Storage); }
+ [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept { return std::get_if<DSi_NAND::NANDImage>(&Storage); }
+
+ void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { Storage = std::move(nand); }
+ void SetSDCard(FATStorage&& sdcard) noexcept { Storage = std::move(sdcard); }
+ void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept
+ {
+ if (sdcard)
+ { // If we're setting a new SD card...
+ Storage = std::move(*sdcard);
+ sdcard = std::nullopt;
+ }
+ else
+ {
+ Storage = Nothing();
+ }
+ }
+
+ void SetStorage(DSiStorage&& storage) noexcept
+ {
+ Storage = std::move(storage);
+ storage = Nothing();
+ // not sure if a moved-from variant is empty or contains a moved-from object;
+ // better to be safe than sorry
+ }
+
+ void Reset() override;
+
+ void DoSavestate(Savestate* file) override;
void SetCID(const u8* cid) { memcpy(CID, cid, sizeof(CID)); }
- void SendCMD(u8 cmd, u32 param);
+ void SendCMD(u8 cmd, u32 param) override;
void SendACMD(u8 cmd, u32 param);
- void ContinueTransfer();
+ void ContinueTransfer() override;
private:
- bool Internal;
- DSi_NAND::NANDImage* NAND;
- FATStorage* SD;
+ static constexpr u8 DSiSDCardCID[16] = {0xBD, 0x12, 0x34, 0x56, 0x78, 0x03, 0x4D, 0x30, 0x30, 0x46, 0x50, 0x41, 0x00, 0x00, 0x15, 0x00};
+ melonDS::DSi& DSi;
+ DSiStorage Storage;
u8 CID[16];
u8 CSD[16];
diff --git a/src/FATStorage.cpp b/src/FATStorage.cpp
index cd0a03c..8799cb4 100644
--- a/src/FATStorage.cpp
+++ b/src/FATStorage.cpp
@@ -29,39 +29,79 @@ namespace melonDS
{
namespace fs = std::filesystem;
using namespace Platform;
+using std::string;
-FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir)
+FATStorage::FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<string>& sourcedir) :
+ FilePath(filename),
+ FileSize(size),
+ ReadOnly(readonly),
+ SourceDir(sourcedir)
{
- ReadOnly = readonly;
Load(filename, size, sourcedir);
- File = nullptr;
+ File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting);
}
-FATStorage::~FATStorage()
+FATStorage::FATStorage(const FATStorageArgs& args) noexcept :
+ FATStorage(args.Filename, args.Size, args.ReadOnly, args.SourceDir)
{
- if (!ReadOnly) Save();
}
+FATStorage::FATStorage(FATStorageArgs&& args) noexcept :
+ FilePath(std::move(args.Filename)),
+ FileSize(args.Size),
+ ReadOnly(args.ReadOnly),
+ SourceDir(std::move(args.SourceDir))
+{
+ Load(FilePath, FileSize, SourceDir);
+
+ File = nullptr;
+}
-bool FATStorage::Open()
+FATStorage::FATStorage(FATStorage&& other) noexcept
{
- File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting);
- if (!File)
+ FilePath = std::move(other.FilePath);
+ IndexPath = std::move(other.IndexPath);
+ SourceDir = std::move(other.SourceDir);
+ ReadOnly = other.ReadOnly;
+ File = other.File;
+ FileSize = other.FileSize;
+ DirIndex = std::move(other.DirIndex);
+ FileIndex = std::move(other.FileIndex);
+
+ other.File = nullptr;
+}
+
+FATStorage& FATStorage::operator=(FATStorage&& other) noexcept
+{
+ if (this != &other)
{
- return false;
+ if (File)
+ CloseFile(File);
+
+ FilePath = std::move(other.FilePath);
+ IndexPath = std::move(other.IndexPath);
+ SourceDir = std::move(other.SourceDir);
+ ReadOnly = other.ReadOnly;
+ File = other.File;
+ FileSize = other.FileSize;
+ DirIndex = std::move(other.DirIndex);
+ FileIndex = std::move(other.FileIndex);
+
+ other.File = nullptr;
}
- return true;
+ return *this;
}
-void FATStorage::Close()
+FATStorage::~FATStorage()
{
+ if (!ReadOnly) Save();
+
if (File) CloseFile(File);
File = nullptr;
}
-
bool FATStorage::InjectFile(const std::string& path, u8* data, u32 len)
{
if (!File) return false;
@@ -930,19 +970,15 @@ u64 FATStorage::GetDirectorySize(fs::path sourcedir)
return ret;
}
-bool FATStorage::Load(const std::string& filename, u64 size, const std::string& sourcedir)
+bool FATStorage::Load(const std::string& filename, u64 size, const std::optional<string>& sourcedir)
{
- FilePath = filename;
- FileSize = size;
- SourceDir = sourcedir;
-
- bool hasdir = !sourcedir.empty();
- if (hasdir)
+ bool hasdir = sourcedir && !sourcedir->empty();
+ if (sourcedir)
{
- if (!fs::is_directory(fs::u8path(sourcedir)))
+ if (!fs::is_directory(fs::u8path(*sourcedir)))
{
hasdir = false;
- SourceDir = "";
+ SourceDir = std::nullopt;
}
}
@@ -1005,7 +1041,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string&
{
if (hasdir)
{
- FileSize = GetDirectorySize(fs::u8path(sourcedir));
+ FileSize = GetDirectorySize(fs::u8path(*sourcedir));
FileSize += 0x8000000ULL; // 128MB leeway
// make it a power of two
@@ -1054,7 +1090,7 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string&
if (res == FR_OK)
{
if (hasdir)
- ImportDirectory(sourcedir);
+ ImportDirectory(*sourcedir);
}
f_unmount("0:");
@@ -1068,9 +1104,9 @@ bool FATStorage::Load(const std::string& filename, u64 size, const std::string&
bool FATStorage::Save()
{
- if (SourceDir.empty())
- {
- return true;
+ if (!SourceDir)
+ { // If we're not syncing the SD card image to a host directory...
+ return true; // Not an error.
}
FF_File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting);
@@ -1094,7 +1130,7 @@ bool FATStorage::Save()
return false;
}
- ExportChanges(SourceDir);
+ ExportChanges(*SourceDir);
SaveIndex();
diff --git a/src/FATStorage.h b/src/FATStorage.h
index 2bdafad..6e348ce 100644
--- a/src/FATStorage.h
+++ b/src/FATStorage.h
@@ -22,6 +22,7 @@
#include <stdio.h>
#include <string>
#include <map>
+#include <optional>
#include <filesystem>
#include "Platform.h"
@@ -30,24 +31,41 @@
namespace melonDS
{
+/// Contains information necessary to load an SD card image.
+/// The intended use case is for loading homebrew NDS ROMs;
+/// you won't know that a ROM is homebrew until you parse it,
+/// so if you load the SD card before the ROM
+/// then you might end up discarding it.
+struct FATStorageArgs
+{
+ std::string Filename;
+ u64 Size;
+ bool ReadOnly;
+ std::optional<std::string> SourceDir;
+};
+
class FATStorage
{
public:
- FATStorage(const std::string& filename, u64 size, bool readonly, const std::string& sourcedir);
+ FATStorage(const std::string& filename, u64 size, bool readonly, const std::optional<std::string>& sourcedir = std::nullopt);
+ FATStorage(const FATStorageArgs& args) noexcept;
+ FATStorage(FATStorageArgs&& args) noexcept;
+ FATStorage(FATStorage&& other) noexcept;
+ FATStorage(const FATStorage& other) = delete;
+ FATStorage& operator=(const FATStorage& other) = delete;
+ FATStorage& operator=(FATStorage&& other) noexcept;
~FATStorage();
- bool Open();
- void Close();
-
bool InjectFile(const std::string& path, u8* data, u32 len);
u32 ReadSectors(u32 start, u32 num, u8* data);
u32 WriteSectors(u32 start, u32 num, u8* data);
+ [[nodiscard]] bool IsReadOnly() const noexcept { return ReadOnly; }
private:
std::string FilePath;
std::string IndexPath;
- std::string SourceDir;
+ std::optional<std::string> SourceDir;
bool ReadOnly;
Platform::FileHandle* File;
@@ -76,7 +94,7 @@ private:
bool ImportDirectory(const std::string& sourcedir);
u64 GetDirectorySize(std::filesystem::path sourcedir);
- bool Load(const std::string& filename, u64 size, const std::string& sourcedir);
+ bool Load(const std::string& filename, u64 size, const std::optional<std::string>& sourcedir);
bool Save();
typedef struct
diff --git a/src/FreeBIOS.cpp b/src/FreeBIOS.cpp
index 7eef18b..4f36e20 100644
--- a/src/FreeBIOS.cpp
+++ b/src/FreeBIOS.cpp
@@ -28,7 +28,7 @@
namespace melonDS
{
-unsigned char bios_arm7_bin[] = {
+std::array<u8, ARM7BIOSSize> bios_arm7_bin = {
0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea, 0x1c, 0x04, 0x00, 0xea,
0x1a, 0x04, 0x00, 0xea, 0x19, 0x04, 0x00, 0xea, 0x18, 0x04, 0x00, 0xea,
0xe3, 0x07, 0x00, 0xea, 0x16, 0x04, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00,
@@ -1397,7 +1397,7 @@ unsigned char bios_arm7_bin[] = {
0x00, 0x00, 0x00, 0x00
};
-unsigned char bios_arm9_bin[] = {
+std::array<u8, ARM9BIOSSize> bios_arm9_bin = {
0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea, 0x3e, 0x00, 0x00, 0xea,
0x3c, 0x00, 0x00, 0xea, 0x3b, 0x00, 0x00, 0xea, 0x3a, 0x00, 0x00, 0xea,
0xad, 0x01, 0x00, 0xea, 0x38, 0x00, 0x00, 0xea, 0x00, 0x00, 0x00, 0x00,
diff --git a/src/FreeBIOS.h b/src/FreeBIOS.h
index ce90725..a60c1f8 100644
--- a/src/FreeBIOS.h
+++ b/src/FreeBIOS.h
@@ -28,10 +28,13 @@
#ifndef FREEBIOS_H
#define FREEBIOS_H
+#include <array>
+#include "MemConstants.h"
+
namespace melonDS
{
-extern unsigned char bios_arm7_bin[16384];
-extern unsigned char bios_arm9_bin[4096];
+extern std::array<u8, ARM7BIOSSize> bios_arm7_bin;
+extern std::array<u8, ARM9BIOSSize> bios_arm9_bin;
}
#endif // FREEBIOS_H
diff --git a/src/GBACart.cpp b/src/GBACart.cpp
index f5e320f..6cd6e39 100644
--- a/src/GBACart.cpp
+++ b/src/GBACart.cpp
@@ -1,903 +1,895 @@
-/*
- Copyright 2016-2023 melonDS team
-
- 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 <assert.h>
-#include <stdio.h>
-#include <string.h>
-#include "NDS.h"
-#include "GBACart.h"
-#include "CRC32.h"
-#include "Platform.h"
-
-namespace melonDS
-{
-using Platform::Log;
-using Platform::LogLevel;
-
-namespace GBACart
-{
-
-const char SOLAR_SENSOR_GAMECODES[10][5] =
-{
- "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan)
- "U3IE", // Boktai - The Sun Is in Your Hand (USA)
- "U3IP", // Boktai - The Sun Is in Your Hand (Europe)
- "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan)
- "U32E", // Boktai 2 - Solar Boy Django (USA)
- "U32P", // Boktai 2 - Solar Boy Django (Europe)
- "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)
- "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample)
-};
-
-CartCommon::CartCommon()
-{
-}
-
-CartCommon::~CartCommon()
-{
-}
-
-void CartCommon::Reset()
-{
-}
-
-void CartCommon::DoSavestate(Savestate* file)
-{
- file->Section("GBCS");
-}
-
-void CartCommon::SetupSave(u32 type)
-{
-}
-
-void CartCommon::LoadSave(const u8* savedata, u32 savelen)
-{
-}
-
-int CartCommon::SetInput(int num, bool pressed)
-{
- return -1;
-}
-
-u16 CartCommon::ROMRead(u32 addr) const
-{
- return 0;
-}
-
-void CartCommon::ROMWrite(u32 addr, u16 val)
-{
-}
-
-u8 CartCommon::SRAMRead(u32 addr)
-{
- return 0;
-}
-
-void CartCommon::SRAMWrite(u32 addr, u8 val)
-{
-}
-
-u8* CartCommon::GetSaveMemory() const
-{
- return nullptr;
-}
-
-u32 CartCommon::GetSaveMemoryLength() const
-{
- return 0;
-}
-
-CartGame::CartGame(u8* rom, u32 len) : CartCommon()
-{
- ROM = rom;
- ROMLength = len;
-
- SRAM = nullptr;
- SRAMLength = 0;
- SRAMType = S_NULL;
- SRAMFlashState = {};
-}
-
-CartGame::~CartGame()
-{
- if (SRAM) delete[] SRAM;
- delete[] ROM;
-}
-
-u32 CartGame::Checksum() const
-{
- u32 crc = CRC32(ROM, 0xC0, 0);
-
- // TODO: hash more contents?
-
- return crc;
-}
-
-void CartGame::Reset()
-{
- memset(&GPIO, 0, sizeof(GPIO));
-}
-
-void CartGame::DoSavestate(Savestate* file)
-{
- CartCommon::DoSavestate(file);
-
- file->Var16(&GPIO.control);
- file->Var16(&GPIO.data);
- file->Var16(&GPIO.direction);
-
- u32 oldlen = SRAMLength;
-
- file->Var32(&SRAMLength);
-
- if (SRAMLength != oldlen)
- {
- // reallocate save memory
- if (oldlen) delete[] SRAM;
- SRAM = nullptr;
- if (SRAMLength) SRAM = new u8[SRAMLength];
- }
- if (SRAMLength)
- {
- // fill save memory if data is present
- file->VarArray(SRAM, SRAMLength);
- }
- else
- {
- // no save data, clear the current state
- SRAMType = SaveType::S_NULL;
- SRAM = nullptr;
- return;
- }
-
- // persist some extra state info
- file->Var8(&SRAMFlashState.bank);
- file->Var8(&SRAMFlashState.cmd);
- file->Var8(&SRAMFlashState.device);
- file->Var8(&SRAMFlashState.manufacturer);
- file->Var8(&SRAMFlashState.state);
-
- file->Var8((u8*)&SRAMType);
-
- if ((!file->Saving) && SRAM)
- Platform::WriteGBASave(SRAM, SRAMLength, 0, SRAMLength);
-}
-
-void CartGame::SetupSave(u32 type)
-{
- if (SRAM) delete[] SRAM;
- SRAM = nullptr;
-
- // TODO: have type be determined from some list, like in NDSCart
- // and not this gross hack!!
- SRAMLength = type;
-
- if (SRAMLength)
- {
- SRAM = new u8[SRAMLength];
- memset(SRAM, 0xFF, SRAMLength);
- }
-
- switch (SRAMLength)
- {
- case 512:
- SRAMType = S_EEPROM4K;
- break;
- case 8192:
- SRAMType = S_EEPROM64K;
- break;
- case 32768:
- SRAMType = S_SRAM256K;
- break;
- case 65536:
- SRAMType = S_FLASH512K;
- break;
- case 128*1024:
- SRAMType = S_FLASH1M;
- break;
- case 0:
- SRAMType = S_NULL;
- break;
- default:
- Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength);
- }
-
- if (SRAMType == S_FLASH512K)
- {
- // Panasonic 64K chip
- SRAMFlashState.device = 0x1B;
- SRAMFlashState.manufacturer = 0x32;
- }
- else if (SRAMType == S_FLASH1M)
- {
- // Sanyo 128K chip
- SRAMFlashState.device = 0x13;
- SRAMFlashState.manufacturer = 0x62;
- }
-}
-
-void CartGame::LoadSave(const u8* savedata, u32 savelen)
-{
- if (!SRAM) return;
-
- u32 len = std::min(savelen, SRAMLength);
- memcpy(SRAM, savedata, len);
- Platform::WriteGBASave(savedata, len, 0, len);
-}
-
-u16 CartGame::ROMRead(u32 addr) const
-{
- addr &= 0x01FFFFFF;
-
- if (addr >= 0xC4 && addr < 0xCA)
- {
- if (GPIO.control & 0x1)
- {
- switch (addr)
- {
- case 0xC4: return GPIO.data;
- case 0xC6: return GPIO.direction;
- case 0xC8: return GPIO.control;
- }
- }
- else
- return 0;
- }
-
- // CHECKME: does ROM mirror?
- if (addr < ROMLength)
- return *(u16*)&ROM[addr];
-
- return 0;
-}
-
-void CartGame::ROMWrite(u32 addr, u16 val)
-{
- addr &= 0x01FFFFFF;
-
- switch (addr)
- {
- case 0xC4:
- GPIO.data &= ~GPIO.direction;
- GPIO.data |= val & GPIO.direction;
- ProcessGPIO();
- break;
-
- case 0xC6:
- GPIO.direction = val;
- break;
-
- case 0xC8:
- GPIO.control = val;
- break;
-
- default:
- Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr);
- break;
- }
-}
-
-u8 CartGame::SRAMRead(u32 addr)
-{
- addr &= 0xFFFF;
-
- switch (SRAMType)
- {
- case S_EEPROM4K:
- case S_EEPROM64K:
- return SRAMRead_EEPROM(addr);
-
- case S_FLASH512K:
- case S_FLASH1M:
- return SRAMRead_FLASH(addr);
-
- case S_SRAM256K:
- return SRAMRead_SRAM(addr);
- default:
- break;
- }
-
- return 0xFF;
-}
-
-void CartGame::SRAMWrite(u32 addr, u8 val)
-{
- addr &= 0xFFFF;
-
- switch (SRAMType)
- {
- case S_EEPROM4K:
- case S_EEPROM64K:
- return SRAMWrite_EEPROM(addr, val);
-
- case S_FLASH512K:
- case S_FLASH1M:
- return SRAMWrite_FLASH(addr, val);
-
- case S_SRAM256K:
- return SRAMWrite_SRAM(addr, val);
- default:
- break;
- }
-}
-
-u8* CartGame::GetSaveMemory() const
-{
- return SRAM;
-}
-
-u32 CartGame::GetSaveMemoryLength() const
-{
- return SRAMLength;
-}
-
-void CartGame::ProcessGPIO()
-{
-}
-
-u8 CartGame::SRAMRead_EEPROM(u32 addr)
-{
- return 0;
-}
-
-void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val)
-{
- // TODO: could be used in homebrew?
-}
-
-// mostly ported from DeSmuME
-u8 CartGame::SRAMRead_FLASH(u32 addr)
-{
- if (SRAMFlashState.cmd == 0) // no cmd
- {
- return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank];
- }
-
- switch (SRAMFlashState.cmd)
- {
- case 0x90: // chip ID
- if (addr == 0x0000) return SRAMFlashState.manufacturer;
- if (addr == 0x0001) return SRAMFlashState.device;
- break;
- case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?)
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- break;
- case 0xA0: // write command
- break; // ignore here, handled in Write_Flash()
- case 0xB0: // bank switching (128K only)
- break; // ignore here, handled in Write_Flash()
- default:
- Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr);
- break;
- }
-
- return 0xFF;
-}
-
-// mostly ported from DeSmuME
-void CartGame::SRAMWrite_FLASH(u32 addr, u8 val)
-{
- switch (SRAMFlashState.state)
- {
- case 0x00:
- if (addr == 0x5555)
- {
- if (val == 0xF0)
- {
- // reset
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- }
- else if (val == 0xAA)
- {
- SRAMFlashState.state = 1;
- return;
- }
- }
- if (addr == 0x0000)
- {
- if (SRAMFlashState.cmd == 0xB0)
- {
- // bank switching
- SRAMFlashState.bank = val;
- SRAMFlashState.cmd = 0;
- return;
- }
- }
- break;
- case 0x01:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 2;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x02:
- if (addr == 0x5555)
- {
- // send command
- switch (val)
- {
- case 0x80: // erase
- SRAMFlashState.state = 0x80;
- break;
- case 0x90: // chip ID
- SRAMFlashState.state = 0x90;
- break;
- case 0xA0: // write
- SRAMFlashState.state = 0;
- break;
- default:
- SRAMFlashState.state = 0;
- break;
- }
-
- SRAMFlashState.cmd = val;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- // erase
- case 0x80:
- if (addr == 0x5555 && val == 0xAA)
- {
- SRAMFlashState.state = 0x81;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x81:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 0x82;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x82:
- if (val == 0x30)
- {
- u32 start_addr = addr + 0x10000 * SRAMFlashState.bank;
- memset((u8*)&SRAM[start_addr], 0xFF, 0x1000);
-
- Platform::WriteGBASave(SRAM, SRAMLength, start_addr, 0x1000);
- }
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- // chip ID
- case 0x90:
- if (addr == 0x5555 && val == 0xAA)
- {
- SRAMFlashState.state = 0x91;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x91:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 0x92;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x92:
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- default:
- break;
- }
-
- if (SRAMFlashState.cmd == 0xA0) // write
- {
- SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val);
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- }
-
- Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n",
- val, addr, SRAMFlashState.state);
-}
-
-u8 CartGame::SRAMRead_SRAM(u32 addr)
-{
- if (addr >= SRAMLength) return 0xFF;
-
- return SRAM[addr];
-}
-
-void CartGame::SRAMWrite_SRAM(u32 addr, u8 val)
-{
- if (addr >= SRAMLength) return;
-
- u8 prev = *(u8*)&SRAM[addr];
- if (prev != val)
- {
- *(u8*)&SRAM[addr] = val;
-
- // TODO: optimize this!!
- Platform::WriteGBASave(SRAM, SRAMLength, addr, 1);
- }
-}
-
-
-const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};
-
-CartGameSolarSensor::CartGameSolarSensor(u8* rom, u32 len) : CartGame(rom, len)
-{
-}
-
-CartGameSolarSensor::~CartGameSolarSensor()
-{
-}
-
-void CartGameSolarSensor::Reset()
-{
- LightEdge = false;
- LightCounter = 0;
- LightSample = 0xFF;
- LightLevel = 0;
-}
-
-void CartGameSolarSensor::DoSavestate(Savestate* file)
-{
- CartGame::DoSavestate(file);
-
- file->Var8((u8*)&LightEdge);
- file->Var8(&LightCounter);
- file->Var8(&LightSample);
- file->Var8(&LightLevel);
-}
-
-int CartGameSolarSensor::SetInput(int num, bool pressed)
-{
- if (!pressed) return -1;
-
- if (num == Input_SolarSensorDown)
- {
- if (LightLevel > 0)
- LightLevel--;
-
- return LightLevel;
- }
- else if (num == Input_SolarSensorUp)
- {
- if (LightLevel < 10)
- LightLevel++;
-
- return LightLevel;
- }
-
- return -1;
-}
-
-void CartGameSolarSensor::ProcessGPIO()
-{
- if (GPIO.data & 4) return; // Boktai chip select
- if (GPIO.data & 2) // Reset
- {
- u8 prev = LightSample;
- LightCounter = 0;
- LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel]));
- Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample);
- }
- if (GPIO.data & 1 && LightEdge) LightCounter++;
-
- LightEdge = !(GPIO.data & 1);
-
- bool sendBit = LightCounter >= LightSample;
- if (GPIO.control & 1)
- {
- GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF);
- }
-}
-
-
-CartRAMExpansion::CartRAMExpansion() : CartCommon()
-{
-}
-
-CartRAMExpansion::~CartRAMExpansion()
-{
-}
-
-void CartRAMExpansion::Reset()
-{
- memset(RAM, 0xFF, sizeof(RAM));
- RAMEnable = 1;
-}
-
-void CartRAMExpansion::DoSavestate(Savestate* file)
-{
- CartCommon::DoSavestate(file);
-
- file->VarArray(RAM, sizeof(RAM));
- file->Var16(&RAMEnable);
-}
-
-u16 CartRAMExpansion::ROMRead(u32 addr) const
-{
- addr &= 0x01FFFFFF;
-
- if (addr < 0x01000000)
- {
- switch (addr)
- {
- case 0xB0: return 0xFFFF;
- case 0xB2: return 0x0000;
- case 0xB4: return 0x2400;
- case 0xB6: return 0x2424;
- case 0xB8: return 0xFFFF;
- case 0xBA: return 0xFFFF;
- case 0xBC: return 0xFFFF;
- case 0xBE: return 0x7FFF;
-
- case 0x1FFFC: return 0xFFFF;
- case 0x1FFFE: return 0x7FFF;
-
- case 0x240000: return RAMEnable;
- case 0x240002: return 0x0000;
- }
-
- return 0xFFFF;
- }
- else if (addr < 0x01800000)
- {
- if (!RAMEnable) return 0xFFFF;
-
- return *(u16*)&RAM[addr & 0x7FFFFF];
- }
-
- return 0xFFFF;
-}
-
-void CartRAMExpansion::ROMWrite(u32 addr, u16 val)
-{
- addr &= 0x01FFFFFF;
-
- if (addr < 0x01000000)
- {
- switch (addr)
- {
- case 0x240000:
- RAMEnable = val & 0x0001;
- return;
- }
- }
- else if (addr < 0x01800000)
- {
- if (!RAMEnable) return;
-
- *(u16*)&RAM[addr & 0x7FFFFF] = val;
- }
-}
-
-void GBACartSlot::Reset() noexcept
-{
- if (Cart) Cart->Reset();
-}
-
-void GBACartSlot::DoSavestate(Savestate* file) noexcept
-{
- file->Section("GBAC"); // Game Boy Advance Cartridge
-
- // little state here
- // no need to save OpenBusDecay, it will be set later
-
- u32 carttype = 0;
- u32 cartchk = 0;
- if (Cart)
- {
- carttype = Cart->Type();
- cartchk = Cart->Checksum();
- }
-
- if (file->Saving)
- {
- file->Var32(&carttype);
- file->Var32(&cartchk);
- }
- else
- {
- u32 savetype;
- file->Var32(&savetype);
- if (savetype != carttype) return;
-
- u32 savechk;
- file->Var32(&savechk);
- if (savechk != cartchk) return;
- }
-
- if (Cart) Cart->DoSavestate(file);
-}
-
-
-std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
-{
- if (romdata == nullptr)
- {
- Log(LogLevel::Error, "GBACart: romdata is null\n");
- return nullptr;
- }
-
- if (romlen == 0)
- {
- Log(LogLevel::Error, "GBACart: romlen is zero\n");
- return nullptr;
- }
-
- u32 cartromsize = 0x200;
- while (cartromsize < romlen)
- cartromsize <<= 1;
-
- u8* cartrom = nullptr;
- try
- {
- cartrom = new u8[cartromsize];
- }
- catch (const std::bad_alloc& e)
- {
- Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize);
-
- return nullptr;
- }
-
- memset(cartrom, 0, cartromsize);
- memcpy(cartrom, romdata, romlen);
-
- char gamecode[5] = { '\0' };
- memcpy(&gamecode, cartrom + 0xAC, 4);
-
- bool solarsensor = false;
- for (const char* i : SOLAR_SENSOR_GAMECODES)
- {
- if (strcmp(gamecode, i) == 0)
- solarsensor = true;
- }
-
- if (solarsensor)
- {
- Log(LogLevel::Info, "GBA solar sensor support detected!\n");
- }
-
- std::unique_ptr<CartCommon> cart;
- if (solarsensor)
- cart = std::make_unique<CartGameSolarSensor>(cartrom, cartromsize);
- else
- cart = std::make_unique<CartGame>(cartrom, cartromsize);
-
- cart->Reset();
-
- // TODO: setup cart save here! from a list or something
-
- // save
- //printf("GBA save file: %s\n", sram);
-
- // TODO: have a list of sorts like in NDSCart? to determine the savemem type
- //if (Cart) Cart->LoadSave(sram, 0);
-
- return cart;
-}
-
-bool GBACartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
-{
- if (!cart) {
- Log(LogLevel::Error, "Failed to insert invalid GBA cart; existing cart (if any) was not ejected.\n");
- return false;
- }
-
- if (Cart != nullptr)
- EjectCart();
-
- Cart = std::move(cart);
-
- const u8* cartrom = Cart->GetROM();
-
- if (cartrom)
- {
- char gamecode[5] = { '\0' };
- memcpy(&gamecode, Cart->GetROM() + 0xAC, 4);
- Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode);
- }
- else
- {
- Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n");
- }
-
- return true;
-}
-
-bool GBACartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept
-{
- std::unique_ptr<CartCommon> data = ParseROM(romdata, romlen);
-
- return InsertROM(std::move(data));
-}
-
-void GBACartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept
-{
- if (Cart)
- {
- // gross hack
- Cart->SetupSave(savelen);
-
- Cart->LoadSave(savedata, savelen);
- }
-}
-
-void GBACartSlot::LoadAddon(int type) noexcept
-{
- switch (type)
- {
- case GBAAddon_RAMExpansion:
- Cart = std::make_unique<CartRAMExpansion>();
- break;
-
- default:
- Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
- return;
- }
-}
-
-void GBACartSlot::EjectCart() noexcept
-{
- Cart = nullptr;
-}
-
-
-int GBACartSlot::SetInput(int num, bool pressed) noexcept
-{
- if (Cart) return Cart->SetInput(num, pressed);
-
- return -1;
-}
-
-
-u16 GBACartSlot::ROMRead(u32 addr) const noexcept
-{
- if (Cart) return Cart->ROMRead(addr);
-
- return ((addr >> 1) & 0xFFFF) | OpenBusDecay;
-}
-
-void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept
-{
- if (Cart) Cart->ROMWrite(addr, val);
-}
-
-u8 GBACartSlot::SRAMRead(u32 addr) noexcept
-{
- if (Cart) return Cart->SRAMRead(addr);
-
- return 0xFF;
-}
-
-void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept
-{
- if (Cart) Cart->SRAMWrite(addr, val);
-}
-
-}
-
+/*
+ Copyright 2016-2023 melonDS team
+
+ 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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "NDS.h"
+#include "GBACart.h"
+#include "CRC32.h"
+#include "Platform.h"
+#include "Utils.h"
+
+namespace melonDS
+{
+using Platform::Log;
+using Platform::LogLevel;
+
+namespace GBACart
+{
+
+const char SOLAR_SENSOR_GAMECODES[10][5] =
+{
+ "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan)
+ "U3IE", // Boktai - The Sun Is in Your Hand (USA)
+ "U3IP", // Boktai - The Sun Is in Your Hand (Europe)
+ "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan)
+ "U32E", // Boktai 2 - Solar Boy Django (USA)
+ "U32P", // Boktai 2 - Solar Boy Django (Europe)
+ "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)
+ "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample)
+};
+
+CartCommon::CartCommon(GBACart::CartType type) : CartType(type)
+{
+}
+
+void CartCommon::Reset()
+{
+}
+
+void CartCommon::DoSavestate(Savestate* file)
+{
+ file->Section("GBCS");
+}
+
+void CartCommon::SetSaveMemory(const u8* savedata, u32 savelen)
+{
+}
+
+int CartCommon::SetInput(int num, bool pressed)
+{
+ return -1;
+}
+
+u16 CartCommon::ROMRead(u32 addr) const
+{
+ return 0;
+}
+
+void CartCommon::ROMWrite(u32 addr, u16 val)
+{
+}
+
+u8 CartCommon::SRAMRead(u32 addr)
+{
+ return 0;
+}
+
+void CartCommon::SRAMWrite(u32 addr, u8 val)
+{
+}
+
+u8* CartCommon::GetSaveMemory() const
+{
+ return nullptr;
+}
+
+u32 CartCommon::GetSaveMemoryLength() const
+{
+ return 0;
+}
+
+CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type) :
+ CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, type)
+{
+}
+
+CartGame::CartGame(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, GBACart::CartType type) :
+ CartCommon(type),
+ ROM(std::move(rom)),
+ ROMLength(len),
+ SRAM(std::move(sram)),
+ SRAMLength(sramlen)
+{
+ if (SRAM && SRAMLength)
+ {
+ SetupSave(sramlen);
+ }
+}
+
+CartGame::~CartGame() = default;
+// unique_ptr cleans up the allocated memory
+
+u32 CartGame::Checksum() const
+{
+ u32 crc = CRC32(ROM.get(), 0xC0, 0);
+
+ // TODO: hash more contents?
+
+ return crc;
+}
+
+void CartGame::Reset()
+{
+ memset(&GPIO, 0, sizeof(GPIO));
+}
+
+void CartGame::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
+
+ file->Var16(&GPIO.control);
+ file->Var16(&GPIO.data);
+ file->Var16(&GPIO.direction);
+
+ u32 oldlen = SRAMLength;
+
+ file->Var32(&SRAMLength);
+
+ if (SRAMLength != oldlen)
+ {
+ // reallocate save memory
+ SRAM = SRAMLength ? std::make_unique<u8[]>(SRAMLength) : nullptr;
+ }
+ if (SRAMLength)
+ {
+ // fill save memory if data is present
+ file->VarArray(SRAM.get(), SRAMLength);
+ }
+ else
+ {
+ // no save data, clear the current state
+ SRAMType = SaveType::S_NULL;
+ SRAM = nullptr;
+ return;
+ }
+
+ // persist some extra state info
+ file->Var8(&SRAMFlashState.bank);
+ file->Var8(&SRAMFlashState.cmd);
+ file->Var8(&SRAMFlashState.device);
+ file->Var8(&SRAMFlashState.manufacturer);
+ file->Var8(&SRAMFlashState.state);
+
+ file->Var8((u8*)&SRAMType);
+
+ if ((!file->Saving) && SRAM)
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength);
+}
+
+void CartGame::SetupSave(u32 type)
+{
+ // TODO: have type be determined from some list, like in NDSCart
+ // and not this gross hack!!
+ SRAMLength = type;
+ switch (SRAMLength)
+ {
+ case 512:
+ SRAMType = S_EEPROM4K;
+ break;
+ case 8192:
+ SRAMType = S_EEPROM64K;
+ break;
+ case 32768:
+ SRAMType = S_SRAM256K;
+ break;
+ case 65536:
+ SRAMType = S_FLASH512K;
+ break;
+ case 128*1024:
+ SRAMType = S_FLASH1M;
+ break;
+ case 0:
+ SRAMType = S_NULL;
+ break;
+ default:
+ Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength);
+ }
+
+ if (SRAMType == S_FLASH512K)
+ {
+ // Panasonic 64K chip
+ SRAMFlashState.device = 0x1B;
+ SRAMFlashState.manufacturer = 0x32;
+ }
+ else if (SRAMType == S_FLASH1M)
+ {
+ // Sanyo 128K chip
+ SRAMFlashState.device = 0x13;
+ SRAMFlashState.manufacturer = 0x62;
+ }
+}
+
+void CartGame::SetSaveMemory(const u8* savedata, u32 savelen)
+{
+ SetupSave(savelen);
+
+ u32 len = std::min(savelen, SRAMLength);
+ memcpy(SRAM.get(), savedata, len);
+ Platform::WriteGBASave(savedata, len, 0, len);
+}
+
+u16 CartGame::ROMRead(u32 addr) const
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr >= 0xC4 && addr < 0xCA)
+ {
+ if (GPIO.control & 0x1)
+ {
+ switch (addr)
+ {
+ case 0xC4: return GPIO.data;
+ case 0xC6: return GPIO.direction;
+ case 0xC8: return GPIO.control;
+ }
+ }
+ else
+ return 0;
+ }
+
+ // CHECKME: does ROM mirror?
+ if (addr < ROMLength)
+ return *(u16*)&ROM[addr];
+
+ return 0;
+}
+
+void CartGame::ROMWrite(u32 addr, u16 val)
+{
+ addr &= 0x01FFFFFF;
+
+ switch (addr)
+ {
+ case 0xC4:
+ GPIO.data &= ~GPIO.direction;
+ GPIO.data |= val & GPIO.direction;
+ ProcessGPIO();
+ break;
+
+ case 0xC6:
+ GPIO.direction = val;
+ break;
+
+ case 0xC8:
+ GPIO.control = val;
+ break;
+
+ default:
+ Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr);
+ break;
+ }
+}
+
+u8 CartGame::SRAMRead(u32 addr)
+{
+ addr &= 0xFFFF;
+
+ switch (SRAMType)
+ {
+ case S_EEPROM4K:
+ case S_EEPROM64K:
+ return SRAMRead_EEPROM(addr);
+
+ case S_FLASH512K:
+ case S_FLASH1M:
+ return SRAMRead_FLASH(addr);
+
+ case S_SRAM256K:
+ return SRAMRead_SRAM(addr);
+ default:
+ break;
+ }
+
+ return 0xFF;
+}
+
+void CartGame::SRAMWrite(u32 addr, u8 val)
+{
+ addr &= 0xFFFF;
+
+ switch (SRAMType)
+ {
+ case S_EEPROM4K:
+ case S_EEPROM64K:
+ return SRAMWrite_EEPROM(addr, val);
+
+ case S_FLASH512K:
+ case S_FLASH1M:
+ return SRAMWrite_FLASH(addr, val);
+
+ case S_SRAM256K:
+ return SRAMWrite_SRAM(addr, val);
+ default:
+ break;
+ }
+}
+
+u8* CartGame::GetSaveMemory() const
+{
+ return SRAM.get();
+}
+
+u32 CartGame::GetSaveMemoryLength() const
+{
+ return SRAMLength;
+}
+
+void CartGame::ProcessGPIO()
+{
+}
+
+u8 CartGame::SRAMRead_EEPROM(u32 addr)
+{
+ return 0;
+}
+
+void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val)
+{
+ // TODO: could be used in homebrew?
+}
+
+// mostly ported from DeSmuME
+u8 CartGame::SRAMRead_FLASH(u32 addr)
+{
+ if (SRAMFlashState.cmd == 0) // no cmd
+ {
+ return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank];
+ }
+
+ switch (SRAMFlashState.cmd)
+ {
+ case 0x90: // chip ID
+ if (addr == 0x0000) return SRAMFlashState.manufacturer;
+ if (addr == 0x0001) return SRAMFlashState.device;
+ break;
+ case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?)
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ break;
+ case 0xA0: // write command
+ break; // ignore here, handled in Write_Flash()
+ case 0xB0: // bank switching (128K only)
+ break; // ignore here, handled in Write_Flash()
+ default:
+ Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr);
+ break;
+ }
+
+ return 0xFF;
+}
+
+// mostly ported from DeSmuME
+void CartGame::SRAMWrite_FLASH(u32 addr, u8 val)
+{
+ switch (SRAMFlashState.state)
+ {
+ case 0x00:
+ if (addr == 0x5555)
+ {
+ if (val == 0xF0)
+ {
+ // reset
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+ else if (val == 0xAA)
+ {
+ SRAMFlashState.state = 1;
+ return;
+ }
+ }
+ if (addr == 0x0000)
+ {
+ if (SRAMFlashState.cmd == 0xB0)
+ {
+ // bank switching
+ SRAMFlashState.bank = val;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+ }
+ break;
+ case 0x01:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 2;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x02:
+ if (addr == 0x5555)
+ {
+ // send command
+ switch (val)
+ {
+ case 0x80: // erase
+ SRAMFlashState.state = 0x80;
+ break;
+ case 0x90: // chip ID
+ SRAMFlashState.state = 0x90;
+ break;
+ case 0xA0: // write
+ SRAMFlashState.state = 0;
+ break;
+ default:
+ SRAMFlashState.state = 0;
+ break;
+ }
+
+ SRAMFlashState.cmd = val;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ // erase
+ case 0x80:
+ if (addr == 0x5555 && val == 0xAA)
+ {
+ SRAMFlashState.state = 0x81;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x81:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 0x82;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x82:
+ if (val == 0x30)
+ {
+ u32 start_addr = addr + 0x10000 * SRAMFlashState.bank;
+ memset((u8*)&SRAM[start_addr], 0xFF, 0x1000);
+
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000);
+ }
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ // chip ID
+ case 0x90:
+ if (addr == 0x5555 && val == 0xAA)
+ {
+ SRAMFlashState.state = 0x91;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x91:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 0x92;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x92:
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ default:
+ break;
+ }
+
+ if (SRAMFlashState.cmd == 0xA0) // write
+ {
+ SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val);
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+
+ Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n",
+ val, addr, SRAMFlashState.state);
+}
+
+u8 CartGame::SRAMRead_SRAM(u32 addr)
+{
+ if (addr >= SRAMLength) return 0xFF;
+
+ return SRAM[addr];
+}
+
+void CartGame::SRAMWrite_SRAM(u32 addr, u8 val)
+{
+ if (addr >= SRAMLength) return;
+
+ u8 prev = *(u8*)&SRAM[addr];
+ if (prev != val)
+ {
+ *(u8*)&SRAM[addr] = val;
+
+ // TODO: optimize this!!
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1);
+ }
+}
+
+
+CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) :
+ CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen)
+{
+}
+
+CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor)
+{
+}
+
+const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};
+
+void CartGameSolarSensor::Reset()
+{
+ CartGame::Reset();
+ LightEdge = false;
+ LightCounter = 0;
+ LightSample = 0xFF;
+ LightLevel = 0;
+}
+
+void CartGameSolarSensor::DoSavestate(Savestate* file)
+{
+ CartGame::DoSavestate(file);
+
+ file->Var8((u8*)&LightEdge);
+ file->Var8(&LightCounter);
+ file->Var8(&LightSample);
+ file->Var8(&LightLevel);
+}
+
+int CartGameSolarSensor::SetInput(int num, bool pressed)
+{
+ if (!pressed) return -1;
+
+ if (num == Input_SolarSensorDown)
+ {
+ if (LightLevel > 0)
+ LightLevel--;
+
+ return LightLevel;
+ }
+ else if (num == Input_SolarSensorUp)
+ {
+ if (LightLevel < 10)
+ LightLevel++;
+
+ return LightLevel;
+ }
+
+ return -1;
+}
+
+void CartGameSolarSensor::ProcessGPIO()
+{
+ if (GPIO.data & 4) return; // Boktai chip select
+ if (GPIO.data & 2) // Reset
+ {
+ u8 prev = LightSample;
+ LightCounter = 0;
+ LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel]));
+ Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample);
+ }
+ if (GPIO.data & 1 && LightEdge) LightCounter++;
+
+ LightEdge = !(GPIO.data & 1);
+
+ bool sendBit = LightCounter >= LightSample;
+ if (GPIO.control & 1)
+ {
+ GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF);
+ }
+}
+
+
+CartRAMExpansion::CartRAMExpansion() : CartCommon(RAMExpansion)
+{
+}
+
+CartRAMExpansion::~CartRAMExpansion() = default;
+
+void CartRAMExpansion::Reset()
+{
+ memset(RAM, 0xFF, sizeof(RAM));
+ RAMEnable = 1;
+}
+
+void CartRAMExpansion::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
+
+ file->VarArray(RAM, sizeof(RAM));
+ file->Var16(&RAMEnable);
+}
+
+u16 CartRAMExpansion::ROMRead(u32 addr) const
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0xB0: return 0xFFFF;
+ case 0xB2: return 0x0000;
+ case 0xB4: return 0x2400;
+ case 0xB6: return 0x2424;
+ case 0xB8: return 0xFFFF;
+ case 0xBA: return 0xFFFF;
+ case 0xBC: return 0xFFFF;
+ case 0xBE: return 0x7FFF;
+
+ case 0x1FFFC: return 0xFFFF;
+ case 0x1FFFE: return 0x7FFF;
+
+ case 0x240000: return RAMEnable;
+ case 0x240002: return 0x0000;
+ }
+
+ return 0xFFFF;
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return 0xFFFF;
+
+ return *(u16*)&RAM[addr & 0x7FFFFF];
+ }
+
+ return 0xFFFF;
+}
+
+void CartRAMExpansion::ROMWrite(u32 addr, u16 val)
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0x240000:
+ RAMEnable = val & 0x0001;
+ return;
+ }
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return;
+
+ *(u16*)&RAM[addr & 0x7FFFFF] = val;
+ }
+}
+
+GBACartSlot::GBACartSlot(std::unique_ptr<CartCommon>&& cart) noexcept : Cart(std::move(cart))
+{
+}
+
+void GBACartSlot::Reset() noexcept
+{
+ if (Cart) Cart->Reset();
+}
+
+void GBACartSlot::DoSavestate(Savestate* file) noexcept
+{
+ file->Section("GBAC"); // Game Boy Advance Cartridge
+
+ // little state here
+ // no need to save OpenBusDecay, it will be set later
+
+ u32 carttype = 0;
+ u32 cartchk = 0;
+ if (Cart)
+ {
+ carttype = Cart->Type();
+ cartchk = Cart->Checksum();
+ }
+
+ if (file->Saving)
+ {
+ file->Var32(&carttype);
+ file->Var32(&cartchk);
+ }
+ else
+ {
+ u32 savetype;
+ file->Var32(&savetype);
+ if (savetype != carttype) return;
+
+ u32 savechk;
+ file->Var32(&savechk);
+ if (savechk != cartchk) return;
+ }
+
+ if (Cart) Cart->DoSavestate(file);
+}
+
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen)
+{
+ return ParseROM(std::move(romdata), romlen, nullptr, 0);
+}
+
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen)
+{
+ auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen);
+
+ return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen);
+}
+
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
+{
+ return ParseROM(romdata, romlen, nullptr, 0);
+}
+
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen)
+{
+ if (romdata == nullptr)
+ {
+ Log(LogLevel::Error, "GBACart: romdata is null\n");
+ return nullptr;
+ }
+
+ if (romlen == 0)
+ {
+ Log(LogLevel::Error, "GBACart: romlen is zero\n");
+ return nullptr;
+ }
+
+ auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen);
+
+ std::unique_ptr<u8[]> cartsram;
+ try
+ {
+ cartsram = sramdata ? std::make_unique<u8[]>(sramlen) : nullptr;
+ }
+ catch (const std::bad_alloc& e)
+ {
+ Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize);
+
+ return nullptr;
+ }
+
+ if (cartsram)
+ {
+ memset(cartsram.get(), 0, sramlen);
+ memcpy(cartsram.get(), sramdata.get(), sramlen);
+ }
+
+ char gamecode[5] = { '\0' };
+ memcpy(&gamecode, cartrom.get() + 0xAC, 4);
+
+ bool solarsensor = false;
+ for (const char* i : SOLAR_SENSOR_GAMECODES)
+ {
+ if (strcmp(gamecode, i) == 0)
+ solarsensor = true;
+ }
+
+ if (solarsensor)
+ {
+ Log(LogLevel::Info, "GBA solar sensor support detected!\n");
+ }
+
+ std::unique_ptr<CartCommon> cart;
+ if (solarsensor)
+ cart = std::make_unique<CartGameSolarSensor>(std::move(cartrom), cartromsize, std::move(cartsram), sramlen);
+ else
+ cart = std::make_unique<CartGame>(std::move(cartrom), cartromsize, std::move(cartsram), sramlen);
+
+ cart->Reset();
+
+ // save
+ //printf("GBA save file: %s\n", sram);
+
+ // TODO: have a list of sorts like in NDSCart? to determine the savemem type
+ //if (Cart) Cart->LoadSave(sram, 0);
+
+ return cart;
+}
+
+void GBACartSlot::SetCart(std::unique_ptr<CartCommon>&& cart) noexcept
+{
+ Cart = std::move(cart);
+
+ if (!Cart)
+ {
+ Log(LogLevel::Info, "Ejected GBA cart");
+ return;
+ }
+
+ const u8* cartrom = Cart->GetROM();
+
+ if (cartrom)
+ {
+ char gamecode[5] = { '\0' };
+ memcpy(&gamecode, Cart->GetROM() + 0xAC, 4);
+ Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode);
+ }
+ else
+ {
+ Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n");
+ }
+}
+
+void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept
+{
+ if (Cart)
+ {
+ Cart->SetSaveMemory(savedata, savelen);
+ }
+}
+
+void GBACartSlot::LoadAddon(int type) noexcept
+{
+ switch (type)
+ {
+ case GBAAddon_RAMExpansion:
+ Cart = std::make_unique<CartRAMExpansion>();
+ break;
+
+ default:
+ Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
+ return;
+ }
+}
+
+std::unique_ptr<CartCommon> GBACartSlot::EjectCart() noexcept
+{
+ return std::move(Cart);
+ // Cart will be nullptr after this function returns, due to the move
+}
+
+
+int GBACartSlot::SetInput(int num, bool pressed) noexcept
+{
+ if (Cart) return Cart->SetInput(num, pressed);
+
+ return -1;
+}
+
+
+u16 GBACartSlot::ROMRead(u32 addr) const noexcept
+{
+ if (Cart) return Cart->ROMRead(addr);
+
+ return ((addr >> 1) & 0xFFFF) | OpenBusDecay;
+}
+
+void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept
+{
+ if (Cart) Cart->ROMWrite(addr, val);
+}
+
+u8 GBACartSlot::SRAMRead(u32 addr) noexcept
+{
+ if (Cart) return Cart->SRAMRead(addr);
+
+ return 0xFF;
+}
+
+void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept
+{
+ if (Cart) Cart->SRAMWrite(addr, val);
+}
+
+}
+
} \ No newline at end of file
diff --git a/src/GBACart.h b/src/GBACart.h
index a557089..493bf6b 100644
--- a/src/GBACart.h
+++ b/src/GBACart.h
@@ -1,257 +1,289 @@
-/*
- Copyright 2016-2023 melonDS team
-
- 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/.
-*/
-
-#ifndef GBACART_H
-#define GBACART_H
-
-#include <memory>
-#include "types.h"
-#include "Savestate.h"
-
-namespace melonDS::GBACart
-{
-
-enum CartType
-{
- Default = 0x001,
- Game = 0x101,
- GameSolarSensor = 0x102,
- RAMExpansion = 0x201,
-};
-
-// CartCommon -- base code shared by all cart types
-class CartCommon
-{
-public:
- CartCommon();
- virtual ~CartCommon();
-
- virtual u32 Type() const = 0;
- virtual u32 Checksum() const { return 0; }
-
- virtual void Reset();
-
- virtual void DoSavestate(Savestate* file);
-
- virtual void SetupSave(u32 type);
- virtual void LoadSave(const u8* savedata, u32 savelen);
-
- virtual int SetInput(int num, bool pressed);
-
- virtual u16 ROMRead(u32 addr) const;
- virtual void ROMWrite(u32 addr, u16 val);
-
- virtual u8 SRAMRead(u32 addr);
- virtual void SRAMWrite(u32 addr, u8 val);
-
- [[nodiscard]] virtual const u8* GetROM() const { return nullptr; }
- [[nodiscard]] virtual u32 GetROMLength() const { return 0; }
-
- virtual u8* GetSaveMemory() const;
- virtual u32 GetSaveMemoryLength() const;
-};
-
-// CartGame -- regular retail game cart (ROM, SRAM)
-class CartGame : public CartCommon
-{
-public:
- CartGame(u8* rom, u32 len);
- virtual ~CartGame() override;
-
- virtual u32 Type() const override { return CartType::Game; }
- virtual u32 Checksum() const override;
-
- virtual void Reset() override;
-
- virtual void DoSavestate(Savestate* file) override;
-
- virtual void SetupSave(u32 type) override;
- virtual void LoadSave(const u8* savedata, u32 savelen) override;
-
- virtual u16 ROMRead(u32 addr) const override;
- virtual void ROMWrite(u32 addr, u16 val) override;
-
- virtual u8 SRAMRead(u32 addr) override;
- virtual void SRAMWrite(u32 addr, u8 val) override;
-
- [[nodiscard]] const u8* GetROM() const override { return ROM; }
- [[nodiscard]] u32 GetROMLength() const override { return ROMLength; }
-
- virtual u8* GetSaveMemory() const override;
- virtual u32 GetSaveMemoryLength() const override;
-protected:
- virtual void ProcessGPIO();
-
- u8 SRAMRead_EEPROM(u32 addr);
- void SRAMWrite_EEPROM(u32 addr, u8 val);
- u8 SRAMRead_FLASH(u32 addr);
- void SRAMWrite_FLASH(u32 addr, u8 val);
- u8 SRAMRead_SRAM(u32 addr);
- void SRAMWrite_SRAM(u32 addr, u8 val);
-
- u8* ROM;
- u32 ROMLength;
-
- struct
- {
- u16 data;
- u16 direction;
- u16 control;
-
- } GPIO;
-
- enum SaveType
- {
- S_NULL,
- S_EEPROM4K,
- S_EEPROM64K,
- S_SRAM256K,
- S_FLASH512K,
- S_FLASH1M
- };
-
- // from DeSmuME
- struct
- {
- u8 state;
- u8 cmd;
- u8 device;
- u8 manufacturer;
- u8 bank;
-
- } SRAMFlashState;
-
- u8* SRAM;
- u32 SRAMLength;
- SaveType SRAMType;
-};
-
-// CartGameSolarSensor -- Boktai game cart
-class CartGameSolarSensor : public CartGame
-{
-public:
- CartGameSolarSensor(u8* rom, u32 len);
- virtual ~CartGameSolarSensor() override;
-
- virtual u32 Type() const override { return CartType::GameSolarSensor; }
-
- virtual void Reset() override;
-
- virtual void DoSavestate(Savestate* file) override;
-
- virtual int SetInput(int num, bool pressed) override;
-
-private:
- virtual void ProcessGPIO() override;
-
- static const int kLuxLevels[11];
-
- bool LightEdge;
- u8 LightCounter;
- u8 LightSample;
- u8 LightLevel;
-};
-
-// CartRAMExpansion -- RAM expansion cart (DS browser, ...)
-class CartRAMExpansion : public CartCommon
-{
-public:
- CartRAMExpansion();
- ~CartRAMExpansion() override;
-
- virtual u32 Type() const override { return CartType::RAMExpansion; }
-
- void Reset() override;
-
- void DoSavestate(Savestate* file) override;
-
- u16 ROMRead(u32 addr) const override;
- void ROMWrite(u32 addr, u16 val) override;
-
-private:
- u8 RAM[0x800000];
- u16 RAMEnable;
-};
-
-// possible inputs for GBA carts that might accept user input
-enum
-{
- Input_SolarSensorDown = 0,
- Input_SolarSensorUp,
-};
-
-class GBACartSlot
-{
-public:
- GBACartSlot() noexcept = default;
- ~GBACartSlot() noexcept = default;
- void Reset() noexcept;
- void DoSavestate(Savestate* file) noexcept;
- /// Applies the GBACartData to the emulator state and unloads an existing ROM if any.
- /// Upon successful insertion, \c cart will be nullptr and the global GBACart state
- /// (\c CartROM, CartInserted, etc.) will be updated.
- bool InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept;
- bool LoadROM(const u8* romdata, u32 romlen) noexcept;
- void LoadSave(const u8* savedata, u32 savelen) noexcept;
-
- void LoadAddon(int type) noexcept;
-
- void EjectCart() noexcept;
-
- // TODO: make more flexible, support nonbinary inputs
- int SetInput(int num, bool pressed) noexcept;
-
- void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; }
-
- u16 ROMRead(u32 addr) const noexcept;
- void ROMWrite(u32 addr, u16 val) noexcept;
-
- u8 SRAMRead(u32 addr) noexcept;
- void SRAMWrite(u32 addr, u8 val) noexcept;
-
- /// This function is intended to allow frontends to save and load SRAM
- /// without using melonDS APIs.
- /// Modifying the emulated SRAM for any other reason is strongly discouraged.
- /// The returned pointer may be invalidated if the emulator is reset,
- /// or when a new game is loaded.
- /// Consequently, don't store the returned pointer for any longer than necessary.
- /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr.
- [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
- [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
-
- /// @returns The length of the buffer returned by ::GetSaveMemory()
- /// if a cart is loaded and supports SRAM, otherwise zero.
- [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; }
-private:
- std::unique_ptr<CartCommon> Cart = nullptr;
- u16 OpenBusDecay = 0;
-};
-
-/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass
-/// that can be inserted into the emulator or used to extract information about the cart beforehand.
-/// @param romdata The ROM data to parse.
-/// The returned cartridge will contain a copy of this data,
-/// so the caller may deallocate \c romdata after this function returns.
-/// @param romlen The length of the ROM data in bytes.
-/// @returns A \c GBACart::CartCommon object representing the parsed ROM,
-/// or \c nullptr if the ROM data couldn't be parsed.
-std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen);
-
-}
-
-#endif // GBACART_H
+/*
+ Copyright 2016-2023 melonDS team
+
+ 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/.
+*/
+
+#ifndef GBACART_H
+#define GBACART_H
+
+#include <memory>
+#include "types.h"
+#include "Savestate.h"
+
+namespace melonDS::GBACart
+{
+
+enum CartType
+{
+ Default = 0x001,
+ Game = 0x101,
+ GameSolarSensor = 0x102,
+ RAMExpansion = 0x201,
+};
+
+// CartCommon -- base code shared by all cart types
+class CartCommon
+{
+public:
+ virtual ~CartCommon() = default;
+
+ [[nodiscard]] u32 Type() const { return CartType; }
+ virtual u32 Checksum() const { return 0; }
+
+ virtual void Reset();
+
+ virtual void DoSavestate(Savestate* file);
+
+ virtual int SetInput(int num, bool pressed);
+
+ virtual u16 ROMRead(u32 addr) const;
+ virtual void ROMWrite(u32 addr, u16 val);
+
+ virtual u8 SRAMRead(u32 addr);
+ virtual void SRAMWrite(u32 addr, u8 val);
+
+ [[nodiscard]] virtual const u8* GetROM() const { return nullptr; }
+ [[nodiscard]] virtual u32 GetROMLength() const { return 0; }
+
+ virtual u8* GetSaveMemory() const;
+ virtual u32 GetSaveMemoryLength() const;
+ virtual void SetSaveMemory(const u8* savedata, u32 savelen);
+protected:
+ CartCommon(GBACart::CartType type);
+ friend class GBACartSlot;
+private:
+ GBACart::CartType CartType;
+};
+
+// CartGame -- regular retail game cart (ROM, SRAM)
+class CartGame : public CartCommon
+{
+public:
+ CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game);
+ CartGame(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, GBACart::CartType type = GBACart::CartType::Game);
+ ~CartGame() override;
+
+ u32 Checksum() const override;
+
+ void Reset() override;
+
+ void DoSavestate(Savestate* file) override;
+
+ u16 ROMRead(u32 addr) const override;
+ void ROMWrite(u32 addr, u16 val) override;
+
+ u8 SRAMRead(u32 addr) override;
+ void SRAMWrite(u32 addr, u8 val) override;
+
+ [[nodiscard]] const u8* GetROM() const override { return ROM.get(); }
+ [[nodiscard]] u32 GetROMLength() const override { return ROMLength; }
+
+ u8* GetSaveMemory() const override;
+ u32 GetSaveMemoryLength() const override;
+ void SetSaveMemory(const u8* savedata, u32 savelen) override;
+protected:
+ virtual void ProcessGPIO();
+
+ u8 SRAMRead_EEPROM(u32 addr);
+ void SRAMWrite_EEPROM(u32 addr, u8 val);
+ u8 SRAMRead_FLASH(u32 addr);
+ void SRAMWrite_FLASH(u32 addr, u8 val);
+ u8 SRAMRead_SRAM(u32 addr);
+ void SRAMWrite_SRAM(u32 addr, u8 val);
+
+ std::unique_ptr<u8[]> ROM;
+ u32 ROMLength;
+
+ struct
+ {
+ u16 data;
+ u16 direction;
+ u16 control;
+
+ } GPIO {};
+
+ enum SaveType
+ {
+ S_NULL,
+ S_EEPROM4K,
+ S_EEPROM64K,
+ S_SRAM256K,
+ S_FLASH512K,
+ S_FLASH1M
+ };
+
+ // from DeSmuME
+ struct
+ {
+ u8 state;
+ u8 cmd;
+ u8 device;
+ u8 manufacturer;
+ u8 bank;
+
+ } SRAMFlashState {};
+
+ std::unique_ptr<u8[]> SRAM = nullptr;
+ u32 SRAMLength = 0;
+ SaveType SRAMType = S_NULL;
+private:
+ void SetupSave(u32 type);
+};
+
+// CartGameSolarSensor -- Boktai game cart
+class CartGameSolarSensor : public CartGame
+{
+public:
+ CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen);
+ CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen);
+
+ void Reset() override;
+
+ void DoSavestate(Savestate* file) override;
+
+ int SetInput(int num, bool pressed) override;
+
+protected:
+ void ProcessGPIO() override;
+
+private:
+ static const int kLuxLevels[11];
+
+ bool LightEdge = false;
+ u8 LightCounter = 0;
+ u8 LightSample = 0;
+ u8 LightLevel = 0;
+};
+
+// CartRAMExpansion -- RAM expansion cart (DS browser, ...)
+class CartRAMExpansion : public CartCommon
+{
+public:
+ CartRAMExpansion();
+ ~CartRAMExpansion() override;
+
+ void Reset() override;
+
+ void DoSavestate(Savestate* file) override;
+
+ u16 ROMRead(u32 addr) const override;
+ void ROMWrite(u32 addr, u16 val) override;
+
+private:
+ u8 RAM[0x800000] {};
+ u16 RAMEnable = 0;
+};
+
+// possible inputs for GBA carts that might accept user input
+enum
+{
+ Input_SolarSensorDown = 0,
+ Input_SolarSensorUp,
+};
+
+class GBACartSlot
+{
+public:
+ GBACartSlot(std::unique_ptr<CartCommon>&& cart = nullptr) noexcept;
+ ~GBACartSlot() noexcept = default;
+ void Reset() noexcept;
+ void DoSavestate(Savestate* file) noexcept;
+
+ /// Ejects the cart in the GBA slot (if any)
+ /// and inserts the given one.
+ ///
+ /// To insert a cart that does not require ROM data
+ /// (such as the RAM expansion pack),
+ /// create it manually with std::make_unique and pass it here.
+ ///
+ /// @param cart Movable \c unique_ptr to the GBA cart object.
+ /// May be \c nullptr, in which case the cart slot remains empty.
+ /// @post \c cart is \c nullptr and the underlying object
+ /// is moved into the cart slot.
+ void SetCart(std::unique_ptr<CartCommon>&& cart) noexcept;
+ [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
+ [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); }
+
+ void LoadAddon(int type) noexcept;
+
+ /// @return The cart that was in the cart slot if any,
+ /// or \c nullptr if the cart slot was empty.
+ std::unique_ptr<CartCommon> EjectCart() noexcept;
+
+ // TODO: make more flexible, support nonbinary inputs
+ int SetInput(int num, bool pressed) noexcept;
+
+ void SetOpenBusDecay(u16 val) noexcept { OpenBusDecay = val; }
+
+ u16 ROMRead(u32 addr) const noexcept;
+ void ROMWrite(u32 addr, u16 val) noexcept;
+
+ u8 SRAMRead(u32 addr) noexcept;
+ void SRAMWrite(u32 addr, u8 val) noexcept;
+
+ /// This function is intended to allow frontends to save and load SRAM
+ /// without using melonDS APIs.
+ /// Modifying the emulated SRAM for any other reason is strongly discouraged.
+ /// The returned pointer may be invalidated if the emulator is reset,
+ /// or when a new game is loaded.
+ /// Consequently, don't store the returned pointer for any longer than necessary.
+ /// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr.
+ [[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
+ [[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
+
+ /// Sets the loaded cart's SRAM.
+ /// Does nothing if no cart is inserted
+ /// or the inserted cart doesn't support SRAM.
+ ///
+ /// @param savedata Buffer containing the raw contents of the SRAM.
+ /// The contents of this buffer are copied into the cart slot,
+ /// so the caller may dispose of it after this method returns.
+ /// @param savelen The length of the buffer in \c savedata, in bytes.
+ void SetSaveMemory(const u8* savedata, u32 savelen) noexcept;
+
+ /// @returns The length of the buffer returned by ::GetSaveMemory()
+ /// if a cart is loaded and supports SRAM, otherwise zero.
+ [[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; }
+private:
+ std::unique_ptr<CartCommon> Cart = nullptr;
+ u16 OpenBusDecay = 0;
+};
+
+/// Parses the given ROM data and constructs a \c GBACart::CartCommon subclass
+/// that can be inserted into the emulator or used to extract information about the cart beforehand.
+/// @param romdata The ROM data to parse.
+/// The returned cartridge will contain a copy of this data,
+/// so the caller may deallocate \c romdata after this function returns.
+/// @param romlen The length of the ROM data in bytes.
+/// @returns A \c GBACart::CartCommon object representing the parsed ROM,
+/// or \c nullptr if the ROM data couldn't be parsed.
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen);
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen);
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen);
+
+/// @param romdata The ROM data to parse. Will be moved-from.
+/// @param romlen Length of romdata in bytes.
+/// @param sramdata The save data to add to the cart.
+/// May be \c nullptr, in which case the cart will have no save data.
+/// @param sramlen Length of sramdata in bytes.
+/// May be zero, in which case the cart will have no save data.
+/// @return Unique pointer to the parsed GBA cart,
+/// or \c nullptr if there was an error.
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen);
+
+}
+
+#endif // GBACART_H
diff --git a/src/NDS.cpp b/src/NDS.cpp
index 3aa4a52..f3e5a1a 100644
--- a/src/NDS.cpp
+++ b/src/NDS.cpp
@@ -34,6 +34,7 @@
#include "AREngine.h"
#include "Platform.h"
#include "FreeBIOS.h"
+#include "Args.h"
#include "DSi.h"
#include "DSi_SPI_TSC.h"
@@ -74,16 +75,31 @@ const s32 kIterationCycleMargin = 8;
NDS* NDS::Current = nullptr;
-NDS::NDS(int type) noexcept :
+NDS::NDS() noexcept :
+ NDS(
+ NDSArgs {
+ nullptr,
+ nullptr,
+ bios_arm9_bin,
+ bios_arm7_bin,
+ Firmware(0),
+ }
+ )
+{
+}
+
+NDS::NDS(NDSArgs&& args, int type) noexcept :
ConsoleType(type),
+ ARM7BIOS(args.ARM7BIOS),
+ ARM9BIOS(args.ARM9BIOS),
JIT(*this),
SPU(*this),
GPU(*this),
- SPI(*this),
+ SPI(*this, std::move(args.Firmware)),
RTC(*this),
Wifi(*this),
- NDSCartSlot(*this),
- GBACartSlot(),
+ NDSCartSlot(*this, std::move(args.NDSROM)),
+ GBACartSlot(type == 1 ? nullptr : std::move(args.GBAROM)),
AREngine(*this),
ARM9(*this),
ARM7(*this),
@@ -238,7 +254,7 @@ bool NDS::NeedsDirectBoot()
return true;
// DSi/3DS firmwares aren't bootable
- if (!SPI.GetFirmware()->IsBootable())
+ if (!SPI.GetFirmware().IsBootable())
return true;
return false;
@@ -710,42 +726,27 @@ bool NDS::DoSavestate(Savestate* file)
return true;
}
-bool NDS::LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen)
+void NDS::SetNDSCart(std::unique_ptr<NDSCart::CartCommon>&& cart)
{
- if (!NDSCartSlot.LoadROM(romdata, romlen))
- return false;
-
- if (savedata && savelen)
- NDSCartSlot.LoadSave(savedata, savelen);
-
- return true;
+ NDSCartSlot.SetCart(std::move(cart));
+ // The existing cart will always be ejected;
+ // if cart is null, then that's equivalent to ejecting a cart
+ // without inserting a new one.
}
-void NDS::LoadSave(const u8* savedata, u32 savelen)
+void NDS::SetNDSSave(const u8* savedata, u32 savelen)
{
if (savedata && savelen)
- NDSCartSlot.LoadSave(savedata, savelen);
-}
-
-void NDS::EjectCart()
-{
- NDSCartSlot.EjectCart();
-}
-
-bool NDS::CartInserted()
-{
- return NDSCartSlot.GetCart() != nullptr;
+ NDSCartSlot.SetSaveMemory(savedata, savelen);
}
-bool NDS::LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen)
+void NDS::SetGBASave(const u8* savedata, u32 savelen)
{
- if (!GBACartSlot.LoadROM(romdata, romlen))
- return false;
-
- if (savedata && savelen)
- GBACartSlot.LoadSave(savedata, savelen);
+ if (ConsoleType == 0 && savedata && savelen)
+ {
+ GBACartSlot.SetSaveMemory(savedata, savelen);
+ }
- return true;
}
void NDS::LoadGBAAddon(int type)
@@ -753,26 +754,11 @@ void NDS::LoadGBAAddon(int type)
GBACartSlot.LoadAddon(type);
}
-void NDS::EjectGBACart()
-{
- GBACartSlot.EjectCart();
-}
-
void NDS::LoadBIOS()
{
Reset();
}
-bool NDS::IsLoadedARM9BIOSBuiltIn()
-{
- return memcmp(ARM9BIOS, bios_arm9_bin, sizeof(NDS::ARM9BIOS)) == 0;
-}
-
-bool NDS::IsLoadedARM7BIOSBuiltIn()
-{
- return memcmp(ARM7BIOS, bios_arm7_bin, sizeof(NDS::ARM7BIOS)) == 0;
-}
-
u64 NDS::NextTarget()
{
u64 minEvent = UINT64_MAX;
@@ -2252,7 +2238,7 @@ bool NDS::ARM9GetMemRegion(u32 addr, bool write, MemRegion* region)
if ((addr & 0xFFFFF000) == 0xFFFF0000 && !write)
{
- region->Mem = ARM9BIOS;
+ region->Mem = &ARM9BIOS[0];
region->Mask = 0xFFF;
return true;
}
@@ -2700,7 +2686,7 @@ bool NDS::ARM7GetMemRegion(u32 addr, bool write, MemRegion* region)
{
if (ARM7.R[15] < 0x4000 && (addr >= ARM7BIOSProt || ARM7.R[15] < ARM7BIOSProt))
{
- region->Mem = ARM7BIOS;
+ region->Mem = &ARM7BIOS[0];
region->Mask = 0x3FFF;
return true;
}
diff --git a/src/NDS.h b/src/NDS.h
index 0979b2f..b9f8291 100644
--- a/src/NDS.h
+++ b/src/NDS.h
@@ -21,7 +21,7 @@
#include <memory>
#include <string>
-#include <memory>
+#include <optional>
#include <functional>
#include "Platform.h"
@@ -37,6 +37,7 @@
#include "GPU.h"
#include "ARMJIT.h"
#include "DMA.h"
+#include "FreeBIOS.h"
// when touching the main loop/timing code, pls test a lot of shit
// with this enabled, to make sure it doesn't desync
@@ -44,7 +45,8 @@
namespace melonDS
{
-
+struct NDSArgs;
+class Firmware;
enum
{
Event_LCD = 0,
@@ -255,8 +257,8 @@ public:
u8 ROMSeed0[2*8];
u8 ROMSeed1[2*8];
- u8 ARM9BIOS[0x1000];
- u8 ARM7BIOS[0x4000];
+ std::array<u8, ARM9BIOSSize> ARM9BIOS;
+ std::array<u8, ARM7BIOSSize> ARM7BIOS;
u16 ARM7BIOSProt;
u8* MainRAM;
@@ -303,25 +305,51 @@ public:
void SetARM9RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq);
void SetARM7RegionTimings(u32 addrstart, u32 addrend, u32 region, int buswidth, int nonseq, int seq);
- // 0=DS 1=DSi
- void SetConsoleType(int type);
-
void LoadBIOS();
- bool IsLoadedARM9BIOSBuiltIn();
- bool IsLoadedARM7BIOSBuiltIn();
+ [[nodiscard]] bool IsLoadedARM9BIOSBuiltIn() const noexcept { return ARM9BIOS == bios_arm9_bin; }
+ [[nodiscard]] bool IsLoadedARM7BIOSBuiltIn() const noexcept { return ARM7BIOS == bios_arm7_bin; }
- virtual bool LoadCart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen);
- void LoadSave(const u8* savedata, u32 savelen);
- virtual void EjectCart();
- bool CartInserted();
+ [[nodiscard]] NDSCart::CartCommon* GetNDSCart() { return NDSCartSlot.GetCart(); }
+ [[nodiscard]] const NDSCart::CartCommon* GetNDSCart() const { return NDSCartSlot.GetCart(); }
+ virtual void SetNDSCart(std::unique_ptr<NDSCart::CartCommon>&& cart);
+ [[nodiscard]] bool CartInserted() const noexcept { return NDSCartSlot.GetCart() != nullptr; }
+ virtual std::unique_ptr<NDSCart::CartCommon> EjectCart() { return NDSCartSlot.EjectCart(); }
+
+ [[nodiscard]] u8* GetNDSSave() { return NDSCartSlot.GetSaveMemory(); }
+ [[nodiscard]] const u8* GetNDSSave() const { return NDSCartSlot.GetSaveMemory(); }
+ [[nodiscard]] u32 GetNDSSaveLength() const { return NDSCartSlot.GetSaveMemoryLength(); }
+ void SetNDSSave(const u8* savedata, u32 savelen);
+
+ const Firmware& GetFirmware() const { return SPI.GetFirmwareMem()->GetFirmware(); }
+ Firmware& GetFirmware() { return SPI.GetFirmwareMem()->GetFirmware(); }
+ void SetFirmware(Firmware&& firmware) { SPI.GetFirmwareMem()->SetFirmware(std::move(firmware)); }
virtual bool NeedsDirectBoot();
void SetupDirectBoot(const std::string& romname);
virtual void SetupDirectBoot();
- bool LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen);
+ [[nodiscard]] GBACart::CartCommon* GetGBACart() { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); }
+ [[nodiscard]] const GBACart::CartCommon* GetGBACart() const { return (ConsoleType == 1) ? nullptr : GBACartSlot.GetCart(); }
+
+ /// Inserts a GBA cart into the emulated console's Slot-2.
+ ///
+ /// @param cart The GBA cart, most likely (but not necessarily) returned from GBACart::ParseROM.
+ /// To insert an accessory that doesn't use a ROM image
+ /// (e.g. the Expansion Pak), create it manually and pass it here.
+ /// If \c nullptr, the existing cart is ejected.
+ /// If this is a DSi, this method does nothing.
+ ///
+ /// @post \c cart is \c nullptr and this NDS takes ownership
+ /// of the cart object it held, if any.
+ void SetGBACart(std::unique_ptr<GBACart::CartCommon>&& cart) { if (ConsoleType == 0) GBACartSlot.SetCart(std::move(cart)); }
+
+ u8* GetGBASave() { return GBACartSlot.GetSaveMemory(); }
+ const u8* GetGBASave() const { return GBACartSlot.GetSaveMemory(); }
+ u32 GetGBASaveLength() const { return GBACartSlot.GetSaveMemoryLength(); }
+ void SetGBASave(const u8* savedata, u32 savelen);
+
void LoadGBAAddon(int type);
- void EjectGBACart();
+ std::unique_ptr<GBACart::CartCommon> EjectGBACart() { return GBACartSlot.EjectCart(); }
u32 RunFrame();
@@ -456,7 +484,8 @@ private:
template <bool EnableJIT>
u32 RunFrame();
public:
- NDS() noexcept : NDS(0) {}
+ NDS(NDSArgs&& args) noexcept : NDS(std::move(args), 0) {}
+ NDS() noexcept;
virtual ~NDS() noexcept;
NDS(const NDS&) = delete;
NDS& operator=(const NDS&) = delete;
@@ -465,7 +494,7 @@ public:
// The frontend should set and unset this manually after creating and destroying the NDS object.
[[deprecated("Temporary workaround until JIT code generation is revised to accommodate multiple NDS objects.")]] static NDS* Current;
protected:
- explicit NDS(int type) noexcept;
+ explicit NDS(NDSArgs&& args, int type) noexcept;
virtual void DoSavestateExtra(Savestate* file) {}
};
diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp
index 95306fc..848c619 100644
--- a/src/NDSCart.cpp
+++ b/src/NDSCart.cpp
@@ -20,12 +20,12 @@
#include "NDS.h"
#include "DSi.h"
#include "NDSCart.h"
-#include "ARM.h"
#include "CRC32.h"
#include "Platform.h"
#include "ROMList.h"
#include "melonDLDI.h"
-#include "xxhash/xxhash.h"
+#include "FATStorage.h"
+#include "Utils.h"
namespace melonDS
{
@@ -43,7 +43,7 @@ enum
// SRAM TODO: emulate write delays???
-u32 ByteSwap(u32 val)
+constexpr u32 ByteSwap(u32 val)
{
return (val >> 24) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
}
@@ -173,27 +173,29 @@ void NDSCartSlot::Key2_Encrypt(u8* data, u32 len) noexcept
}
-CartCommon::CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams)
+CartCommon::CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) :
+ CartCommon(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, type)
{
- ROM = rom;
- ROMLength = len;
- ChipID = chipid;
- ROMParams = romparams;
+}
- memcpy(&Header, rom, sizeof(Header));
+CartCommon::CartCommon(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, melonDS::NDSCart::CartType type) :
+ ROM(std::move(rom)),
+ ROMLength(len),
+ ChipID(chipid),
+ ROMParams(romparams),
+ CartType(type)
+{
+ memcpy(&Header, ROM.get(), sizeof(Header));
IsDSi = Header.IsDSi() && !badDSiDump;
DSiBase = Header.DSiRegionStart << 19;
}
-CartCommon::~CartCommon()
-{
- delete[] ROM;
-}
+CartCommon::~CartCommon() = default;
u32 CartCommon::Checksum() const
{
const NDSHeader& header = GetHeader();
- u32 crc = CRC32(ROM, 0x40);
+ u32 crc = CRC32(ROM.get(), 0x40);
crc = CRC32(&ROM[header.ARM9ROMOffset], header.ARM9Size, crc);
crc = CRC32(&ROM[header.ARM7ROMOffset], header.ARM7Size, crc);
@@ -230,14 +232,6 @@ void CartCommon::DoSavestate(Savestate* file)
file->Bool32(&DSiMode);
}
-void CartCommon::SetupSave(u32 type)
-{
-}
-
-void CartCommon::LoadSave(const u8* savedata, u32 savelen)
-{
-}
-
int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len)
{
if (CmdEncMode == 0)
@@ -267,7 +261,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da
case 0x3C:
CmdEncMode = 1;
- cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, nds.ARM7BIOS, sizeof(NDS::ARM7BIOS));
+ cartslot.Key1_InitKeycode(false, *(u32*)&ROM[0xC], 2, 2, &nds.ARM7BIOS[0], sizeof(NDS::ARM7BIOS));
DSiMode = false;
return 0;
@@ -276,7 +270,7 @@ int CartCommon::ROMCommandStart(NDS& nds, NDSCartSlot& cartslot, u8* cmd, u8* da
{
auto& dsi = static_cast<DSi&>(nds);
CmdEncMode = 1;
- cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS));
+ cartslot.Key1_InitKeycode(true, *(u32*)&ROM[0xC], 1, 2, &dsi.ARM7iBIOS[0], sizeof(DSi::ARM7iBIOS));
DSiMode = true;
}
return 0;
@@ -360,23 +354,13 @@ u8 CartCommon::SPIWrite(u8 val, u32 pos, bool last)
return 0xFF;
}
-u8 *CartCommon::GetSaveMemory() const
-{
- return nullptr;
-}
-
-u32 CartCommon::GetSaveMemoryLength() const
-{
- return 0;
-}
-
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);
+ memcpy(data+offset, ROM.get()+addr, len);
}
const NDSBanner* CartCommon::Banner() const
@@ -385,22 +369,64 @@ const NDSBanner* CartCommon::Banner() const
size_t bannersize = header.IsDSi() ? 0x23C0 : 0xA40;
if (header.BannerOffset >= 0x200 && header.BannerOffset < (ROMLength - bannersize))
{
- return reinterpret_cast<const NDSBanner*>(ROM + header.BannerOffset);
+ return reinterpret_cast<const NDSBanner*>(ROM.get() + header.BannerOffset);
}
return nullptr;
}
-CartRetail::CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams) : CartCommon(rom, len, chipid, badDSiDump, romparams)
+CartRetail::CartRetail(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, melonDS::NDSCart::CartType type) :
+ CartRetail(CopyToUnique(rom, len), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, type)
{
- SRAM = nullptr;
}
-CartRetail::~CartRetail()
+CartRetail::CartRetail(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen, melonDS::NDSCart::CartType type) :
+ CartCommon(std::move(rom), len, chipid, badDSiDump, romparams, type)
{
- if (SRAM) delete[] SRAM;
+ u32 savememtype = ROMParams.SaveMemType <= 10 ? ROMParams.SaveMemType : 0;
+ constexpr int sramlengths[] =
+ {
+ 0,
+ 512,
+ 8192, 65536, 128*1024,
+ 256*1024, 512*1024, 1024*1024,
+ 8192*1024, 16384*1024, 65536*1024
+ };
+ SRAMLength = sramlengths[savememtype];
+
+ if (SRAMLength)
+ { // If this cart should have any save data...
+ if (sram && sramlen == SRAMLength)
+ { // If we were given save data that already has the correct length...
+ SRAM = std::move(sram);
+ }
+ else
+ { // Copy in what we can, truncate the rest.
+ SRAM = std::make_unique<u8[]>(SRAMLength);
+ memset(SRAM.get(), 0xFF, SRAMLength);
+ memcpy(SRAM.get(), sram.get(), std::min(sramlen, SRAMLength));
+ }
+ }
+
+ switch (savememtype)
+ {
+ 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:
+ case 10: SRAMType = 4; break; // NAND
+ default: SRAMType = 0; break; // ...whatever else
+ }
}
+CartRetail::~CartRetail() = default;
+// std::unique_ptr cleans up the SRAM and ROM
+
void CartRetail::Reset()
{
CartCommon::Reset();
@@ -425,13 +451,11 @@ void CartRetail::DoSavestate(Savestate* file)
Log(LogLevel::Warn, "savestate: VERY BAD!!!! SRAM LENGTH DIFFERENT. %d -> %d\n", oldlen, SRAMLength);
Log(LogLevel::Warn, "oh well. loading it anyway. adsfgdsf\n");
- if (oldlen) delete[] SRAM;
- SRAM = nullptr;
- if (SRAMLength) SRAM = new u8[SRAMLength];
+ SRAM = SRAMLength ? std::make_unique<u8[]>(SRAMLength) : nullptr;
}
if (SRAMLength)
{
- file->VarArray(SRAM, SRAMLength);
+ file->VarArray(SRAM.get(), SRAMLength);
}
// SPI status shito
@@ -441,53 +465,15 @@ void CartRetail::DoSavestate(Savestate* file)
file->Var8(&SRAMStatus);
if ((!file->Saving) && SRAM)
- Platform::WriteNDSSave(SRAM, SRAMLength, 0, SRAMLength);
-}
-
-void CartRetail::SetupSave(u32 type)
-{
- if (SRAM) delete[] SRAM;
- SRAM = nullptr;
-
- if (type > 10) type = 0;
- int sramlen[] =
- {
- 0,
- 512,
- 8192, 65536, 128*1024,
- 256*1024, 512*1024, 1024*1024,
- 8192*1024, 16384*1024, 65536*1024
- };
- SRAMLength = sramlen[type];
-
- if (SRAMLength)
- {
- SRAM = new u8[SRAMLength];
- memset(SRAM, 0xFF, SRAMLength);
- }
-
- switch (type)
- {
- 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:
- case 10: SRAMType = 4; break; // NAND
- default: SRAMType = 0; break; // ...whatever else
- }
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength, 0, SRAMLength);
}
-void CartRetail::LoadSave(const u8* savedata, u32 savelen)
+void CartRetail::SetSaveMemory(const u8* savedata, u32 savelen)
{
if (!SRAM) return;
u32 len = std::min(savelen, SRAMLength);
- memcpy(SRAM, savedata, len);
+ memcpy(SRAM.get(), savedata, len);
Platform::WriteNDSSave(savedata, len, 0, len);
}
@@ -551,16 +537,6 @@ u8 CartRetail::SPIWrite(u8 val, u32 pos, bool last)
}
}
-u8 *CartRetail::GetSaveMemory() const
-{
- return SRAM;
-}
-
-u32 CartRetail::GetSaveMemoryLength() const
-{
- return SRAMLength;
-}
-
void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset)
{
addr &= (ROMLength-1);
@@ -578,7 +554,7 @@ void CartRetail::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset)
addr = 0x8000 + (addr & 0x1FF);
}
- memcpy(data+offset, ROM+addr, len);
+ memcpy(data+offset, ROM.get()+addr, len);
}
u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last)
@@ -613,7 +589,7 @@ u8 CartRetail::SRAMWrite_EEPROMTiny(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
(SRAMFirstAddr + ((SRAMCmd==0x0A)?0x100:0)) & 0x1FF, SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -677,7 +653,7 @@ u8 CartRetail::SRAMWrite_EEPROM(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -734,7 +710,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -771,7 +747,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -817,7 +793,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -840,7 +816,7 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
if (last)
{
SRAMStatus &= ~(1<<1);
- Platform::WriteNDSSave(SRAM, SRAMLength,
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength,
SRAMFirstAddr & (SRAMLength-1), SRAMAddr-SRAMFirstAddr);
}
return 0;
@@ -852,15 +828,19 @@ u8 CartRetail::SRAMWrite_FLASH(u8 val, u32 pos, bool last)
}
}
-
-CartRetailNAND::CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams)
+CartRetailNAND::CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen)
{
}
-CartRetailNAND::~CartRetailNAND()
+CartRetailNAND::CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailNAND)
{
+ BuildSRAMID();
}
+CartRetailNAND::~CartRetailNAND() = default;
+
void CartRetailNAND::Reset()
{
CartRetail::Reset();
@@ -889,9 +869,9 @@ void CartRetailNAND::DoSavestate(Savestate* file)
BuildSRAMID();
}
-void CartRetailNAND::LoadSave(const u8* savedata, u32 savelen)
+void CartRetailNAND::SetSaveMemory(const u8* savedata, u32 savelen)
{
- CartRetail::LoadSave(savedata, savelen);
+ CartRetail::SetSaveMemory(savedata, savelen);
BuildSRAMID();
}
@@ -924,7 +904,7 @@ int CartRetailNAND::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8
if (SRAMLength && SRAMAddr < (SRAMBase+SRAMLength-0x20000))
{
memcpy(&SRAM[SRAMAddr - SRAMBase], SRAMWriteBuffer, 0x800);
- Platform::WriteNDSSave(SRAM, SRAMLength, SRAMAddr - SRAMBase, 0x800);
+ Platform::WriteNDSSave(SRAM.get(), SRAMLength, SRAMAddr - SRAMBase, 0x800);
}
SRAMAddr = 0;
@@ -1080,15 +1060,28 @@ void CartRetailNAND::BuildSRAMID()
}
-CartRetailIR::CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams) : CartRetail(rom, len, chipid, badDSiDump, romparams)
+CartRetailIR::CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartRetailIR(CopyToUnique(rom, len), len, chipid, irversion, badDSiDump, romparams, std::move(sram), sramlen)
{
- IRVersion = irversion;
}
-CartRetailIR::~CartRetailIR()
+CartRetailIR::CartRetailIR(
+ std::unique_ptr<u8[]>&& rom,
+ u32 len,
+ u32 chipid,
+ u32 irversion,
+ bool badDSiDump,
+ ROMListEntry romparams,
+ std::unique_ptr<u8[]>&& sram,
+ u32 sramlen
+) :
+ CartRetail(std::move(rom), len, chipid, badDSiDump, romparams, std::move(sram), sramlen, CartType::RetailIR),
+ IRVersion(irversion)
{
}
+CartRetailIR::~CartRetailIR() = default;
+
void CartRetailIR::Reset()
{
CartRetail::Reset();
@@ -1125,25 +1118,18 @@ u8 CartRetailIR::SPIWrite(u8 val, u32 pos, bool last)
return 0;
}
-
-CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams)
+CartRetailBT::CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen)
{
- Log(LogLevel::Info,"POKETYPE CART\n");
}
-CartRetailBT::~CartRetailBT()
+CartRetailBT::CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT)
{
+ Log(LogLevel::Info,"POKETYPE CART\n");
}
-void CartRetailBT::Reset()
-{
- CartRetail::Reset();
-}
-
-void CartRetailBT::DoSavestate(Savestate* file)
-{
- CartRetail::DoSavestate(file);
-}
+CartRetailBT::~CartRetailBT() = default;
u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last)
{
@@ -1159,50 +1145,30 @@ u8 CartRetailBT::SPIWrite(u8 val, u32 pos, bool last)
return 0;
}
-
-CartHomebrew::CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartCommon(rom, len, chipid, false, romparams)
+CartHomebrew::CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
+ CartHomebrew(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard))
{
- SD = nullptr;
}
-CartHomebrew::~CartHomebrew()
+CartHomebrew::CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard) :
+ CartCommon(std::move(rom), len, chipid, false, romparams, CartType::Homebrew),
+ SD(std::move(sdcard))
{
- if (SD)
- {
- SD->Close();
- delete SD;
- }
+ sdcard = std::nullopt;
+ // std::move on optionals usually results in an optional with a moved-from object
}
+CartHomebrew::~CartHomebrew() = default;
+// The SD card is destroyed by the optional's destructor
+
void CartHomebrew::Reset()
{
CartCommon::Reset();
- ReadOnly = Platform::GetConfigBool(Platform::DLDI_ReadOnly);
-
if (SD)
{
- SD->Close();
- delete SD;
+ ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), SD->IsReadOnly());
}
-
- if (Platform::GetConfigBool(Platform::DLDI_Enable))
- {
- std::string folderpath;
- if (Platform::GetConfigBool(Platform::DLDI_FolderSync))
- folderpath = Platform::GetConfigString(Platform::DLDI_FolderPath);
- else
- folderpath = "";
-
- ApplyDLDIPatch(melonDLDI, sizeof(melonDLDI), ReadOnly);
- SD = new FATStorage(Platform::GetConfigString(Platform::DLDI_ImagePath),
- (u64)Platform::GetConfigInt(Platform::DLDI_ImageSize) * 1024 * 1024,
- ReadOnly,
- folderpath);
- SD->Open();
- }
- else
- SD = nullptr;
}
void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds)
@@ -1213,7 +1179,7 @@ void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds)
{
// add the ROM to the SD volume
- if (!SD->InjectFile(romname, ROM, ROMLength))
+ if (!SD->InjectFile(romname, ROM.get(), ROMLength))
return;
// setup argv command line
@@ -1240,11 +1206,6 @@ void CartHomebrew::SetupDirectBoot(const std::string& romname, NDS& nds)
}
}
-void CartHomebrew::DoSavestate(Savestate* file)
-{
- CartCommon::DoSavestate(file);
-}
-
int CartHomebrew::ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len)
{
if (CmdEncMode != 2) return CartCommon::ROMCommandStart(nds, cartslot, cmd, data, len);
@@ -1293,7 +1254,7 @@ void CartHomebrew::ROMCommandFinish(u8* cmd, u8* data, u32 len)
case 0xC1:
{
u32 sector = (cmd[1]<<24) | (cmd[2]<<16) | (cmd[3]<<8) | cmd[4];
- if (SD && (!ReadOnly)) SD->WriteSectors(sector, len>>9, data);
+ if (SD && !SD->IsReadOnly()) SD->WriteSectors(sector, len>>9, data);
}
break;
@@ -1439,17 +1400,20 @@ void CartHomebrew::ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset)
addr &= (ROMLength-1);
- memcpy(data+offset, ROM+addr, len);
+ memcpy(data+offset, ROM.get()+addr, len);
}
-NDSCartSlot::NDSCartSlot(melonDS::NDS& nds) noexcept : NDS(nds)
+NDSCartSlot::NDSCartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& rom) noexcept : NDS(nds)
{
NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_PrepareData, MemberEventFunc(NDSCartSlot, ROMPrepareData));
NDS.RegisterEventFunc(Event_ROMTransfer, ROMTransfer_End, MemberEventFunc(NDSCartSlot, ROMEndTransfer));
NDS.RegisterEventFunc(Event_ROMSPITransfer, 0, MemberEventFunc(NDSCartSlot, SPITransferDone));
// All fields are default-constructed because they're listed as such in the class declaration
+
+ if (rom)
+ SetCart(std::move(rom));
}
NDSCartSlot::~NDSCartSlot() noexcept
@@ -1569,16 +1533,13 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept
memcpy(out, &cartrom[arm9base], 0x800);
- Key1_InitKeycode(false, gamecode, 2, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS));
+ Key1_InitKeycode(false, gamecode, 2, 2, &NDS.ARM7BIOS[0], sizeof(NDS::ARM7BIOS));
Key1_Decrypt((u32*)&out[0]);
- Key1_InitKeycode(false, gamecode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS));
+ Key1_InitKeycode(false, gamecode, 3, 2, &NDS.ARM7BIOS[0], 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");
@@ -1593,7 +1554,12 @@ void NDSCartSlot::DecryptSecureArea(u8* out) noexcept
}
}
-std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
+{
+ return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args));
+}
+
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args)
{
if (romdata == nullptr)
{
@@ -1607,28 +1573,10 @@ std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
return nullptr;
}
- u32 cartromsize = 0x200;
- while (cartromsize < romlen)
- cartromsize <<= 1; // ROM size must be a power of 2
-
- u8* cartrom = nullptr;
- try
- {
- 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 nullptr;
- }
-
- // copy romdata into cartrom then zero out the remaining space
- memcpy(cartrom, romdata, romlen);
- memset(cartrom + romlen, 0, cartromsize - romlen);
+ auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen);
NDSHeader header {};
- memcpy(&header, cartrom, sizeof(header));
+ memcpy(&header, cartrom.get(), sizeof(header));
bool dsi = header.IsDSi();
bool badDSiDump = false;
@@ -1694,30 +1642,24 @@ std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
}
std::unique_ptr<CartCommon> cart;
+ auto [sram, sramlen] = args ? std::move(*args->SRAM) : std::make_pair(nullptr, 0);
if (homebrew)
- cart = std::make_unique<CartHomebrew>(cartrom, cartromsize, cartid, romparams);
+ cart = std::make_unique<CartHomebrew>(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt);
else if (cartid & 0x08000000)
- cart = std::make_unique<CartRetailNAND>(cartrom, cartromsize, cartid, romparams);
+ cart = std::make_unique<CartRetailNAND>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
else if (irversion != 0)
- cart = std::make_unique<CartRetailIR>(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams);
+ cart = std::make_unique<CartRetailIR>(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen);
else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx
- cart = std::make_unique<CartRetailBT>(cartrom, cartromsize, cartid, romparams);
+ cart = std::make_unique<CartRetailBT>(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen);
else
- cart = std::make_unique<CartRetail>(cartrom, cartromsize, cartid, badDSiDump, romparams);
-
- if (romparams.SaveMemType > 0)
- cart->SetupSave(romparams.SaveMemType);
+ cart = std::make_unique<CartRetail>(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen);
+ args = std::nullopt;
return cart;
}
-bool NDSCartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
+void NDSCartSlot::SetCart(std::unique_ptr<CartCommon>&& cart) noexcept
{
- if (!cart) {
- Log(LogLevel::Error, "Failed to insert invalid cart; existing cart (if any) was not ejected.\n");
- return false;
- }
-
if (Cart)
EjectCart();
@@ -1725,6 +1667,10 @@ bool NDSCartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
// and cloning polymorphic objects without knowing the underlying type is annoying.
Cart = std::move(cart);
+ if (!Cart)
+ // If we're ejecting an existing cart without inserting a new one...
+ return;
+
Cart->Reset();
const NDSHeader& header = Cart->GetHeader();
@@ -1739,11 +1685,11 @@ bool NDSCartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
strncpy((char*)&cartrom[header.ARM9ROMOffset], "encryObj", 8);
- Key1_InitKeycode(false, romparams.GameCode, 3, 2, NDS.ARM7BIOS, sizeof(NDS::ARM7BIOS));
+ Key1_InitKeycode(false, romparams.GameCode, 3, 2, &NDS.ARM7BIOS[0], 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_InitKeycode(false, romparams.GameCode, 2, 2, &NDS.ARM7BIOS[0], sizeof(NDS::ARM7BIOS));
Key1_Encrypt((u32*)&cartrom[header.ARM9ROMOffset]);
Log(LogLevel::Debug, "Re-encrypted cart secure area\n");
@@ -1757,21 +1703,12 @@ bool NDSCartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
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);
-
- return true;
-}
-
-bool NDSCartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept
-{
- std::unique_ptr<CartCommon> cart = ParseROM(romdata, romlen);
-
- return InsertROM(std::move(cart));
}
-void NDSCartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept
+void NDSCartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept
{
if (Cart)
- Cart->LoadSave(savedata, savelen);
+ Cart->SetSaveMemory(savedata, savelen);
}
void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept
@@ -1780,15 +1717,15 @@ void NDSCartSlot::SetupDirectBoot(const std::string& romname) noexcept
Cart->SetupDirectBoot(romname, NDS);
}
-void NDSCartSlot::EjectCart() noexcept
+std::unique_ptr<CartCommon> NDSCartSlot::EjectCart() noexcept
{
- if (!Cart) return;
+ if (!Cart) return nullptr;
// ejecting the cart triggers the gamecard IRQ
NDS.SetIRQ(0, IRQ_CartIREQMC);
NDS.SetIRQ(1, IRQ_CartIREQMC);
- Cart = nullptr;
+ return std::move(Cart);
// CHECKME: does an eject imply anything for the ROM/SPI transfer registers?
}
diff --git a/src/NDSCart.h b/src/NDSCart.h
index 24f9f9e..43bf1fc 100644
--- a/src/NDSCart.h
+++ b/src/NDSCart.h
@@ -22,7 +22,7 @@
#include <array>
#include <string>
#include <memory>
-#include <array>
+#include <variant>
#include "types.h"
#include "Savestate.h"
@@ -49,14 +49,32 @@ enum CartType
class NDSCartSlot;
+/// Arguments used to create and populate an NDS cart of unknown type.
+/// Different carts take different subsets of these arguments,
+/// but we won't know which ones to use
+/// until we parse the header at runtime.
+struct NDSCartArgs
+{
+ /// The arguments used to load a homebrew SD card image.
+ /// If \c nullopt, then the cart will not have an SD card.
+ /// Ignored for retail ROMs.
+ std::optional<FATStorageArgs> SDCard = std::nullopt;
+
+ /// Save RAM to load into the cartridge.
+ /// If \c nullopt, then the cart's SRAM buffer will be empty.
+ /// Ignored for homebrew ROMs.
+ std::optional<std::pair<std::unique_ptr<u8[]>, u32>> SRAM = std::nullopt;
+};
+
// CartCommon -- base code shared by all cart types
class CartCommon
{
public:
- CartCommon(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams);
+ CartCommon(const u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type);
+ CartCommon(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, CartType type);
virtual ~CartCommon();
- virtual u32 Type() const = 0;
+ [[nodiscard]] u32 Type() const { return CartType; };
[[nodiscard]] u32 Checksum() const;
virtual void Reset();
@@ -64,16 +82,16 @@ public:
virtual void DoSavestate(Savestate* file);
- virtual void SetupSave(u32 type);
- virtual void LoadSave(const u8* savedata, u32 savelen);
virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len);
virtual void ROMCommandFinish(u8* cmd, u8* data, u32 len);
virtual u8 SPIWrite(u8 val, u32 pos, bool last);
- virtual u8* GetSaveMemory() const;
- virtual u32 GetSaveMemoryLength() const;
+ virtual u8* GetSaveMemory() { return nullptr; }
+ virtual const u8* GetSaveMemory() const { return nullptr; }
+ virtual u32 GetSaveMemoryLength() const { return 0; }
+ virtual void SetSaveMemory(const u8* savedata, u32 savelen) {};
[[nodiscard]] const NDSHeader& GetHeader() const { return Header; }
[[nodiscard]] NDSHeader& GetHeader() { return Header; }
@@ -82,48 +100,65 @@ public:
[[nodiscard]] const NDSBanner* Banner() const;
[[nodiscard]] const ROMListEntry& GetROMParams() const { return ROMParams; };
[[nodiscard]] u32 ID() const { return ChipID; }
- [[nodiscard]] const u8* GetROM() const { return ROM; }
+ [[nodiscard]] const u8* GetROM() const { return ROM.get(); }
[[nodiscard]] u32 GetROMLength() const { return ROMLength; }
protected:
void ReadROM(u32 addr, u32 len, u8* data, u32 offset);
- u8* ROM;
- u32 ROMLength;
- u32 ChipID;
- bool IsDSi;
- bool DSiMode;
- u32 DSiBase;
+ std::unique_ptr<u8[]> ROM = nullptr;
+ u32 ROMLength = 0;
+ u32 ChipID = 0;
+ bool IsDSi = false;
+ bool DSiMode = false;
+ u32 DSiBase = 0;
- u32 CmdEncMode;
- u32 DataEncMode;
+ u32 CmdEncMode = 0;
+ u32 DataEncMode = 0;
// Kept separate from the ROM data so we can decrypt the modcrypt area
// without touching the overall ROM data
- NDSHeader Header;
- ROMListEntry ROMParams;
+ NDSHeader Header {};
+ ROMListEntry ROMParams {};
+ const melonDS::NDSCart::CartType CartType = Default;
};
// CartRetail -- regular retail cart (ROM, SPI SRAM)
class CartRetail : public CartCommon
{
public:
- CartRetail(u8* rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams);
- virtual ~CartRetail() override;
+ CartRetail(
+ const u8* rom,
+ u32 len,
+ u32 chipid,
+ bool badDSiDump,
+ ROMListEntry romparams,
+ std::unique_ptr<u8[]>&& sram,
+ u32 sramlen,
+ melonDS::NDSCart::CartType type = CartType::Retail
+ );
+ CartRetail(
+ std::unique_ptr<u8[]>&& rom,
+ u32 len, u32 chipid,
+ bool badDSiDump,
+ ROMListEntry romparams,
+ std::unique_ptr<u8[]>&& sram,
+ u32 sramlen,
+ melonDS::NDSCart::CartType type = CartType::Retail
+ );
+ ~CartRetail() override;
- virtual u32 Type() const override { return CartType::Retail; }
-
- virtual void Reset() override;
+ void Reset() override;
- virtual void DoSavestate(Savestate* file) override;
+ void DoSavestate(Savestate* file) override;
- virtual void SetupSave(u32 type) override;
- virtual void LoadSave(const u8* savedata, u32 savelen) override;
+ void SetSaveMemory(const u8* savedata, u32 savelen) override;
- virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override;
+ int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override;
- virtual u8 SPIWrite(u8 val, u32 pos, bool last) override;
+ u8 SPIWrite(u8 val, u32 pos, bool last) override;
- virtual u8* GetSaveMemory() const override;
- virtual u32 GetSaveMemoryLength() const override;
+ u8* GetSaveMemory() override { return SRAM.get(); }
+ const u8* GetSaveMemory() const override { return SRAM.get(); }
+ u32 GetSaveMemoryLength() const override { return SRAMLength; }
protected:
void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset);
@@ -132,30 +167,29 @@ protected:
u8 SRAMWrite_EEPROM(u8 val, u32 pos, bool last);
u8 SRAMWrite_FLASH(u8 val, u32 pos, bool last);
- u8* SRAM;
- u32 SRAMLength;
- u32 SRAMType;
+ std::unique_ptr<u8[]> SRAM = nullptr;
+ u32 SRAMLength = 0;
+ u32 SRAMType = 0;
- u8 SRAMCmd;
- u32 SRAMAddr;
- u32 SRAMFirstAddr;
- u8 SRAMStatus;
+ u8 SRAMCmd = 0;
+ u32 SRAMAddr = 0;
+ u32 SRAMFirstAddr = 0;
+ u8 SRAMStatus = 0;
};
// CartRetailNAND -- retail cart with NAND SRAM (WarioWare DIY, Jam with the Band, ...)
class CartRetailNAND : public CartRetail
{
public:
- CartRetailNAND(u8* rom, u32 len, u32 chipid, ROMListEntry romparams);
+ CartRetailNAND(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
+ CartRetailNAND(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
~CartRetailNAND() override;
- virtual u32 Type() const override { return CartType::RetailNAND; }
-
void Reset() override;
void DoSavestate(Savestate* file) override;
- void LoadSave(const u8* savedata, u32 savelen) override;
+ void SetSaveMemory(const u8* savedata, u32 savelen) override;
int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override;
void ROMCommandFinish(u8* cmd, u8* data, u32 len) override;
@@ -165,22 +199,21 @@ public:
private:
void BuildSRAMID();
- u32 SRAMBase;
- u32 SRAMWindow;
+ u32 SRAMBase = 0;
+ u32 SRAMWindow = 0;
- u8 SRAMWriteBuffer[0x800];
- u32 SRAMWritePos;
+ u8 SRAMWriteBuffer[0x800] {};
+ u32 SRAMWritePos = 0;
};
// CartRetailIR -- SPI IR device and SRAM
class CartRetailIR : public CartRetail
{
public:
- CartRetailIR(u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams);
+ CartRetailIR(const u8* rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
+ CartRetailIR(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
~CartRetailIR() override;
- virtual u32 Type() const override { return CartType::RetailIR; }
-
void Reset() override;
void DoSavestate(Savestate* file) override;
@@ -188,23 +221,18 @@ public:
u8 SPIWrite(u8 val, u32 pos, bool last) override;
private:
- u32 IRVersion;
- u8 IRCmd;
+ u32 IRVersion = 0;
+ u8 IRCmd = 0;
};
// CartRetailBT - Pok�mon Typing Adventure (SPI BT controller)
class CartRetailBT : public CartRetail
{
public:
- CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams);
+ CartRetailBT(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
+ CartRetailBT(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr<u8[]>&& sram, u32 sramlen);
~CartRetailBT() override;
- virtual u32 Type() const override { return CartType::RetailBT; }
-
- void Reset() override;
-
- void DoSavestate(Savestate* file) override;
-
u8 SPIWrite(u8 val, u32 pos, bool last) override;
};
@@ -212,32 +240,38 @@ public:
class CartHomebrew : public CartCommon
{
public:
- CartHomebrew(u8* rom, u32 len, u32 chipid, ROMListEntry romparams);
+ CartHomebrew(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
+ CartHomebrew(std::unique_ptr<u8[]>&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional<FATStorage>&& sdcard = std::nullopt);
~CartHomebrew() override;
- virtual u32 Type() const override { return CartType::Homebrew; }
-
void Reset() override;
void SetupDirectBoot(const std::string& romname, NDS& nds) override;
- void DoSavestate(Savestate* file) override;
-
int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override;
void ROMCommandFinish(u8* cmd, u8* data, u32 len) override;
+ [[nodiscard]] const std::optional<FATStorage>& GetSDCard() const noexcept { return SD; }
+ void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); }
+ void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept
+ {
+ SD = std::move(sdcard);
+ sdcard = std::nullopt;
+ // moving from an optional doesn't set it to nullopt,
+ // it just leaves behind an optional with a moved-from value
+ }
+
private:
void ApplyDLDIPatchAt(u8* binary, u32 dldioffset, const u8* patch, u32 patchlen, bool readonly);
void ApplyDLDIPatch(const u8* patch, u32 patchlen, bool readonly);
void ReadROM_B7(u32 addr, u32 len, u8* data, u32 offset);
- FATStorage* SD;
- bool ReadOnly;
+ std::optional<FATStorage> SD {};
};
class NDSCartSlot
{
public:
- NDSCartSlot(melonDS::NDS& nds) noexcept;
+ explicit NDSCartSlot(melonDS::NDS& nds, std::unique_ptr<CartCommon>&& rom = nullptr) noexcept;
~NDSCartSlot() noexcept;
void Reset() noexcept;
void ResetCart() noexcept;
@@ -252,25 +286,16 @@ public:
/// If the provided cart is not valid,
/// then the currently-loaded ROM will not be ejected.
///
- /// @param cart Movable reference to the cart.
- /// @returns \c true if the cart was successfully loaded,
- /// \c false otherwise.
+ /// @param cart Movable reference to the cart,
+ /// or \c nullptr to eject the cart.
/// @post If the cart was successfully loaded,
/// then \c cart will be \c nullptr
/// and \c Cart will contain the object that \c cart previously pointed to.
/// Otherwise, \c cart and \c Cart will be both be unchanged.
- bool InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept;
-
- /// Parses a ROM image and loads it into the emulator.
- /// This function is equivalent to calling ::ParseROM() and ::InsertROM() in sequence.
- /// @param romdata Pointer to the ROM image.
- /// The cart emulator maintains its own copy of this data,
- /// so the caller is free to discard this data after calling this function.
- /// @param romlen The length of the ROM image, in bytes.
- /// @returns \c true if the ROM image was successfully loaded,
- /// \c false if not.
- bool LoadROM(const u8* romdata, u32 romlen) noexcept;
- void LoadSave(const u8* savedata, u32 savelen) noexcept;
+ void SetCart(std::unique_ptr<CartCommon>&& cart) noexcept;
+ [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
+ [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); }
+
void SetupDirectBoot(const std::string& romname) noexcept;
/// This function is intended to allow frontends to save and load SRAM
@@ -282,11 +307,15 @@ public:
/// @returns Pointer to this cart's SRAM if a cart is loaded and supports SRAM, otherwise \c nullptr.
[[nodiscard]] const u8* GetSaveMemory() const noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
[[nodiscard]] u8* GetSaveMemory() noexcept { return Cart ? Cart->GetSaveMemory() : nullptr; }
+ void SetSaveMemory(const u8* savedata, u32 savelen) noexcept;
/// @returns The length of the buffer returned by ::GetSaveMemory()
/// if a cart is loaded and supports SRAM, otherwise zero.
[[nodiscard]] u32 GetSaveMemoryLength() const noexcept { return Cart ? Cart->GetSaveMemoryLength() : 0; }
- void EjectCart() noexcept;
+
+ /// @return The cart that was in the slot before it was ejected,
+ /// or \c nullptr if the slot was already empty.
+ std::unique_ptr<CartCommon> EjectCart() noexcept;
u32 ReadROMData() noexcept;
void WriteROMData(u32 val) noexcept;
void WriteSPICnt(u16 val) noexcept;
@@ -294,9 +323,6 @@ public:
[[nodiscard]] u8 ReadSPIData() const noexcept;
void WriteSPIData(u8 val) noexcept;
- [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); }
- [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); }
-
[[nodiscard]] u8 GetROMCommand(u8 index) const noexcept { return ROMCommand[index]; }
void SetROMCommand(u8 index, u8 val) noexcept { ROMCommand[index] = val; }
@@ -306,27 +332,27 @@ public:
private:
friend class CartCommon;
melonDS::NDS& NDS;
- u16 SPICnt {};
- u32 ROMCnt {};
+ u16 SPICnt = 0;
+ u32 ROMCnt = 0;
std::array<u8, 8> ROMCommand {};
- u8 SPIData;
- u32 SPIDataPos;
- bool SPIHold;
+ u8 SPIData = 0;
+ u32 SPIDataPos = 0;
+ bool SPIHold = false;
- u32 ROMData;
+ u32 ROMData = 0;
- std::array<u8, 0x4000> TransferData;
- u32 TransferPos;
- u32 TransferLen;
- u32 TransferDir;
- std::array<u8, 8> TransferCmd;
+ std::array<u8, 0x4000> TransferData {};
+ u32 TransferPos = 0;
+ u32 TransferLen = 0;
+ u32 TransferDir = 0;
+ std::array<u8, 8> TransferCmd {};
- std::unique_ptr<CartCommon> Cart;
+ std::unique_ptr<CartCommon> Cart = nullptr;
- std::array<u32, 0x412> Key1_KeyBuf;
+ std::array<u32, 0x412> Key1_KeyBuf {};
- u64 Key2_X;
- u64 Key2_Y;
+ u64 Key2_X = 0;
+ u64 Key2_Y = 0;
void Key1_Encrypt(u32* data) noexcept;
void Key1_Decrypt(u32* data) noexcept;
@@ -346,9 +372,13 @@ private:
/// The returned cartridge will contain a copy of this data,
/// so the caller may deallocate \c romdata after this function returns.
/// @param romlen The length of the ROM data in bytes.
+/// @param sdcard The arguments to use for initializing the SD card.
+/// Ignored if the parsed ROM is not homebrew.
+/// If not given, the cart will not have an SD card.
/// @returns A \c NDSCart::CartCommon object representing the parsed ROM,
/// or \c nullptr if the ROM data couldn't be parsed.
-std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen);
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::optional<NDSCartArgs>&& args = std::nullopt);
}
#endif
diff --git a/src/Platform.h b/src/Platform.h
index f8b0453..2c9a6a4 100644
--- a/src/Platform.h
+++ b/src/Platform.h
@@ -106,24 +106,8 @@ enum ConfigEntry
ExternalBIOSEnable,
- DLDI_Enable,
- DLDI_ImagePath,
- DLDI_ImageSize,
- DLDI_ReadOnly,
- DLDI_FolderSync,
- DLDI_FolderPath,
-
- DSiSD_Enable,
- DSiSD_ImagePath,
- DSiSD_ImageSize,
- DSiSD_ReadOnly,
- DSiSD_FolderSync,
- DSiSD_FolderPath,
-
Firm_MAC,
- WifiSettingsPath,
-
AudioBitDepth,
DSi_FullBIOSBoot,
@@ -139,7 +123,6 @@ enum ConfigEntry
int GetConfigInt(ConfigEntry entry);
bool GetConfigBool(ConfigEntry entry);
-std::string GetConfigString(ConfigEntry entry);
bool GetConfigArray(ConfigEntry entry, void* data);
/**
diff --git a/src/SPI.cpp b/src/SPI.cpp
index b3e5b4e..3974e31 100644
--- a/src/SPI.cpp
+++ b/src/SPI.cpp
@@ -59,31 +59,22 @@ u16 CRC16(const u8* data, u32 len, u32 start)
bool FirmwareMem::VerifyCRC16(u32 start, u32 offset, u32 len, u32 crcoffset)
{
- u16 crc_stored = *(u16*)&Firmware->Buffer()[crcoffset];
- u16 crc_calced = CRC16(&Firmware->Buffer()[offset], len, start);
+ u16 crc_stored = *(u16*)&FirmwareData.Buffer()[crcoffset];
+ u16 crc_calced = CRC16(&FirmwareData.Buffer()[offset], len, start);
return (crc_stored == crc_calced);
}
-FirmwareMem::FirmwareMem(melonDS::NDS& nds) : SPIDevice(nds)
+FirmwareMem::FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware) : SPIDevice(nds), FirmwareData(std::move(firmware))
{
}
-FirmwareMem::~FirmwareMem()
-{
- RemoveFirmware();
-}
+FirmwareMem::~FirmwareMem() = default;
void FirmwareMem::Reset()
{
- if (!Firmware)
- {
- Log(LogLevel::Warn, "SPI firmware: no firmware loaded! Using default\n");
- Firmware = std::make_unique<class Firmware>(NDS.ConsoleType);
- }
-
// fix touchscreen coords
- for (auto& u : Firmware->GetUserData())
+ for (auto& u : FirmwareData.GetUserData())
{
u.TouchCalibrationADC1[0] = 0;
u.TouchCalibrationADC1[1] = 0;
@@ -95,17 +86,17 @@ void FirmwareMem::Reset()
u.TouchCalibrationPixel2[1] = 191;
}
- Firmware->UpdateChecksums();
+ FirmwareData.UpdateChecksums();
// disable autoboot
//Firmware[userdata+0x64] &= 0xBF;
- MacAddress mac = Firmware->GetHeader().MacAddr;
+ MacAddress mac = FirmwareData.GetHeader().MacAddr;
Log(LogLevel::Info, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// verify shit
- u32 mask = Firmware->Mask();
- Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&Firmware->Buffer()[0x2C], 0x2A)?"GOOD":"BAD");
+ u32 mask = FirmwareData.Mask();
+ Log(LogLevel::Debug, "FW: WIFI CRC16 = %s\n", VerifyCRC16(0x0000, 0x2C, *(u16*)&FirmwareData.Buffer()[0x2C], 0x2A)?"GOOD":"BAD");
Log(LogLevel::Debug, "FW: AP1 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FA00&mask, 0xFE, 0x7FAFE&mask)?"GOOD":"BAD");
Log(LogLevel::Debug, "FW: AP2 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FB00&mask, 0xFE, 0x7FBFE&mask)?"GOOD":"BAD");
Log(LogLevel::Debug, "FW: AP3 CRC16 = %s\n", VerifyCRC16(0x0000, 0x7FC00&mask, 0xFE, 0x7FCFE&mask)?"GOOD":"BAD");
@@ -136,8 +127,8 @@ void FirmwareMem::DoSavestate(Savestate* file)
void FirmwareMem::SetupDirectBoot()
{
- const auto& header = Firmware->GetHeader();
- const auto& userdata = Firmware->GetEffectiveUserData();
+ const auto& header = FirmwareData.GetHeader();
+ const auto& userdata = FirmwareData.GetEffectiveUserData();
if (NDS.ConsoleType == 1)
{
// The ARMWrite methods are virtual, they'll delegate to DSi if necessary
@@ -163,58 +154,9 @@ void FirmwareMem::SetupDirectBoot()
}
}
-const class Firmware* FirmwareMem::GetFirmware()
-{
- return Firmware.get();
-}
-
bool FirmwareMem::IsLoadedFirmwareBuiltIn()
{
- return Firmware->GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER;
-}
-
-bool FirmwareMem::InstallFirmware(class Firmware&& firmware)
-{
- if (!firmware.Buffer())
- {
- Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n");
- return false;
- }
-
- Firmware = std::make_unique<class Firmware>(std::move(firmware));
-
- FirmwareIdentifier id = Firmware->GetHeader().Identifier;
- Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]);
-
- return true;
-}
-
-bool FirmwareMem::InstallFirmware(std::unique_ptr<class Firmware>&& firmware)
-{
- if (!firmware)
- {
- Log(LogLevel::Error, "SPI firmware: firmware is null!\n");
- return false;
- }
-
- if (!firmware->Buffer())
- {
- Log(LogLevel::Error, "SPI firmware: firmware buffer is null!\n");
- return false;
- }
-
- Firmware = std::move(firmware);
-
- FirmwareIdentifier id = Firmware->GetHeader().Identifier;
- Log(LogLevel::Debug, "Installed firmware (Identifier: %c%c%c%c)\n", id[0], id[1], id[2], id[3]);
-
- return true;
-}
-
-void FirmwareMem::RemoveFirmware()
-{
- Firmware.reset();
- Log(LogLevel::Debug, "Removed installed firmware (if any)\n");
+ return FirmwareData.GetHeader().Identifier == GENERATED_FIRMWARE_IDENTIFIER;
}
void FirmwareMem::Write(u8 val)
@@ -256,7 +198,7 @@ void FirmwareMem::Write(u8 val)
}
else
{
- Data = Firmware->Buffer()[Addr & Firmware->Mask()];
+ Data = FirmwareData.Buffer()[Addr & FirmwareData.Mask()];
Addr++;
}
@@ -279,7 +221,7 @@ void FirmwareMem::Write(u8 val)
}
else
{
- Firmware->Buffer()[Addr & Firmware->Mask()] = val;
+ FirmwareData.Buffer()[Addr & FirmwareData.Mask()] = val;
Data = val;
Addr++;
}
@@ -314,11 +256,11 @@ void FirmwareMem::Release()
{ // If the SPI firmware chip just finished a write...
// We only notify the frontend of changes to the Wi-fi/userdata settings region
// (although it might still decide to flush the whole thing)
- u32 wifioffset = Firmware->GetWifiAccessPointOffset();
+ u32 wifioffset = FirmwareData.GetWifiAccessPointOffset();
// Request that the start of the Wi-fi/userdata settings region
// through the end of the firmware blob be flushed to disk
- Platform::WriteFirmware(*Firmware, wifioffset, Firmware->Length() - wifioffset);
+ Platform::WriteFirmware(FirmwareData, wifioffset, FirmwareData.Length() - wifioffset);
}
SPIDevice::Release();
@@ -530,11 +472,11 @@ void TSC::Write(u8 val)
-SPIHost::SPIHost(melonDS::NDS& nds) : NDS(nds)
+SPIHost::SPIHost(melonDS::NDS& nds, Firmware&& firmware) : NDS(nds)
{
NDS.RegisterEventFunc(Event_SPITransfer, 0, MemberEventFunc(SPIHost, TransferDone));
- Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS);
+ Devices[SPIDevice_FirmwareMem] = new FirmwareMem(NDS, std::move(firmware));
Devices[SPIDevice_PowerMan] = new PowerMan(NDS);
if (NDS.ConsoleType == 1)
diff --git a/src/SPI.h b/src/SPI.h
index f27f0c3..aee4165 100644
--- a/src/SPI.h
+++ b/src/SPI.h
@@ -66,24 +66,23 @@ protected:
class FirmwareMem : public SPIDevice
{
public:
- FirmwareMem(melonDS::NDS& nds);
+ FirmwareMem(melonDS::NDS& nds, melonDS::Firmware&& firmware);
~FirmwareMem() override;
void Reset() override;
void DoSavestate(Savestate* file) override;
void SetupDirectBoot();
- const class Firmware* GetFirmware();
+ Firmware& GetFirmware() noexcept { return FirmwareData; }
+ [[nodiscard]] const Firmware& GetFirmware() const noexcept { return FirmwareData; }
+ void SetFirmware(Firmware&& firmware) { FirmwareData = std::move(firmware); }
bool IsLoadedFirmwareBuiltIn();
- bool InstallFirmware(class Firmware&& firmware);
- bool InstallFirmware(std::unique_ptr<class Firmware>&& firmware);
- void RemoveFirmware();
void Write(u8 val) override;
void Release() override;
private:
- std::unique_ptr<class Firmware> Firmware;
+ Firmware FirmwareData;
u8 CurCmd;
@@ -141,16 +140,19 @@ protected:
class SPIHost
{
public:
- SPIHost(melonDS::NDS& nds);
+ SPIHost(melonDS::NDS& nds, Firmware&& firmware);
~SPIHost();
void Reset();
void DoSavestate(Savestate* file);
FirmwareMem* GetFirmwareMem() { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; }
+ const FirmwareMem* GetFirmwareMem() const { return (FirmwareMem*)Devices[SPIDevice_FirmwareMem]; }
PowerMan* GetPowerMan() { return (PowerMan*)Devices[SPIDevice_PowerMan]; }
TSC* GetTSC() { return (TSC*)Devices[SPIDevice_TSC]; }
- const Firmware* GetFirmware() { return GetFirmwareMem()->GetFirmware(); }
+ const Firmware& GetFirmware() const { return GetFirmwareMem()->GetFirmware(); }
+ Firmware& GetFirmware() { return GetFirmwareMem()->GetFirmware(); }
+ void SetFirmware(Firmware&& firmware) { GetFirmwareMem()->SetFirmware(std::move(firmware)); }
u16 ReadCnt() { return Cnt; }
void WriteCnt(u16 val);
diff --git a/src/Utils.cpp b/src/Utils.cpp
new file mode 100644
index 0000000..698cf9b
--- /dev/null
+++ b/src/Utils.cpp
@@ -0,0 +1,66 @@
+/*
+ Copyright 2016-2023 melonDS team
+
+ 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 "Utils.h"
+
+#include <string.h>
+
+namespace melonDS
+{
+std::pair<std::unique_ptr<u8[]>, u32> PadToPowerOf2(std::unique_ptr<u8[]>&& data, u32 len) noexcept
+{
+ if (data == nullptr || len == 0)
+ return {nullptr, 0};
+
+ if ((len & (len - 1)) == 0)
+ return {std::move(data), len};
+
+ u32 newlen = 1;
+ while (newlen < len)
+ newlen <<= 1;
+
+ auto newdata = std::make_unique<u8[]>(newlen);
+ memcpy(newdata.get(), data.get(), len);
+ data = nullptr;
+ return {std::move(newdata), newlen};
+}
+
+std::pair<std::unique_ptr<u8[]>, u32> PadToPowerOf2(const u8* data, u32 len) noexcept
+{
+ if (len == 0)
+ return {nullptr, 0};
+
+ u32 newlen = 1;
+ while (newlen < len)
+ newlen <<= 1;
+
+ auto newdata = std::make_unique<u8[]>(newlen);
+ memcpy(newdata.get(), data, len);
+ return {std::move(newdata), newlen};
+}
+
+std::unique_ptr<u8[]> CopyToUnique(const u8* data, u32 len) noexcept
+{
+ if (data == nullptr || len == 0)
+ return nullptr;
+
+ auto newdata = std::make_unique<u8[]>(len);
+ memcpy(newdata.get(), data, len);
+ return newdata;
+}
+} \ No newline at end of file
diff --git a/src/Utils.h b/src/Utils.h
new file mode 100644
index 0000000..63be217
--- /dev/null
+++ b/src/Utils.h
@@ -0,0 +1,43 @@
+/*
+ Copyright 2016-2023 melonDS team
+
+ 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/.
+*/
+
+#ifndef MELONDS_UTILS_H
+#define MELONDS_UTILS_H
+
+#include <memory>
+#include "types.h"
+#include <utility>
+
+namespace melonDS
+{
+/// Ensures that the given array is a power of 2 in length.
+///
+/// @return If \c len is a power of 2, returns \c data and \c len unchanged
+/// without copying anything.
+/// If \c data is \c nullptr, returns <tt>{nullptr, 0}</tt>.
+/// Otherwise, return a copy of \c data with zero-padding to the next power of 2.
+/// @post \c data is \c nullptr, even if it doesn't need to be copied.
+std::pair<std::unique_ptr<u8[]>, u32> PadToPowerOf2(std::unique_ptr<u8[]>&& data, u32 len) noexcept;
+
+std::pair<std::unique_ptr<u8[]>, u32> PadToPowerOf2(const u8* data, u32 len) noexcept;
+
+std::unique_ptr<u8[]> CopyToUnique(const u8* data, u32 len) noexcept;
+
+}
+
+#endif // MELONDS_UTILS_H
diff --git a/src/Wifi.cpp b/src/Wifi.cpp
index 5027523..9dc696b 100644
--- a/src/Wifi.cpp
+++ b/src/Wifi.cpp
@@ -158,12 +158,12 @@ void Wifi::Reset()
}
#undef BBREG_FIXED
- const Firmware* fw = NDS.SPI.GetFirmware();
+ const Firmware& fw = NDS.SPI.GetFirmware();
- RFVersion = fw->GetHeader().RFChipType;
+ RFVersion = fw.GetHeader().RFChipType;
memset(RFRegs, 0, 4*0x40);
- Firmware::FirmwareConsoleType console = fw->GetHeader().ConsoleType;
+ Firmware::FirmwareConsoleType console = fw.GetHeader().ConsoleType;
if (console == Firmware::FirmwareConsoleType::DS)
IOPORT(0x000) = 0x1440;
else if (console == Firmware::FirmwareConsoleType::DSLite)
diff --git a/src/frontend/qt_sdl/ArchiveUtil.cpp b/src/frontend/qt_sdl/ArchiveUtil.cpp
index 7d1eca9..aa508e8 100644
--- a/src/frontend/qt_sdl/ArchiveUtil.cpp
+++ b/src/frontend/qt_sdl/ArchiveUtil.cpp
@@ -120,13 +120,12 @@ QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteA
}
-s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize)
+s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr<u8[]>& filedata, u32* filesize)
{
struct archive *a = archive_read_new();
struct archive_entry *entry;
int r;
- if (!filedata) return -1;
archive_read_support_format_all(a);
archive_read_support_filter_all(a);
@@ -148,8 +147,8 @@ s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32*
size_t bytesToRead = archive_entry_size(entry);
if (filesize) *filesize = bytesToRead;
- *filedata = new u8[bytesToRead];
- ssize_t bytesRead = archive_read_data(a, *filedata, bytesToRead);
+ filedata = std::make_unique<u8[]>(bytesToRead);
+ ssize_t bytesRead = archive_read_data(a, filedata.get(), bytesToRead);
archive_read_close(a);
archive_read_free(a);
diff --git a/src/frontend/qt_sdl/ArchiveUtil.h b/src/frontend/qt_sdl/ArchiveUtil.h
index eaffb0d..246670e 100644
--- a/src/frontend/qt_sdl/ArchiveUtil.h
+++ b/src/frontend/qt_sdl/ArchiveUtil.h
@@ -37,7 +37,7 @@ namespace Archive
using namespace melonDS;
QVector<QString> ListArchive(QString path);
-s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize);
+s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr<u8[]>& filedata, u32* filesize);
//QVector<QString> ExtractFileFromArchive(QString path, QString wantedFile, QByteArray *romBuffer);
//u32 ExtractFileFromArchive(const char* path, const char* wantedFile, u8 **romdata);
diff --git a/src/frontend/qt_sdl/Platform.cpp b/src/frontend/qt_sdl/Platform.cpp
index c2e2f47..d410d4f 100644
--- a/src/frontend/qt_sdl/Platform.cpp
+++ b/src/frontend/qt_sdl/Platform.cpp
@@ -204,10 +204,6 @@ int GetConfigInt(ConfigEntry entry)
case JIT_MaxBlockSize: return Config::JIT_MaxBlockSize;
#endif
- case DLDI_ImageSize: return imgsizes[Config::DLDISize];
-
- case DSiSD_ImageSize: return imgsizes[Config::DSiSDSize];
-
case AudioBitDepth: return Config::AudioBitDepth;
#ifdef GDBSTUB_ENABLED
@@ -232,14 +228,6 @@ bool GetConfigBool(ConfigEntry entry)
case ExternalBIOSEnable: return Config::ExternalBIOSEnable != 0;
- case DLDI_Enable: return Config::DLDIEnable != 0;
- case DLDI_ReadOnly: return Config::DLDIReadOnly != 0;
- case DLDI_FolderSync: return Config::DLDIFolderSync != 0;
-
- case DSiSD_Enable: return Config::DSiSDEnable != 0;
- case DSiSD_ReadOnly: return Config::DSiSDReadOnly != 0;
- case DSiSD_FolderSync: return Config::DSiSDFolderSync != 0;
-
case DSi_FullBIOSBoot: return Config::DSiFullBIOSBoot != 0;
#ifdef GDBSTUB_ENABLED
@@ -252,22 +240,6 @@ bool GetConfigBool(ConfigEntry entry)
return false;
}
-std::string GetConfigString(ConfigEntry entry)
-{
- switch (entry)
- {
- case DLDI_ImagePath: return Config::DLDISDPath;
- case DLDI_FolderPath: return Config::DLDIFolderPath;
-
- case DSiSD_ImagePath: return Config::DSiSDPath;
- case DSiSD_FolderPath: return Config::DSiSDFolderPath;
-
- case WifiSettingsPath: return Config::WifiSettingsPath;
- }
-
- return "";
-}
-
bool GetConfigArray(ConfigEntry entry, void* data)
{
switch (entry)
diff --git a/src/frontend/qt_sdl/ROMManager.cpp b/src/frontend/qt_sdl/ROMManager.cpp
index 2c0cee2..fda043a 100644
--- a/src/frontend/qt_sdl/ROMManager.cpp
+++ b/src/frontend/qt_sdl/ROMManager.cpp
@@ -68,8 +68,8 @@ std::string BaseGBAROMDir = "";
std::string BaseGBAROMName = "";
std::string BaseGBAAssetName = "";
-SaveManager* NDSSave = nullptr;
-SaveManager* GBASave = nullptr;
+std::unique_ptr<SaveManager> NDSSave = nullptr;
+std::unique_ptr<SaveManager> GBASave = nullptr;
std::unique_ptr<SaveManager> FirmwareSave = nullptr;
std::unique_ptr<Savestate> BackupState = nullptr;
@@ -303,6 +303,28 @@ QString VerifySetup()
return "";
}
+std::string GetEffectiveFirmwareSavePath(EmuThread* thread)
+{
+ if (!Config::ExternalBIOSEnable)
+ {
+ return Config::WifiSettingsPath;
+ }
+ if (thread->NDS->ConsoleType == 1)
+ {
+ return Config::DSiFirmwarePath;
+ }
+ else
+ {
+ return Config::FirmwarePath;
+ }
+}
+
+// Initializes the firmware save manager with the selected firmware image's path
+// OR the path to the wi-fi settings.
+void InitFirmwareSaveManager(EmuThread* thread) noexcept
+{
+ FirmwareSave = std::make_unique<SaveManager>(GetEffectiveFirmwareSavePath(thread));
+}
std::string GetSavestateName(int slot)
{
@@ -482,6 +504,11 @@ void LoadCheats(NDS& nds)
std::optional<std::array<u8, ARM9BIOSSize>> LoadARM9BIOS() noexcept
{
+ if (!Config::ExternalBIOSEnable)
+ {
+ return Config::ConsoleType == 0 ? std::make_optional(bios_arm9_bin) : std::nullopt;
+ }
+
if (FileHandle* f = OpenLocalFile(Config::BIOS9Path, Read))
{
std::array<u8, ARM9BIOSSize> bios {};
@@ -498,6 +525,11 @@ std::optional<std::array<u8, ARM9BIOSSize>> LoadARM9BIOS() noexcept
std::optional<std::array<u8, ARM7BIOSSize>> LoadARM7BIOS() noexcept
{
+ if (!Config::ExternalBIOSEnable)
+ {
+ return Config::ConsoleType == 0 ? std::make_optional(bios_arm7_bin) : std::nullopt;
+ }
+
if (FileHandle* f = OpenLocalFile(Config::BIOS7Path, Read))
{
std::array<u8, ARM7BIOSSize> bios {};
@@ -518,6 +550,16 @@ std::optional<std::array<u8, DSiBIOSSize>> LoadDSiARM9BIOS() noexcept
std::array<u8, DSiBIOSSize> bios {};
FileRead(bios.data(), sizeof(bios), 1, f);
CloseFile(f);
+
+ if (!Config::DSiFullBIOSBoot)
+ {
+ // herp
+ *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector
+
+ // TODO!!!!
+ // hax the upper 32K out of the goddamn DSi
+ // done that :) -pcy
+ }
Log(Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str());
return bios;
}
@@ -533,6 +575,16 @@ std::optional<std::array<u8, DSiBIOSSize>> LoadDSiARM7BIOS() noexcept
std::array<u8, DSiBIOSSize> bios {};
FileRead(bios.data(), sizeof(bios), 1, f);
CloseFile(f);
+
+ if (!Config::DSiFullBIOSBoot)
+ {
+ // herp
+ *(u32*)&bios[0] = 0xEAFFFFFE; // overwrites the reset vector
+
+ // TODO!!!!
+ // hax the upper 32K out of the goddamn DSi
+ // done that :) -pcy
+ }
Log(Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str());
return bios;
}
@@ -589,6 +641,16 @@ Firmware GenerateFirmware(int type) noexcept
std::optional<Firmware> LoadFirmware(int type) noexcept
{
+ if (!Config::ExternalBIOSEnable)
+ { // If we're using built-in firmware...
+ if (type == 1)
+ {
+ Log(Error, "DSi firmware: cannot use built-in firmware in DSi mode!\n");
+ return std::nullopt;
+ }
+
+ return GenerateFirmware(type);
+ }
const string& firmwarepath = type == 1 ? Config::DSiFirmwarePath : Config::FirmwarePath;
Log(Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str());
@@ -609,7 +671,10 @@ std::optional<Firmware> LoadFirmware(int type) noexcept
return std::nullopt;
}
- CustomizeFirmware(firmware);
+ if (Config::FirmwareOverrideSettings)
+ {
+ CustomizeFirmware(firmware);
+ }
return firmware;
}
@@ -694,7 +759,20 @@ std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& a
return nandImage;
}
-constexpr int imgsizes[] = {0, 256, 512, 1024, 2048, 4096};
+constexpr u64 imgsizes[] = {0, 256, 512, 1024, 2048, 4096};
+std::optional<FATStorageArgs> GetDSiSDCardArgs() noexcept
+{
+ if (!Config::DSiSDEnable)
+ return std::nullopt;
+
+ return FATStorageArgs {
+ Config::DSiSDPath,
+ imgsizes[Config::DSiSDSize],
+ Config::DSiSDReadOnly,
+ Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt
+ };
+}
+
std::optional<FATStorage> LoadDSiSDCard() noexcept
{
if (!Config::DSiSDEnable)
@@ -704,97 +782,34 @@ std::optional<FATStorage> LoadDSiSDCard() noexcept
Config::DSiSDPath,
imgsizes[Config::DSiSDSize],
Config::DSiSDReadOnly,
- Config::DSiSDFolderSync ? Config::DSiSDFolderPath : ""
+ Config::DSiSDFolderSync ? std::make_optional(Config::DSiSDFolderPath) : std::nullopt
);
}
-void LoadBIOSFiles(NDS& nds)
+std::optional<FATStorageArgs> GetDLDISDCardArgs() noexcept
{
- if (Config::ExternalBIOSEnable)
- {
- if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read))
- {
- FileRewind(f);
- FileRead(nds.ARM9BIOS, sizeof(NDS::ARM9BIOS), 1, f);
-
- Log(LogLevel::Info, "ARM9 BIOS loaded from %s\n", Config::BIOS9Path.c_str());
- Platform::CloseFile(f);
- }
- else
- {
- Log(LogLevel::Warn, "ARM9 BIOS not found\n");
-
- for (int i = 0; i < 16; i++)
- ((u32*)nds.ARM9BIOS)[i] = 0xE7FFDEFF;
- }
-
- if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS7Path, FileMode::Read))
- {
- FileRead(nds.ARM7BIOS, sizeof(NDS::ARM7BIOS), 1, f);
-
- Log(LogLevel::Info, "ARM7 BIOS loaded from\n", Config::BIOS7Path.c_str());
- Platform::CloseFile(f);
- }
- else
- {
- Log(LogLevel::Warn, "ARM7 BIOS not found\n");
-
- for (int i = 0; i < 16; i++)
- ((u32*)nds.ARM7BIOS)[i] = 0xE7FFDEFF;
- }
- }
- else
- {
- Log(LogLevel::Info, "Using built-in ARM7 and ARM9 BIOSes\n");
- memcpy(nds.ARM9BIOS, bios_arm9_bin, sizeof(bios_arm9_bin));
- memcpy(nds.ARM7BIOS, bios_arm7_bin, sizeof(bios_arm7_bin));
- }
-
- if (Config::ConsoleType == 1)
- {
- DSi& dsi = static_cast<DSi&>(nds);
- if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS9Path, FileMode::Read))
- {
- FileRead(dsi.ARM9iBIOS, sizeof(DSi::ARM9iBIOS), 1, f);
-
- Log(LogLevel::Info, "ARM9i BIOS loaded from %s\n", Config::DSiBIOS9Path.c_str());
- Platform::CloseFile(f);
- }
- else
- {
- Log(LogLevel::Warn, "ARM9i BIOS not found\n");
-
- for (int i = 0; i < 16; i++)
- ((u32*)dsi.ARM9iBIOS)[i] = 0xE7FFDEFF;
- }
-
- if (FileHandle* f = Platform::OpenLocalFile(Config::DSiBIOS7Path, FileMode::Read))
- {
- // TODO: check if the first 32 bytes are crapoed
- FileRead(dsi.ARM7iBIOS, sizeof(DSi::ARM7iBIOS), 1, f);
-
- Log(LogLevel::Info, "ARM7i BIOS loaded from %s\n", Config::DSiBIOS7Path.c_str());
- CloseFile(f);
- }
- else
- {
- Log(LogLevel::Warn, "ARM7i BIOS not found\n");
+ if (!Config::DLDIEnable)
+ return std::nullopt;
- for (int i = 0; i < 16; i++)
- ((u32*)dsi.ARM7iBIOS)[i] = 0xE7FFDEFF;
- }
+ return FATStorageArgs{
+ Config::DLDISDPath,
+ imgsizes[Config::DLDISize],
+ Config::DLDIReadOnly,
+ Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt
+ };
+}
- if (!Config::DSiFullBIOSBoot)
- {
- // herp
- *(u32*)&dsi.ARM9iBIOS[0] = 0xEAFFFFFE;
- *(u32*)&dsi.ARM7iBIOS[0] = 0xEAFFFFFE;
+std::optional<FATStorage> LoadDLDISDCard() noexcept
+{
+ if (!Config::DLDIEnable)
+ return std::nullopt;
- // TODO!!!!
- // hax the upper 32K out of the goddamn DSi
- // done that :) -pcy
- }
- }
+ return FATStorage(
+ Config::DLDISDPath,
+ imgsizes[Config::DLDISize],
+ Config::DLDIReadOnly,
+ Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt
+ );
}
void EnableCheats(NDS& nds, bool enable)
@@ -835,16 +850,10 @@ void SetDateTime(NDS& nds)
void Reset(EmuThread* thread)
{
- thread->RecreateConsole();
+ thread->UpdateConsole(Keep {}, Keep {});
if (Config::ConsoleType == 1) EjectGBACart(*thread->NDS);
- LoadBIOSFiles(*thread->NDS);
- InstallFirmware(*thread->NDS);
- if (Config::ConsoleType == 1)
- {
- InstallNAND(static_cast<DSi&>(*thread->NDS));
- }
thread->NDS->Reset();
SetBatteryLevels(*thread->NDS);
SetDateTime(*thread->NDS);
@@ -867,6 +876,7 @@ void Reset(EmuThread* thread)
GBASave->SetPath(newsave, false);
}
+ InitFirmwareSaveManager(thread);
if (FirmwareSave)
{
std::string oldsave = FirmwareSave->GetPath();
@@ -899,36 +909,25 @@ void Reset(EmuThread* thread)
}
-bool LoadBIOS(EmuThread* thread)
+bool BootToMenu(EmuThread* thread)
{
- thread->RecreateConsole();
-
- LoadBIOSFiles(*thread->NDS);
-
- if (!InstallFirmware(*thread->NDS))
- return false;
-
- if (Config::ConsoleType == 1 && !InstallNAND(static_cast<DSi&>(*thread->NDS)))
+ // Keep whatever cart is in the console, if any.
+ if (!thread->UpdateConsole(Keep {}, Keep {}))
+ // Try to update the console, but keep the existing cart. If that fails...
return false;
+ // BIOS and firmware files are loaded, patched, and installed in UpdateConsole
if (thread->NDS->NeedsDirectBoot())
return false;
- /*if (NDSSave) delete NDSSave;
- NDSSave = nullptr;
-
- CartType = -1;
- BaseROMDir = "";
- BaseROMName = "";
- BaseAssetName = "";*/
-
+ InitFirmwareSaveManager(thread);
thread->NDS->Reset();
SetBatteryLevels(*thread->NDS);
SetDateTime(*thread->NDS);
return true;
}
-u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent)
+u32 DecompressROM(const u8* inContent, const u32 inSize, unique_ptr<u8[]>& outContent)
{
u64 realSize = ZSTD_getFrameContentSize(inContent, inSize);
const u32 maxSize = 0x40000000;
@@ -940,16 +939,15 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent)
if (realSize != ZSTD_CONTENTSIZE_UNKNOWN)
{
- u8* realContent = new u8[realSize];
- u64 decompressed = ZSTD_decompress(realContent, realSize, inContent, inSize);
+ outContent = make_unique<u8[]>(realSize);
+ u64 decompressed = ZSTD_decompress(outContent.get(), realSize, inContent, inSize);
if (ZSTD_isError(decompressed))
{
- delete[] realContent;
+ outContent = nullptr;
return 0;
}
- *outContent = realContent;
return realSize;
}
else
@@ -1005,8 +1003,8 @@ u32 DecompressROM(const u8* inContent, const u32 inSize, u8** outContent)
} while (inBuf.pos < inBuf.size);
ZSTD_freeDStream(dStream);
- *outContent = new u8[outBuf.pos];
- memcpy(*outContent, outBuf.dst, outBuf.pos);
+ outContent = make_unique<u8[]>(outBuf.pos);
+ memcpy(outContent.get(), outBuf.dst, outBuf.pos);
ZSTD_freeDStream(dStream);
free(outBuf.dst);
@@ -1023,42 +1021,6 @@ void ClearBackupState()
}
}
-// We want both the firmware object and the path that was used to load it,
-// since we'll need to give it to the save manager later
-pair<unique_ptr<Firmware>, string> LoadFirmwareFromFile()
-{
- string loadedpath;
- unique_ptr<Firmware> firmware = nullptr;
- string firmwarepath = Config::ConsoleType == 0 ? Config::FirmwarePath : Config::DSiFirmwarePath;
-
- Log(LogLevel::Debug, "SPI firmware: loading from file %s\n", firmwarepath.c_str());
-
- string firmwareinstancepath = firmwarepath + Platform::InstanceFileSuffix();
-
- loadedpath = firmwareinstancepath;
- FileHandle* f = Platform::OpenLocalFile(firmwareinstancepath, FileMode::Read);
- if (!f)
- {
- loadedpath = firmwarepath;
- f = Platform::OpenLocalFile(firmwarepath, FileMode::Read);
- }
-
- if (f)
- {
- firmware = make_unique<Firmware>(f);
- if (!firmware->Buffer())
- {
- Log(LogLevel::Warn, "Couldn't read firmware file!\n");
- firmware = nullptr;
- loadedpath = "";
- }
-
- CloseFile(f);
- }
-
- return std::make_pair(std::move(firmware), loadedpath);
-}
-
pair<unique_ptr<Firmware>, string> GenerateDefaultFirmware()
{
// Construct the default firmware...
@@ -1068,7 +1030,7 @@ pair<unique_ptr<Firmware>, string> GenerateDefaultFirmware()
// Try to open the instanced Wi-fi settings, falling back to the regular Wi-fi settings if they don't exist.
// We don't need to save the whole firmware, just the part that may actually change.
- std::string wfcsettingspath = Platform::GetConfigString(ConfigEntry::WifiSettingsPath);
+ std::string wfcsettingspath = Config::WifiSettingsPath;
settingspath = wfcsettingspath + Platform::InstanceFileSuffix();
FileHandle* f = Platform::OpenLocalFile(settingspath, FileMode::Read);
if (!f)
@@ -1201,155 +1163,12 @@ void CustomizeFirmware(Firmware& firmware) noexcept
firmware.UpdateChecksums();
}
-static Platform::FileHandle* OpenNANDFile() noexcept
-{
- std::string nandpath = Config::DSiNANDPath;
- std::string instnand = nandpath + Platform::InstanceFileSuffix();
-
- FileHandle* nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting);
- if ((!nandfile) && (Platform::InstanceID() > 0))
- {
- FileHandle* orig = Platform::OpenLocalFile(nandpath, FileMode::Read);
- if (!orig)
- {
- Log(LogLevel::Error, "Failed to open DSi NAND from %s\n", nandpath.c_str());
- return nullptr;
- }
-
- QFile::copy(QString::fromStdString(nandpath), QString::fromStdString(instnand));
-
- nandfile = Platform::OpenLocalFile(instnand, FileMode::ReadWriteExisting);
- }
-
- return nandfile;
-}
-
-bool InstallNAND(DSi& dsi)
-{
- Platform::FileHandle* nandfile = OpenNANDFile();
- if (!nandfile)
- return false;
-
- DSi_NAND::NANDImage nandImage(nandfile, &dsi.ARM7iBIOS[0x8308]);
- if (!nandImage)
- {
- Log(LogLevel::Error, "Failed to parse DSi NAND\n");
- return false;
- }
-
- // scoped so that mount isn't alive when we move the NAND image to DSi::NANDImage
- {
- auto mount = DSi_NAND::NANDMount(nandImage);
- if (!mount)
- {
- Log(LogLevel::Error, "Failed to mount DSi NAND\n");
- return false;
- }
-
- DSi_NAND::DSiFirmwareSystemSettings settings {};
- if (!mount.ReadUserData(settings))
- {
- Log(LogLevel::Error, "Failed to read DSi NAND user data\n");
- return false;
- }
-
- // override user settings, if needed
- if (Config::FirmwareOverrideSettings)
- {
- // we store relevant strings as UTF-8, so we need to convert them to UTF-16
- auto converter = wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{};
-
- // setting up username
- std::u16string username = converter.from_bytes(Config::FirmwareUsername);
- size_t usernameLength = std::min(username.length(), (size_t) 10);
- memset(&settings.Nickname, 0, sizeof(settings.Nickname));
- memcpy(&settings.Nickname, username.data(), usernameLength * sizeof(char16_t));
-
- // setting language
- settings.Language = static_cast<Firmware::Language>(Config::FirmwareLanguage);
-
- // setting up color
- settings.FavoriteColor = Config::FirmwareFavouriteColour;
-
- // setting up birthday
- settings.BirthdayMonth = Config::FirmwareBirthdayMonth;
- settings.BirthdayDay = Config::FirmwareBirthdayDay;
-
- // setup message
- std::u16string message = converter.from_bytes(Config::FirmwareMessage);
- size_t messageLength = std::min(message.length(), (size_t) 26);
- memset(&settings.Message, 0, sizeof(settings.Message));
- memcpy(&settings.Message, message.data(), messageLength * sizeof(char16_t));
-
- // TODO: make other items configurable?
- }
-
- // fix touchscreen coords
- settings.TouchCalibrationADC1 = {0, 0};
- settings.TouchCalibrationPixel1 = {0, 0};
- settings.TouchCalibrationADC2 = {255 << 4, 191 << 4};
- settings.TouchCalibrationPixel2 = {255, 191};
-
- settings.UpdateHash();
-
- if (!mount.ApplyUserData(settings))
- {
- Log(LogLevel::Error, "Failed to write patched DSi NAND user data\n");
- return false;
- }
- }
-
- dsi.NANDImage = std::make_unique<DSi_NAND::NANDImage>(std::move(nandImage));
- return true;
-}
-
-bool InstallFirmware(NDS& nds)
-{
- FirmwareSave.reset();
- unique_ptr<Firmware> firmware;
- string firmwarepath;
- bool generated = false;
-
- if (Config::ExternalBIOSEnable)
- { // If we want to try loading a firmware dump...
-
- tie(firmware, firmwarepath) = LoadFirmwareFromFile();
- if (!firmware)
- { // Try to load the configured firmware dump. If that fails...
- Log(LogLevel::Warn, "Firmware not found! Generating default firmware.\n");
- }
- }
-
- if (!firmware)
- { // If we haven't yet loaded firmware (either because the load failed or we want to use the default...)
- tie(firmware, firmwarepath) = GenerateDefaultFirmware();
- }
-
- if (!firmware)
- return false;
-
- if (Config::FirmwareOverrideSettings)
- {
- CustomizeFirmware(*firmware);
- }
-
- FirmwareSave = std::make_unique<SaveManager>(firmwarepath);
-
- return nds.SPI.GetFirmwareMem()->InstallFirmware(std::move(firmware));
-}
-
-bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
+// Loads ROM data without parsing it. Works for GBA and NDS ROMs.
+bool LoadROMData(const QStringList& filepath, std::unique_ptr<u8[]>& filedata, u32& filelen, string& basepath, string& romname) noexcept
{
if (filepath.empty()) return false;
- u8* filedata = nullptr;
- u32 filelen;
-
- std::string basepath;
- std::string romname;
-
- int num = filepath.count();
- if (num == 1)
+ if (int num = filepath.count(); num == 1)
{
// regular file
@@ -1361,38 +1180,35 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
if (len > 0x40000000)
{
Platform::CloseFile(f);
- delete[] filedata;
return false;
}
Platform::FileRewind(f);
- filedata = new u8[len];
- size_t nread = Platform::FileRead(filedata, (size_t)len, 1, f);
+ filedata = make_unique<u8[]>(len);
+ size_t nread = Platform::FileRead(filedata.get(), (size_t)len, 1, f);
+ Platform::CloseFile(f);
if (nread != 1)
{
- Platform::CloseFile(f);
- delete[] filedata;
+ filedata = nullptr;
return false;
}
- Platform::CloseFile(f);
filelen = (u32)len;
if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst")
{
- u8* outContent = nullptr;
- u32 decompressed = DecompressROM(filedata, len, &outContent);
+ filelen = DecompressROM(filedata.get(), len, filedata);
- if (decompressed > 0)
+ if (filelen > 0)
{
- delete[] filedata;
- filedata = outContent;
- filelen = decompressed;
filename = filename.substr(0, filename.length() - 4);
}
else
{
- delete[] filedata;
+ filedata = nullptr;
+ filelen = 0;
+ basepath = "";
+ romname = "";
return false;
}
}
@@ -1400,19 +1216,21 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
int pos = LastSep(filename);
if(pos != -1)
basepath = filename.substr(0, pos);
+
romname = filename.substr(pos+1);
+ return true;
}
#ifdef ARCHIVE_SUPPORT_ENABLED
else if (num == 2)
{
// file inside archive
- s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
+ s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), filedata, &filelen);
if (lenread < 0) return false;
if (!filedata) return false;
if (lenread != filelen)
{
- delete[] filedata;
+ filedata = nullptr;
return false;
}
@@ -1421,38 +1239,31 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
std::string std_romname = filepath.at(1).toStdString();
romname = std_romname.substr(LastSep(std_romname)+1);
+ return true;
}
#endif
else
return false;
+}
+
+bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
+{
+ unique_ptr<u8[]> filedata = nullptr;
+ u32 filelen;
+ std::string basepath;
+ std::string romname;
+
+ if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
+ return false;
- if (NDSSave) delete NDSSave;
NDSSave = nullptr;
BaseROMDir = basepath;
BaseROMName = romname;
BaseAssetName = romname.substr(0, romname.rfind('.'));
- emuthread->RecreateConsole();
- if (!InstallFirmware(*emuthread->NDS))
- {
- return false;
- }
-
- if (reset)
- {
- emuthread->NDS->EjectCart();
- LoadBIOSFiles(*emuthread->NDS);
- if (Config::ConsoleType == 1)
- InstallNAND(static_cast<DSi&>(*emuthread->NDS));
-
- emuthread->NDS->Reset();
- SetBatteryLevels(*emuthread->NDS);
- SetDateTime(*emuthread->NDS);
- }
-
u32 savelen = 0;
- u8* savedata = nullptr;
+ std::unique_ptr<u8[]> savedata = nullptr;
std::string savname = GetAssetPath(false, Config::SaveFilePath, ".sav");
std::string origsav = savname;
@@ -1465,36 +1276,56 @@ bool LoadROM(EmuThread* emuthread, QStringList filepath, bool reset)
savelen = (u32)Platform::FileLength(sav);
FileRewind(sav);
- savedata = new u8[savelen];
- FileRead(savedata, savelen, 1, sav);
+ savedata = std::make_unique<u8[]>(savelen);
+ FileRead(savedata.get(), savelen, 1, sav);
CloseFile(sav);
}
- bool res = emuthread->NDS->LoadCart(filedata, filelen, savedata, savelen);
- if (res && reset)
+ NDSCart::NDSCartArgs cartargs {
+ // Don't load the SD card itself yet, because we don't know if
+ // the ROM is homebrew or not.
+ // So this is the card we *would* load if the ROM were homebrew.
+ .SDCard = GetDLDISDCardArgs(),
+
+ .SRAM = std::make_pair(std::move(savedata), savelen),
+ };
+
+ auto cart = NDSCart::ParseROM(std::move(filedata), filelen, std::move(cartargs));
+ if (!cart)
+ // If we couldn't parse the ROM...
+ return false;
+
+ if (reset)
{
+ if (!emuthread->UpdateConsole(std::move(cart), Keep {}))
+ return false;
+
+ InitFirmwareSaveManager(emuthread);
+ emuthread->NDS->Reset();
+
if (Config::DirectBoot || emuthread->NDS->NeedsDirectBoot())
- {
+ { // If direct boot is enabled or forced...
emuthread->NDS->SetupDirectBoot(romname);
}
- }
- if (res)
+ SetBatteryLevels(*emuthread->NDS);
+ SetDateTime(*emuthread->NDS);
+ }
+ else
{
- CartType = 0;
- NDSSave = new SaveManager(savname);
-
- LoadCheats(*emuthread->NDS);
+ assert(emuthread->NDS != nullptr);
+ emuthread->NDS->SetNDSCart(std::move(cart));
}
- if (savedata) delete[] savedata;
- delete[] filedata;
- return res;
+ CartType = 0;
+ NDSSave = std::make_unique<SaveManager>(savname);
+ LoadCheats(*emuthread->NDS);
+
+ return true;
}
void EjectCart(NDS& nds)
{
- if (NDSSave) delete NDSSave;
NDSSave = nullptr;
UnloadCheats(nds);
@@ -1529,92 +1360,16 @@ QString CartLabel()
bool LoadGBAROM(NDS& nds, QStringList filepath)
{
- if (Config::ConsoleType == 1) return false;
- if (filepath.empty()) return false;
+ if (nds.ConsoleType == 1) return false; // DSi doesn't have a GBA slot
- u8* filedata;
+ unique_ptr<u8[]> filedata = nullptr;
u32 filelen;
-
std::string basepath;
std::string romname;
- int num = filepath.count();
- if (num == 1)
- {
- // regular file
-
- std::string filename = filepath.at(0).toStdString();
- FileHandle* f = Platform::OpenFile(filename, FileMode::Read);
- if (!f) return false;
-
- long len = FileLength(f);
- if (len > 0x40000000)
- {
- CloseFile(f);
- return false;
- }
-
- FileRewind(f);
- filedata = new u8[len];
- size_t nread = FileRead(filedata, (size_t)len, 1, f);
- if (nread != 1)
- {
- CloseFile(f);
- delete[] filedata;
- return false;
- }
-
- CloseFile(f);
- filelen = (u32)len;
-
- if (filename.length() > 4 && filename.substr(filename.length() - 4) == ".zst")
- {
- u8* outContent = nullptr;
- u32 decompressed = DecompressROM(filedata, len, &outContent);
-
- if (decompressed > 0)
- {
- delete[] filedata;
- filedata = outContent;
- filelen = decompressed;
- filename = filename.substr(0, filename.length() - 4);
- }
- else
- {
- delete[] filedata;
- return false;
- }
- }
-
- int pos = LastSep(filename);
- basepath = filename.substr(0, pos);
- romname = filename.substr(pos+1);
- }
-#ifdef ARCHIVE_SUPPORT_ENABLED
- else if (num == 2)
- {
- // file inside archive
-
- s32 lenread = Archive::ExtractFileFromArchive(filepath.at(0), filepath.at(1), &filedata, &filelen);
- if (lenread < 0) return false;
- if (!filedata) return false;
- if (lenread != filelen)
- {
- delete[] filedata;
- return false;
- }
-
- std::string std_archivepath = filepath.at(0).toStdString();
- basepath = std_archivepath.substr(0, LastSep(std_archivepath));
-
- std::string std_romname = filepath.at(1).toStdString();
- romname = std_romname.substr(LastSep(std_romname)+1);
- }
-#endif
- else
+ if (!LoadROMData(filepath, filedata, filelen, basepath, romname))
return false;
- if (GBASave) delete GBASave;
GBASave = nullptr;
BaseGBAROMDir = basepath;
@@ -1622,7 +1377,7 @@ bool LoadGBAROM(NDS& nds, QStringList filepath)
BaseGBAAssetName = romname.substr(0, romname.rfind('.'));
u32 savelen = 0;
- u8* savedata = nullptr;
+ std::unique_ptr<u8[]> savedata = nullptr;
std::string savname = GetAssetPath(true, Config::SaveFilePath, ".sav");
std::string origsav = savname;
@@ -1634,30 +1389,29 @@ bool LoadGBAROM(NDS& nds, QStringList filepath)
{
savelen = (u32)FileLength(sav);
- FileRewind(sav);
- savedata = new u8[savelen];
- FileRead(savedata, savelen, 1, sav);
+ if (savelen > 0)
+ {
+ FileRewind(sav);
+ savedata = std::make_unique<u8[]>(savelen);
+ FileRead(savedata.get(), savelen, 1, sav);
+ }
CloseFile(sav);
}
- bool res = nds.LoadGBACart(filedata, filelen, savedata, savelen);
-
- if (res)
- {
- GBACartType = 0;
- GBASave = new SaveManager(savname);
- }
+ auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen);
+ if (!cart)
+ return false;
- if (savedata) delete[] savedata;
- delete[] filedata;
- return res;
+ nds.SetGBACart(std::move(cart));
+ GBACartType = 0;
+ GBASave = std::make_unique<SaveManager>(savname);
+ return true;
}
void LoadGBAAddon(NDS& nds, int type)
{
if (Config::ConsoleType == 1) return;
- if (GBASave) delete GBASave;
GBASave = nullptr;
nds.LoadGBAAddon(type);
@@ -1670,7 +1424,6 @@ void LoadGBAAddon(NDS& nds, int type)
void EjectGBACart(NDS& nds)
{
- if (GBASave) delete GBASave;
GBASave = nullptr;
nds.EjectGBACart();
diff --git a/src/frontend/qt_sdl/ROMManager.h b/src/frontend/qt_sdl/ROMManager.h
index 2163a68..0b640c8 100644
--- a/src/frontend/qt_sdl/ROMManager.h
+++ b/src/frontend/qt_sdl/ROMManager.h
@@ -35,34 +35,43 @@ namespace melonDS
class NDS;
class DSi;
class FATStorage;
+class FATStorageArgs;
}
class EmuThread;
namespace ROMManager
{
using namespace melonDS;
-extern SaveManager* NDSSave;
-extern SaveManager* GBASave;
+extern std::unique_ptr<SaveManager> NDSSave;
+extern std::unique_ptr<SaveManager> GBASave;
extern std::unique_ptr<SaveManager> FirmwareSave;
QString VerifySetup();
void Reset(EmuThread* thread);
-bool LoadBIOS(EmuThread* thread);
+
+/// Boots the emulated console into its system menu without starting a game.
+bool BootToMenu(EmuThread* thread);
void ClearBackupState();
+/// Returns the configured ARM9 BIOS loaded from disk,
+/// the FreeBIOS if external BIOS is disabled and we're in NDS mode,
+/// or nullopt if loading failed.
std::optional<std::array<u8, ARM9BIOSSize>> LoadARM9BIOS() noexcept;
std::optional<std::array<u8, ARM7BIOSSize>> LoadARM7BIOS() noexcept;
std::optional<std::array<u8, DSiBIOSSize>> LoadDSiARM9BIOS() noexcept;
std::optional<std::array<u8, DSiBIOSSize>> LoadDSiARM7BIOS() noexcept;
+std::optional<FATStorageArgs> GetDSiSDCardArgs() noexcept;
std::optional<FATStorage> LoadDSiSDCard() noexcept;
+std::optional<FATStorageArgs> GetDLDISDCardArgs() noexcept;
+std::optional<FATStorage> LoadDLDISDCard() noexcept;
void CustomizeFirmware(Firmware& firmware) noexcept;
Firmware GenerateFirmware(int type) noexcept;
/// Loads and customizes a firmware image based on the values in Config
std::optional<Firmware> LoadFirmware(int type) noexcept;
/// Loads and customizes a NAND image based on the values in Config
std::optional<DSi_NAND::NANDImage> LoadNAND(const std::array<u8, DSiBIOSSize>& arm7ibios) noexcept;
-bool InstallFirmware(NDS& nds);
-bool InstallNAND(DSi& dsi);
+
+/// Inserts a ROM into the emulated console.
bool LoadROM(EmuThread*, QStringList filepath, bool reset);
void EjectCart(NDS& nds);
bool CartInserted();
diff --git a/src/frontend/qt_sdl/main.cpp b/src/frontend/qt_sdl/main.cpp
index a0ac086..5bd4d1b 100644
--- a/src/frontend/qt_sdl/main.cpp
+++ b/src/frontend/qt_sdl/main.cpp
@@ -81,6 +81,7 @@
#include "FrontendUtil.h"
#include "OSD.h"
+#include "Args.h"
#include "NDS.h"
#include "NDSCart.h"
#include "GBACart.h"
@@ -204,29 +205,164 @@ EmuThread::EmuThread(QObject* parent) : QThread(parent)
static_cast<ScreenPanelGL*>(mainWindow->panel)->transferLayout(this);
}
-std::unique_ptr<NDS> EmuThread::CreateConsole()
+std::unique_ptr<NDS> EmuThread::CreateConsole(
+ std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
+ std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
+) noexcept
{
+ auto arm7bios = ROMManager::LoadARM7BIOS();
+ if (!arm7bios)
+ return nullptr;
+
+ auto arm9bios = ROMManager::LoadARM9BIOS();
+ if (!arm9bios)
+ return nullptr;
+
+ auto firmware = ROMManager::LoadFirmware(Config::ConsoleType);
+ if (!firmware)
+ return nullptr;
+
+ NDSArgs ndsargs {
+ std::move(ndscart),
+ std::move(gbacart),
+ *arm9bios,
+ *arm7bios,
+ std::move(*firmware),
+ };
+
if (Config::ConsoleType == 1)
{
- return std::make_unique<melonDS::DSi>();
+ auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
+ if (!arm7ibios)
+ return nullptr;
+
+ auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
+ if (!arm9ibios)
+ return nullptr;
+
+ auto nand = ROMManager::LoadNAND(*arm7ibios);
+ if (!nand)
+ return nullptr;
+
+ auto sdcard = ROMManager::LoadDSiSDCard();
+ DSiArgs args {
+ std::move(ndsargs),
+ *arm9ibios,
+ *arm7ibios,
+ std::move(*nand),
+ std::move(sdcard),
+ };
+
+ args.GBAROM = nullptr;
+
+ return std::make_unique<melonDS::DSi>(std::move(args));
}
- return std::make_unique<melonDS::NDS>();
+ return std::make_unique<melonDS::NDS>(std::move(ndsargs));
}
-void EmuThread::RecreateConsole()
+bool EmuThread::UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept
{
- if (!NDS || NDS->ConsoleType != Config::ConsoleType)
+ // Let's get the cart we want to use;
+ // if we wnat to keep the cart, we'll eject it from the existing console first.
+ std::unique_ptr<NDSCart::CartCommon> nextndscart;
+ if (std::holds_alternative<Keep>(ndsargs))
+ { // If we want to keep the existing cart (if any)...
+ nextndscart = NDS ? NDS->EjectCart() : nullptr;
+ ndsargs = {};
+ }
+ else if (const auto ptr = std::get_if<std::unique_ptr<NDSCart::CartCommon>>(&ndsargs))
{
- NDS = nullptr; // To ensure the destructor is called before a new one is created
+ nextndscart = std::move(*ptr);
+ ndsargs = {};
+ }
+
+ if (nextndscart && nextndscart->Type() == NDSCart::Homebrew)
+ {
+ // Load DLDISDCard will return nullopt if the SD card is disabled;
+ // SetSDCard will accept nullopt, which means no SD card
+ auto& homebrew = static_cast<NDSCart::CartHomebrew&>(*nextndscart);
+ homebrew.SetSDCard(ROMManager::LoadDLDISDCard());
+ }
+
+ std::unique_ptr<GBACart::CartCommon> nextgbacart;
+ if (std::holds_alternative<Keep>(gbaargs))
+ {
+ nextgbacart = NDS ? NDS->EjectGBACart() : nullptr;
+ }
+ else if (const auto ptr = std::get_if<std::unique_ptr<GBACart::CartCommon>>(&gbaargs))
+ {
+ nextgbacart = std::move(*ptr);
+ gbaargs = {};
+ }
+
+ if (!NDS || NDS->ConsoleType != Config::ConsoleType)
+ { // If we're switching between DS and DSi mode, or there's no console...
+ // To ensure the destructor is called before a new one is created,
+ // as the presence of global signal handlers still complicates things a bit
+ NDS = nullptr;
NDS::Current = nullptr;
- NDS = CreateConsole();
- // TODO: Insert ROMs
+ NDS = CreateConsole(std::move(nextndscart), std::move(nextgbacart));
NDS::Current = NDS.get();
+
+ return NDS != nullptr;
}
-}
+ auto arm9bios = ROMManager::LoadARM9BIOS();
+ if (!arm9bios)
+ return false;
+
+ auto arm7bios = ROMManager::LoadARM7BIOS();
+ if (!arm7bios)
+ return false;
+
+ auto firmware = ROMManager::LoadFirmware(NDS->ConsoleType);
+ if (!firmware)
+ return false;
+
+ if (NDS->ConsoleType == 1)
+ { // If the console we're updating is a DSi...
+ DSi& dsi = static_cast<DSi&>(*NDS);
+
+ auto arm9ibios = ROMManager::LoadDSiARM9BIOS();
+ if (!arm9ibios)
+ return false;
+
+ auto arm7ibios = ROMManager::LoadDSiARM7BIOS();
+ if (!arm7ibios)
+ return false;
+
+ auto nandimage = ROMManager::LoadNAND(*arm7ibios);
+ if (!nandimage)
+ return false;
+
+ auto dsisdcard = ROMManager::LoadDSiSDCard();
+
+ dsi.ARM7iBIOS = *arm7ibios;
+ dsi.ARM9iBIOS = *arm9ibios;
+ dsi.SetNAND(std::move(*nandimage));
+ dsi.SetSDCard(std::move(dsisdcard));
+ // We're moving the optional, not the card
+ // (inserting std::nullopt here is okay, it means no card)
+
+ dsi.EjectGBACart();
+ }
+
+ if (NDS->ConsoleType == 0)
+ {
+ NDS->SetGBACart(std::move(nextgbacart));
+ }
+
+ NDS->ARM7BIOS = *arm7bios;
+ NDS->ARM9BIOS = *arm9bios;
+ NDS->SetFirmware(std::move(*firmware));
+ NDS->SetNDSCart(std::move(nextndscart));
+
+ NDS::Current = NDS.get();
+
+ return true;
+}
void EmuThread::updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix)
{
@@ -343,7 +479,8 @@ void EmuThread::run()
u32 mainScreenPos[3];
Platform::FileHandle* file;
- RecreateConsole();
+ UpdateConsole(nullptr, nullptr);
+ // No carts are inserted when melonDS first boots
mainScreenPos[0] = 0;
mainScreenPos[1] = 0;
@@ -2507,7 +2644,7 @@ void MainWindow::onBootFirmware()
return;
}
- if (!ROMManager::LoadBIOS(emuThread))
+ if (!ROMManager::BootToMenu(emuThread))
{
// TODO: better error reporting?
QMessageBox::critical(this, "melonDS", "This firmware is not bootable.");
@@ -2750,13 +2887,12 @@ void MainWindow::onImportSavefile()
u32 len = FileLength(f);
- u8* data = new u8[len];
+ std::unique_ptr<u8[]> data = std::make_unique<u8[]>(len);
Platform::FileRewind(f);
- Platform::FileRead(data, len, 1, f);
+ Platform::FileRead(data.get(), len, 1, f);
assert(emuThread->NDS != nullptr);
- emuThread->NDS->LoadSave(data, len);
- delete[] data;
+ emuThread->NDS->SetNDSSave(data.get(), len);
CloseFile(f);
emuThread->emuUnpause();
diff --git a/src/frontend/qt_sdl/main.h b/src/frontend/qt_sdl/main.h
index 72ebfb1..ee2f720 100644
--- a/src/frontend/qt_sdl/main.h
+++ b/src/frontend/qt_sdl/main.h
@@ -34,12 +34,18 @@
#include <QCloseEvent>
#include <atomic>
-
+#include <variant>
#include <optional>
#include "FrontendUtil.h"
#include "duckstation/gl/context.h"
+#include "NDSCart.h"
+#include "GBACart.h"
+
+using Keep = std::monostate;
+using UpdateConsoleNDSArgs = std::variant<Keep, std::unique_ptr<melonDS::NDSCart::CartCommon>>;
+using UpdateConsoleGBAArgs = std::variant<Keep, std::unique_ptr<melonDS::GBACart::CartCommon>>;
namespace melonDS
{
class NDS;
@@ -72,7 +78,13 @@ public:
QMutex FrontBufferLock;
void updateScreenSettings(bool filter, const WindowInfo& windowInfo, int numScreens, int* screenKind, float* screenMatrix);
- void RecreateConsole();
+
+ /// Applies the config in args.
+ /// Creates a new NDS console if needed,
+ /// modifies the existing one if possible.
+ /// @return \c true if the console was updated.
+ /// If this returns \c false, then the existing NDS console is not modified.
+ bool UpdateConsole(UpdateConsoleNDSArgs&& ndsargs, UpdateConsoleGBAArgs&& gbaargs) noexcept;
std::unique_ptr<melonDS::NDS> NDS; // TODO: Proper encapsulation and synchronization
signals:
void windowUpdate();
@@ -96,7 +108,10 @@ signals:
void syncVolumeLevel();
private:
- std::unique_ptr<melonDS::NDS> CreateConsole();
+ std::unique_ptr<melonDS::NDS> CreateConsole(
+ std::unique_ptr<melonDS::NDSCart::CartCommon>&& ndscart,
+ std::unique_ptr<melonDS::GBACart::CartCommon>&& gbacart
+ ) noexcept;
void drawScreenGL();
void initOpenGL();
void deinitOpenGL();