aboutsummaryrefslogtreecommitdiff
path: root/src/GBACart.cpp
diff options
context:
space:
mode:
authorJesse Talavera <jesse@jesse.tg>2023-12-04 11:57:22 -0500
committerGitHub <noreply@github.com>2023-12-04 17:57:22 +0100
commitbb42c8b6392e4e99634dc52137d2974781192482 (patch)
tree6b2de3d553aa331567e7ab0efa89ed841732f943 /src/GBACart.cpp
parentda8d413ad9e339c40178be18da20aee1435c8cd4 (diff)
Refactor how save data (including SD cards) is initialized (#1898)
* Remove `FATStorage::Open` and `FATStorage::Close` - That's what the constructor and destructor are for, respectively * Add `FATStorage::IsReadOnly` * Slight cleanup of `FATStorage` - Make it move-constructible and move-assignable - Represent the absence of a sync directory with `std::optional`, not an empty string - Add `FATStorageArgs` for later use * Refactor `CartHomebrew` to accept an optional `FATStorageArgs` - `CartHomebrew` uses it to load an SD card image - Not passing a `FATStorage` directly because we won't know if we need to load the card until we parse the ROM - Store the `FATStorage` inside a `std::optional` instead of a pointer - `CartHomebrew::Reset` no longer reloads the SD card; the frontend needs to set it with the `SetSDCard` method * Close `NANDImage::CurFile` when move-assigning - Whoops * Add `Args.h` - To construct a `NDS` or `DSi` with arguments - Mostly intended for system files * Fix incorrect `final` placement * Refactor how `DSi`'s NAND and SD card are set - Provide them via a `DSiArgs` argument in the constructor - Give `DSi_MMCStorage` ownership of the `NANDImage` or `FATStorage` as needed, and expose getters/setters - Replace `DSi_SDHost::Ports` with a `array<unique_ptr, 2>` to reduce the risk of leaks - Store `DSi_MMCStorage`'s disk images in a `std::variant` - The SD card and NAND image are no longer reset in `Reset()`; the frontend will need to do that itself * Add getters/setters on `DSi` itself for its storage media * Remove newly-unused `Platform::ConfigEntry`s * Use `DSi::SetNAND` in the frontend * Add `EmuThread::NeedToRecreateConsole` * Document `NDSArgs` and give its fields default values * Refactor how system files are loaded upon construction - Pass `NDSArgs&&` into `NDS`'s constructor - Use `std::array` for the emulator's BIOS images and the built-in FreeBIOS, to simplify copying and comparison - Initialize the BIOS, firmware, and SD cards from `NDSArgs` or `DSiArgs` - Add a new default constructor for `NDS` (not `DSi`) that initializes the DS with default system files - Embed `FirmwareMem::Firmware` directly instead of in a `unique_ptr` - `SPIHost` now takes a `Firmware&&` that it forwards to `FirmwareMem` - Add `Firmware` getters/setters plus `const` variants for `NDS`, `Firmware`, and `FirmwareMem` - Simplify installation of firmware * Initialize the DSi BIOS in the constructor - Change `DSi::ARM9iBIOS` and `ARM7iBIOS` to `std::array` * Update the frontend to reflect the core's changes * Remove `DSi_SDHost::CloseHandles` * Pass `nullopt` instead of the empty string when folder sync is off * Deduplicate ROM extraction logic - `LoadGBAROM` and `LoadROM` now delegate to `LoadROMData` - Also use `unique_ptr` instead of `new[]` * Oops, missed some `get()`'s * Move `NDS::IsLoadedARM9BIOSBuiltIn` to the header - So it's likelier to be inlined - Same for the ARM7 version * Remove `NDS::SetConsoleType` * Add `NDS::SetFirmware` * Move `GBACart::SetupSave` to be `protected` - It was only ever used inside the class * Rename `GBACart::LoadSave` to `SetSaveMemory` - Same for the cart slot * Declare `GBACartSlot` as a friend of `GBACart::CartCommon` * Revise `GBACartSlot`'s getters and setters - Rename `InsertROM` and `LoadROM` to `SetCart` - Add a `GetCart` method * Clean up getters and setters for NDS and GBA carts * Clean up how carts are inserted into the slots - Remove setters that operate directly on pointers, to simplify error-handling (use ParseROM instead) - Add overloads for all carts that accept a `const u8*` (to copy the ROM data) and a `unique_ptr<u8[]>` (to move the ROM data) - Store all ROM and RAM data in `unique_ptr` - Default-initialize all fields - Simplify constructors and destructors, inheriting where applicable * Refactor GBA save data insertion - Make `SetupSave` private and non-virtual and move its logic to be in `SetSaveMemory` - Add overloads for setting save data in the constructor - Update the SRAM completely in `SetSaveMemory` * Clean up `NDSCart::CartCommon::SetSaveMemory` - Move its declaration next to the other `SaveMemory` methods - Move its (empty) implementation to the header * Add some comments * Add Utils.cpp and Utils.h * Rename some functions in Utils for clarity * Add `GBACart::ParseROM` and `NDSCart::ParseROM` overloads that accept `unique_ptr<u8[]>` - The `u8*` overloads delegate to these new overloads - Also move `SetupSave` for both kinds of carts to be private non-virtual methods * Finalize the `NDSCart` refactor - Add `NDSCartArgs` to pass to `ParseROM` - Add SRAM arguments for all retail carts - Initialize SRAM inside the constructor - Delegate to other constructors where possible * Replace `ROMManager::NDSSave` and `GBASave` with `unique_ptr` * Make both cart slots return the previously-inserted cart in `EjectCart` - Primarily intended for reusing carts when resetting the console * Make `NDS::EjectCart` return the old cart * Initialize both cart slots with the provided ROM (if any) * Make `NDS::EjectGBACart` return the ejected cart * Clean up some comments in Args.h * Rename `ROMManager::LoadBIOS` to `BootToMenu` - Clarifies the intent * Add `ROMManager::LoadDLDISDCard` * Add a doc comment * Refactor how the `NDS` is created or updated - Rewrite `CreateConsole` to read from `Config` and load system files, but accept carts as arguments - Fail without creating an `NDS` if any required system file doesn't load - Add `UpdateConsole`, which delegates to `CreateConsole` if switching modes or starting the app - Use `std::variant` to indicate whether a cart should be removed, inserted, or reused - Load all system files (plus SD cards) in `UpdateConsole` - Eject the cart and reinsert it into the new console if applicable * Respect some more `Config` settings in the `Load*` functions * Remove `InstallNAND` in favor of `LoadNAND` * Fix some potential bugs in `LoadROMData` * Oops, forgot to delete the definition of `InstallNAND` * Add functions to get `FATStorageArgs` - Not the cards themselves, but to get the arguments you _would_ use to load the cards * Refactor `ROMManager::LoadROM` - Load the ROM and save data before trying to initialize the console * Clean up `ROMManager::Reset` and `BootToMenu` - Let `EmuThread::UpdateConsole` do the heavy lifting * Clean up `LoadGBAROM` * Remove some unused functions * Set the default DSi BIOS to be broken in `DSiArgs` * Respect `Config::DSiFullBIOSBoot` when loading DSi BIOS files * Remove some more unused functions * Remove redundant `virtual` specifiers * Refactor `NDSCart::CartCommon::Type()` to return a member instead of a constant - One less virtual dispatch - The cart type is read in `NDSCartSlot::DoSavestate`, which is a path downstream (due to rewinding) * Remove a hash that I computed for debugging purposes * Make `ByteSwap` `constexpr` * Remove an unused `#include` * Remove unnecessary functions from the NDS carts - Mostly overrides that added nothing * Default-initialize all NDSCart fields * Make `GBACart::Type()` not rely on virtual dispatch - `GBACartSlot::DoSavestate` calls it, and savestates can be a hot path downstream * Don't forget to reset the base class in `CartGameSolarSensor::Reset()` * Remove redundant `virtual` specifiers * Default-initialize some fields in `GBACart` * Fix ROMs not loading from archives in the frontend - Whoops * Change how the `Firmware` member is declared * Forgot an include in Utils.cpp * Rename `FirmwareMem::Firmware` to `FirmwareData` to fix a build error on Linux - One of these days I'll convince you people to let me use `camelCaseMemberNames` * Add `override` to places in `DSi_MMCStorage` that warrant it * Fix firmware saving on the frontend - Remove `GetConfigString` and `ConfigEntry::WifiSettingsPath` while I'm at it * Add a non-const `GetNAND()`
Diffstat (limited to 'src/GBACart.cpp')
-rw-r--r--src/GBACart.cpp1796
1 files changed, 894 insertions, 902 deletions
diff --git a/src/GBACart.cpp b/src/GBACart.cpp
index f5e320f..6cd6e39 100644
--- a/src/GBACart.cpp
+++ b/src/GBACart.cpp
@@ -1,903 +1,895 @@
-/*
- Copyright 2016-2023 melonDS team
-
- This file is part of melonDS.
-
- melonDS is free software: you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free
- Software Foundation, either version 3 of the License, or (at your option)
- any later version.
-
- melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with melonDS. If not, see http://www.gnu.org/licenses/.
-*/
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-#include "NDS.h"
-#include "GBACart.h"
-#include "CRC32.h"
-#include "Platform.h"
-
-namespace melonDS
-{
-using Platform::Log;
-using Platform::LogLevel;
-
-namespace GBACart
-{
-
-const char SOLAR_SENSOR_GAMECODES[10][5] =
-{
- "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan)
- "U3IE", // Boktai - The Sun Is in Your Hand (USA)
- "U3IP", // Boktai - The Sun Is in Your Hand (Europe)
- "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan)
- "U32E", // Boktai 2 - Solar Boy Django (USA)
- "U32P", // Boktai 2 - Solar Boy Django (Europe)
- "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)
- "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample)
-};
-
-CartCommon::CartCommon()
-{
-}
-
-CartCommon::~CartCommon()
-{
-}
-
-void CartCommon::Reset()
-{
-}
-
-void CartCommon::DoSavestate(Savestate* file)
-{
- file->Section("GBCS");
-}
-
-void CartCommon::SetupSave(u32 type)
-{
-}
-
-void CartCommon::LoadSave(const u8* savedata, u32 savelen)
-{
-}
-
-int CartCommon::SetInput(int num, bool pressed)
-{
- return -1;
-}
-
-u16 CartCommon::ROMRead(u32 addr) const
-{
- return 0;
-}
-
-void CartCommon::ROMWrite(u32 addr, u16 val)
-{
-}
-
-u8 CartCommon::SRAMRead(u32 addr)
-{
- return 0;
-}
-
-void CartCommon::SRAMWrite(u32 addr, u8 val)
-{
-}
-
-u8* CartCommon::GetSaveMemory() const
-{
- return nullptr;
-}
-
-u32 CartCommon::GetSaveMemoryLength() const
-{
- return 0;
-}
-
-CartGame::CartGame(u8* rom, u32 len) : CartCommon()
-{
- ROM = rom;
- ROMLength = len;
-
- SRAM = nullptr;
- SRAMLength = 0;
- SRAMType = S_NULL;
- SRAMFlashState = {};
-}
-
-CartGame::~CartGame()
-{
- if (SRAM) delete[] SRAM;
- delete[] ROM;
-}
-
-u32 CartGame::Checksum() const
-{
- u32 crc = CRC32(ROM, 0xC0, 0);
-
- // TODO: hash more contents?
-
- return crc;
-}
-
-void CartGame::Reset()
-{
- memset(&GPIO, 0, sizeof(GPIO));
-}
-
-void CartGame::DoSavestate(Savestate* file)
-{
- CartCommon::DoSavestate(file);
-
- file->Var16(&GPIO.control);
- file->Var16(&GPIO.data);
- file->Var16(&GPIO.direction);
-
- u32 oldlen = SRAMLength;
-
- file->Var32(&SRAMLength);
-
- if (SRAMLength != oldlen)
- {
- // reallocate save memory
- if (oldlen) delete[] SRAM;
- SRAM = nullptr;
- if (SRAMLength) SRAM = new u8[SRAMLength];
- }
- if (SRAMLength)
- {
- // fill save memory if data is present
- file->VarArray(SRAM, SRAMLength);
- }
- else
- {
- // no save data, clear the current state
- SRAMType = SaveType::S_NULL;
- SRAM = nullptr;
- return;
- }
-
- // persist some extra state info
- file->Var8(&SRAMFlashState.bank);
- file->Var8(&SRAMFlashState.cmd);
- file->Var8(&SRAMFlashState.device);
- file->Var8(&SRAMFlashState.manufacturer);
- file->Var8(&SRAMFlashState.state);
-
- file->Var8((u8*)&SRAMType);
-
- if ((!file->Saving) && SRAM)
- Platform::WriteGBASave(SRAM, SRAMLength, 0, SRAMLength);
-}
-
-void CartGame::SetupSave(u32 type)
-{
- if (SRAM) delete[] SRAM;
- SRAM = nullptr;
-
- // TODO: have type be determined from some list, like in NDSCart
- // and not this gross hack!!
- SRAMLength = type;
-
- if (SRAMLength)
- {
- SRAM = new u8[SRAMLength];
- memset(SRAM, 0xFF, SRAMLength);
- }
-
- switch (SRAMLength)
- {
- case 512:
- SRAMType = S_EEPROM4K;
- break;
- case 8192:
- SRAMType = S_EEPROM64K;
- break;
- case 32768:
- SRAMType = S_SRAM256K;
- break;
- case 65536:
- SRAMType = S_FLASH512K;
- break;
- case 128*1024:
- SRAMType = S_FLASH1M;
- break;
- case 0:
- SRAMType = S_NULL;
- break;
- default:
- Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength);
- }
-
- if (SRAMType == S_FLASH512K)
- {
- // Panasonic 64K chip
- SRAMFlashState.device = 0x1B;
- SRAMFlashState.manufacturer = 0x32;
- }
- else if (SRAMType == S_FLASH1M)
- {
- // Sanyo 128K chip
- SRAMFlashState.device = 0x13;
- SRAMFlashState.manufacturer = 0x62;
- }
-}
-
-void CartGame::LoadSave(const u8* savedata, u32 savelen)
-{
- if (!SRAM) return;
-
- u32 len = std::min(savelen, SRAMLength);
- memcpy(SRAM, savedata, len);
- Platform::WriteGBASave(savedata, len, 0, len);
-}
-
-u16 CartGame::ROMRead(u32 addr) const
-{
- addr &= 0x01FFFFFF;
-
- if (addr >= 0xC4 && addr < 0xCA)
- {
- if (GPIO.control & 0x1)
- {
- switch (addr)
- {
- case 0xC4: return GPIO.data;
- case 0xC6: return GPIO.direction;
- case 0xC8: return GPIO.control;
- }
- }
- else
- return 0;
- }
-
- // CHECKME: does ROM mirror?
- if (addr < ROMLength)
- return *(u16*)&ROM[addr];
-
- return 0;
-}
-
-void CartGame::ROMWrite(u32 addr, u16 val)
-{
- addr &= 0x01FFFFFF;
-
- switch (addr)
- {
- case 0xC4:
- GPIO.data &= ~GPIO.direction;
- GPIO.data |= val & GPIO.direction;
- ProcessGPIO();
- break;
-
- case 0xC6:
- GPIO.direction = val;
- break;
-
- case 0xC8:
- GPIO.control = val;
- break;
-
- default:
- Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr);
- break;
- }
-}
-
-u8 CartGame::SRAMRead(u32 addr)
-{
- addr &= 0xFFFF;
-
- switch (SRAMType)
- {
- case S_EEPROM4K:
- case S_EEPROM64K:
- return SRAMRead_EEPROM(addr);
-
- case S_FLASH512K:
- case S_FLASH1M:
- return SRAMRead_FLASH(addr);
-
- case S_SRAM256K:
- return SRAMRead_SRAM(addr);
- default:
- break;
- }
-
- return 0xFF;
-}
-
-void CartGame::SRAMWrite(u32 addr, u8 val)
-{
- addr &= 0xFFFF;
-
- switch (SRAMType)
- {
- case S_EEPROM4K:
- case S_EEPROM64K:
- return SRAMWrite_EEPROM(addr, val);
-
- case S_FLASH512K:
- case S_FLASH1M:
- return SRAMWrite_FLASH(addr, val);
-
- case S_SRAM256K:
- return SRAMWrite_SRAM(addr, val);
- default:
- break;
- }
-}
-
-u8* CartGame::GetSaveMemory() const
-{
- return SRAM;
-}
-
-u32 CartGame::GetSaveMemoryLength() const
-{
- return SRAMLength;
-}
-
-void CartGame::ProcessGPIO()
-{
-}
-
-u8 CartGame::SRAMRead_EEPROM(u32 addr)
-{
- return 0;
-}
-
-void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val)
-{
- // TODO: could be used in homebrew?
-}
-
-// mostly ported from DeSmuME
-u8 CartGame::SRAMRead_FLASH(u32 addr)
-{
- if (SRAMFlashState.cmd == 0) // no cmd
- {
- return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank];
- }
-
- switch (SRAMFlashState.cmd)
- {
- case 0x90: // chip ID
- if (addr == 0x0000) return SRAMFlashState.manufacturer;
- if (addr == 0x0001) return SRAMFlashState.device;
- break;
- case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?)
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- break;
- case 0xA0: // write command
- break; // ignore here, handled in Write_Flash()
- case 0xB0: // bank switching (128K only)
- break; // ignore here, handled in Write_Flash()
- default:
- Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr);
- break;
- }
-
- return 0xFF;
-}
-
-// mostly ported from DeSmuME
-void CartGame::SRAMWrite_FLASH(u32 addr, u8 val)
-{
- switch (SRAMFlashState.state)
- {
- case 0x00:
- if (addr == 0x5555)
- {
- if (val == 0xF0)
- {
- // reset
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- }
- else if (val == 0xAA)
- {
- SRAMFlashState.state = 1;
- return;
- }
- }
- if (addr == 0x0000)
- {
- if (SRAMFlashState.cmd == 0xB0)
- {
- // bank switching
- SRAMFlashState.bank = val;
- SRAMFlashState.cmd = 0;
- return;
- }
- }
- break;
- case 0x01:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 2;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x02:
- if (addr == 0x5555)
- {
- // send command
- switch (val)
- {
- case 0x80: // erase
- SRAMFlashState.state = 0x80;
- break;
- case 0x90: // chip ID
- SRAMFlashState.state = 0x90;
- break;
- case 0xA0: // write
- SRAMFlashState.state = 0;
- break;
- default:
- SRAMFlashState.state = 0;
- break;
- }
-
- SRAMFlashState.cmd = val;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- // erase
- case 0x80:
- if (addr == 0x5555 && val == 0xAA)
- {
- SRAMFlashState.state = 0x81;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x81:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 0x82;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x82:
- if (val == 0x30)
- {
- u32 start_addr = addr + 0x10000 * SRAMFlashState.bank;
- memset((u8*)&SRAM[start_addr], 0xFF, 0x1000);
-
- Platform::WriteGBASave(SRAM, SRAMLength, start_addr, 0x1000);
- }
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- // chip ID
- case 0x90:
- if (addr == 0x5555 && val == 0xAA)
- {
- SRAMFlashState.state = 0x91;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x91:
- if (addr == 0x2AAA && val == 0x55)
- {
- SRAMFlashState.state = 0x92;
- return;
- }
- SRAMFlashState.state = 0;
- break;
- case 0x92:
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- default:
- break;
- }
-
- if (SRAMFlashState.cmd == 0xA0) // write
- {
- SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val);
- SRAMFlashState.state = 0;
- SRAMFlashState.cmd = 0;
- return;
- }
-
- Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n",
- val, addr, SRAMFlashState.state);
-}
-
-u8 CartGame::SRAMRead_SRAM(u32 addr)
-{
- if (addr >= SRAMLength) return 0xFF;
-
- return SRAM[addr];
-}
-
-void CartGame::SRAMWrite_SRAM(u32 addr, u8 val)
-{
- if (addr >= SRAMLength) return;
-
- u8 prev = *(u8*)&SRAM[addr];
- if (prev != val)
- {
- *(u8*)&SRAM[addr] = val;
-
- // TODO: optimize this!!
- Platform::WriteGBASave(SRAM, SRAMLength, addr, 1);
- }
-}
-
-
-const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};
-
-CartGameSolarSensor::CartGameSolarSensor(u8* rom, u32 len) : CartGame(rom, len)
-{
-}
-
-CartGameSolarSensor::~CartGameSolarSensor()
-{
-}
-
-void CartGameSolarSensor::Reset()
-{
- LightEdge = false;
- LightCounter = 0;
- LightSample = 0xFF;
- LightLevel = 0;
-}
-
-void CartGameSolarSensor::DoSavestate(Savestate* file)
-{
- CartGame::DoSavestate(file);
-
- file->Var8((u8*)&LightEdge);
- file->Var8(&LightCounter);
- file->Var8(&LightSample);
- file->Var8(&LightLevel);
-}
-
-int CartGameSolarSensor::SetInput(int num, bool pressed)
-{
- if (!pressed) return -1;
-
- if (num == Input_SolarSensorDown)
- {
- if (LightLevel > 0)
- LightLevel--;
-
- return LightLevel;
- }
- else if (num == Input_SolarSensorUp)
- {
- if (LightLevel < 10)
- LightLevel++;
-
- return LightLevel;
- }
-
- return -1;
-}
-
-void CartGameSolarSensor::ProcessGPIO()
-{
- if (GPIO.data & 4) return; // Boktai chip select
- if (GPIO.data & 2) // Reset
- {
- u8 prev = LightSample;
- LightCounter = 0;
- LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel]));
- Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample);
- }
- if (GPIO.data & 1 && LightEdge) LightCounter++;
-
- LightEdge = !(GPIO.data & 1);
-
- bool sendBit = LightCounter >= LightSample;
- if (GPIO.control & 1)
- {
- GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF);
- }
-}
-
-
-CartRAMExpansion::CartRAMExpansion() : CartCommon()
-{
-}
-
-CartRAMExpansion::~CartRAMExpansion()
-{
-}
-
-void CartRAMExpansion::Reset()
-{
- memset(RAM, 0xFF, sizeof(RAM));
- RAMEnable = 1;
-}
-
-void CartRAMExpansion::DoSavestate(Savestate* file)
-{
- CartCommon::DoSavestate(file);
-
- file->VarArray(RAM, sizeof(RAM));
- file->Var16(&RAMEnable);
-}
-
-u16 CartRAMExpansion::ROMRead(u32 addr) const
-{
- addr &= 0x01FFFFFF;
-
- if (addr < 0x01000000)
- {
- switch (addr)
- {
- case 0xB0: return 0xFFFF;
- case 0xB2: return 0x0000;
- case 0xB4: return 0x2400;
- case 0xB6: return 0x2424;
- case 0xB8: return 0xFFFF;
- case 0xBA: return 0xFFFF;
- case 0xBC: return 0xFFFF;
- case 0xBE: return 0x7FFF;
-
- case 0x1FFFC: return 0xFFFF;
- case 0x1FFFE: return 0x7FFF;
-
- case 0x240000: return RAMEnable;
- case 0x240002: return 0x0000;
- }
-
- return 0xFFFF;
- }
- else if (addr < 0x01800000)
- {
- if (!RAMEnable) return 0xFFFF;
-
- return *(u16*)&RAM[addr & 0x7FFFFF];
- }
-
- return 0xFFFF;
-}
-
-void CartRAMExpansion::ROMWrite(u32 addr, u16 val)
-{
- addr &= 0x01FFFFFF;
-
- if (addr < 0x01000000)
- {
- switch (addr)
- {
- case 0x240000:
- RAMEnable = val & 0x0001;
- return;
- }
- }
- else if (addr < 0x01800000)
- {
- if (!RAMEnable) return;
-
- *(u16*)&RAM[addr & 0x7FFFFF] = val;
- }
-}
-
-void GBACartSlot::Reset() noexcept
-{
- if (Cart) Cart->Reset();
-}
-
-void GBACartSlot::DoSavestate(Savestate* file) noexcept
-{
- file->Section("GBAC"); // Game Boy Advance Cartridge
-
- // little state here
- // no need to save OpenBusDecay, it will be set later
-
- u32 carttype = 0;
- u32 cartchk = 0;
- if (Cart)
- {
- carttype = Cart->Type();
- cartchk = Cart->Checksum();
- }
-
- if (file->Saving)
- {
- file->Var32(&carttype);
- file->Var32(&cartchk);
- }
- else
- {
- u32 savetype;
- file->Var32(&savetype);
- if (savetype != carttype) return;
-
- u32 savechk;
- file->Var32(&savechk);
- if (savechk != cartchk) return;
- }
-
- if (Cart) Cart->DoSavestate(file);
-}
-
-
-std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
-{
- if (romdata == nullptr)
- {
- Log(LogLevel::Error, "GBACart: romdata is null\n");
- return nullptr;
- }
-
- if (romlen == 0)
- {
- Log(LogLevel::Error, "GBACart: romlen is zero\n");
- return nullptr;
- }
-
- u32 cartromsize = 0x200;
- while (cartromsize < romlen)
- cartromsize <<= 1;
-
- u8* cartrom = nullptr;
- try
- {
- cartrom = new u8[cartromsize];
- }
- catch (const std::bad_alloc& e)
- {
- Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize);
-
- return nullptr;
- }
-
- memset(cartrom, 0, cartromsize);
- memcpy(cartrom, romdata, romlen);
-
- char gamecode[5] = { '\0' };
- memcpy(&gamecode, cartrom + 0xAC, 4);
-
- bool solarsensor = false;
- for (const char* i : SOLAR_SENSOR_GAMECODES)
- {
- if (strcmp(gamecode, i) == 0)
- solarsensor = true;
- }
-
- if (solarsensor)
- {
- Log(LogLevel::Info, "GBA solar sensor support detected!\n");
- }
-
- std::unique_ptr<CartCommon> cart;
- if (solarsensor)
- cart = std::make_unique<CartGameSolarSensor>(cartrom, cartromsize);
- else
- cart = std::make_unique<CartGame>(cartrom, cartromsize);
-
- cart->Reset();
-
- // TODO: setup cart save here! from a list or something
-
- // save
- //printf("GBA save file: %s\n", sram);
-
- // TODO: have a list of sorts like in NDSCart? to determine the savemem type
- //if (Cart) Cart->LoadSave(sram, 0);
-
- return cart;
-}
-
-bool GBACartSlot::InsertROM(std::unique_ptr<CartCommon>&& cart) noexcept
-{
- if (!cart) {
- Log(LogLevel::Error, "Failed to insert invalid GBA cart; existing cart (if any) was not ejected.\n");
- return false;
- }
-
- if (Cart != nullptr)
- EjectCart();
-
- Cart = std::move(cart);
-
- const u8* cartrom = Cart->GetROM();
-
- if (cartrom)
- {
- char gamecode[5] = { '\0' };
- memcpy(&gamecode, Cart->GetROM() + 0xAC, 4);
- Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode);
- }
- else
- {
- Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n");
- }
-
- return true;
-}
-
-bool GBACartSlot::LoadROM(const u8* romdata, u32 romlen) noexcept
-{
- std::unique_ptr<CartCommon> data = ParseROM(romdata, romlen);
-
- return InsertROM(std::move(data));
-}
-
-void GBACartSlot::LoadSave(const u8* savedata, u32 savelen) noexcept
-{
- if (Cart)
- {
- // gross hack
- Cart->SetupSave(savelen);
-
- Cart->LoadSave(savedata, savelen);
- }
-}
-
-void GBACartSlot::LoadAddon(int type) noexcept
-{
- switch (type)
- {
- case GBAAddon_RAMExpansion:
- Cart = std::make_unique<CartRAMExpansion>();
- break;
-
- default:
- Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
- return;
- }
-}
-
-void GBACartSlot::EjectCart() noexcept
-{
- Cart = nullptr;
-}
-
-
-int GBACartSlot::SetInput(int num, bool pressed) noexcept
-{
- if (Cart) return Cart->SetInput(num, pressed);
-
- return -1;
-}
-
-
-u16 GBACartSlot::ROMRead(u32 addr) const noexcept
-{
- if (Cart) return Cart->ROMRead(addr);
-
- return ((addr >> 1) & 0xFFFF) | OpenBusDecay;
-}
-
-void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept
-{
- if (Cart) Cart->ROMWrite(addr, val);
-}
-
-u8 GBACartSlot::SRAMRead(u32 addr) noexcept
-{
- if (Cart) return Cart->SRAMRead(addr);
-
- return 0xFF;
-}
-
-void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept
-{
- if (Cart) Cart->SRAMWrite(addr, val);
-}
-
-}
-
+/*
+ Copyright 2016-2023 melonDS team
+
+ This file is part of melonDS.
+
+ melonDS is free software: you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with melonDS. If not, see http://www.gnu.org/licenses/.
+*/
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include "NDS.h"
+#include "GBACart.h"
+#include "CRC32.h"
+#include "Platform.h"
+#include "Utils.h"
+
+namespace melonDS
+{
+using Platform::Log;
+using Platform::LogLevel;
+
+namespace GBACart
+{
+
+const char SOLAR_SENSOR_GAMECODES[10][5] =
+{
+ "U3IJ", // Bokura no Taiyou - Taiyou Action RPG (Japan)
+ "U3IE", // Boktai - The Sun Is in Your Hand (USA)
+ "U3IP", // Boktai - The Sun Is in Your Hand (Europe)
+ "U32J", // Zoku Bokura no Taiyou - Taiyou Shounen Django (Japan)
+ "U32E", // Boktai 2 - Solar Boy Django (USA)
+ "U32P", // Boktai 2 - Solar Boy Django (Europe)
+ "U33J", // Shin Bokura no Taiyou - Gyakushuu no Sabata (Japan)
+ "A3IJ" // Boktai - The Sun Is in Your Hand (USA) (Sample)
+};
+
+CartCommon::CartCommon(GBACart::CartType type) : CartType(type)
+{
+}
+
+void CartCommon::Reset()
+{
+}
+
+void CartCommon::DoSavestate(Savestate* file)
+{
+ file->Section("GBCS");
+}
+
+void CartCommon::SetSaveMemory(const u8* savedata, u32 savelen)
+{
+}
+
+int CartCommon::SetInput(int num, bool pressed)
+{
+ return -1;
+}
+
+u16 CartCommon::ROMRead(u32 addr) const
+{
+ return 0;
+}
+
+void CartCommon::ROMWrite(u32 addr, u16 val)
+{
+}
+
+u8 CartCommon::SRAMRead(u32 addr)
+{
+ return 0;
+}
+
+void CartCommon::SRAMWrite(u32 addr, u8 val)
+{
+}
+
+u8* CartCommon::GetSaveMemory() const
+{
+ return nullptr;
+}
+
+u32 CartCommon::GetSaveMemoryLength() const
+{
+ return 0;
+}
+
+CartGame::CartGame(const u8* rom, u32 len, const u8* sram, u32 sramlen, GBACart::CartType type) :
+ CartGame(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen, type)
+{
+}
+
+CartGame::CartGame(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen, GBACart::CartType type) :
+ CartCommon(type),
+ ROM(std::move(rom)),
+ ROMLength(len),
+ SRAM(std::move(sram)),
+ SRAMLength(sramlen)
+{
+ if (SRAM && SRAMLength)
+ {
+ SetupSave(sramlen);
+ }
+}
+
+CartGame::~CartGame() = default;
+// unique_ptr cleans up the allocated memory
+
+u32 CartGame::Checksum() const
+{
+ u32 crc = CRC32(ROM.get(), 0xC0, 0);
+
+ // TODO: hash more contents?
+
+ return crc;
+}
+
+void CartGame::Reset()
+{
+ memset(&GPIO, 0, sizeof(GPIO));
+}
+
+void CartGame::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
+
+ file->Var16(&GPIO.control);
+ file->Var16(&GPIO.data);
+ file->Var16(&GPIO.direction);
+
+ u32 oldlen = SRAMLength;
+
+ file->Var32(&SRAMLength);
+
+ if (SRAMLength != oldlen)
+ {
+ // reallocate save memory
+ SRAM = SRAMLength ? std::make_unique<u8[]>(SRAMLength) : nullptr;
+ }
+ if (SRAMLength)
+ {
+ // fill save memory if data is present
+ file->VarArray(SRAM.get(), SRAMLength);
+ }
+ else
+ {
+ // no save data, clear the current state
+ SRAMType = SaveType::S_NULL;
+ SRAM = nullptr;
+ return;
+ }
+
+ // persist some extra state info
+ file->Var8(&SRAMFlashState.bank);
+ file->Var8(&SRAMFlashState.cmd);
+ file->Var8(&SRAMFlashState.device);
+ file->Var8(&SRAMFlashState.manufacturer);
+ file->Var8(&SRAMFlashState.state);
+
+ file->Var8((u8*)&SRAMType);
+
+ if ((!file->Saving) && SRAM)
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, 0, SRAMLength);
+}
+
+void CartGame::SetupSave(u32 type)
+{
+ // TODO: have type be determined from some list, like in NDSCart
+ // and not this gross hack!!
+ SRAMLength = type;
+ switch (SRAMLength)
+ {
+ case 512:
+ SRAMType = S_EEPROM4K;
+ break;
+ case 8192:
+ SRAMType = S_EEPROM64K;
+ break;
+ case 32768:
+ SRAMType = S_SRAM256K;
+ break;
+ case 65536:
+ SRAMType = S_FLASH512K;
+ break;
+ case 128*1024:
+ SRAMType = S_FLASH1M;
+ break;
+ case 0:
+ SRAMType = S_NULL;
+ break;
+ default:
+ Log(LogLevel::Warn, "!! BAD GBA SAVE LENGTH %d\n", SRAMLength);
+ }
+
+ if (SRAMType == S_FLASH512K)
+ {
+ // Panasonic 64K chip
+ SRAMFlashState.device = 0x1B;
+ SRAMFlashState.manufacturer = 0x32;
+ }
+ else if (SRAMType == S_FLASH1M)
+ {
+ // Sanyo 128K chip
+ SRAMFlashState.device = 0x13;
+ SRAMFlashState.manufacturer = 0x62;
+ }
+}
+
+void CartGame::SetSaveMemory(const u8* savedata, u32 savelen)
+{
+ SetupSave(savelen);
+
+ u32 len = std::min(savelen, SRAMLength);
+ memcpy(SRAM.get(), savedata, len);
+ Platform::WriteGBASave(savedata, len, 0, len);
+}
+
+u16 CartGame::ROMRead(u32 addr) const
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr >= 0xC4 && addr < 0xCA)
+ {
+ if (GPIO.control & 0x1)
+ {
+ switch (addr)
+ {
+ case 0xC4: return GPIO.data;
+ case 0xC6: return GPIO.direction;
+ case 0xC8: return GPIO.control;
+ }
+ }
+ else
+ return 0;
+ }
+
+ // CHECKME: does ROM mirror?
+ if (addr < ROMLength)
+ return *(u16*)&ROM[addr];
+
+ return 0;
+}
+
+void CartGame::ROMWrite(u32 addr, u16 val)
+{
+ addr &= 0x01FFFFFF;
+
+ switch (addr)
+ {
+ case 0xC4:
+ GPIO.data &= ~GPIO.direction;
+ GPIO.data |= val & GPIO.direction;
+ ProcessGPIO();
+ break;
+
+ case 0xC6:
+ GPIO.direction = val;
+ break;
+
+ case 0xC8:
+ GPIO.control = val;
+ break;
+
+ default:
+ Log(LogLevel::Warn, "Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr);
+ break;
+ }
+}
+
+u8 CartGame::SRAMRead(u32 addr)
+{
+ addr &= 0xFFFF;
+
+ switch (SRAMType)
+ {
+ case S_EEPROM4K:
+ case S_EEPROM64K:
+ return SRAMRead_EEPROM(addr);
+
+ case S_FLASH512K:
+ case S_FLASH1M:
+ return SRAMRead_FLASH(addr);
+
+ case S_SRAM256K:
+ return SRAMRead_SRAM(addr);
+ default:
+ break;
+ }
+
+ return 0xFF;
+}
+
+void CartGame::SRAMWrite(u32 addr, u8 val)
+{
+ addr &= 0xFFFF;
+
+ switch (SRAMType)
+ {
+ case S_EEPROM4K:
+ case S_EEPROM64K:
+ return SRAMWrite_EEPROM(addr, val);
+
+ case S_FLASH512K:
+ case S_FLASH1M:
+ return SRAMWrite_FLASH(addr, val);
+
+ case S_SRAM256K:
+ return SRAMWrite_SRAM(addr, val);
+ default:
+ break;
+ }
+}
+
+u8* CartGame::GetSaveMemory() const
+{
+ return SRAM.get();
+}
+
+u32 CartGame::GetSaveMemoryLength() const
+{
+ return SRAMLength;
+}
+
+void CartGame::ProcessGPIO()
+{
+}
+
+u8 CartGame::SRAMRead_EEPROM(u32 addr)
+{
+ return 0;
+}
+
+void CartGame::SRAMWrite_EEPROM(u32 addr, u8 val)
+{
+ // TODO: could be used in homebrew?
+}
+
+// mostly ported from DeSmuME
+u8 CartGame::SRAMRead_FLASH(u32 addr)
+{
+ if (SRAMFlashState.cmd == 0) // no cmd
+ {
+ return *(u8*)&SRAM[addr + 0x10000 * SRAMFlashState.bank];
+ }
+
+ switch (SRAMFlashState.cmd)
+ {
+ case 0x90: // chip ID
+ if (addr == 0x0000) return SRAMFlashState.manufacturer;
+ if (addr == 0x0001) return SRAMFlashState.device;
+ break;
+ case 0xF0: // terminate command (TODO: break if non-Macronix chip and not at the end of an ID call?)
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ break;
+ case 0xA0: // write command
+ break; // ignore here, handled in Write_Flash()
+ case 0xB0: // bank switching (128K only)
+ break; // ignore here, handled in Write_Flash()
+ default:
+ Log(LogLevel::Warn, "GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr);
+ break;
+ }
+
+ return 0xFF;
+}
+
+// mostly ported from DeSmuME
+void CartGame::SRAMWrite_FLASH(u32 addr, u8 val)
+{
+ switch (SRAMFlashState.state)
+ {
+ case 0x00:
+ if (addr == 0x5555)
+ {
+ if (val == 0xF0)
+ {
+ // reset
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+ else if (val == 0xAA)
+ {
+ SRAMFlashState.state = 1;
+ return;
+ }
+ }
+ if (addr == 0x0000)
+ {
+ if (SRAMFlashState.cmd == 0xB0)
+ {
+ // bank switching
+ SRAMFlashState.bank = val;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+ }
+ break;
+ case 0x01:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 2;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x02:
+ if (addr == 0x5555)
+ {
+ // send command
+ switch (val)
+ {
+ case 0x80: // erase
+ SRAMFlashState.state = 0x80;
+ break;
+ case 0x90: // chip ID
+ SRAMFlashState.state = 0x90;
+ break;
+ case 0xA0: // write
+ SRAMFlashState.state = 0;
+ break;
+ default:
+ SRAMFlashState.state = 0;
+ break;
+ }
+
+ SRAMFlashState.cmd = val;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ // erase
+ case 0x80:
+ if (addr == 0x5555 && val == 0xAA)
+ {
+ SRAMFlashState.state = 0x81;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x81:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 0x82;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x82:
+ if (val == 0x30)
+ {
+ u32 start_addr = addr + 0x10000 * SRAMFlashState.bank;
+ memset((u8*)&SRAM[start_addr], 0xFF, 0x1000);
+
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, start_addr, 0x1000);
+ }
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ // chip ID
+ case 0x90:
+ if (addr == 0x5555 && val == 0xAA)
+ {
+ SRAMFlashState.state = 0x91;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x91:
+ if (addr == 0x2AAA && val == 0x55)
+ {
+ SRAMFlashState.state = 0x92;
+ return;
+ }
+ SRAMFlashState.state = 0;
+ break;
+ case 0x92:
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ default:
+ break;
+ }
+
+ if (SRAMFlashState.cmd == 0xA0) // write
+ {
+ SRAMWrite_SRAM(addr + 0x10000 * SRAMFlashState.bank, val);
+ SRAMFlashState.state = 0;
+ SRAMFlashState.cmd = 0;
+ return;
+ }
+
+ Log(LogLevel::Debug, "GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n",
+ val, addr, SRAMFlashState.state);
+}
+
+u8 CartGame::SRAMRead_SRAM(u32 addr)
+{
+ if (addr >= SRAMLength) return 0xFF;
+
+ return SRAM[addr];
+}
+
+void CartGame::SRAMWrite_SRAM(u32 addr, u8 val)
+{
+ if (addr >= SRAMLength) return;
+
+ u8 prev = *(u8*)&SRAM[addr];
+ if (prev != val)
+ {
+ *(u8*)&SRAM[addr] = val;
+
+ // TODO: optimize this!!
+ Platform::WriteGBASave(SRAM.get(), SRAMLength, addr, 1);
+ }
+}
+
+
+CartGameSolarSensor::CartGameSolarSensor(const u8* rom, u32 len, const u8* sram, u32 sramlen) :
+ CartGameSolarSensor(CopyToUnique(rom, len), len, CopyToUnique(sram, sramlen), sramlen)
+{
+}
+
+CartGameSolarSensor::CartGameSolarSensor(std::unique_ptr<u8[]>&& rom, u32 len, std::unique_ptr<u8[]>&& sram, u32 sramlen) :
+ CartGame(std::move(rom), len, std::move(sram), sramlen, CartType::GameSolarSensor)
+{
+}
+
+const int CartGameSolarSensor::kLuxLevels[11] = {0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183};
+
+void CartGameSolarSensor::Reset()
+{
+ CartGame::Reset();
+ LightEdge = false;
+ LightCounter = 0;
+ LightSample = 0xFF;
+ LightLevel = 0;
+}
+
+void CartGameSolarSensor::DoSavestate(Savestate* file)
+{
+ CartGame::DoSavestate(file);
+
+ file->Var8((u8*)&LightEdge);
+ file->Var8(&LightCounter);
+ file->Var8(&LightSample);
+ file->Var8(&LightLevel);
+}
+
+int CartGameSolarSensor::SetInput(int num, bool pressed)
+{
+ if (!pressed) return -1;
+
+ if (num == Input_SolarSensorDown)
+ {
+ if (LightLevel > 0)
+ LightLevel--;
+
+ return LightLevel;
+ }
+ else if (num == Input_SolarSensorUp)
+ {
+ if (LightLevel < 10)
+ LightLevel++;
+
+ return LightLevel;
+ }
+
+ return -1;
+}
+
+void CartGameSolarSensor::ProcessGPIO()
+{
+ if (GPIO.data & 4) return; // Boktai chip select
+ if (GPIO.data & 2) // Reset
+ {
+ u8 prev = LightSample;
+ LightCounter = 0;
+ LightSample = (0xFF - (0x16 + kLuxLevels[LightLevel]));
+ Log(LogLevel::Debug, "Solar sensor reset (sample: 0x%02X -> 0x%02X)\n", prev, LightSample);
+ }
+ if (GPIO.data & 1 && LightEdge) LightCounter++;
+
+ LightEdge = !(GPIO.data & 1);
+
+ bool sendBit = LightCounter >= LightSample;
+ if (GPIO.control & 1)
+ {
+ GPIO.data = (GPIO.data & GPIO.direction) | ((sendBit << 3) & ~GPIO.direction & 0xF);
+ }
+}
+
+
+CartRAMExpansion::CartRAMExpansion() : CartCommon(RAMExpansion)
+{
+}
+
+CartRAMExpansion::~CartRAMExpansion() = default;
+
+void CartRAMExpansion::Reset()
+{
+ memset(RAM, 0xFF, sizeof(RAM));
+ RAMEnable = 1;
+}
+
+void CartRAMExpansion::DoSavestate(Savestate* file)
+{
+ CartCommon::DoSavestate(file);
+
+ file->VarArray(RAM, sizeof(RAM));
+ file->Var16(&RAMEnable);
+}
+
+u16 CartRAMExpansion::ROMRead(u32 addr) const
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0xB0: return 0xFFFF;
+ case 0xB2: return 0x0000;
+ case 0xB4: return 0x2400;
+ case 0xB6: return 0x2424;
+ case 0xB8: return 0xFFFF;
+ case 0xBA: return 0xFFFF;
+ case 0xBC: return 0xFFFF;
+ case 0xBE: return 0x7FFF;
+
+ case 0x1FFFC: return 0xFFFF;
+ case 0x1FFFE: return 0x7FFF;
+
+ case 0x240000: return RAMEnable;
+ case 0x240002: return 0x0000;
+ }
+
+ return 0xFFFF;
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return 0xFFFF;
+
+ return *(u16*)&RAM[addr & 0x7FFFFF];
+ }
+
+ return 0xFFFF;
+}
+
+void CartRAMExpansion::ROMWrite(u32 addr, u16 val)
+{
+ addr &= 0x01FFFFFF;
+
+ if (addr < 0x01000000)
+ {
+ switch (addr)
+ {
+ case 0x240000:
+ RAMEnable = val & 0x0001;
+ return;
+ }
+ }
+ else if (addr < 0x01800000)
+ {
+ if (!RAMEnable) return;
+
+ *(u16*)&RAM[addr & 0x7FFFFF] = val;
+ }
+}
+
+GBACartSlot::GBACartSlot(std::unique_ptr<CartCommon>&& cart) noexcept : Cart(std::move(cart))
+{
+}
+
+void GBACartSlot::Reset() noexcept
+{
+ if (Cart) Cart->Reset();
+}
+
+void GBACartSlot::DoSavestate(Savestate* file) noexcept
+{
+ file->Section("GBAC"); // Game Boy Advance Cartridge
+
+ // little state here
+ // no need to save OpenBusDecay, it will be set later
+
+ u32 carttype = 0;
+ u32 cartchk = 0;
+ if (Cart)
+ {
+ carttype = Cart->Type();
+ cartchk = Cart->Checksum();
+ }
+
+ if (file->Saving)
+ {
+ file->Var32(&carttype);
+ file->Var32(&cartchk);
+ }
+ else
+ {
+ u32 savetype;
+ file->Var32(&savetype);
+ if (savetype != carttype) return;
+
+ u32 savechk;
+ file->Var32(&savechk);
+ if (savechk != cartchk) return;
+ }
+
+ if (Cart) Cart->DoSavestate(file);
+}
+
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen)
+{
+ return ParseROM(std::move(romdata), romlen, nullptr, 0);
+}
+
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen, const u8* sramdata, u32 sramlen)
+{
+ auto [romcopy, romcopylen] = PadToPowerOf2(romdata, romlen);
+
+ return ParseROM(std::move(romcopy), romcopylen, CopyToUnique(sramdata, sramlen), sramlen);
+}
+
+std::unique_ptr<CartCommon> ParseROM(const u8* romdata, u32 romlen)
+{
+ return ParseROM(romdata, romlen, nullptr, 0);
+}
+
+std::unique_ptr<CartCommon> ParseROM(std::unique_ptr<u8[]>&& romdata, u32 romlen, std::unique_ptr<u8[]>&& sramdata, u32 sramlen)
+{
+ if (romdata == nullptr)
+ {
+ Log(LogLevel::Error, "GBACart: romdata is null\n");
+ return nullptr;
+ }
+
+ if (romlen == 0)
+ {
+ Log(LogLevel::Error, "GBACart: romlen is zero\n");
+ return nullptr;
+ }
+
+ auto [cartrom, cartromsize] = PadToPowerOf2(std::move(romdata), romlen);
+
+ std::unique_ptr<u8[]> cartsram;
+ try
+ {
+ cartsram = sramdata ? std::make_unique<u8[]>(sramlen) : nullptr;
+ }
+ catch (const std::bad_alloc& e)
+ {
+ Log(LogLevel::Error, "GBACart: failed to allocate memory for ROM (%d bytes)\n", cartromsize);
+
+ return nullptr;
+ }
+
+ if (cartsram)
+ {
+ memset(cartsram.get(), 0, sramlen);
+ memcpy(cartsram.get(), sramdata.get(), sramlen);
+ }
+
+ char gamecode[5] = { '\0' };
+ memcpy(&gamecode, cartrom.get() + 0xAC, 4);
+
+ bool solarsensor = false;
+ for (const char* i : SOLAR_SENSOR_GAMECODES)
+ {
+ if (strcmp(gamecode, i) == 0)
+ solarsensor = true;
+ }
+
+ if (solarsensor)
+ {
+ Log(LogLevel::Info, "GBA solar sensor support detected!\n");
+ }
+
+ std::unique_ptr<CartCommon> cart;
+ if (solarsensor)
+ cart = std::make_unique<CartGameSolarSensor>(std::move(cartrom), cartromsize, std::move(cartsram), sramlen);
+ else
+ cart = std::make_unique<CartGame>(std::move(cartrom), cartromsize, std::move(cartsram), sramlen);
+
+ cart->Reset();
+
+ // save
+ //printf("GBA save file: %s\n", sram);
+
+ // TODO: have a list of sorts like in NDSCart? to determine the savemem type
+ //if (Cart) Cart->LoadSave(sram, 0);
+
+ return cart;
+}
+
+void GBACartSlot::SetCart(std::unique_ptr<CartCommon>&& cart) noexcept
+{
+ Cart = std::move(cart);
+
+ if (!Cart)
+ {
+ Log(LogLevel::Info, "Ejected GBA cart");
+ return;
+ }
+
+ const u8* cartrom = Cart->GetROM();
+
+ if (cartrom)
+ {
+ char gamecode[5] = { '\0' };
+ memcpy(&gamecode, Cart->GetROM() + 0xAC, 4);
+ Log(LogLevel::Info, "Inserted GBA cart with game code: %s\n", gamecode);
+ }
+ else
+ {
+ Log(LogLevel::Info, "Inserted GBA cart with no game code (it's probably an accessory)\n");
+ }
+}
+
+void GBACartSlot::SetSaveMemory(const u8* savedata, u32 savelen) noexcept
+{
+ if (Cart)
+ {
+ Cart->SetSaveMemory(savedata, savelen);
+ }
+}
+
+void GBACartSlot::LoadAddon(int type) noexcept
+{
+ switch (type)
+ {
+ case GBAAddon_RAMExpansion:
+ Cart = std::make_unique<CartRAMExpansion>();
+ break;
+
+ default:
+ Log(LogLevel::Warn, "GBACart: !! invalid addon type %d\n", type);
+ return;
+ }
+}
+
+std::unique_ptr<CartCommon> GBACartSlot::EjectCart() noexcept
+{
+ return std::move(Cart);
+ // Cart will be nullptr after this function returns, due to the move
+}
+
+
+int GBACartSlot::SetInput(int num, bool pressed) noexcept
+{
+ if (Cart) return Cart->SetInput(num, pressed);
+
+ return -1;
+}
+
+
+u16 GBACartSlot::ROMRead(u32 addr) const noexcept
+{
+ if (Cart) return Cart->ROMRead(addr);
+
+ return ((addr >> 1) & 0xFFFF) | OpenBusDecay;
+}
+
+void GBACartSlot::ROMWrite(u32 addr, u16 val) noexcept
+{
+ if (Cart) Cart->ROMWrite(addr, val);
+}
+
+u8 GBACartSlot::SRAMRead(u32 addr) noexcept
+{
+ if (Cart) return Cart->SRAMRead(addr);
+
+ return 0xFF;
+}
+
+void GBACartSlot::SRAMWrite(u32 addr, u8 val) noexcept
+{
+ if (Cart) Cart->SRAMWrite(addr, val);
+}
+
+}
+
} \ No newline at end of file