diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a1110f1f..03a4bfe0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(core STATIC CP15.cpp CRC32.cpp DMA.cpp + GBACart.cpp GPU.cpp GPU2D.cpp GPU3D.cpp diff --git a/src/GBACart.cpp b/src/GBACart.cpp new file mode 100644 index 00000000..4d44d3a1 --- /dev/null +++ b/src/GBACart.cpp @@ -0,0 +1,773 @@ +/* + Copyright 2019 Arisotura, Raphaël Zumer + + 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 "GBACart.h" +#include "CRC32.h" +#include "Platform.h" + + +namespace GBACart_SRAM +{ + +enum SaveType { + S_NULL, + S_EEPROM4K, + S_EEPROM64K, + S_SRAM256K, + S_FLASH512K, + S_FLASH1M +}; + +// from DeSmuME +struct FlashProperties +{ + u8 state; + u8 cmd; + u8 device; + u8 manufacturer; + u8 bank; +}; + +u8* SRAM; +FILE* SRAMFile; +u32 SRAMLength; +SaveType SRAMType; +FlashProperties SRAMFlashState; + +char SRAMPath[1024]; + +void (*WriteFunc)(u32 addr, u8 val); + + +void Write_Null(u32 addr, u8 val); +void Write_EEPROM(u32 addr, u8 val); +void Write_SRAM(u32 addr, u8 val); +void Write_Flash(u32 addr, u8 val); + + +bool Init() +{ + SRAM = NULL; + SRAMFile = NULL; + return true; +} + +void DeInit() +{ + if (SRAMFile) fclose(SRAMFile); + if (SRAM) delete[] SRAM; +} + +void Reset() +{ + // do nothing, we don't want to clear GBA SRAM on reset +} + +void Eject() +{ + if (SRAMFile) fclose(SRAMFile); + if (SRAM) delete[] SRAM; + SRAM = NULL; + SRAMFile = NULL; + SRAMLength = 0; + SRAMType = S_NULL; + SRAMFlashState = {}; +} + +void DoSavestate(Savestate* file) +{ + file->Section("GBCS"); // Game Boy [Advance] Cart Save + + // logic mostly copied from NDSCart_SRAM + + u32 oldlen = SRAMLength; + + file->Var32(&SRAMLength); + + if (SRAMLength != oldlen) + { + // reallocate save memory + if (oldlen) delete[] SRAM; + 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; + if (SRAMFile) fclose(SRAMFile); + SRAM = NULL; + SRAMFile = NULL; + 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); +} + +void LoadSave(const char* path) +{ + if (SRAM) delete[] SRAM; + + strncpy(SRAMPath, path, 1023); + SRAMPath[1023] = '\0'; + SRAMLength = 0; + + FILE* f = Platform::OpenFile(SRAMPath, "r+b"); + if (f) + { + fseek(f, 0, SEEK_END); + SRAMLength = (u32)ftell(f); + SRAM = new u8[SRAMLength]; + + fseek(f, 0, SEEK_SET); + fread(SRAM, SRAMLength, 1, f); + + SRAMFile = f; + } + + switch (SRAMLength) + { + case 512: + SRAMType = S_EEPROM4K; + WriteFunc = Write_EEPROM; + break; + case 8192: + SRAMType = S_EEPROM64K; + WriteFunc = Write_EEPROM; + break; + case 32768: + SRAMType = S_SRAM256K; + WriteFunc = Write_SRAM; + break; + case 65536: + SRAMType = S_FLASH512K; + WriteFunc = Write_Flash; + break; + case 128*1024: + SRAMType = S_FLASH1M; + WriteFunc = Write_Flash; + break; + default: + printf("!! BAD SAVE LENGTH %d\n", SRAMLength); + case 0: + SRAMType = S_NULL; + WriteFunc = Write_Null; + break; + } + + 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 RelocateSave(const char* path, bool write) +{ + if (!write) + { + LoadSave(path); // lazy + return; + } + + strncpy(SRAMPath, path, 1023); + SRAMPath[1023] = '\0'; + + FILE *f = Platform::OpenFile(path, "r+b"); + if (!f) + { + printf("GBACart_SRAM::RelocateSave: failed to create new file. fuck\n"); + return; + } + + SRAMFile = f; + fwrite(SRAM, SRAMLength, 1, SRAMFile); +} + +// mostly ported from DeSmuME +u8 Read_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: + printf("GBACart_SRAM::Read_Flash: unknown command 0x%02X @ 0x%04X\n", SRAMFlashState.cmd, addr); + break; + } + + return 0xFF; +} + +void Write_Null(u32 addr, u8 val) {} + +void Write_EEPROM(u32 addr, u8 val) +{ + // TODO: could be used in homebrew? +} + +// mostly ported from DeSmuME +void Write_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); + + if (SRAMFile) + { + fseek(SRAMFile, start_addr, SEEK_SET); + fwrite((u8*)&SRAM[start_addr], 1, 0x1000, SRAMFile); + } + } + 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 + { + Write_SRAM(addr + 0x10000 * SRAMFlashState.bank, val); + SRAMFlashState.state = 0; + SRAMFlashState.cmd = 0; + return; + } + + printf("GBACart_SRAM::Write_Flash: unknown write 0x%02X @ 0x%04X (state: 0x%02X)\n", + val, addr, SRAMFlashState.state); +} + +void Write_SRAM(u32 addr, u8 val) +{ + u8 prev = *(u8*)&SRAM[addr]; + + if (prev != val) + { + *(u8*)&SRAM[addr] = val; + + if (SRAMFile) + { + fseek(SRAMFile, addr, SEEK_SET); + fwrite((u8*)&SRAM[addr], 1, 1, SRAMFile); + } + } +} + +u8 Read8(u32 addr) +{ + if (SRAMType == S_NULL) + { + return 0xFF; + } + + if (SRAMType == S_FLASH512K || SRAMType == S_FLASH1M) + { + return Read_Flash(addr); + } + + return *(u8*)&SRAM[addr]; +} + +u16 Read16(u32 addr) +{ + if (SRAMType == S_NULL) + { + return 0xFFFF; + } + + if (SRAMType == S_FLASH512K || SRAMType == S_FLASH1M) + { + u16 val = Read_Flash(addr + 0) | + (Read_Flash(addr + 1) << 8); + return val; + } + + return *(u16*)&SRAM[addr]; +} + +u32 Read32(u32 addr) +{ + if (SRAMType == S_NULL) + { + return 0xFFFFFFFF; + } + + if (SRAMType == S_FLASH512K || SRAMType == S_FLASH1M) + { + u32 val = Read_Flash(addr + 0) | + (Read_Flash(addr + 1) << 8) | + (Read_Flash(addr + 2) << 16) | + (Read_Flash(addr + 3) << 24); + return val; + } + + return *(u32*)&SRAM[addr]; +} + +void Write8(u32 addr, u8 val) +{ + u8 prev = *(u8*)&SRAM[addr]; + + WriteFunc(addr, val); +} + +void Write16(u32 addr, u16 val) +{ + u16 prev = *(u16*)&SRAM[addr]; + + WriteFunc(addr + 0, val & 0xFF); + WriteFunc(addr + 1, val >> 8 & 0xFF); +} + +void Write32(u32 addr, u32 val) +{ + u32 prev = *(u32*)&SRAM[addr]; + + WriteFunc(addr + 0, val & 0xFF); + WriteFunc(addr + 1, val >> 8 & 0xFF); + WriteFunc(addr + 2, val >> 16 & 0xFF); + WriteFunc(addr + 3, val >> 24 & 0xFF); +} + +} + + +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) +}; + + +bool CartInserted; +bool HasSolarSensor; +u8* CartROM; +u32 CartROMSize; +u32 CartCRC; +u32 CartID; +GPIO CartGPIO; // overridden GPIO parameters + + +bool Init() +{ + if (!GBACart_SRAM::Init()) return false; + + CartROM = NULL; + + return true; +} + +void DeInit() +{ + if (CartROM) delete[] CartROM; + + GBACart_SRAM::DeInit(); +} + +void Reset() +{ + // Do not reset cartridge ROM. + // Prefer keeping the inserted cartridge on reset. + // This allows resetting a DS game without losing GBA state, + // and resetting to firmware without the slot being emptied. + // The Stop function will clear the cartridge state via Eject(). + + GBACart_SRAM::Reset(); + GBACart_SolarSensor::Reset(); +} + +void Eject() +{ + if (CartROM) delete[] CartROM; + + CartInserted = false; + HasSolarSensor = false; + CartROM = NULL; + CartROMSize = 0; + CartCRC = NULL; + CartID = NULL; + CartGPIO = {}; + + GBACart_SRAM::Eject(); + Reset(); +} + +void DoSavestate(Savestate* file) +{ + file->Section("GBAC"); // Game Boy Advance Cartridge + + // logic mostly copied from NDSCart + + // first we need to reload the cart itself, + // since unlike with DS, it's not loaded in advance + + file->Var32(&CartROMSize); + if (!CartROMSize) // no GBA cartridge state? nothing to do here + { + // do eject the cartridge if something is inserted + Eject(); + return; + } + + u32 oldCRC = CartCRC; + file->Var32(&CartCRC); + + if (CartCRC != oldCRC) + { + // delete and reallocate ROM so that it is zero-padded to its full length + if (CartROM) delete[] CartROM; + CartROM = new u8[CartROMSize]; + + // clear the SRAM file handle; writes will not be committed + if (GBACart_SRAM::SRAMFile) + { + fclose(GBACart_SRAM::SRAMFile); + GBACart_SRAM::SRAMFile = NULL; + } + } + + // only save/load the cartridge header + // + // GBA connectivity on DS mainly involves identifying the title currently + // inserted, reading save data, and issuing commands intercepted here + // (e.g. solar sensor signals). we don't know of any case where GBA ROM is + // read directly from DS software. therefore, it is more practical, both + // from the development and user experience perspectives, to avoid dealing + // with file dependencies, and store a small portion of ROM data that should + // satisfy the needs of all known software that reads from the GBA slot. + // + // note: in case of a state load, only the cartridge header is restored, but + // the rest of the ROM data is only cleared (zero-initialized) if the CRC + // differs. Therefore, loading the GBA cartridge associated with the save state + // in advance will maintain access to the full ROM contents. + file->VarArray(CartROM, 192); + + CartInserted = true; // known, because CartROMSize > 0 + file->Var32(&CartCRC); + file->Var32(&CartID); + + file->Var8((u8*)&HasSolarSensor); + + file->Var16(&CartGPIO.control); + file->Var16(&CartGPIO.data); + file->Var16(&CartGPIO.direction); + + // now do the rest + + GBACart_SRAM::DoSavestate(file); + if (HasSolarSensor) GBACart_SolarSensor::DoSavestate(file); +} + +bool LoadROM(const char* path, const char* sram) +{ + FILE* f = Platform::OpenFile(path, "rb"); + if (!f) + { + return false; + } + + if (CartInserted) + { + Reset(); + } + + fseek(f, 0, SEEK_END); + u32 len = (u32)ftell(f); + + CartROMSize = 0x200; + while (CartROMSize < len) + CartROMSize <<= 1; + + char gamecode[5] = { '\0' }; + fseek(f, 0xAC, SEEK_SET); + fread(&gamecode, 1, 4, f); + printf("Game code: %s\n", gamecode); + + for (int i = 0; i < sizeof(SOLAR_SENSOR_GAMECODES)/sizeof(SOLAR_SENSOR_GAMECODES[0]); i++) + { + if (strcmp(gamecode, SOLAR_SENSOR_GAMECODES[i]) == 0) HasSolarSensor = true; + } + + if (HasSolarSensor) + { + printf("GBA solar sensor support detected!\n"); + } + + CartROM = new u8[CartROMSize]; + memset(CartROM, 0, CartROMSize); + fseek(f, 0, SEEK_SET); + fread(CartROM, 1, len, f); + + fclose(f); + + CartCRC = CRC32(CartROM, CartROMSize); + printf("ROM CRC32: %08X\n", CartCRC); + + CartInserted = true; + + // save + printf("Save file: %s\n", sram); + GBACart_SRAM::LoadSave(sram); + + return true; +} + +void RelocateSave(const char* path, bool write) +{ + // derp herp + GBACart_SRAM::RelocateSave(path, write); +} + +// referenced from mGBA +void WriteGPIO(u32 addr, u16 val) +{ + switch (addr) + { + case 0xC4: + CartGPIO.data &= ~CartGPIO.direction; + CartGPIO.data |= val & CartGPIO.direction; + if (HasSolarSensor) GBACart_SolarSensor::Process(&CartGPIO); + break; + case 0xC6: + CartGPIO.direction = val; + break; + case 0xC8: + CartGPIO.control = val; + break; + default: + printf("Unknown GBA GPIO write 0x%02X @ 0x%04X\n", val, addr); + } + + // write the GPIO values in the ROM (if writable) + if (CartGPIO.control & 1) + { + *(u16*)&CartROM[0xC4] = CartGPIO.data; + *(u16*)&CartROM[0xC6] = CartGPIO.direction; + *(u16*)&CartROM[0xC8] = CartGPIO.control; + } + else + { + // GBATEK: "in write-only mode, reads return 00h (or [possibly] other data (...))" + // ambiguous, but mGBA sets ROM to 00h when switching to write-only, so do the same + *(u16*)&CartROM[0xC4] = 0; + *(u16*)&CartROM[0xC6] = 0; + *(u16*)&CartROM[0xC8] = 0; + } +} + +} + + +namespace GBACart_SolarSensor +{ + +bool LightEdge; +u8 LightCounter; +u8 LightSample; +u8 LightLevel; // 0-10 range + +// levels from mGBA +const int GBA_LUX_LEVELS[11] = { 0, 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 }; +#define LIGHT_VALUE (0xFF - (0x16 + GBA_LUX_LEVELS[LightLevel])) + + +void Reset() +{ + LightEdge = false; + LightCounter = 0; + LightSample = 0xFF; + LightLevel = 0; +} + +void DoSavestate(Savestate* file) +{ + file->Var8((u8*)&LightEdge); + file->Var8(&LightCounter); + file->Var8(&LightSample); + file->Var8(&LightLevel); +} + +void Process(GBACart::GPIO* gpio) +{ + if (gpio->data & 4) return; // Boktai chip select + if (gpio->data & 2) // Reset + { + u8 prev = LightSample; + LightCounter = 0; + LightSample = LIGHT_VALUE; + printf("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); + } +} + +} diff --git a/src/GBACart.h b/src/GBACart.h new file mode 100644 index 00000000..96a05b8b --- /dev/null +++ b/src/GBACart.h @@ -0,0 +1,87 @@ +/* + Copyright 2019 Arisotura, Raphaël Zumer + + 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 "types.h" +#include "Savestate.h" + + +namespace GBACart_SRAM +{ + +extern u8* SRAM; +extern u32 SRAMLength; + +void Reset(); +void DoSavestate(Savestate* file); + +u8 Read8(u32 addr); +u16 Read16(u32 addr); +u32 Read32(u32 addr); + +void Write8(u32 addr, u8 val); +void Write16(u32 addr, u16 val); +void Write32(u32 addr, u32 val); + +} + + +namespace GBACart +{ + +struct GPIO +{ + u16 data; + u16 direction; + u16 control; +}; + +extern bool CartInserted; +extern bool HasSolarSensor; +extern u8* CartROM; +extern u32 CartROMSize; +extern u32 CartCRC; + +bool Init(); +void DeInit(); +void Reset(); +void Eject(); + +void DoSavestate(Savestate* file); +bool LoadROM(const char* path, const char* sram); +void RelocateSave(const char* path, bool write); + +void WriteGPIO(u32 addr, u16 val); + +} + + +namespace GBACart_SolarSensor +{ + +extern u8 LightLevel; + +void Reset(); +void DoSavestate(Savestate* file); +void Process(GBACart::GPIO* gpio); + +} + +#endif // GBACART_H diff --git a/src/NDS.cpp b/src/NDS.cpp index ceeeb793..47f96c90 100644 --- a/src/NDS.cpp +++ b/src/NDS.cpp @@ -22,6 +22,7 @@ #include "NDS.h" #include "ARM.h" #include "NDSCart.h" +#include "GBACart.h" #include "DMA.h" #include "FIFO.h" #include "GPU.h" @@ -169,6 +170,7 @@ bool Init() IPCFIFO7 = new FIFO(16); if (!NDSCart::Init()) return false; + if (!GBACart::Init()) return false; if (!GPU::Init()) return false; if (!SPU::Init()) return false; if (!SPI::Init()) return false; @@ -190,6 +192,7 @@ void DeInit() delete IPCFIFO7; NDSCart::DeInit(); + GBACart::DeInit(); GPU::DeInit(); SPU::DeInit(); SPI::DeInit(); @@ -491,6 +494,7 @@ void Reset() RCnt = 0; NDSCart::Reset(); + GBACart::Reset(); GPU::Reset(); SPU::Reset(); SPI::Reset(); @@ -692,6 +696,7 @@ bool DoSavestate(Savestate* file) ARM7->DoSavestate(file); NDSCart::DoSavestate(file); + GBACart::DoSavestate(file); GPU::DoSavestate(file); SPU::DoSavestate(file); SPI::DoSavestate(file); @@ -720,6 +725,19 @@ bool LoadROM(const char* path, const char* sram, bool direct) } } +bool LoadGBAROM(const char* path, const char* sram) +{ + if (GBACart::LoadROM(path, sram)) + { + return true; + } + else + { + printf("Failed to load ROM %s\n", path); + return false; + } +} + void LoadBIOS() { Reset(); @@ -1610,16 +1628,20 @@ u8 ARM9Read8(u32 addr) case 0x08000000: case 0x09000000: - if (ExMemCnt[0] & (1<<7)) return 0xFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFF; + if (ExMemCnt[0] & (1<<7)) return 0x00; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u8*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFF; // TODO: proper open bus case 0x0A000000: - if (ExMemCnt[0] & (1<<7)) return 0xFF; // TODO: proper open bus - // TODO!!! - return 0xFF; + if (ExMemCnt[0] & (1<<7)) return 0x00; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read8(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFF; // TODO: proper open bus } printf("unknown arm9 read8 %08X\n", addr); @@ -1671,16 +1693,20 @@ u16 ARM9Read16(u32 addr) case 0x08000000: case 0x09000000: - if (ExMemCnt[0] & (1<<7)) return 0xFFFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFFFF; + if (ExMemCnt[0] & (1<<7)) return 0x0000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u16*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFFFF; // TODO: proper open bus case 0x0A000000: - if (ExMemCnt[0] & (1<<7)) return 0xFFFF; // TODO: proper open bus - // TODO!!! - return 0xFFFF; + if (ExMemCnt[0] & (1<<7)) return 0x0000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read16(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFFFF; // TODO: proper open bus } //printf("unknown arm9 read16 %08X %08X\n", addr, ARM9->R[15]); @@ -1732,16 +1758,20 @@ u32 ARM9Read32(u32 addr) case 0x08000000: case 0x09000000: - if (ExMemCnt[0] & (1<<7)) return 0xFFFFFFFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFFFFFFFF; + if (ExMemCnt[0] & (1<<7)) return 0x00000000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u32*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFFFFFFFF; // TODO: proper open bus case 0x0A000000: - if (ExMemCnt[0] & (1<<7)) return 0xFFFFFFFF; // TODO: proper open bus - // TODO!!! - return 0xFFFFFFFF; + if (ExMemCnt[0] & (1<<7)) return 0x00000000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read32(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFFFFFFFF; // TODO: proper open bus } printf("unknown arm9 read32 %08X | %08X %08X\n", addr, ARM9->R[15], ARM9->R[12]); @@ -1772,6 +1802,27 @@ void ARM9Write8(u32 addr, u8 val) case 0x07000000: // checkme return; + + case 0x08000000: + case 0x09000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + if ((addr & 0x00FFFFFF) >= 0xC4 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val); + return; + } + } + break; + + case 0x0A000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write8(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } printf("unknown arm9 write8 %08X %02X\n", addr, val); @@ -1815,6 +1866,29 @@ void ARM9Write16(u32 addr, u16 val) if (!(PowerControl9 & ((addr & 0x400) ? (1<<9) : (1<<1)))) return; *(u16*)&GPU::OAM[addr & 0x7FF] = val; return; + + case 0x08000000: + case 0x09000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + // Note: the lower bound is adjusted such that a write starting + // there will hit the first byte of the GPIO region. + if ((addr & 0x00FFFFFF) >= 0xC3 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val); + return; + } + } + break; + + case 0x0A000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write16(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } //printf("unknown arm9 write16 %08X %04X\n", addr, val); @@ -1858,6 +1932,30 @@ void ARM9Write32(u32 addr, u32 val) if (!(PowerControl9 & ((addr & 0x400) ? (1<<9) : (1<<1)))) return; *(u32*)&GPU::OAM[addr & 0x7FF] = val; return; + + case 0x08000000: + case 0x09000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + // Note: the lower bound is adjusted such that a write starting + // there will hit the first byte of the GPIO region. + if ((addr & 0x00FFFFFF) >= 0xC1 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val & 0xFF); + GBACart::WriteGPIO((addr + 2) & (GBACart::CartROMSize-1), (val >> 16) & 0xFF); + return; + } + } + break; + + case 0x0A000000: + if (ExMemCnt[0] & (1<<7)) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write32(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } printf("unknown arm9 write32 %08X %08X | %08X\n", addr, val, ARM9->R[15]); @@ -1935,16 +2033,20 @@ u8 ARM7Read8(u32 addr) case 0x08000000: case 0x09000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x00; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u8*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFF; // TODO: proper open bus case 0x0A000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFF; // TODO: proper open bus - // TODO!!! - return 0xFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x00; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read8(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFF; // TODO: proper open bus } printf("unknown arm7 read8 %08X %08X %08X/%08X\n", addr, ARM7->R[15], ARM7->R[0], ARM7->R[1]); @@ -1998,16 +2100,20 @@ u16 ARM7Read16(u32 addr) case 0x08000000: case 0x09000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFFFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFFFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x0000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u16*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFFFF; // TODO: proper open bus case 0x0A000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFFFF; // TODO: proper open bus - // TODO!!! - return 0xFFFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x0000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read16(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFFFF; // TODO: proper open bus } printf("unknown arm7 read16 %08X %08X\n", addr, ARM7->R[15]); @@ -2061,16 +2167,20 @@ u32 ARM7Read32(u32 addr) case 0x08000000: case 0x09000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFFFFFFFF; // TODO: proper open bus - //return *(u8*)&NDSCart::CartROM[addr & (NDSCart::CartROMSize-1)]; - //printf("GBA read8 %08X\n", addr); - // TODO!!! - return 0xFFFFFFFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x00000000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return *(u32*)&GBACart::CartROM[addr & (GBACart::CartROMSize-1)]; + } + return 0xFFFFFFFF; // TODO: proper open bus case 0x0A000000: - if (!(ExMemCnt[0] & (1<<7))) return 0xFFFFFFFF; // TODO: proper open bus - // TODO!!! - return 0xFFFFFFFF; + if (!(ExMemCnt[0] & (1<<7))) return 0x00000000; // deselected CPU is 00h-filled + if (GBACart::CartInserted) + { + return GBACart_SRAM::Read32(addr & (GBACart_SRAM::SRAMLength-1)); + } + return 0xFFFFFFFF; // TODO: proper open bus } printf("unknown arm7 read32 %08X | %08X\n", addr, ARM7->R[15]); @@ -2110,6 +2220,27 @@ void ARM7Write8(u32 addr, u8 val) case 0x06800000: GPU::WriteVRAM_ARM7(addr, val); return; + + case 0x08000000: + case 0x09000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + if ((addr & 0x00FFFFFF) >= 0xC4 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val); + return; + } + } + break; + + case 0x0A000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write8(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } printf("unknown arm7 write8 %08X %02X @ %08X\n", addr, val, ARM7->R[15]); @@ -2156,6 +2287,29 @@ void ARM7Write16(u32 addr, u16 val) case 0x06800000: GPU::WriteVRAM_ARM7(addr, val); return; + + case 0x08000000: + case 0x09000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + // Note: the lower bound is adjusted such that a write starting + // there will hit the first byte of the GPIO region. + if ((addr & 0x00FFFFFF) >= 0xC3 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val); + return; + } + } + break; + + case 0x0A000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write16(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } //printf("unknown arm7 write16 %08X %04X @ %08X\n", addr, val, ARM7->R[15]); @@ -2203,6 +2357,30 @@ void ARM7Write32(u32 addr, u32 val) case 0x06800000: GPU::WriteVRAM_ARM7(addr, val); return; + + case 0x08000000: + case 0x09000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + // Note: the lower bound is adjusted such that a write starting + // there will hit the first byte of the GPIO region. + if ((addr & 0x00FFFFFF) >= 0xC1 && (addr & 0x00FFFFFF) <= 0xC9) + { + GBACart::WriteGPIO(addr & (GBACart::CartROMSize-1), val & 0xFF); + GBACart::WriteGPIO((addr + 2) & (GBACart::CartROMSize-1), (val >> 16) & 0xFF); + return; + } + } + break; + + case 0x0A000000: + if (!(ExMemCnt[0] & (1<<7))) return; // deselected CPU, skip the write + if (GBACart::CartInserted) + { + GBACart_SRAM::Write32(addr & (GBACart_SRAM::SRAMLength-1), val); + } + return; } //printf("unknown arm7 write32 %08X %08X @ %08X\n", addr, val, ARM7->R[15]); diff --git a/src/NDS.h b/src/NDS.h index 32433374..16c42efd 100644 --- a/src/NDS.h +++ b/src/NDS.h @@ -135,6 +135,7 @@ void SetARM9RegionTimings(u32 addrstart, u32 addrend, int buswidth, int nonseq, void SetARM7RegionTimings(u32 addrstart, u32 addrend, int buswidth, int nonseq, int seq); bool LoadROM(const char* path, const char* sram, bool direct); +bool LoadGBAROM(const char* path, const char* sram); void LoadBIOS(); void SetupDirectBoot(); void RelocateSave(const char* path, bool write); diff --git a/src/NDSCart.cpp b/src/NDSCart.cpp index 0ecd304a..014ae5ae 100644 --- a/src/NDSCart.cpp +++ b/src/NDSCart.cpp @@ -48,7 +48,6 @@ void Write_Null(u8 val, bool islast); void Write_EEPROMTiny(u8 val, bool islast); void Write_EEPROM(u8 val, bool islast); void Write_Flash(u8 val, bool islast); -void Write_Discover(u8 val, bool islast); bool Init() @@ -399,7 +398,7 @@ void Write(u8 val, u32 hold) switch (CurCmd) { case 0x00: - // Pokémon carts have an IR transceiver thing, and send this + // Pokémon carts have an IR transceiver thing, and send this // to bypass it and access SRAM. // TODO: design better CurCmd = val; diff --git a/src/libui_sdl/DlgInputConfig.cpp b/src/libui_sdl/DlgInputConfig.cpp index a80e2ec7..21394b13 100644 --- a/src/libui_sdl/DlgInputConfig.cpp +++ b/src/libui_sdl/DlgInputConfig.cpp @@ -71,7 +71,9 @@ char hotkeylabels[HK_MAX][32] = "Pause/resume:", "Reset:", "Fast forward:", - "Fast forward (toggle):" + "Fast forward (toggle):", + "Decrease sunlight (Boktai):", + "Increase sunlight (Boktai):" }; int openedmask; @@ -514,7 +516,7 @@ void Open(int type) memcpy(dlg->keymap, Config::HKKeyMapping, sizeof(int)*HK_MAX); memcpy(dlg->joymap, Config::HKJoyMapping, sizeof(int)*HK_MAX); - dlg->win = uiNewWindow("Hotkey config - melonDS", 600, 100, 0, 0, 0); + dlg->win = uiNewWindow("Hotkey config - melonDS", 700, 100, 0, 0, 0); } uiControl(dlg->win)->UserData = dlg; diff --git a/src/libui_sdl/PlatformConfig.cpp b/src/libui_sdl/PlatformConfig.cpp index c8ec19fb..a2eaab94 100644 --- a/src/libui_sdl/PlatformConfig.cpp +++ b/src/libui_sdl/PlatformConfig.cpp @@ -93,25 +93,29 @@ ConfigEntry PlatformConfigFile[] = {"Joy_X", 0, &JoyMapping[10], -1, NULL, 0}, {"Joy_Y", 0, &JoyMapping[11], -1, NULL, 0}, - {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], 0x0D, NULL, 0}, - {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], 0x35, NULL, 0}, - {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, NULL, 0}, - {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, NULL, 0}, - {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], 0x0F, NULL, 0}, - {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, NULL, 0}, + {"HKKey_Lid", 0, &HKKeyMapping[HK_Lid], 0x0D, NULL, 0}, + {"HKKey_Mic", 0, &HKKeyMapping[HK_Mic], 0x35, NULL, 0}, + {"HKKey_Pause", 0, &HKKeyMapping[HK_Pause], -1, NULL, 0}, + {"HKKey_Reset", 0, &HKKeyMapping[HK_Reset], -1, NULL, 0}, + {"HKKey_FastForward", 0, &HKKeyMapping[HK_FastForward], 0x0F, NULL, 0}, + {"HKKey_FastForwardToggle", 0, &HKKeyMapping[HK_FastForwardToggle], -1, NULL, 0}, + {"HKKey_SolarSensorDecrease", 0, &HKKeyMapping[HK_SolarSensorDecrease], 0x4B, NULL, 0}, + {"HKKey_SolarSensorIncrease", 0, &HKKeyMapping[HK_SolarSensorIncrease], 0x4D, NULL, 0}, - {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, NULL, 0}, - {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, NULL, 0}, - {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, NULL, 0}, - {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, NULL, 0}, - {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, NULL, 0}, - {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, NULL, 0}, + {"HKJoy_Lid", 0, &HKJoyMapping[HK_Lid], -1, NULL, 0}, + {"HKJoy_Mic", 0, &HKJoyMapping[HK_Mic], -1, NULL, 0}, + {"HKJoy_Pause", 0, &HKJoyMapping[HK_Pause], -1, NULL, 0}, + {"HKJoy_Reset", 0, &HKJoyMapping[HK_Reset], -1, NULL, 0}, + {"HKJoy_FastForward", 0, &HKJoyMapping[HK_FastForward], -1, NULL, 0}, + {"HKJoy_FastForwardToggle", 0, &HKJoyMapping[HK_FastForwardToggle], -1, NULL, 0}, + {"HKJoy_SolarSensorDecrease", 0, &HKJoyMapping[HK_SolarSensorDecrease], -1, NULL, 0}, + {"HKJoy_SolarSensorIncrease", 0, &HKJoyMapping[HK_SolarSensorIncrease], -1, NULL, 0}, {"JoystickID", 0, &JoystickID, 0, NULL, 0}, {"WindowWidth", 0, &WindowWidth, 256, NULL, 0}, {"WindowHeight", 0, &WindowHeight, 384, NULL, 0}, - {"WindowMax", 0, &WindowMaximized, 0, NULL, 0}, + {"WindowMax", 0, &WindowMaximized, 0, NULL, 0}, {"ScreenRotation", 0, &ScreenRotation, 0, NULL, 0}, {"ScreenGap", 0, &ScreenGap, 0, NULL, 0}, diff --git a/src/libui_sdl/PlatformConfig.h b/src/libui_sdl/PlatformConfig.h index 842e72ae..682b9bc1 100644 --- a/src/libui_sdl/PlatformConfig.h +++ b/src/libui_sdl/PlatformConfig.h @@ -29,6 +29,8 @@ enum HK_Reset, HK_FastForward, HK_FastForwardToggle, + HK_SolarSensorDecrease, + HK_SolarSensorIncrease, HK_MAX }; diff --git a/src/libui_sdl/main.cpp b/src/libui_sdl/main.cpp index 2742e4a6..4c0d6dd6 100644 --- a/src/libui_sdl/main.cpp +++ b/src/libui_sdl/main.cpp @@ -38,6 +38,7 @@ #include "DlgWifiSettings.h" #include "../NDS.h" +#include "../GBACart.h" #include "../GPU.h" #include "../SPU.h" #include "../Wifi.h" @@ -105,9 +106,9 @@ int EmuRunning; volatile int EmuStatus; bool RunningSomething; -char ROMPath[1024]; -char SRAMPath[1024]; -char PrevSRAMPath[1024]; // for savestate 'undo load' +char ROMPath[2][1024]; +char SRAMPath[2][1024]; +char PrevSRAMPath[2][1024]; // for savestate 'undo load' bool SavestateLoaded; @@ -184,7 +185,7 @@ void SetupScreenRects(int width, int height); void TogglePause(void* blarg); void Reset(void* blarg); -void SetupSRAMPath(); +void SetupSRAMPath(int slot); void SaveState(int slot); void LoadState(int slot); @@ -954,6 +955,24 @@ int EmuThreadFunc(void* burp) if (HotkeyPressed(HK_Pause)) uiQueueMain(TogglePause, NULL); if (HotkeyPressed(HK_Reset)) uiQueueMain(Reset, NULL); + if (GBACart::CartInserted && GBACart::HasSolarSensor) + { + if (HotkeyPressed(HK_SolarSensorDecrease)) + { + if (GBACart_SolarSensor::LightLevel > 0) GBACart_SolarSensor::LightLevel--; + char msg[64]; + sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel); + OSD::AddMessage(0, msg); + } + if (HotkeyPressed(HK_SolarSensorIncrease)) + { + if (GBACart_SolarSensor::LightLevel < 10) GBACart_SolarSensor::LightLevel++; + char msg[64]; + sprintf(msg, "Solar sensor level set to %d", GBACart_SolarSensor::LightLevel); + OSD::AddMessage(0, msg); + } + } + if (EmuRunning == 1) { EmuStatus = 1; @@ -1663,12 +1682,18 @@ void Reset(void* blarg) SavestateLoaded = false; uiMenuItemDisable(MenuItem_UndoStateLoad); - if (ROMPath[0] == '\0') + if (ROMPath[0][0] == '\0') NDS::LoadBIOS(); else { - SetupSRAMPath(); - NDS::LoadROM(ROMPath, SRAMPath, Config::DirectBoot); + SetupSRAMPath(0); + NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot); + } + + if (ROMPath[1][0] != '\0') + { + SetupSRAMPath(1); + NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); } Run(); @@ -1683,6 +1708,10 @@ void Stop(bool internal) while (EmuStatus != 2); RunningSomething = false; + // eject any inserted GBA cartridge + GBACart::Eject(); + ROMPath[1][0] = '\0'; + uiWindowSetTitle(MainWindow, "melonDS " MELONDS_VERSION); for (int i = 0; i < 9; i++) uiMenuItemDisable(MenuItem_SaveStateSlot[i]); @@ -1703,41 +1732,53 @@ void Stop(bool internal) OSD::AddMessage(0xFFC040, "Shutdown"); } -void SetupSRAMPath() +void SetupSRAMPath(int slot) { - strncpy(SRAMPath, ROMPath, 1023); - SRAMPath[1023] = '\0'; - strncpy(SRAMPath + strlen(ROMPath) - 3, "sav", 3); + strncpy(SRAMPath[slot], ROMPath[slot], 1023); + SRAMPath[slot][1023] = '\0'; + strncpy(SRAMPath[slot] + strlen(ROMPath[slot]) - 3, "sav", 3); } -void TryLoadROM(char* file, int prevstatus) +void TryLoadROM(char* file, int slot, int prevstatus) { char oldpath[1024]; char oldsram[1024]; - strncpy(oldpath, ROMPath, 1024); - strncpy(oldsram, SRAMPath, 1024); + strncpy(oldpath, ROMPath[slot], 1024); + strncpy(oldsram, SRAMPath[slot], 1024); - strncpy(ROMPath, file, 1023); - ROMPath[1023] = '\0'; + strncpy(ROMPath[slot], file, 1023); + ROMPath[slot][1023] = '\0'; - SetupSRAMPath(); + SetupSRAMPath(0); + SetupSRAMPath(1); - if (NDS::LoadROM(ROMPath, SRAMPath, Config::DirectBoot)) + if (slot == 0 && NDS::LoadROM(ROMPath[slot], SRAMPath[slot], Config::DirectBoot)) { SavestateLoaded = false; uiMenuItemDisable(MenuItem_UndoStateLoad); - strncpy(PrevSRAMPath, SRAMPath, 1024); // safety + // Reload the inserted GBA cartridge (if any) + if (ROMPath[1][0] != '\0') NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); + + strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety Run(); } + else if (slot == 1 && NDS::LoadGBAROM(ROMPath[slot], SRAMPath[slot])) + { + SavestateLoaded = false; + uiMenuItemDisable(MenuItem_UndoStateLoad); + + strncpy(PrevSRAMPath[slot], SRAMPath[slot], 1024); // safety + if (RunningSomething) Run(); // do not start just from a GBA cart + } else { uiMsgBoxError(MainWindow, "Failed to load the ROM", "Make sure the file can be accessed and isn't opened in another application."); - strncpy(ROMPath, oldpath, 1024); - strncpy(SRAMPath, oldsram, 1024); + strncpy(ROMPath[slot], oldpath, 1024); + strncpy(SRAMPath[slot], oldsram, 1024); EmuRunning = prevstatus; } } @@ -1750,22 +1791,22 @@ void GetSavestateName(int slot, char* filename, int len) { int pos; - if (ROMPath[0] == '\0') // running firmware, no ROM + if (ROMPath[0][0] == '\0') // running firmware, no ROM { strcpy(filename, "firmware"); pos = 8; } else { - int l = strlen(ROMPath); + int l = strlen(ROMPath[0]); pos = l; - while (ROMPath[pos] != '.' && pos > 0) pos--; + while (ROMPath[0][pos] != '.' && pos > 0) pos--; if (pos == 0) pos = l; // avoid buffer overflow. shoddy if (pos > len-5) pos = len-5; - strncpy(&filename[0], ROMPath, pos); + strncpy(&filename[0], ROMPath[0], pos); } strcpy(&filename[pos], ".ml"); filename[pos+3] = '0'+slot; @@ -1809,6 +1850,8 @@ void LoadState(int slot) return; } + u32 oldGBACartCRC = GBACart::CartCRC; + // backup Savestate* backup = new Savestate("timewarp.mln", true); NDS::DoSavestate(backup); @@ -1833,21 +1876,36 @@ void LoadState(int slot) if (!failed) { - if (Config::SavestateRelocSRAM && ROMPath[0]!='\0') + if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0') { - strncpy(PrevSRAMPath, SRAMPath, 1024); + strncpy(PrevSRAMPath[0], SRAMPath[0], 1024); - strncpy(SRAMPath, filename, 1019); - int len = strlen(SRAMPath); - strcpy(&SRAMPath[len], ".sav"); - SRAMPath[len+4] = '\0'; + strncpy(SRAMPath[0], filename, 1019); + int len = strlen(SRAMPath[0]); + strcpy(&SRAMPath[0][len], ".sav"); + SRAMPath[0][len+4] = '\0'; - NDS::RelocateSave(SRAMPath, false); + NDS::RelocateSave(SRAMPath[0], false); + } + + bool loadedPartialGBAROM = false; + + // in case we have a GBA cart inserted, and the GBA ROM changes + // due to having loaded a save state, we do not want to reload + // the previous cartridge on reset, or commit writes to any + // loaded save file. therefore, their paths are "nulled". + if (GBACart::CartInserted && GBACart::CartCRC != oldGBACartCRC) + { + ROMPath[1][0] = '\0'; + SRAMPath[1][0] = '\0'; + loadedPartialGBAROM = true; } char msg[64]; - if (slot > 0) sprintf(msg, "State loaded from slot %d", slot); - else sprintf(msg, "State loaded from file"); + if (slot > 0) sprintf(msg, "State loaded from slot %d%s", + slot, loadedPartialGBAROM ? " (GBA ROM header only)" : ""); + else sprintf(msg, "State loaded from file%s", + loadedPartialGBAROM ? " (GBA ROM header only)" : ""); OSD::AddMessage(0, msg); SavestateLoaded = true; @@ -1898,14 +1956,14 @@ void SaveState(int slot) if (slot > 0) uiMenuItemEnable(MenuItem_LoadStateSlot[slot-1]); - if (Config::SavestateRelocSRAM && ROMPath[0]!='\0') + if (Config::SavestateRelocSRAM && ROMPath[0][0]!='\0') { - strncpy(SRAMPath, filename, 1019); - int len = strlen(SRAMPath); - strcpy(&SRAMPath[len], ".sav"); - SRAMPath[len+4] = '\0'; + strncpy(SRAMPath[0], filename, 1019); + int len = strlen(SRAMPath[0]); + strcpy(&SRAMPath[0][len], ".sav"); + SRAMPath[0][len+4] = '\0'; - NDS::RelocateSave(SRAMPath, true); + NDS::RelocateSave(SRAMPath[0], true); } } @@ -1932,10 +1990,10 @@ void UndoStateLoad() NDS::DoSavestate(backup); delete backup; - if (ROMPath[0]!='\0') + if (ROMPath[0][0]!='\0') { - strncpy(SRAMPath, PrevSRAMPath, 1024); - NDS::RelocateSave(SRAMPath, false); + strncpy(SRAMPath[0], PrevSRAMPath[0], 1024); + NDS::RelocateSave(SRAMPath[0], false); } OSD::AddMessage(0, "State load undone"); @@ -1979,7 +2037,11 @@ void OnDropFile(uiWindow* window, char* file, void* blarg) while (EmuStatus != 2); } - TryLoadROM(file, prevstatus); + TryLoadROM(file, 0, prevstatus); + } + else if (!strcasecmp(ext, "gba")) + { + TryLoadROM(file, 1, prevstatus); } } @@ -2010,7 +2072,7 @@ void OnOpenFile(uiMenuItem* item, uiWindow* window, void* blarg) EmuRunning = 2; while (EmuStatus != 2); - char* file = uiOpenFile(window, "DS ROM (*.nds)|*.nds;*.srl|Any file|*.*", Config::LastROMFolder); + char* file = uiOpenFile(window, "DS ROM (*.nds)|*.nds;*.srl|GBA ROM (*.gba)|*.gba|Any file|*.*", Config::LastROMFolder); if (!file) { EmuRunning = prevstatus; @@ -2021,8 +2083,17 @@ void OnOpenFile(uiMenuItem* item, uiWindow* window, void* blarg) while (file[pos] != '/' && file[pos] != '\\' && pos > 0) pos--; strncpy(Config::LastROMFolder, file, pos); Config::LastROMFolder[pos] = '\0'; + char* ext = &file[strlen(file)-3]; + + if (!strcasecmp(ext, "gba")) + { + TryLoadROM(file, 1, prevstatus); + } + else + { + TryLoadROM(file, 0, prevstatus); + } - TryLoadROM(file, prevstatus); uiFreeText(file); } @@ -2047,8 +2118,14 @@ void OnRun(uiMenuItem* item, uiWindow* window, void* blarg) { if (!RunningSomething) { - ROMPath[0] = '\0'; + ROMPath[0][0] = '\0'; NDS::LoadBIOS(); + + if (ROMPath[1][0] != '\0') + { + SetupSRAMPath(1); + NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); + } } Run(); @@ -2867,14 +2944,30 @@ int main(int argc, char** argv) if (!strcasecmp(ext, "nds") || !strcasecmp(ext, "srl")) { - strncpy(ROMPath, file, 1023); - ROMPath[1023] = '\0'; + strncpy(ROMPath[0], file, 1023); + ROMPath[0][1023] = '\0'; - SetupSRAMPath(); + SetupSRAMPath(0); - if (NDS::LoadROM(ROMPath, SRAMPath, Config::DirectBoot)) + if (NDS::LoadROM(ROMPath[0], SRAMPath[0], Config::DirectBoot)) Run(); } + + if (argc > 2) + { + file = argv[2]; + ext = &file[strlen(file)-3]; + + if (!strcasecmp(ext, "gba")) + { + strncpy(ROMPath[1], file, 1023); + ROMPath[1][1023] = '\0'; + + SetupSRAMPath(1); + + NDS::LoadGBAROM(ROMPath[1], SRAMPath[1]); + } + } } uiMain();