diff --git a/src/Args.h b/src/Args.h new file mode 100644 index 00000000..bfa1b13a --- /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 +#include +#include + +#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 +constexpr std::array BrokenBIOS = []() constexpr { + std::array 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 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 GBAROM = nullptr; + + /// NDS ARM9 BIOS to install. + /// Defaults to FreeBIOS, which is not compatible with DSi mode. + std::array ARM9BIOS = bios_arm9_bin; + + /// NDS ARM7 BIOS to install. + /// Defaults to FreeBIOS, which is not compatible with DSi mode. + std::array 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 ARM9iBIOS = BrokenBIOS; + std::array ARM7iBIOS = BrokenBIOS; + + /// 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 DSiSDCard; +}; +} +#endif //MELONDS_ARGS_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fe93ae5..ddb1b3c4 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 f2781e48..5dcd4193 100644 --- a/src/DSi.cpp +++ b/src/DSi.cpp @@ -19,6 +19,7 @@ #include #include #include +#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&& 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 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(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 a221b5d7..acd85c14 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 NANDImage; + std::array ARM9iBIOS; + std::array 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&& cart) override; + std::unique_ptr 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&& 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 8e04586a..47a613eb 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 59f582fd..b6b83ab6 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 9e006e2c..a6177dec 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 4cbf595d..ff88defb 100644 --- a/src/DSi_SD.cpp +++ b/src/DSi_SD.cpp @@ -18,6 +18,7 @@ #include #include +#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&& sdcard) noexcept : DSi(dsi), Num(0) { - Num = num; - - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_TX, MemberEventFunc(DSi_SDHost, FinishTX)); - DSi.RegisterEventFunc(Num ? Event_DSi_SDIOTransfer : Event_DSi_SDMMCTransfer, + DSi.RegisterEventFunc( Event_DSi_SDMMCTransfer, Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); - Ports[0] = nullptr; + Ports[0] = sdcard ? std::make_unique(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, this, std::move(nand)); +} + +// 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(Event_DSi_SDIOTransfer, + Transfer_RX, MemberEventFunc(DSi_SDHost, FinishRX)); + + Ports[0] = std::make_unique(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) +FATStorage* DSi_SDHost::GetSDCard() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +const FATStorage* DSi_SDHost::GetSDCard() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[0].get())->GetSDCard(); +} + +DSi_NAND::NANDImage* DSi_SDHost::GetNAND() noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +const DSi_NAND::NANDImage* DSi_SDHost::GetNAND() const noexcept +{ + if (Num != 0) return nullptr; + return static_cast(Ports[1].get())->GetNAND(); +} + +void DSi_SDHost::SetSDCard(FATStorage&& sdcard) noexcept +{ + if (Num != 0) return; + + static_cast(Ports[0].get())->SetSDCard(std::move(sdcard)); +} + +void DSi_SDHost::SetSDCard(std::optional&& sdcard) noexcept +{ + if (Num != 0) return; + + if (sdcard) { - DSi_MMCStorage* sd; - DSi_MMCStorage* mmc; - - if (Platform::GetConfigBool(Platform::DSiSD_Enable)) + 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, 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(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(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(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(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(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(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(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(&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(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(&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(&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(&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(&Storage)) { - SD->WriteSectors((u32)(addr >> 9), 1, data); + sd->WriteSectors((u32)(addr >> 9), 1, data); } - else if (NAND) + else if (auto* nand = get_if(&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 17ba8d30..05f8c9dd 100644 --- a/src/DSi_SD.h +++ b/src/DSi_SD.h @@ -20,28 +20,30 @@ #define DSI_SD_H #include +#include #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; 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&& 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&& 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, 2> Ports {}; u32 CurFIFO; // FIFO accessible for read/write FIFO 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(); + 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; - void Reset(); + [[nodiscard]] FATStorage* GetSDCard() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const FATStorage* GetSDCard() const noexcept { return std::get_if(&Storage); } + [[nodiscard]] DSi_NAND::NANDImage* GetNAND() noexcept { return std::get_if(&Storage); } + [[nodiscard]] const DSi_NAND::NANDImage* GetNAND() const noexcept { return std::get_if(&Storage); } - void DoSavestate(Savestate* file); + void SetNAND(DSi_NAND::NANDImage&& nand) noexcept { Storage = std::move(nand); } + void SetSDCard(FATStorage&& sdcard) noexcept { Storage = std::move(sdcard); } + void SetSDCard(std::optional&& 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 cd0a03c8..8799cb4b 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& sourcedir) : + FilePath(filename), + FileSize(size), + ReadOnly(readonly), + SourceDir(sourcedir) { - ReadOnly = readonly; Load(filename, size, sourcedir); + File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); +} + +FATStorage::FATStorage(const FATStorageArgs& args) noexcept : + FATStorage(args.Filename, args.Size, args.ReadOnly, args.SourceDir) +{ +} + +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; } +FATStorage::FATStorage(FATStorage&& other) noexcept +{ + 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) + { + 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 *this; +} + FATStorage::~FATStorage() { if (!ReadOnly) Save(); -} - -bool FATStorage::Open() -{ - File = Platform::OpenLocalFile(FilePath, FileMode::ReadWriteExisting); - if (!File) - { - return false; - } - - return true; -} - -void FATStorage::Close() -{ 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& 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 2bdafad8..6e348ce6 100644 --- a/src/FATStorage.h +++ b/src/FATStorage.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #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 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& 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 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& sourcedir); bool Save(); typedef struct diff --git a/src/FreeBIOS.cpp b/src/FreeBIOS.cpp index 7eef18b7..4f36e200 100644 --- a/src/FreeBIOS.cpp +++ b/src/FreeBIOS.cpp @@ -28,7 +28,7 @@ namespace melonDS { -unsigned char bios_arm7_bin[] = { +std::array 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 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 ce90725d..a60c1f88 100644 --- a/src/FreeBIOS.h +++ b/src/FreeBIOS.h @@ -28,10 +28,13 @@ #ifndef FREEBIOS_H #define FREEBIOS_H +#include +#include "MemConstants.h" + namespace melonDS { -extern unsigned char bios_arm7_bin[16384]; -extern unsigned char bios_arm9_bin[4096]; +extern std::array bios_arm7_bin; +extern std::array bios_arm9_bin; } #endif // FREEBIOS_H diff --git a/src/GBACart.cpp b/src/GBACart.cpp index f5e320f9..6cd6e39d 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 -#include -#include -#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 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 cart; - if (solarsensor) - cart = std::make_unique(cartrom, cartromsize); - else - cart = std::make_unique(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&& 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 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(); - 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 +#include +#include +#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&& rom, u32 len, std::unique_ptr&& 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(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&& rom, u32 len, std::unique_ptr&& 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&& 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 ParseROM(std::unique_ptr&& romdata, u32 romlen) +{ + return ParseROM(std::move(romdata), romlen, nullptr, 0); +} + +std::unique_ptr 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 ParseROM(const u8* romdata, u32 romlen) +{ + return ParseROM(romdata, romlen, nullptr, 0); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& 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 cartsram; + try + { + cartsram = sramdata ? std::make_unique(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 cart; + if (solarsensor) + cart = std::make_unique(std::move(cartrom), cartromsize, std::move(cartsram), sramlen); + else + cart = std::make_unique(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&& 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(); + break; + + default: + Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type); + return; + } +} + +std::unique_ptr 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 a5570897..493bf6b8 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 -#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&& 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 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 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 +#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&& rom, u32 len, std::unique_ptr&& 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 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 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&& rom, u32 len, std::unique_ptr&& 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&& 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&& 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 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 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 ParseROM(const u8* romdata, u32 romlen); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen); +std::unique_ptr 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 ParseROM(std::unique_ptr&& romdata, u32 romlen, std::unique_ptr&& sramdata, u32 sramlen); + +} + +#endif // GBACART_H diff --git a/src/NDS.cpp b/src/NDS.cpp index 3aa4a52a..f3e5a1af 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&& 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); + NDSCartSlot.SetSaveMemory(savedata, savelen); } -void NDS::EjectCart() +void NDS::SetGBASave(const u8* savedata, u32 savelen) { - NDSCartSlot.EjectCart(); -} + if (ConsoleType == 0 && savedata && savelen) + { + GBACartSlot.SetSaveMemory(savedata, savelen); + } -bool NDS::CartInserted() -{ - return NDSCartSlot.GetCart() != nullptr; -} - -bool NDS::LoadGBACart(const u8* romdata, u32 romlen, const u8* savedata, u32 savelen) -{ - if (!GBACartSlot.LoadROM(romdata, romlen)) - return false; - - if (savedata && savelen) - GBACartSlot.LoadSave(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 0979b2fa..b9f82916 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -21,7 +21,7 @@ #include #include -#include +#include #include #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 ARM9BIOS; + std::array 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&& cart); + [[nodiscard]] bool CartInserted() const noexcept { return NDSCartSlot.GetCart() != nullptr; } + virtual std::unique_ptr 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&& 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 EjectGBACart() { return GBACartSlot.EjectCart(); } u32 RunFrame(); @@ -456,7 +484,8 @@ private: template 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 95306fc5..848c6197 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&& 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(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(ROM + header.BannerOffset); + return reinterpret_cast(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&& 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&& rom, u32 len, u32 chipid, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& 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(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(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); + Platform::WriteNDSSave(SRAM.get(), 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 - } -} - -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&& sram, u32 sramlen) : + CartRetailNAND(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) { } -CartRetailNAND::~CartRetailNAND() +CartRetailNAND::CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& 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&& 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&& rom, + u32 len, + u32 chipid, + u32 irversion, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& 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(const u8* rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetailBT(CopyToUnique(rom, len), len, chipid, romparams, std::move(sram), sramlen) +{ +} -CartRetailBT::CartRetailBT(u8* rom, u32 len, u32 chipid, ROMListEntry romparams) : CartRetail(rom, len, chipid, false, romparams) +CartRetailBT::CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& sram, u32 sramlen) : + CartRetail(std::move(rom), len, chipid, false, romparams, std::move(sram), sramlen, CartType::RetailBT) { Log(LogLevel::Info,"POKETYPE CART\n"); } -CartRetailBT::~CartRetailBT() -{ -} - -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&& sdcard) : + CartHomebrew(CopyToUnique(rom, len), len, chipid, romparams, std::move(sdcard)) { - SD = nullptr; } -CartHomebrew::~CartHomebrew() +CartHomebrew::CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& 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&& 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 ParseROM(const u8* romdata, u32 romlen) +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args) +{ + return ParseROM(CopyToUnique(romdata, romlen), romlen, std::move(args)); +} + +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args) { if (romdata == nullptr) { @@ -1607,28 +1573,10 @@ std::unique_ptr 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 ParseROM(const u8* romdata, u32 romlen) } std::unique_ptr cart; + auto [sram, sramlen] = args ? std::move(*args->SRAM) : std::make_pair(nullptr, 0); if (homebrew) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, args ? std::move(args->SDCard) : std::nullopt); else if (cartid & 0x08000000) - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else if (irversion != 0) - cart = std::make_unique(cartrom, cartromsize, cartid, irversion, badDSiDump, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, irversion, badDSiDump, romparams, std::move(sram), sramlen); else if ((gamecode & 0xFFFFFF) == 0x505A55) // UZPx - cart = std::make_unique(cartrom, cartromsize, cartid, romparams); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, romparams, std::move(sram), sramlen); else - cart = std::make_unique(cartrom, cartromsize, cartid, badDSiDump, romparams); - - if (romparams.SaveMemType > 0) - cart->SetupSave(romparams.SaveMemType); + cart = std::make_unique(std::move(cartrom), cartromsize, cartid, badDSiDump, romparams, std::move(sram), sramlen); + args = std::nullopt; return cart; } -bool NDSCartSlot::InsertROM(std::unique_ptr&& cart) noexcept +void NDSCartSlot::SetCart(std::unique_ptr&& 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&& 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&& 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&& 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 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 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 24f9f9ee..43bf1fc9 100644 --- a/src/NDSCart.h +++ b/src/NDSCart.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #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 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, 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&& 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 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&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + CartRetail( + std::unique_ptr&& rom, + u32 len, u32 chipid, + bool badDSiDump, + ROMListEntry romparams, + std::unique_ptr&& sram, + u32 sramlen, + melonDS::NDSCart::CartType type = CartType::Retail + ); + ~CartRetail() override; - virtual u32 Type() const override { return CartType::Retail; } + void Reset() override; - virtual void Reset() override; + void DoSavestate(Savestate* file) override; - virtual void DoSavestate(Savestate* file) override; + void SetSaveMemory(const u8* savedata, u32 savelen) override; - virtual void SetupSave(u32 type) override; - virtual void LoadSave(const u8* savedata, u32 savelen) override; + int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; - virtual int ROMCommandStart(NDS& nds, NDSCart::NDSCartSlot& cartslot, u8* cmd, u8* data, u32 len) override; + u8 SPIWrite(u8 val, u32 pos, bool last) override; - virtual 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 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&& sram, u32 sramlen); + CartRetailNAND(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& 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&& sram, u32 sramlen); + CartRetailIR(std::unique_ptr&& rom, u32 len, u32 chipid, u32 irversion, bool badDSiDump, ROMListEntry romparams, std::unique_ptr&& 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&& sram, u32 sramlen); + CartRetailBT(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::unique_ptr&& 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&& sdcard = std::nullopt); + CartHomebrew(std::unique_ptr&& rom, u32 len, u32 chipid, ROMListEntry romparams, std::optional&& 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& GetSDCard() const noexcept { return SD; } + void SetSDCard(FATStorage&& sdcard) noexcept { SD = std::move(sdcard); } + void SetSDCard(std::optional&& 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 SD {}; }; class NDSCartSlot { public: - NDSCartSlot(melonDS::NDS& nds) noexcept; + explicit NDSCartSlot(melonDS::NDS& nds, std::unique_ptr&& 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&& cart) noexcept; + void SetCart(std::unique_ptr&& cart) noexcept; + [[nodiscard]] CartCommon* GetCart() noexcept { return Cart.get(); } + [[nodiscard]] const CartCommon* GetCart() const noexcept { return Cart.get(); } - /// 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 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 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 ROMCommand {}; - u8 SPIData; - u32 SPIDataPos; - bool SPIHold; + u8 SPIData = 0; + u32 SPIDataPos = 0; + bool SPIHold = false; - u32 ROMData; + u32 ROMData = 0; - std::array TransferData; - u32 TransferPos; - u32 TransferLen; - u32 TransferDir; - std::array TransferCmd; + std::array TransferData {}; + u32 TransferPos = 0; + u32 TransferLen = 0; + u32 TransferDir = 0; + std::array TransferCmd {}; - std::unique_ptr Cart; + std::unique_ptr Cart = nullptr; - std::array Key1_KeyBuf; + std::array 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 ParseROM(const u8* romdata, u32 romlen); +std::unique_ptr ParseROM(const u8* romdata, u32 romlen, std::optional&& args = std::nullopt); +std::unique_ptr ParseROM(std::unique_ptr&& romdata, u32 romlen, std::optional&& args = std::nullopt); } #endif diff --git a/src/Platform.h b/src/Platform.h index f8b0453c..2c9a6a4a 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 b3e5b4e1..3974e316 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(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(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&& 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 f27f0c3e..aee41658 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&& firmware); - void RemoveFirmware(); void Write(u8 val) override; void Release() override; private: - std::unique_ptr 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 00000000..698cf9bd --- /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 + +namespace melonDS +{ +std::pair, u32> PadToPowerOf2(std::unique_ptr&& 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(newlen); + memcpy(newdata.get(), data.get(), len); + data = nullptr; + return {std::move(newdata), newlen}; +} + +std::pair, 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(newlen); + memcpy(newdata.get(), data, len); + return {std::move(newdata), newlen}; +} + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept +{ + if (data == nullptr || len == 0) + return nullptr; + + auto newdata = std::make_unique(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 00000000..63be217b --- /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 +#include "types.h" +#include + +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 {nullptr, 0}. +/// 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, u32> PadToPowerOf2(std::unique_ptr&& data, u32 len) noexcept; + +std::pair, u32> PadToPowerOf2(const u8* data, u32 len) noexcept; + +std::unique_ptr CopyToUnique(const u8* data, u32 len) noexcept; + +} + +#endif // MELONDS_UTILS_H diff --git a/src/Wifi.cpp b/src/Wifi.cpp index 50275236..9dc696b6 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 7d1eca9c..aa508e8d 100644 --- a/src/frontend/qt_sdl/ArchiveUtil.cpp +++ b/src/frontend/qt_sdl/ArchiveUtil.cpp @@ -120,13 +120,12 @@ QVector 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& 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(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 eaffb0dd..246670e7 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 ListArchive(QString path); -s32 ExtractFileFromArchive(QString path, QString wantedFile, u8** filedata, u32* filesize); +s32 ExtractFileFromArchive(QString path, QString wantedFile, std::unique_ptr& filedata, u32* filesize); //QVector 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 c2e2f47b..d410d4fb 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 2c0cee2c..fda043a4 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 NDSSave = nullptr; +std::unique_ptr GBASave = nullptr; std::unique_ptr FirmwareSave = nullptr; std::unique_ptr 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(GetEffectiveFirmwareSavePath(thread)); +} std::string GetSavestateName(int slot) { @@ -482,6 +504,11 @@ void LoadCheats(NDS& nds) std::optional> 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 bios {}; @@ -498,6 +525,11 @@ std::optional> LoadARM9BIOS() noexcept std::optional> 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 bios {}; @@ -518,6 +550,16 @@ std::optional> LoadDSiARM9BIOS() noexcept std::array 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> LoadDSiARM7BIOS() noexcept std::array 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 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 LoadFirmware(int type) noexcept return std::nullopt; } - CustomizeFirmware(firmware); + if (Config::FirmwareOverrideSettings) + { + CustomizeFirmware(firmware); + } return firmware; } @@ -694,7 +759,20 @@ std::optional LoadNAND(const std::array& a return nandImage; } -constexpr int imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +constexpr u64 imgsizes[] = {0, 256, 512, 1024, 2048, 4096}; +std::optional 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 LoadDSiSDCard() noexcept { if (!Config::DSiSDEnable) @@ -704,97 +782,34 @@ std::optional 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 GetDLDISDCardArgs() noexcept { - if (Config::ExternalBIOSEnable) - { - if (FileHandle* f = Platform::OpenLocalFile(Config::BIOS9Path, FileMode::Read)) - { - FileRewind(f); - FileRead(nds.ARM9BIOS, sizeof(NDS::ARM9BIOS), 1, f); + if (!Config::DLDIEnable) + return std::nullopt; - 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"); + return FATStorageArgs{ + Config::DLDISDPath, + imgsizes[Config::DLDISize], + Config::DLDIReadOnly, + Config::DLDIFolderSync ? std::make_optional(Config::DLDIFolderPath) : std::nullopt + }; +} - for (int i = 0; i < 16; i++) - ((u32*)nds.ARM9BIOS)[i] = 0xE7FFDEFF; - } +std::optional LoadDLDISDCard() noexcept +{ + if (!Config::DLDIEnable) + return std::nullopt; - 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(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"); - - for (int i = 0; i < 16; i++) - ((u32*)dsi.ARM7iBIOS)[i] = 0xE7FFDEFF; - } - - if (!Config::DSiFullBIOSBoot) - { - // herp - *(u32*)&dsi.ARM9iBIOS[0] = 0xEAFFFFFE; - *(u32*)&dsi.ARM7iBIOS[0] = 0xEAFFFFFE; - - // 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(*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(*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& 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(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(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, string> LoadFirmwareFromFile() -{ - string loadedpath; - unique_ptr 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(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, string> GenerateDefaultFirmware() { // Construct the default firmware... @@ -1068,7 +1030,7 @@ pair, 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, 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(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(std::move(nandImage)); - return true; -} - -bool InstallFirmware(NDS& nds) -{ - FirmwareSave.reset(); - unique_ptr 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(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& 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(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 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(*emuthread->NDS)); - - emuthread->NDS->Reset(); - SetBatteryLevels(*emuthread->NDS); - SetDateTime(*emuthread->NDS); - } - u32 savelen = 0; - u8* savedata = nullptr; + std::unique_ptr 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(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(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 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 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(savelen); + FileRead(savedata.get(), savelen, 1, sav); + } CloseFile(sav); } - bool res = nds.LoadGBACart(filedata, filelen, savedata, savelen); + auto cart = GBACart::ParseROM(std::move(filedata), filelen, std::move(savedata), savelen); + if (!cart) + return false; - if (res) - { - GBACartType = 0; - GBASave = new SaveManager(savname); - } - - if (savedata) delete[] savedata; - delete[] filedata; - return res; + nds.SetGBACart(std::move(cart)); + GBACartType = 0; + GBASave = std::make_unique(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 2163a680..0b640c84 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 NDSSave; +extern std::unique_ptr GBASave; extern std::unique_ptr 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> LoadARM9BIOS() noexcept; std::optional> LoadARM7BIOS() noexcept; std::optional> LoadDSiARM9BIOS() noexcept; std::optional> LoadDSiARM7BIOS() noexcept; +std::optional GetDSiSDCardArgs() noexcept; std::optional LoadDSiSDCard() noexcept; +std::optional GetDLDISDCardArgs() noexcept; +std::optional 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 LoadFirmware(int type) noexcept; /// Loads and customizes a NAND image based on the values in Config std::optional LoadNAND(const std::array& 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 a0ac0860..5bd4d1b1 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(mainWindow->panel)->transferLayout(this); } -std::unique_ptr EmuThread::CreateConsole() +std::unique_ptr EmuThread::CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& 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(); + 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(std::move(args)); } - return std::make_unique(); + return std::make_unique(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 nextndscart; + if (std::holds_alternative(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>(&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(*nextndscart); + homebrew.SetSDCard(ROMManager::LoadDLDISDCard()); + } + + std::unique_ptr nextgbacart; + if (std::holds_alternative(gbaargs)) + { + nextgbacart = NDS ? NDS->EjectGBACart() : nullptr; + } + else if (const auto ptr = std::get_if>(&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(*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 data = std::make_unique(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 72ebfb19..ee2f7201 100644 --- a/src/frontend/qt_sdl/main.h +++ b/src/frontend/qt_sdl/main.h @@ -34,12 +34,18 @@ #include #include - +#include #include #include "FrontendUtil.h" #include "duckstation/gl/context.h" +#include "NDSCart.h" +#include "GBACart.h" + +using Keep = std::monostate; +using UpdateConsoleNDSArgs = std::variant>; +using UpdateConsoleGBAArgs = std::variant>; 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 NDS; // TODO: Proper encapsulation and synchronization signals: void windowUpdate(); @@ -96,7 +108,10 @@ signals: void syncVolumeLevel(); private: - std::unique_ptr CreateConsole(); + std::unique_ptr CreateConsole( + std::unique_ptr&& ndscart, + std::unique_ptr&& gbacart + ) noexcept; void drawScreenGL(); void initOpenGL(); void deinitOpenGL();