From 18269da46b1e1340c17b51d9fbad2c05076cd03d Mon Sep 17 00:00:00 2001 From: Sour Date: Fri, 3 Jan 2025 09:09:39 +0900 Subject: [PATCH] SNES: Add support for Sufami Turbo games --- Core/Core.vcxproj | 1 + Core/Core.vcxproj.filters | 6 + Core/Debugger/DebugUtilities.h | 6 + Core/SNES/BaseCartridge.cpp | 63 ++++++-- Core/SNES/BaseCartridge.h | 9 +- .../Coprocessors/SufamiTurbo/SufamiTurbo.h | 148 ++++++++++++++++++ Core/SNES/SnesConsole.cpp | 1 + Core/SNES/SnesConsole.h | 2 +- Core/Shared/BatteryManager.cpp | 13 +- Core/Shared/BatteryManager.h | 2 +- Core/Shared/FirmwareHelper.h | 20 +++ .../Shared/Interfaces/INotificationListener.h | 1 + Core/Shared/MemoryType.h | 3 + UI/Config/IntegrationConfig.cs | 5 +- UI/Debugger/Utilities/SaveRomActionHelper.cs | 1 + UI/Interop/DebugApi.cs | 3 + UI/Interop/EmuApi.cs | 7 + UI/Interop/FirmwareTypeExtensions.cs | 2 + UI/Interop/MemoryTypeExtensions.cs | 12 ++ UI/Interop/NotificationListener.cs | 1 + UI/Localization/resources.en.xml | 8 +- UI/Utilities/FileDialogHelper.cs | 5 +- UI/Utilities/FolderHelper.cs | 2 +- UI/Views/EmulationConfigView.axaml | 25 +-- UI/Windows/MainWindow.axaml.cs | 22 ++- Utilities/VirtualFile.cpp | 2 +- 26 files changed, 333 insertions(+), 37 deletions(-) create mode 100644 Core/SNES/Coprocessors/SufamiTurbo/SufamiTurbo.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 6432703cc..3524739b0 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -119,6 +119,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 5e6db8e0b..7cf8063e9 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -2970,6 +2970,9 @@ Shared + + SNES\Coprocessors\SufamiTurbo + @@ -3457,5 +3460,8 @@ {387c0b44-8063-45e8-a28a-b6d9e27b5a3f} + + {bfdd4bb9-41c2-4255-a8c3-0176c990cbf3} + \ No newline at end of file diff --git a/Core/Debugger/DebugUtilities.h b/Core/Debugger/DebugUtilities.h index 74abb206e..dfb87ac30 100644 --- a/Core/Debugger/DebugUtilities.h +++ b/Core/Debugger/DebugUtilities.h @@ -61,6 +61,9 @@ class DebugUtilities case MemoryType::SnesWorkRam: case MemoryType::BsxMemoryPack: case MemoryType::BsxPsRam: + case MemoryType::SufamiTurboFirmware: + case MemoryType::SufamiTurboSecondCart: + case MemoryType::SufamiTurboSecondCartRam: case MemoryType::SnesRegister: return CpuType::Snes; @@ -232,6 +235,8 @@ class DebugUtilities case MemoryType::DspProgramRom: case MemoryType::St018PrgRom: case MemoryType::St018DataRom: + case MemoryType::SufamiTurboFirmware: + case MemoryType::SufamiTurboSecondCart: case MemoryType::SpcRom: case MemoryType::SmsPrgRom: case MemoryType::SmsBootRom: @@ -255,6 +260,7 @@ class DebugUtilities case MemoryType::NesSaveRam: case MemoryType::GbCartRam: case MemoryType::SnesSaveRam: + case MemoryType::SufamiTurboSecondCartRam: case MemoryType::PceSaveRam: case MemoryType::SnesRegister: case MemoryType::SmsCartRam: diff --git a/Core/SNES/BaseCartridge.cpp b/Core/SNES/BaseCartridge.cpp index 73a489294..22f0c65be 100644 --- a/Core/SNES/BaseCartridge.cpp +++ b/Core/SNES/BaseCartridge.cpp @@ -19,6 +19,7 @@ #include "SNES/Coprocessors/BSX/BsxCart.h" #include "SNES/Coprocessors/BSX/BsxMemoryPack.h" #include "SNES/Coprocessors/SGB/SuperGameboy.h" +#include "SNES/Coprocessors/SufamiTurbo/SufamiTurbo.h" #include "Shared/EmuSettings.h" #include "Shared/SettingTypes.h" #include "Shared/BatteryManager.h" @@ -64,12 +65,18 @@ unique_ptr BaseCartridge::CreateCartridge(SnesConsole* console, V } cart->LoadRom(); cart->_emu->RegisterMemory(MemoryType::SnesPrgRom, cart->_prgRom, cart->_prgRomSize); + } else if(fileExt == ".st") { + if(cart->LoadSufamiTurbo(romFile)) { + return cart; + } else { + return nullptr; + } } else if(fileExt == ".gb" || fileExt == ".gbc" || fileExt == ".gbx") { if(cart->LoadGameboy(romFile)) { return cart; } else { return nullptr; - } + } } else { if(romData.size() < 0x8000) { return nullptr; @@ -88,7 +95,7 @@ unique_ptr BaseCartridge::CreateCartridge(SnesConsole* console, V } } else { cart->LoadRom(); - cart->EnsureValidPrgRomSize(); + BaseCartridge::EnsureValidPrgRomSize(cart->_prgRomSize, cart->_prgRom); } cart->_emu->RegisterMemory(MemoryType::SnesPrgRom, cart->_prgRom, cart->_prgRomSize); @@ -100,18 +107,18 @@ unique_ptr BaseCartridge::CreateCartridge(SnesConsole* console, V } } -void BaseCartridge::EnsureValidPrgRomSize() +void BaseCartridge::EnsureValidPrgRomSize(uint32_t& size, uint8_t*& rom) { - if((_prgRomSize & 0xFFF) != 0) { + if((size & 0xFFF) != 0) { //Round up to the next 4kb size, to ensure we have access to all the rom's data //Memory mappings expect a multiple of 4kb to work properly - uint32_t orgPrgSize = _prgRomSize; - _prgRomSize = (_prgRomSize & ~0xFFF) + 0x1000; - uint8_t* expandedPrgRom = new uint8_t[_prgRomSize]; - memset(expandedPrgRom, 0, _prgRomSize); - memcpy(expandedPrgRom, _prgRom, orgPrgSize); - delete[] _prgRom; - _prgRom = expandedPrgRom; + uint32_t orgPrgSize = size; + size = (size & ~0xFFF) + 0x1000; + uint8_t* expandedPrgRom = new uint8_t[size]; + memset(expandedPrgRom, 0, size); + memcpy(expandedPrgRom, rom, orgPrgSize); + delete[] rom; + rom = expandedPrgRom; } } @@ -404,6 +411,10 @@ void BaseCartridge::SaveBattery() if(_gameboy) { _gameboy->SaveBattery(); } + + if(_sufamiTurbo) { + _sufamiTurbo->SaveBattery(); + } } void BaseCartridge::Init(MemoryMappings &mm) @@ -555,7 +566,11 @@ bool BaseCartridge::MapSpecificCarts(MemoryMappings &mm) { string name = GetCartName(); string code = GetGameCode(); - if(GetCartName() == "DEZAEMON") { + + if(_sufamiTurbo) { + _sufamiTurbo->InitializeMappings(mm, _prgRomHandlers, _saveRamHandlers); + return true; + } else if(GetCartName() == "DEZAEMON") { //LOROM with mirrored SRAM? mm.RegisterHandler(0x00, 0x7D, 0x8000, 0xFFFF, _prgRomHandlers); mm.RegisterHandler(0x80, 0xFF, 0x8000, 0xFFFF, _prgRomHandlers); @@ -644,6 +659,30 @@ void BaseCartridge::LoadSpc() SetupCpuHalt(); } +bool BaseCartridge::LoadSufamiTurbo(VirtualFile& romFile) +{ + _sufamiTurbo.reset(SufamiTurbo::Init(_emu, romFile)); + if(!_sufamiTurbo) { + return false; + } + + vector romData; + romFile.ReadFile(romData); + + _prgRomSize = (uint32_t)romData.size(); + _prgRom = new uint8_t[_prgRomSize]; + memcpy(_prgRom, romData.data(), romData.size()); + BaseCartridge::EnsureValidPrgRomSize(_prgRomSize, _prgRom); + _emu->RegisterMemory(MemoryType::SnesPrgRom, _prgRom, _prgRomSize); + + _saveRamSize = SufamiTurbo::GetSaveRamSize(romData); + _saveRam = new uint8_t[_saveRamSize]; + _emu->RegisterMemory(MemoryType::SnesSaveRam, _saveRam, _saveRamSize); + _emu->GetSettings()->InitializeRam(GetRamPowerOnState(), _saveRam, _saveRamSize); + + return true; +} + bool BaseCartridge::LoadGameboy(VirtualFile& romFile) { _cartInfo = { }; diff --git a/Core/SNES/BaseCartridge.h b/Core/SNES/BaseCartridge.h index dd61dd844..80c3abad8 100644 --- a/Core/SNES/BaseCartridge.h +++ b/Core/SNES/BaseCartridge.h @@ -21,6 +21,7 @@ class Gameboy; class SnesConsole; class Emulator; class SpcFileData; +class SufamiTurbo; enum class ConsoleRegion; enum class RamState; @@ -47,6 +48,7 @@ class BaseCartridge : public ISerializable BsxCart* _bsx = nullptr; unique_ptr _bsxMemPack; unique_ptr _gameboy; + unique_ptr _sufamiTurbo; CartFlags::CartFlags _flags = CartFlags::CartFlags::None; CoprocessorType _coprocessorType = CoprocessorType::None; @@ -83,10 +85,13 @@ class BaseCartridge : public ISerializable void InitRamPowerOnState(); void LoadRom(); - void EnsureValidPrgRomSize(); void LoadSpc(); + + bool LoadSufamiTurbo(VirtualFile& romFile); + bool LoadGameboy(VirtualFile& romFile); + void SetupCpuHalt(); void InitCoprocessor(); void LoadEmbeddedFirmware(); @@ -99,6 +104,8 @@ class BaseCartridge : public ISerializable static unique_ptr CreateCartridge(SnesConsole* console, VirtualFile &romFile); + static void EnsureValidPrgRomSize(uint32_t& size, uint8_t*& rom); + void Reset(); void SaveBattery(); diff --git a/Core/SNES/Coprocessors/SufamiTurbo/SufamiTurbo.h b/Core/SNES/Coprocessors/SufamiTurbo/SufamiTurbo.h new file mode 100644 index 000000000..b485d29df --- /dev/null +++ b/Core/SNES/Coprocessors/SufamiTurbo/SufamiTurbo.h @@ -0,0 +1,148 @@ +#pragma once +#include "pch.h" +#include "SNES/IMemoryHandler.h" +#include "SNES/RomHandler.h" +#include "SNES/MemoryMappings.h" +#include "Shared/Emulator.h" +#include "Shared/BatteryManager.h" +#include "Shared/FirmwareHelper.h" +#include "Utilities/VirtualFile.h" + +struct SufamiTurboFilePromptMessage +{ + char Filename[5000]; +}; + +class SufamiTurbo +{ +private: + Emulator* _emu = nullptr; + string _nameSlotA; + + uint8_t* _firmware = nullptr; + uint32_t _firmwareSize = 0; + + string _cartName; + uint8_t* _cartRom = nullptr; + uint32_t _cartRomSize = 0; + + uint8_t* _cartRam = nullptr; + uint32_t _cartRamSize = 0; + + vector> _firmwareHandlers; + vector> _cartRomHandlers; + vector> _cartRamHandlers; + + SufamiTurbo() {} + +public: + static SufamiTurbo* Init(Emulator* emu, VirtualFile& slotA) + { + vector firmware; + if(!FirmwareHelper::LoadSufamiTurboFirmware(emu, firmware)) { + return nullptr; + } + + SufamiTurbo* st = new SufamiTurbo(); + st->_emu = emu; + st->_nameSlotA = FolderUtilities::GetFilename(slotA.GetFileName(), false); + + st->_firmwareSize = (uint32_t)firmware.size(); + st->_firmware = new uint8_t[st->_firmwareSize]; + memcpy(st->_firmware, firmware.data(), firmware.size()); + BaseCartridge::EnsureValidPrgRomSize(st->_firmwareSize, st->_firmware); + emu->RegisterMemory(MemoryType::SufamiTurboFirmware, st->_firmware, st->_firmwareSize); + for(uint32_t i = 0; i < st->_firmwareSize; i += 0x1000) { + st->_firmwareHandlers.push_back(unique_ptr(new RomHandler(st->_firmware, i, st->_firmwareSize, MemoryType::SufamiTurboFirmware))); + } + + SufamiTurboFilePromptMessage msg = {}; + emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::SufamiTurboFilePrompt, &msg); + + string slot2File = string(msg.Filename, strlen(msg.Filename)); + if(slot2File.size()) { + VirtualFile file = slot2File; + if(file.IsValid()) { + vector cart; + file.ReadFile(cart); + + st->_cartName = FolderUtilities::GetFilename(file.GetFileName(), false); + if(st->_nameSlotA == st->_cartName) { + st->_cartName += "_SlotB"; + } + + st->_cartRomSize = (uint32_t)cart.size(); + st->_cartRom = new uint8_t[st->_cartRomSize]; + memcpy(st->_cartRom, cart.data(), cart.size()); + BaseCartridge::EnsureValidPrgRomSize(st->_cartRomSize, st->_cartRom); + + emu->RegisterMemory(MemoryType::SufamiTurboSecondCart, st->_cartRom, st->_cartRomSize); + + for(uint32_t i = 0; i < st->_cartRomSize; i += 0x1000) { + st->_cartRomHandlers.push_back(unique_ptr(new RomHandler(st->_cartRom, i, st->_cartRomSize, MemoryType::SufamiTurboSecondCart))); + } + + st->_cartRamSize = GetSaveRamSize(cart); + st->_cartRam = new uint8_t[st->_cartRamSize]; + emu->RegisterMemory(MemoryType::SufamiTurboSecondCartRam, st->_cartRam, st->_cartRamSize); + memset(st->_cartRam, 0, st->_cartRamSize); + + emu->GetBatteryManager()->LoadBattery(st->_cartName + ".srm", st->_cartRam, st->_cartRamSize); + + for(uint32_t i = 0; i < st->_cartRamSize; i += 0x1000) { + st->_cartRamHandlers.push_back(unique_ptr(new RamHandler(st->_cartRam, i, st->_cartRamSize, MemoryType::SufamiTurboSecondCartRam))); + } + } + } + + return st; + } + + static uint32_t GetSaveRamSize(vector& cart) + { + auto checkMarker = [&](string marker) { + return std::search((char*)cart.data(), (char*)cart.data()+cart.size(), marker.c_str(), marker.c_str()+marker.size()) != (char*)cart.data() + cart.size(); + }; + + if(checkMarker("POIPOI.Ver") || checkMarker("SDBATTLE ")) { + return 0x800; + } else if(checkMarker("SD \xB6\xDE\xDD\xC0\xDE\xD1 GN")) { + //SD ガンダム GN + return 0x2000; + } + + return 0; + } + + void InitializeMappings(MemoryMappings& mm, vector>& prgRomHandlers, vector>& saveRamHandlers) + { + mm.RegisterHandler(0x20, 0x3F, 0x8000, 0xFFFF, prgRomHandlers); + mm.RegisterHandler(0xA0, 0xBF, 0x8000, 0xFFFF, prgRomHandlers); + + mm.RegisterHandler(0x60, 0x63, 0x8000, 0xFFFF, saveRamHandlers); + mm.RegisterHandler(0xE0, 0xE3, 0x8000, 0xFFFF, saveRamHandlers); + + mm.RegisterHandler(0x00, 0x1F, 0x8000, 0xFFFF, _firmwareHandlers); + mm.RegisterHandler(0x80, 0x9F, 0x8000, 0xFFFF, _firmwareHandlers); + + mm.RegisterHandler(0x40, 0x5F, 0x8000, 0xFFFF, _cartRomHandlers); + mm.RegisterHandler(0xC0, 0xDF, 0x8000, 0xFFFF, _cartRomHandlers); + + mm.RegisterHandler(0x70, 0x73, 0x8000, 0xFFFF, _cartRamHandlers); + mm.RegisterHandler(0xF0, 0xF3, 0x8000, 0xFFFF, _cartRamHandlers); + } + + void SaveBattery() + { + if(_cartRam) { + _emu->GetBatteryManager()->SaveBattery(_cartName + ".srm", _cartRam, _cartRamSize); + } + } + + ~SufamiTurbo() + { + delete[] _firmware; + delete[] _cartRom; + delete[] _cartRam; + } +}; \ No newline at end of file diff --git a/Core/SNES/SnesConsole.cpp b/Core/SNES/SnesConsole.cpp index 1c683b935..e589070c0 100644 --- a/Core/SNES/SnesConsole.cpp +++ b/Core/SNES/SnesConsole.cpp @@ -472,6 +472,7 @@ AddressInfo SnesConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpu case MemoryType::SnesPrgRom: case MemoryType::SnesWorkRam: case MemoryType::SnesSaveRam: + case MemoryType::SufamiTurboFirmware: { if(!mappings) { return unmapped; diff --git a/Core/SNES/SnesConsole.h b/Core/SNES/SnesConsole.h index f99df5b1b..6a1efb565 100644 --- a/Core/SNES/SnesConsole.h +++ b/Core/SNES/SnesConsole.h @@ -72,7 +72,7 @@ class SnesConsole final : public IConsole SnesConsole(Emulator* emu); ~SnesConsole(); - static vector GetSupportedExtensions() { return { ".sfc", ".swc", ".fig", ".smc", ".bs", ".gb", ".gbc", ".gbx", ".spc" }; } + static vector GetSupportedExtensions() { return { ".sfc", ".swc", ".fig", ".smc", ".bs", ".gb", ".gbc", ".gbx", ".spc", ".st" }; } static vector GetSupportedSignatures() { return { "SNES-SPC700 Sound File Data" }; } void Initialize(); diff --git a/Core/Shared/BatteryManager.cpp b/Core/Shared/BatteryManager.cpp index fbb991029..958db2a07 100644 --- a/Core/Shared/BatteryManager.cpp +++ b/Core/Shared/BatteryManager.cpp @@ -2,6 +2,7 @@ #include "Shared/BatteryManager.h" #include "Utilities/VirtualFile.h" #include "Utilities/FolderUtilities.h" +#include "Utilities/StringUtilities.h" void BatteryManager::Initialize(string romName, bool setBatteryFlag) { @@ -9,9 +10,13 @@ void BatteryManager::Initialize(string romName, bool setBatteryFlag) _hasBattery = setBatteryFlag; } -string BatteryManager::GetBasePath() +string BatteryManager::GetBasePath(string& extension) { - return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), _romName); + if(StringUtilities::StartsWith(extension, ".")) { + return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), _romName + extension); + } else { + return FolderUtilities::CombinePath(FolderUtilities::GetSaveFolder(), extension); + } } void BatteryManager::SetBatteryProvider(shared_ptr provider) @@ -32,7 +37,7 @@ void BatteryManager::SaveBattery(string extension, uint8_t* data, uint32_t lengt } _hasBattery = true; - ofstream out(GetBasePath() + extension, ios::binary); + ofstream out(GetBasePath(extension), ios::binary); if(out) { out.write((char*)data, length); } @@ -52,7 +57,7 @@ vector BatteryManager::LoadBattery(string extension) //Used by movie player to provider initial state of ram at startup batteryData = provider->LoadBattery(extension); } else { - VirtualFile file = GetBasePath() + extension; + VirtualFile file = GetBasePath(extension); if(file.IsValid()) { file.ReadFile(batteryData); } diff --git a/Core/Shared/BatteryManager.h b/Core/Shared/BatteryManager.h index 329f0e682..54cd8683d 100644 --- a/Core/Shared/BatteryManager.h +++ b/Core/Shared/BatteryManager.h @@ -22,7 +22,7 @@ class BatteryManager std::weak_ptr _provider; std::weak_ptr _recorder; - string GetBasePath(); + string GetBasePath(string& extension); public: void Initialize(string romName, bool setBatteryFlag = false); diff --git a/Core/Shared/FirmwareHelper.h b/Core/Shared/FirmwareHelper.h index 5df7f24ec..a00f0b0d2 100644 --- a/Core/Shared/FirmwareHelper.h +++ b/Core/Shared/FirmwareHelper.h @@ -16,6 +16,7 @@ enum class FirmwareType ST011, ST018, Satellaview, + SufamiTurbo, Gameboy, GameboyColor, GameboyAdvance, @@ -185,6 +186,25 @@ class FirmwareHelper return false; } + static bool LoadSufamiTurboFirmware(Emulator* emu, vector& data) + { + string filename = "SufamiTurbo.sfc"; + + if(AttemptLoadFirmware(data, filename, 0x40000)) { + return true; + } + + MissingFirmwareMessage msg(filename.c_str(), FirmwareType::SufamiTurbo, 0x40000); + emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::MissingFirmware, &msg); + + if(AttemptLoadFirmware(data, filename, 0x40000)) { + return true; + } + + MessageManager::DisplayMessage("Error", "Could not find firmware file for Sufami Turbo"); + return false; + } + static bool LoadSgbFirmware(Emulator* emu, uint8_t** prgRom, uint32_t& prgSize, bool useSgb2, bool promptForFirmware) { string filename = useSgb2 ? "SGB2.sfc" : "SGB1.sfc"; diff --git a/Core/Shared/Interfaces/INotificationListener.h b/Core/Shared/Interfaces/INotificationListener.h index ee1391ac8..076cef4aa 100644 --- a/Core/Shared/Interfaces/INotificationListener.h +++ b/Core/Shared/Interfaces/INotificationListener.h @@ -22,6 +22,7 @@ enum class ConsoleNotificationType ViewerRefresh, EventViewerRefresh, MissingFirmware, + SufamiTurboFilePrompt, BeforeGameUnload, BeforeGameLoad, GameLoadFailed, diff --git a/Core/Shared/MemoryType.h b/Core/Shared/MemoryType.h index 56bdd1d53..609cbfec1 100644 --- a/Core/Shared/MemoryType.h +++ b/Core/Shared/MemoryType.h @@ -38,6 +38,9 @@ enum class MemoryType St018PrgRom, St018DataRom, St018WorkRam, + SufamiTurboFirmware, + SufamiTurboSecondCart, + SufamiTurboSecondCartRam, GbPrgRom, GbWorkRam, diff --git a/UI/Config/IntegrationConfig.cs b/UI/Config/IntegrationConfig.cs index 5ada29635..0bea090a7 100644 --- a/UI/Config/IntegrationConfig.cs +++ b/UI/Config/IntegrationConfig.cs @@ -76,6 +76,8 @@ public bool IsMemoryTypeImportEnabled(MemoryType memType) case MemoryType.PcePrgRom: case MemoryType.SmsPrgRom: case MemoryType.SpcRom: + case MemoryType.SufamiTurboFirmware: + case MemoryType.SufamiTurboSecondCart: case MemoryType.DspProgramRom: case MemoryType.DspDataRom: case MemoryType.GbBootRom: @@ -107,11 +109,12 @@ public bool IsMemoryTypeImportEnabled(MemoryType memType) return ImportWorkRamLabels; case MemoryType.SnesSaveRam: + case MemoryType.BsxMemoryPack: + case MemoryType.SufamiTurboSecondCartRam: case MemoryType.NesSaveRam: case MemoryType.PceSaveRam: case MemoryType.GbCartRam: case MemoryType.SmsCartRam: - case MemoryType.BsxMemoryPack: case MemoryType.GbaSaveRam: case MemoryType.WsCartRam: return ImportSaveRamLabels; diff --git a/UI/Debugger/Utilities/SaveRomActionHelper.cs b/UI/Debugger/Utilities/SaveRomActionHelper.cs index 0b67f8030..e1e74c31b 100644 --- a/UI/Debugger/Utilities/SaveRomActionHelper.cs +++ b/UI/Debugger/Utilities/SaveRomActionHelper.cs @@ -58,6 +58,7 @@ private static bool IsSaveRomSupported() RomFormat.Sg => true, RomFormat.ColecoVision => true, RomFormat.Gba => true, + RomFormat.Ws => true, _ => false }; } diff --git a/UI/Interop/DebugApi.cs b/UI/Interop/DebugApi.cs index 2fea84636..cd43fb1f1 100644 --- a/UI/Interop/DebugApi.cs +++ b/UI/Interop/DebugApi.cs @@ -621,6 +621,9 @@ public enum MemoryType St018PrgRom, St018DataRom, St018WorkRam, + SufamiTurboFirmware, + SufamiTurboSecondCart, + SufamiTurboSecondCartRam, GbPrgRom, GbWorkRam, diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index 3c3801779..e6ff861ce 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -257,6 +257,7 @@ public enum FirmwareType ST011, ST018, Satellaview, + SufamiTurbo, Gameboy, GameboyColor, GameboyAdvance, @@ -285,6 +286,12 @@ public struct MissingFirmwareMessage public UInt32 AltSize; } + public struct SufamiTurboFilePromptMessage + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5000)] + public byte[] Filename; + } + public struct ExecuteShortcutParams { public EmulatorShortcut Shortcut; diff --git a/UI/Interop/FirmwareTypeExtensions.cs b/UI/Interop/FirmwareTypeExtensions.cs index 87a5b7072..59d754f1e 100644 --- a/UI/Interop/FirmwareTypeExtensions.cs +++ b/UI/Interop/FirmwareTypeExtensions.cs @@ -18,6 +18,8 @@ public static FirmwareFiles GetFirmwareInfo(this FirmwareType type) case FirmwareType.ST011: return new("st011.rom") { new(0xD000, "8B2B3F3F3E6E29F4D21D8BC736B400BC988B7D2214EBEE15643F01C1FEE2F364") }; case FirmwareType.ST018: return new("st018.rom") { new(0x28000, "6DF209AB5D2524D1839C038BE400AE5EB20DAFC14A3771A3239CD9E8ACD53806") }; + case FirmwareType.SufamiTurbo: return new("SufamiTurbo.sfc") { new(0x40000, "EDACB453DA14F825F05D1134D6035F4BF034E55F7CFB97C70C4EE107EABC7342") }; + case FirmwareType.Satellaview: return new("BS-X.bin") { new(1024 * 1024, "27CFDB99F7E4252BF3740D420147B63C4C88616883BC5E7FE43F2F30BF8C8CBB", //Japan, no DRM diff --git a/UI/Interop/MemoryTypeExtensions.cs b/UI/Interop/MemoryTypeExtensions.cs index 77a7aa292..ec1103f1e 100644 --- a/UI/Interop/MemoryTypeExtensions.cs +++ b/UI/Interop/MemoryTypeExtensions.cs @@ -71,6 +71,9 @@ public static CpuType ToCpuType(this MemoryType memType) case MemoryType.SnesCgRam: case MemoryType.BsxPsRam: case MemoryType.BsxMemoryPack: + case MemoryType.SufamiTurboFirmware: + case MemoryType.SufamiTurboSecondCart: + case MemoryType.SufamiTurboSecondCartRam: case MemoryType.SnesRegister: return CpuType.Snes; @@ -246,6 +249,8 @@ public static bool IsRomMemory(this MemoryType memType) case MemoryType.DspProgramRom: case MemoryType.St018PrgRom: case MemoryType.St018DataRom: + case MemoryType.SufamiTurboFirmware: + case MemoryType.SufamiTurboSecondCart: case MemoryType.SpcRom: case MemoryType.SmsPrgRom: case MemoryType.SmsBootRom: @@ -272,6 +277,9 @@ public static bool SupportsLabels(this MemoryType memType) case MemoryType.St018PrgRom: case MemoryType.St018DataRom: case MemoryType.St018WorkRam: + case MemoryType.SufamiTurboFirmware: + case MemoryType.SufamiTurboSecondCart: + case MemoryType.SufamiTurboSecondCartRam: //Gameboy case MemoryType.GbPrgRom: @@ -461,6 +469,10 @@ public static string GetShortName(this MemoryType memType) MemoryType.BsxPsRam => "PSRAM", MemoryType.BsxMemoryPack => "MPACK", + + MemoryType.SufamiTurboFirmware => "BOOT", + MemoryType.SufamiTurboSecondCart => "BPRG", + MemoryType.SufamiTurboSecondCartRam => "BRAM", MemoryType.GameboyMemory => "CPU", MemoryType.GbPrgRom => "PRG", diff --git a/UI/Interop/NotificationListener.cs b/UI/Interop/NotificationListener.cs index 7ade30a2e..afa388258 100644 --- a/UI/Interop/NotificationListener.cs +++ b/UI/Interop/NotificationListener.cs @@ -80,6 +80,7 @@ public enum ConsoleNotificationType ViewerRefresh, EventViewerRefresh, MissingFirmware, + SufamiTurboFilePrompt, BeforeGameUnload, BeforeGameLoad, GameLoadFailed, diff --git a/UI/Localization/resources.en.xml b/UI/Localization/resources.en.xml index 5633927dc..3b286f3dc 100644 --- a/UI/Localization/resources.en.xml +++ b/UI/Localization/resources.en.xml @@ -1725,6 +1725,8 @@ Save changes? Keep changes? + Do you want to load a second Sufami Turbo ROM to insert into slot B? + This file does not match any of the known firmwares and may not work properly. Expected (SHA256): {0} Current (SHA256): {1} This file does not have the required size. Expected: {0} bytes Current: {1} bytes The following file will be deleted permanently. Are you sure? {0} @@ -2683,6 +2685,7 @@ E ST010 ST011 ST018 + Sufami Turbo Satellaview (BS-X) Game Boy CPU Game Boy Color CPU @@ -2749,7 +2752,10 @@ E ST018 PRG ROM ST018 Data ROM ST018 Work RAM - + Sufami Turbo Firmware + Sufami Slot B ROM + Sufami Slot B RAM + GB - CPU Memory GB - PRG ROM GB - Work RAM diff --git a/UI/Utilities/FileDialogHelper.cs b/UI/Utilities/FileDialogHelper.cs index 90cfab90f..564d22abb 100644 --- a/UI/Utilities/FileDialogHelper.cs +++ b/UI/Utilities/FileDialogHelper.cs @@ -39,6 +39,7 @@ public class FileDialogHelper public const string NesAsmLabelExt = "fns"; public const string BinExt = "bin"; public const string NesExt = "nes"; + public const string SufamiTurboExt = "st"; public static async Task OpenFile(string? initialFolder, IRenderRoot? parent, params string[] extensions) { @@ -51,7 +52,7 @@ public class FileDialogHelper foreach(string ext in extensions) { if(ext == FileDialogHelper.RomExt) { filter.Add(new FilePickerFileType("All ROM files") { Patterns = new List() { - "*.sfc", "*.fig", "*.smc", "*.bs", "*.spc", + "*.sfc", "*.fig", "*.smc", "*.bs", "*.st", "*.spc", "*.nes", "*.fds", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe", "*.gb", "*.gbc", "*.gbx", "*.gbs", "*.pce", "*.sgx", "*.cue", "*.hes", @@ -60,7 +61,7 @@ public class FileDialogHelper "*.ws", "*.wsc", "*.zip", "*.7z" } }); - filter.Add(new FilePickerFileType("SNES ROM files") { Patterns = new List() { "*.sfc", "*.fig", "*.smc", "*.bs", "*.spc" } }); + filter.Add(new FilePickerFileType("SNES ROM files") { Patterns = new List() { "*.sfc", "*.fig", "*.smc", "*.bs", "*.st", "*.spc" } }); filter.Add(new FilePickerFileType("NES ROM files") { Patterns = new List() { "*.nes", "*.fds", "*.unif", "*.unf", "*.studybox", "*.nsf", "*.nsfe" } }); filter.Add(new FilePickerFileType("GB ROM files") { Patterns = new List() { "*.gb", "*.gbc", "*.gbx", "*.gbs" } }); filter.Add(new FilePickerFileType("GBA ROM files") { Patterns = new List() { "*.gba" } }); diff --git a/UI/Utilities/FolderHelper.cs b/UI/Utilities/FolderHelper.cs index d5b636529..bdebbe988 100644 --- a/UI/Utilities/FolderHelper.cs +++ b/UI/Utilities/FolderHelper.cs @@ -11,7 +11,7 @@ namespace Mesen.Utilities public static class FolderHelper { private static HashSet _romExtensions = new HashSet() { - ".sfc", ".smc", ".fig", ".swc", ".bs", + ".sfc", ".smc", ".fig", ".swc", ".bs", ".st", ".gb", ".gbc", ".gbx", ".nes", ".unif", ".unf", ".fds", ".studybox", ".pce", ".sgx", ".cue", diff --git a/UI/Views/EmulationConfigView.axaml b/UI/Views/EmulationConfigView.axaml index 8ebb0c21e..b8a5dbdfa 100644 --- a/UI/Views/EmulationConfigView.axaml +++ b/UI/Views/EmulationConfigView.axaml @@ -92,7 +92,7 @@ - + @@ -111,20 +111,23 @@ - - + + - - + + - - + + - - + + - - + + + + + diff --git a/UI/Windows/MainWindow.axaml.cs b/UI/Windows/MainWindow.axaml.cs index 770858a76..9e22dab5c 100644 --- a/UI/Windows/MainWindow.axaml.cs +++ b/UI/Windows/MainWindow.axaml.cs @@ -24,6 +24,7 @@ using Mesen.Localization; using System.Diagnostics; using Avalonia.VisualTree; +using System.Text; namespace Mesen.Windows { @@ -367,7 +368,7 @@ private void OnNotification(NotificationEventArgs e) }); break; - case ConsoleNotificationType.MissingFirmware: + case ConsoleNotificationType.MissingFirmware: { MissingFirmwareMessage msg = Marshal.PtrToStructure(e.Parameter); TaskCompletionSource tcs = new TaskCompletionSource(); Dispatcher.UIThread.Post(async () => { @@ -376,6 +377,25 @@ private void OnNotification(NotificationEventArgs e) }); tcs.Task.Wait(); break; + } + + case ConsoleNotificationType.SufamiTurboFilePrompt: { + SufamiTurboFilePromptMessage msg = Marshal.PtrToStructure(e.Parameter); + TaskCompletionSource tcs = new TaskCompletionSource(); + Dispatcher.UIThread.Post(async () => { + if(await MesenMsgBox.Show(this, "PromptLoadSufamiTurbo", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { + string? selectedFile = await FileDialogHelper.OpenFile(null, this, FileDialogHelper.SufamiTurboExt); + if(selectedFile != null) { + byte[] file = Encoding.UTF8.GetBytes(selectedFile); + Array.Copy(file, msg.Filename, file.Length); + Marshal.StructureToPtr(msg, e.Parameter, false); + } + } + tcs.SetResult(); + }); + tcs.Task.Wait(); + break; + } case ConsoleNotificationType.BeforeGameLoad: Dispatcher.UIThread.Post(() => { diff --git a/Utilities/VirtualFile.cpp b/Utilities/VirtualFile.cpp index ef0fa3396..a8dfb8f30 100644 --- a/Utilities/VirtualFile.cpp +++ b/Utilities/VirtualFile.cpp @@ -13,7 +13,7 @@ const std::initializer_list VirtualFile::RomExtensions = { ".nes", ".fds", ".unif", ".unf", ".nsf", ".nsfe", ".studybox", - ".sfc", ".swc", ".fig", ".smc", ".bs", ".spc", + ".sfc", ".swc", ".fig", ".smc", ".bs", ".st", ".spc", ".gb", ".gbc", ".gbx", ".gbs", ".pce", ".sgx", ".cue", ".hes", ".sms", ".gg", ".sg", ".col",