diff --git a/Core/BaseCartridge.cpp b/Core/BaseCartridge.cpp index a59447b..21d829f 100644 --- a/Core/BaseCartridge.cpp +++ b/Core/BaseCartridge.cpp @@ -271,6 +271,8 @@ CoprocessorType BaseCartridge::GetCoprocessorType() } break; } + } else if(GetGameCode() == "042J") { + return CoprocessorType::SGB; } return CoprocessorType::None; @@ -629,7 +631,8 @@ bool BaseCartridge::LoadGameboy(VirtualFile &romFile) _headerOffset = Gameboy::HeaderOffset; if(_gameboy->IsSgb()) { - if(!FirmwareHelper::LoadSgbFirmware(_console, &_prgRom, _prgRomSize)) { + EmulationConfig cfg = _console->GetSettings()->GetEmulationConfig(); + if(!FirmwareHelper::LoadSgbFirmware(_console, &_prgRom, _prgRomSize, cfg.UseSgb2)) { return false; } LoadRom(); diff --git a/Core/CartTypes.h b/Core/CartTypes.h index 72dc6d8..07e4b12 100644 --- a/Core/CartTypes.h +++ b/Core/CartTypes.h @@ -64,7 +64,8 @@ enum class FirmwareType Gameboy, GameboyColor, SgbGameboyCpu, - SGB + SGB1, + SGB2 }; struct RomInfo diff --git a/Core/FirmwareHelper.h b/Core/FirmwareHelper.h index 13a047c..7bdcee9 100644 --- a/Core/FirmwareHelper.h +++ b/Core/FirmwareHelper.h @@ -107,20 +107,21 @@ public: return false; } - static bool LoadSgbFirmware(Console* console, uint8_t** prgRom, uint32_t& prgSize) + static bool LoadSgbFirmware(Console* console, uint8_t** prgRom, uint32_t& prgSize, bool useSgb2) { - prgSize = 0x40000; - if(AttemptLoadFirmware(prgRom, "SgbBios.sfc", prgSize)) { + string filename = useSgb2 ? "SGB2.sfc" : "SGB1.sfc"; + prgSize = useSgb2 ? 0x80000 : 0x40000; + if(AttemptLoadFirmware(prgRom, filename, prgSize)) { return true; } MissingFirmwareMessage msg; - msg.Filename = "SgbBios.sfc"; - msg.Firmware = FirmwareType::SGB; + msg.Filename = filename.c_str(); + msg.Firmware = useSgb2 ? FirmwareType::SGB2 : FirmwareType::SGB1; msg.Size = prgSize; console->GetNotificationManager()->SendNotification(ConsoleNotificationType::MissingFirmware, &msg); - if(AttemptLoadFirmware(prgRom, "SgbBios.sfc", prgSize)) { + if(AttemptLoadFirmware(prgRom, filename, prgSize)) { return true; } diff --git a/Core/Gameboy.cpp b/Core/Gameboy.cpp index 3944459..b63dbff 100644 --- a/Core/Gameboy.cpp +++ b/Core/Gameboy.cpp @@ -15,7 +15,6 @@ #include "EmuSettings.h" #include "MessageManager.h" #include "FirmwareHelper.h" -#include "SuperGameboy.h" #include "GbBootRom.h" #include "../Utilities/VirtualFile.h" #include "../Utilities/Serializer.h" @@ -100,7 +99,11 @@ Gameboy* Gameboy::Create(Console* console, VirtualFile &romFile) case GameboyModel::SuperGameboy: gb->_bootRom = new uint8_t[gb->_bootRomSize]; - memcpy(gb->_bootRom, sgbBootRom, gb->_bootRomSize); + if(cfg.UseSgb2) { + memcpy(gb->_bootRom, sgb2BootRom, gb->_bootRomSize); + } else { + memcpy(gb->_bootRom, sgbBootRom, gb->_bootRomSize); + } break; } } @@ -139,6 +142,8 @@ Gameboy::~Gameboy() void Gameboy::PowerOn(SuperGameboy* superGameboy) { + _superGameboy = superGameboy; + shared_ptr settings = _console->GetSettings(); settings->InitializeRam(_cartRam, _cartRamSize); settings->InitializeRam(_workRam, _workRamSize); @@ -158,8 +163,6 @@ void Gameboy::PowerOn(SuperGameboy* superGameboy) _cpu->Init(_console, this, _memoryManager.get()); _ppu->Init(_console, this, _memoryManager.get(), _dmaController.get(), _videoRam, _spriteRam); _dmaController->Init(_memoryManager.get(), _ppu.get(), _cpu.get()); - - _superGameboy = superGameboy; } void Gameboy::Exec() @@ -167,15 +170,9 @@ void Gameboy::Exec() _cpu->Exec(); } -void Gameboy::Run(uint64_t masterClock) +void Gameboy::Run(uint64_t runUntilClock) { - if(!(_superGameboy->GetControl() & 0x80)) { - return; - } - - //TODO support SGB2 timings - masterClock = (masterClock - _superGameboy->GetResetClock()) / 5; - while(_memoryManager->GetCycleCount() < masterClock) { + while(_memoryManager->GetCycleCount() < runUntilClock) { _cpu->Exec(); } } diff --git a/Core/Gameboy.h b/Core/Gameboy.h index b3f07a2..b199c46 100644 --- a/Core/Gameboy.h +++ b/Core/Gameboy.h @@ -66,7 +66,7 @@ public: void PowerOn(SuperGameboy* superGameboy = nullptr); void Exec(); - void Run(uint64_t masterClock); + void Run(uint64_t runUntilClock); void LoadBattery(); void SaveBattery(); diff --git a/Core/GbApu.cpp b/Core/GbApu.cpp index 267b8e2..34f9d5c 100644 --- a/Core/GbApu.cpp +++ b/Core/GbApu.cpp @@ -4,6 +4,7 @@ #include "Gameboy.h" #include "SoundMixer.h" #include "EmuSettings.h" +#include "SuperGameboy.h" #include "../Utilities/Serializer.h" GbApu::GbApu() @@ -36,8 +37,8 @@ void GbApu::Init(Console* console, Gameboy* gameboy) blip_clear(_rightChannel); if(_gameboy->IsSgb()) { - blip_set_rates(_leftChannel, _console->GetMasterClockRate() / 5, GbApu::SampleRate); - blip_set_rates(_rightChannel, _console->GetMasterClockRate() / 5, GbApu::SampleRate); + blip_set_rates(_leftChannel, _gameboy->GetSgb()->GetClockRate(), GbApu::SampleRate); + blip_set_rates(_rightChannel, _gameboy->GetSgb()->GetClockRate(), GbApu::SampleRate); } else { blip_set_rates(_leftChannel, GbApu::ApuFrequency, GbApu::SampleRate); blip_set_rates(_rightChannel, GbApu::ApuFrequency, GbApu::SampleRate); diff --git a/Core/GbBootRom.h b/Core/GbBootRom.h index f5790fa..b6dc6fa 100644 --- a/Core/GbBootRom.h +++ b/Core/GbBootRom.h @@ -54,6 +54,32 @@ uint8_t sgbBootRom[256] = { 0x00, 0x00, 0xE0, 0x50 }; +uint8_t sgb2BootRom[256] = { + 0x31, 0xFE, 0xFF, 0x21, 0x00, 0x80, 0x22, 0xCB, 0x6C, 0x28, 0xFB, 0x3E, + 0x80, 0xE0, 0x26, 0xE0, 0x11, 0x3E, 0xF3, 0xE0, 0x12, 0xE0, 0x25, 0x3E, + 0x77, 0xE0, 0x24, 0x3E, 0x00, 0xE0, 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, + 0x80, 0x1A, 0x47, 0xCD, 0xC9, 0x00, 0xCD, 0xC9, 0x00, 0x13, 0x7B, 0xEE, + 0x34, 0x20, 0xF2, 0x11, 0xEA, 0x00, 0x0E, 0x08, 0x1A, 0x13, 0x22, 0x23, + 0x0D, 0x20, 0xF9, 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, + 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 0xF9, 0x2E, 0x0F, 0x18, 0xF5, + 0x3E, 0x91, 0xE0, 0x40, 0x3E, 0xF1, 0xE0, 0x80, 0x21, 0x04, 0x01, 0xAF, + 0x4F, 0xAF, 0xE2, 0x3E, 0x30, 0xE2, 0xF0, 0x80, 0xCD, 0xB7, 0x00, 0xE5, + 0x06, 0x0E, 0x16, 0x00, 0xCD, 0xAD, 0x00, 0x82, 0x57, 0x05, 0x20, 0xF8, + 0xCD, 0xB7, 0x00, 0xE1, 0x06, 0x0E, 0xCD, 0xAD, 0x00, 0xCD, 0xB7, 0x00, + 0x05, 0x20, 0xF7, 0x3E, 0x20, 0xE2, 0x3E, 0x30, 0xE2, 0xF0, 0x80, 0xC6, + 0x02, 0xE0, 0x80, 0x3E, 0x58, 0xBD, 0x20, 0xC9, 0x0E, 0x13, 0x3E, 0xC1, + 0xE2, 0x0C, 0x3E, 0x07, 0xE2, 0x3E, 0xFC, 0xE0, 0x47, 0x3E, 0xFF, 0x21, + 0x60, 0xC0, 0xC3, 0xFE, 0x00, 0x3E, 0x4F, 0xBD, 0x38, 0x02, 0x2A, 0xC9, + 0x23, 0xAF, 0xC9, 0x5F, 0x16, 0x08, 0x3E, 0x10, 0xCB, 0x1B, 0x38, 0x01, + 0x87, 0xE2, 0x3E, 0x30, 0xE2, 0x15, 0xC8, 0x18, 0xF1, 0x3E, 0x04, 0x0E, + 0x00, 0xCB, 0x20, 0xF5, 0xCB, 0x11, 0xF1, 0xCB, 0x11, 0x3D, 0x20, 0xF5, + 0x79, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xE5, 0x21, 0x0F, 0xFF, 0xCB, 0x86, + 0xCB, 0x46, 0x28, 0xFC, 0xE1, 0xC9, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, + 0x42, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xE0, 0x50 +}; + + uint8_t cgbBootRom[2304] = { 0x31, 0xFE, 0xFF, 0xCD, 0x26, 0x06, 0x3E, 0x02, 0x0E, 0x70, 0xE2, 0x26, 0xD0, 0xCD, 0x29, 0x06, 0xE2, 0x26, 0xFE, 0x0E, 0xA0, 0x22, 0x0D, 0x20, diff --git a/Core/SettingTypes.h b/Core/SettingTypes.h index 8a71314..c936da1 100644 --- a/Core/SettingTypes.h +++ b/Core/SettingTypes.h @@ -300,6 +300,7 @@ struct EmulationConfig int64_t BsxCustomDate = -1; GameboyModel GbModel = GameboyModel::Auto; + bool UseSgb2 = true; }; struct PreferencesConfig diff --git a/Core/SuperGameboy.cpp b/Core/SuperGameboy.cpp index a1bc6f9..c58b053 100644 --- a/Core/SuperGameboy.cpp +++ b/Core/SuperGameboy.cpp @@ -2,6 +2,7 @@ #include "SuperGameboy.h" #include "Console.h" #include "MemoryManager.h" +#include "EmuSettings.h" #include "BaseCartridge.h" #include "Gameboy.h" #include "GbApu.h" @@ -12,20 +13,25 @@ SuperGameboy::SuperGameboy(Console* console) : BaseCoprocessor(SnesMemoryType::Register) { + _mixBuffer = new int16_t[0x10000]; + _console = console; _memoryManager = console->GetMemoryManager().get(); _cart = _console->GetCartridge().get(); _gameboy = _cart->GetGameboy(); - _gameboy->PowerOn(this); _ppu = _gameboy->GetPpu(); - _mixBuffer = new int16_t[0x10000]; + _control = 0x01; //Divider = 5, gameboy = not running + UpdateClockRatio(); + MemoryMappings* cpuMappings = _memoryManager->GetMemoryMappings(); for(int i = 0; i <= 0x3F; i++) { cpuMappings->RegisterHandler(i, i, 0x6000, 0x7FFF, this); cpuMappings->RegisterHandler(i + 0x80, i + 0x80, 0x6000, 0x7FFF, this); } + + _gameboy->PowerOn(this); } SuperGameboy::~SuperGameboy() @@ -109,6 +115,8 @@ void SuperGameboy::Write(uint32_t addr, uint8_t value) } _control = value; _inputIndex %= GetPlayerCount(); + + UpdateClockRatio(); break; } @@ -119,11 +127,6 @@ void SuperGameboy::Write(uint32_t addr, uint8_t value) } } -void SuperGameboy::Run() -{ - _gameboy->Run(_memoryManager->GetMasterClock()); -} - void SuperGameboy::ProcessInputPortWrite(uint8_t value) { if(_inputValue == value) { @@ -242,14 +245,36 @@ void SuperGameboy::MixAudio(uint32_t targetRate, int16_t* soundSamples, uint32_t } } -uint8_t SuperGameboy::GetControl() +void SuperGameboy::Run() { - return _control; + if(!(_control & 0x80)) { + return; + } + + _gameboy->Run((uint64_t)((_memoryManager->GetMasterClock() - _resetClock) * _clockRatio)); } -uint64_t SuperGameboy::GetResetClock() +void SuperGameboy::UpdateClockRatio() { - return _resetClock; + bool isSgb2 = _console->GetSettings()->GetEmulationConfig().UseSgb2; + uint32_t masterRate = isSgb2 ? 20971520 : _console->GetMasterClockRate(); + uint8_t divider = 5; + + //TODO: This doesn't actually work properly if the speed is changed while the SGB is running (but this most likely never happens?) + switch(_control & 0x03) { + case 0: divider = 4; break; + case 1: divider = 5; break; + case 2: divider = 7; break; + case 3: divider = 9; break; + } + + double effectiveRate = (double)masterRate / divider; + _clockRatio = effectiveRate / _console->GetMasterClockRate(); +} + +uint32_t SuperGameboy::GetClockRate() +{ + return (uint32_t)(_console->GetMasterClockRate() * _clockRatio); } uint8_t SuperGameboy::GetInputIndex() @@ -281,7 +306,7 @@ void SuperGameboy::Serialize(Serializer& s) { s.Stream( _control, _resetClock, _input[0], _input[1], _input[2], _input[3], _inputIndex, _listeningForPacket, _packetReady, - _inputWriteClock, _inputValue, _packetByte, _packetBit, _lcdRowSelect, _readPosition, _waitForHigh + _inputWriteClock, _inputValue, _packetByte, _packetBit, _lcdRowSelect, _readPosition, _waitForHigh, _clockRatio ); s.StreamArray(_packetData, 16); diff --git a/Core/SuperGameboy.h b/Core/SuperGameboy.h index 84e6bbe..b003fb0 100644 --- a/Core/SuperGameboy.h +++ b/Core/SuperGameboy.h @@ -20,6 +20,7 @@ private: uint8_t _control = 0; uint64_t _resetClock = 0; + double _clockRatio = 0; uint8_t _input[4] = {}; uint8_t _inputIndex = 0; @@ -62,8 +63,9 @@ public: void MixAudio(uint32_t targetRate, int16_t* soundSamples, uint32_t sampleCount); - uint8_t GetControl(); - uint64_t GetResetClock(); + void UpdateClockRatio(); + uint32_t GetClockRate(); + uint8_t GetInputIndex(); uint8_t GetInput(); diff --git a/Libretro/libretro.cpp b/Libretro/libretro.cpp index 4d881cc..64e1c22 100644 --- a/Libretro/libretro.cpp +++ b/Libretro/libretro.cpp @@ -52,6 +52,7 @@ static constexpr const char* MesenOverclock = "mesen-s_overclock"; static constexpr const char* MesenOverclockType = "mesen-s_overclock_type"; static constexpr const char* MesenSuperFxOverclock = "mesen-s_superfx_overclock"; static constexpr const char* MesenGbModel = "mesen-s_gbmodel"; +static constexpr const char* MesenGbSgb2 = "mesen-s_sgb2"; extern "C" { void logMessage(retro_log_level level, const char* message) @@ -115,6 +116,7 @@ extern "C" { { MesenNtscFilter, "NTSC filter; Disabled|Composite (Blargg)|S-Video (Blargg)|RGB (Blargg)|Monochrome (Blargg)" }, { MesenRegion, "Region; Auto|NTSC|PAL" }, { MesenGbModel, "Game Boy Model; Auto|Game Boy|Game Boy Color|Super Game Boy" }, + { MesenGbSgb2, "Use SGB2; enabled|disabled" }, { MesenOverscanVertical, "Vertical Overscan; None|8px|16px" }, { MesenOverscanHorizontal, "Horizontal Overscan; None|8px|16px" }, { MesenAspectRatio, "Aspect Ratio; Auto|No Stretching|NTSC|PAL|4:3|16:9" }, @@ -404,6 +406,11 @@ extern "C" { } } + if(readVariable(MesenGbSgb2, var)) { + string value = string(var.value); + emulation.UseSgb2 = (value == "enabled"); + } + auto getKeyCode = [=](int port, int retroKey) { return (port << 8) | (retroKey + 1); }; diff --git a/UI/Config/EmulationConfig.cs b/UI/Config/EmulationConfig.cs index 08af538..efb3d7e 100644 --- a/UI/Config/EmulationConfig.cs +++ b/UI/Config/EmulationConfig.cs @@ -30,6 +30,7 @@ namespace Mesen.GUI.Config public long BsxCustomDate = -1; public GameboyModel GbModel = GameboyModel.Auto; + [MarshalAs(UnmanagedType.I1)] public bool UseSgb2 = true; public void ApplyConfig() { diff --git a/UI/Forms/Config/frmEmulationConfig.Designer.cs b/UI/Forms/Config/frmEmulationConfig.Designer.cs index 3d50487..1918700 100644 --- a/UI/Forms/Config/frmEmulationConfig.Designer.cs +++ b/UI/Forms/Config/frmEmulationConfig.Designer.cs @@ -79,6 +79,7 @@ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.nudGsuClockSpeed = new Mesen.GUI.Controls.MesenNumericUpDown(); this.lblGsuClockSpeed = new System.Windows.Forms.Label(); + this.chkUseSgb2 = new System.Windows.Forms.CheckBox(); this.tabMain.SuspendLayout(); this.tpgGeneral.SuspendLayout(); this.tableLayoutPanel4.SuspendLayout(); @@ -450,13 +451,14 @@ this.tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel7.Controls.Add(this.lblGameboy, 0, 0); this.tableLayoutPanel7.Controls.Add(this.cboGameboyModel, 1, 0); + this.tableLayoutPanel7.Controls.Add(this.chkUseSgb2, 0, 1); this.tableLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel7.Location = new System.Drawing.Point(3, 3); this.tableLayoutPanel7.Name = "tableLayoutPanel7"; - this.tableLayoutPanel7.RowCount = 2; + this.tableLayoutPanel7.RowCount = 3; + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); this.tableLayoutPanel7.Size = new System.Drawing.Size(431, 258); this.tableLayoutPanel7.TabIndex = 0; // @@ -867,6 +869,17 @@ this.lblGsuClockSpeed.TabIndex = 0; this.lblGsuClockSpeed.Text = "Super FX clock speed (%):"; // + // chkUseSgb2 + // + this.chkUseSgb2.AutoSize = true; + this.tableLayoutPanel7.SetColumnSpan(this.chkUseSgb2, 2); + this.chkUseSgb2.Location = new System.Drawing.Point(3, 30); + this.chkUseSgb2.Name = "chkUseSgb2"; + this.chkUseSgb2.Size = new System.Drawing.Size(237, 17); + this.chkUseSgb2.TabIndex = 2; + this.chkUseSgb2.Text = "Use Super Game Boy 2 timings and behavior"; + this.chkUseSgb2.UseVisualStyleBackColor = true; + // // frmEmulationConfig // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -971,5 +984,6 @@ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel7; private System.Windows.Forms.Label lblGameboy; private System.Windows.Forms.ComboBox cboGameboyModel; + private System.Windows.Forms.CheckBox chkUseSgb2; } } \ No newline at end of file diff --git a/UI/Forms/Config/frmEmulationConfig.cs b/UI/Forms/Config/frmEmulationConfig.cs index 96e0996..cd2b696 100644 --- a/UI/Forms/Config/frmEmulationConfig.cs +++ b/UI/Forms/Config/frmEmulationConfig.cs @@ -39,6 +39,7 @@ namespace Mesen.GUI.Forms.Config AddBinding(nameof(EmulationConfig.GsuClockSpeed), nudGsuClockSpeed); AddBinding(nameof(EmulationConfig.GbModel), cboGameboyModel); + AddBinding(nameof(EmulationConfig.UseSgb2), chkUseSgb2); long customDate = ConfigManager.Config.Emulation.BsxCustomDate; if(customDate >= 0) { diff --git a/UI/Interop/EmuApi.cs b/UI/Interop/EmuApi.cs index bff8f7e..482c8da 100644 --- a/UI/Interop/EmuApi.cs +++ b/UI/Interop/EmuApi.cs @@ -251,7 +251,8 @@ namespace Mesen.GUI Gameboy, GameboyColor, SgbGameboyCpu, - SGB + SGB1, + SGB2, } public struct MissingFirmwareMessage diff --git a/UI/Utilities/FirmwareHelper.cs b/UI/Utilities/FirmwareHelper.cs index 8a3104a..0720f17 100644 --- a/UI/Utilities/FirmwareHelper.cs +++ b/UI/Utilities/FirmwareHelper.cs @@ -52,13 +52,15 @@ namespace Mesen.GUI.Utilities "0E4DDFF32FC9D1EEAAE812A157DD246459B00C9E14F2F61751F661F32361E360", //SGB1 "FD243C4FB27008986316CE3DF29E9CFBCDC0CD52704970555A8BB76EDBEC3988" //SGB2 }; - case FirmwareType.SGB: return new List() { + case FirmwareType.SGB1: return new List() { "BBA9C269273BEDB9B38BD5EB23BFAA6E509B8DECC7CB80BB5513905AF04F4CEB", //Rev 0 (Japan) "C6C4DAAB5C899B69900C460787DE6089EDABE94B760F96D9F583D30CC0A5BB30", //Rev 1 (Japan+USA) "A75160F7B89B1F0E20FD2F6441BB86285C7378DB5035EF6885485EAFF6059376", //Rev 2 (World) + }; + case FirmwareType.SGB2: + return new List() { "C172498A23D1176672931BAB33B629C7D28F914A43DCA9E540B8AF1B37CCF2C6", //SGB2 (Japan) }; - } throw new Exception("Unexpected firmware type"); }