diff --git a/Assets/dll/libgambatte.dll b/Assets/dll/libgambatte.dll index 654e96c23e..05b42a7f48 100644 Binary files a/Assets/dll/libgambatte.dll and b/Assets/dll/libgambatte.dll differ diff --git a/Assets/dll/libgambatte.so b/Assets/dll/libgambatte.so index b590e91d69..469aebf60a 100644 Binary files a/Assets/dll/libgambatte.so and b/Assets/dll/libgambatte.so differ diff --git a/src/BizHawk.Client.Common/config/Config.cs b/src/BizHawk.Client.Common/config/Config.cs index e84ee45d03..5cc794b8e3 100644 --- a/src/BizHawk.Client.Common/config/Config.cs +++ b/src/BizHawk.Client.Common/config/Config.cs @@ -24,7 +24,7 @@ namespace BizHawk.Client.Common (new[] { "SNES" }, new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }), (new[] { "SGB" }, - new[] { CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}), + new[] { CoreNames.Gambatte, CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}), (new[] { "GB", "GBC" }, new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }), (new[] { "DGB" }, diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 7015c740d0..40f335074c 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -2004,6 +2004,7 @@ namespace BizHawk.Client.EmuHawk case "GB": case "GBC": case "SGB" when Emulator is Sameboy: + case "SGB" when Emulator is Gameboy: GBSubMenu.Visible = true; break; case "SNES" when Emulator is LibsnesCore { IsSGB: true }: // doesn't use "SGB" sysID diff --git a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.Designer.cs b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.Designer.cs index 29eab0f680..b281d3cbef 100644 --- a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.Designer.cs @@ -28,84 +28,98 @@ /// private void InitializeComponent() { - this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); - this.buttonDefaults = new System.Windows.Forms.Button(); - this.buttonPalette = new System.Windows.Forms.Button(); - this.cbRgbdsSyntax = new System.Windows.Forms.CheckBox(); - this.checkBoxMuted = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // propertyGrid1 - // - this.propertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.buttonDefaults = new System.Windows.Forms.Button(); + this.buttonPalette = new System.Windows.Forms.Button(); + this.cbRgbdsSyntax = new System.Windows.Forms.CheckBox(); + this.checkBoxMuted = new System.Windows.Forms.CheckBox(); + this.cbShowBorder = new System.Windows.Forms.CheckBox(); + this.SuspendLayout(); + // + // propertyGrid1 + // + this.propertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.propertyGrid1.Location = new System.Drawing.Point(3, 3); - this.propertyGrid1.Name = "propertyGrid1"; - this.propertyGrid1.PropertySort = System.Windows.Forms.PropertySort.NoSort; - this.propertyGrid1.Size = new System.Drawing.Size(338, 279); - this.propertyGrid1.TabIndex = 0; - this.propertyGrid1.ToolbarVisible = false; - this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.PropertyGrid1_PropertyValueChanged); - // - // buttonDefaults - // - this.buttonDefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonDefaults.Location = new System.Drawing.Point(266, 288); - this.buttonDefaults.Name = "buttonDefaults"; - this.buttonDefaults.Size = new System.Drawing.Size(75, 23); - this.buttonDefaults.TabIndex = 1; - this.buttonDefaults.Text = "Defaults"; - this.buttonDefaults.UseVisualStyleBackColor = true; - this.buttonDefaults.Click += new System.EventHandler(this.ButtonDefaults_Click); - // - // buttonPalette - // - this.buttonPalette.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.buttonPalette.Location = new System.Drawing.Point(3, 288); - this.buttonPalette.Name = "buttonPalette"; - this.buttonPalette.Size = new System.Drawing.Size(75, 23); - this.buttonPalette.TabIndex = 2; - this.buttonPalette.Text = "Palette..."; - this.buttonPalette.UseVisualStyleBackColor = true; - this.buttonPalette.Click += new System.EventHandler(this.ButtonPalette_Click); - // - // checkBoxMuted - // - this.checkBoxMuted.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.checkBoxMuted.AutoSize = true; - this.checkBoxMuted.Location = new System.Drawing.Point(82, 292); - this.checkBoxMuted.Name = "checkBoxMuted"; - this.checkBoxMuted.Size = new System.Drawing.Size(50, 17); - this.checkBoxMuted.TabIndex = 3; - this.checkBoxMuted.Text = "Mute"; - this.checkBoxMuted.UseVisualStyleBackColor = true; - this.checkBoxMuted.CheckedChanged += new System.EventHandler(this.CheckBoxMuted_CheckedChanged); - // - // cbRgbdsSyntax - // - this.cbRgbdsSyntax.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.cbRgbdsSyntax.AutoSize = true; - this.cbRgbdsSyntax.Location = new System.Drawing.Point(130, 292); - this.cbRgbdsSyntax.Name = "cbRgbdsSyntax"; - this.cbRgbdsSyntax.Size = new System.Drawing.Size(150, 17); - this.cbRgbdsSyntax.TabIndex = 7; - this.cbRgbdsSyntax.Text = "RGBDS Syntax"; - this.cbRgbdsSyntax.UseVisualStyleBackColor = true; - this.cbRgbdsSyntax.CheckedChanged += new System.EventHandler(this.CbRgbdsSyntax_CheckedChanged); - // - // GBPrefControl - // - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; - this.Controls.Add(this.cbRgbdsSyntax); - this.Controls.Add(this.checkBoxMuted); - this.Controls.Add(this.buttonPalette); - this.Controls.Add(this.buttonDefaults); - this.Controls.Add(this.propertyGrid1); - this.Name = "GBPrefControl"; - this.Size = new System.Drawing.Size(344, 314); - this.ResumeLayout(false); - this.PerformLayout(); + this.propertyGrid1.Location = new System.Drawing.Point(3, 3); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.PropertySort = System.Windows.Forms.PropertySort.NoSort; + this.propertyGrid1.Size = new System.Drawing.Size(402, 368); + this.propertyGrid1.TabIndex = 0; + this.propertyGrid1.ToolbarVisible = false; + this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.PropertyGrid1_PropertyValueChanged); + // + // buttonDefaults + // + this.buttonDefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonDefaults.Location = new System.Drawing.Point(330, 377); + this.buttonDefaults.Name = "buttonDefaults"; + this.buttonDefaults.Size = new System.Drawing.Size(75, 23); + this.buttonDefaults.TabIndex = 1; + this.buttonDefaults.Text = "Defaults"; + this.buttonDefaults.UseVisualStyleBackColor = true; + this.buttonDefaults.Click += new System.EventHandler(this.ButtonDefaults_Click); + // + // buttonPalette + // + this.buttonPalette.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.buttonPalette.Location = new System.Drawing.Point(3, 377); + this.buttonPalette.Name = "buttonPalette"; + this.buttonPalette.Size = new System.Drawing.Size(75, 23); + this.buttonPalette.TabIndex = 2; + this.buttonPalette.Text = "Palette..."; + this.buttonPalette.UseVisualStyleBackColor = true; + this.buttonPalette.Click += new System.EventHandler(this.ButtonPalette_Click); + // + // cbRgbdsSyntax + // + this.cbRgbdsSyntax.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cbRgbdsSyntax.AutoSize = true; + this.cbRgbdsSyntax.Location = new System.Drawing.Point(138, 381); + this.cbRgbdsSyntax.Name = "cbRgbdsSyntax"; + this.cbRgbdsSyntax.Size = new System.Drawing.Size(99, 17); + this.cbRgbdsSyntax.TabIndex = 3; + this.cbRgbdsSyntax.Text = "RGBDS Syntax"; + this.cbRgbdsSyntax.UseVisualStyleBackColor = true; + this.cbRgbdsSyntax.CheckedChanged += new System.EventHandler(this.CbRgbdsSyntax_CheckedChanged); + // + // checkBoxMuted + // + this.checkBoxMuted.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.checkBoxMuted.AutoSize = true; + this.checkBoxMuted.Location = new System.Drawing.Point(82, 381); + this.checkBoxMuted.Name = "checkBoxMuted"; + this.checkBoxMuted.Size = new System.Drawing.Size(50, 17); + this.checkBoxMuted.TabIndex = 4; + this.checkBoxMuted.Text = "Mute"; + this.checkBoxMuted.UseVisualStyleBackColor = true; + this.checkBoxMuted.CheckedChanged += new System.EventHandler(this.CheckBoxMuted_CheckedChanged); + // + // cbShowBorder + // + this.cbShowBorder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.cbShowBorder.AutoSize = true; + this.cbShowBorder.Location = new System.Drawing.Point(243, 381); + this.cbShowBorder.Name = "cbShowBorder"; + this.cbShowBorder.Size = new System.Drawing.Size(87, 17); + this.cbShowBorder.TabIndex = 5; + this.cbShowBorder.Text = "Show Border"; + this.cbShowBorder.UseVisualStyleBackColor = true; + this.cbShowBorder.CheckedChanged += new System.EventHandler(this.CbShowBorder_CheckedChanged); + // + // GBPrefControl + // + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; + this.Controls.Add(this.cbRgbdsSyntax); + this.Controls.Add(this.checkBoxMuted); + this.Controls.Add(this.cbShowBorder); + this.Controls.Add(this.buttonPalette); + this.Controls.Add(this.buttonDefaults); + this.Controls.Add(this.propertyGrid1); + this.Name = "GBPrefControl"; + this.Size = new System.Drawing.Size(408, 403); + this.ResumeLayout(false); + this.PerformLayout(); } @@ -116,5 +130,6 @@ private System.Windows.Forms.Button buttonPalette; private System.Windows.Forms.CheckBox cbRgbdsSyntax; private System.Windows.Forms.CheckBox checkBoxMuted; + private System.Windows.Forms.CheckBox cbShowBorder; } } diff --git a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.cs b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.cs index 28df5296f9..cfafd88dff 100644 --- a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.cs +++ b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefControl.cs @@ -43,6 +43,7 @@ namespace BizHawk.Client.EmuHawk propertyGrid1.Enabled = movieSession.Movie.NotActive(); checkBoxMuted.Checked = _s.Muted; cbRgbdsSyntax.Checked = _s.RgbdsSyntax; + cbShowBorder.Checked = _s.ShowBorder; } public void GetSettings(out Gameboy.GambatteSettings s, out Gameboy.GambatteSyncSettings ss) @@ -86,5 +87,10 @@ namespace BizHawk.Client.EmuHawk { _s.RgbdsSyntax = ((CheckBox)sender).Checked; } + + private void CbShowBorder_CheckedChanged(object sender, EventArgs e) + { + _s.ShowBorder = ((CheckBox)sender).Checked; + } } } diff --git a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.Designer.cs b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.Designer.cs index cd3bbe6bc8..f77a3fe4a0 100644 --- a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.Designer.cs @@ -28,58 +28,58 @@ /// private void InitializeComponent() { - this.buttonOK = new System.Windows.Forms.Button(); - this.buttonCancel = new System.Windows.Forms.Button(); - this.gbPrefControl1 = new GBPrefControl(); - this.SuspendLayout(); - // - // buttonOK - // - this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK; - this.buttonOK.Location = new System.Drawing.Point(280, 363); - this.buttonOK.Name = "buttonOK"; - this.buttonOK.Size = new System.Drawing.Size(75, 23); - this.buttonOK.TabIndex = 1; - this.buttonOK.Text = "OK"; - this.buttonOK.UseVisualStyleBackColor = true; - // - // buttonCancel - // - this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.buttonCancel.Location = new System.Drawing.Point(361, 363); - this.buttonCancel.Name = "buttonCancel"; - this.buttonCancel.Size = new System.Drawing.Size(75, 23); - this.buttonCancel.TabIndex = 2; - this.buttonCancel.Text = "Cancel"; - this.buttonCancel.UseVisualStyleBackColor = true; - // - // gbPrefControl1 - // - this.gbPrefControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + this.buttonOK = new System.Windows.Forms.Button(); + this.buttonCancel = new System.Windows.Forms.Button(); + this.gbPrefControl1 = new BizHawk.Client.EmuHawk.GBPrefControl(); + this.SuspendLayout(); + // + // buttonOK + // + this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonOK.DialogResult = System.Windows.Forms.DialogResult.OK; + this.buttonOK.Location = new System.Drawing.Point(280, 363); + this.buttonOK.Name = "buttonOK"; + this.buttonOK.Size = new System.Drawing.Size(75, 23); + this.buttonOK.TabIndex = 1; + this.buttonOK.Text = "OK"; + this.buttonOK.UseVisualStyleBackColor = true; + // + // buttonCancel + // + this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.buttonCancel.Location = new System.Drawing.Point(361, 363); + this.buttonCancel.Name = "buttonCancel"; + this.buttonCancel.Size = new System.Drawing.Size(75, 23); + this.buttonCancel.TabIndex = 2; + this.buttonCancel.Text = "Cancel"; + this.buttonCancel.UseVisualStyleBackColor = true; + // + // gbPrefControl1 + // + this.gbPrefControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.gbPrefControl1.ColorGameBoy = false; - this.gbPrefControl1.Location = new System.Drawing.Point(12, 12); - this.gbPrefControl1.Name = "gbPrefControl1"; - this.gbPrefControl1.Size = new System.Drawing.Size(424, 345); - this.gbPrefControl1.TabIndex = 0; - // - // GBPrefs - // - this.AcceptButton = this.buttonOK; - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.CancelButton = this.buttonCancel; - this.ClientSize = new System.Drawing.Size(448, 398); - this.Controls.Add(this.buttonCancel); - this.Controls.Add(this.buttonOK); - this.Controls.Add(this.gbPrefControl1); - this.Name = "GBPrefs"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Game Boy Settings"; - this.ResumeLayout(false); + this.gbPrefControl1.ColorGameBoy = false; + this.gbPrefControl1.Location = new System.Drawing.Point(12, 12); + this.gbPrefControl1.Name = "gbPrefControl1"; + this.gbPrefControl1.Size = new System.Drawing.Size(424, 345); + this.gbPrefControl1.TabIndex = 0; + // + // GBPrefs + // + this.AcceptButton = this.buttonOK; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.buttonCancel; + this.ClientSize = new System.Drawing.Size(448, 398); + this.Controls.Add(this.buttonCancel); + this.Controls.Add(this.buttonOK); + this.Controls.Add(this.gbPrefControl1); + this.Name = "GBPrefs"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Game Boy Settings"; + this.ResumeLayout(false); } diff --git a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.cs b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.cs index c2e4f3ca49..5c459e5dc2 100644 --- a/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.cs +++ b/src/BizHawk.Client.EmuHawk/config/GB/GBPrefs.cs @@ -24,7 +24,7 @@ namespace BizHawk.Client.EmuHawk using var dlg = new GBPrefs(mainForm.DialogController); dlg.gbPrefControl1.PutSettings(config, game, movieSession, s, ss); - dlg.gbPrefControl1.ColorGameBoy = gb.IsCGBMode(); + dlg.gbPrefControl1.ColorGameBoy = gb.IsCGBMode() || gb.IsSgb; if (mainForm.ShowDialogAsChild(dlg).IsOk()) { dlg.gbPrefControl1.GetSettings(out s, out ss); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs index 4d36da2364..298c06bd6f 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IEmulator.cs @@ -9,12 +9,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { public IEmulatorServiceProvider ServiceProvider { get; } - public ControllerDefinition ControllerDefinition => (_syncSettings.FrameLength == GambatteSyncSettings.FrameLengthType.UserDefinedFrames) ? SubGbController : GbController; + public ControllerDefinition ControllerDefinition { get; set; } public bool FrameAdvance(IController controller, bool render, bool rendersound) { FrameAdvancePrep(controller); uint samplesEmitted; + uint samplesEmittedInFrame = 0; // for sgb switch (_syncSettings.FrameLength) { case GambatteSyncSettings.FrameLengthType.VBlankDrivenFrames: @@ -25,10 +26,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0) { Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); + if (IsSgb) + { + if (LibGambatte.gambatte_updatescreenborder(GambatteState, SgbVideoBuffer, 256) != 0) + { + throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_updatescreenborder)}() returned non-zero (border error???)"); + } + } } _cycleCount += samplesEmitted; + samplesEmittedInFrame += samplesEmitted; frameOverflow = 0; + if (rendersound && !Muted) { ProcessSound((int)samplesEmitted); @@ -43,10 +53,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0) { Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); + if (IsSgb) + { + if (LibGambatte.gambatte_updatescreenborder(GambatteState, SgbVideoBuffer, 256) != 0) + { + throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_updatescreenborder)}() returned non-zero (border error???)"); + } + } } // account for actual number of samples emitted _cycleCount += samplesEmitted; + samplesEmittedInFrame += samplesEmitted; frameOverflow += samplesEmitted; if (rendersound && !Muted) @@ -76,10 +94,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0) { Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); + if (IsSgb) + { + if (LibGambatte.gambatte_updatescreenborder(GambatteState, SgbVideoBuffer, 256) != 0) + { + throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_updatescreenborder)}() returned non-zero (border error???)"); + } + } } // account for actual number of samples emitted _cycleCount += samplesEmitted; + samplesEmittedInFrame += samplesEmitted; frameOverflow += samplesEmitted; if (rendersound && !Muted) @@ -96,6 +122,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy break; } + if (IsSgb) + { + ProcessSgbSound((int)samplesEmittedInFrame, (rendersound && !Muted)); + } + if (rendersound && !Muted) { ProcessSoundEnd(); @@ -108,7 +139,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public int Frame { get; private set; } - public string SystemId => "GB"; + public string SystemId => IsSgb ? "SGB" : "GB"; public string BoardName { get; } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs index 55d0d2369e..de7b9d6367 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISettable.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { _settings = o; _disassembler.UseRGBDSSyntax = _settings.RgbdsSyntax; - if (IsCGBMode()) + if (IsCGBMode() || IsSgb) { SetCGBColors(_settings.CGBColors); } @@ -77,11 +77,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// public bool RgbdsSyntax; + /// + /// true to show sgb border (sgb mode only) + /// + public bool ShowBorder; + public GambatteSettings() { GBPalette = (int[])DefaultPalette.Clone(); CGBColors = GBColors.ColorType.gambatte; RgbdsSyntax = true; + ShowBorder = true; } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs index 422a845182..c2b779b52a 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.ISoundProvider.cs @@ -37,6 +37,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy // sample pairs before resampling private readonly short[] _soundbuff = new short[(35112 + 2064) * 2]; + private readonly short[] _sgbsoundbuff = new short[2048 * 2]; private int _soundoutbuffcontains = 0; @@ -45,6 +46,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy private int _latchL = 0; private int _latchR = 0; + private int _sgbLatchL = 0; + private int _sgbLatchR = 0; + private BlipBuffer _blipL, _blipR; private uint _blipAccumulate; @@ -74,6 +78,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy } } + private void ProcessSgbSound(int nsamp, bool processSound) + { + int remainder = LibGambatte.gambatte_generatesgbsamples(GambatteState, _sgbsoundbuff, out uint samples); + if (remainder < 0) + { + throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_generatesgbsamples)}() returned negative (spc error???)"); + } + uint t = 65 - (uint)remainder; + for (int i = 0; i < samples; i++, t += 65) + { + int ls = _sgbsoundbuff[i * 2] - _sgbLatchL; + int rs = _sgbsoundbuff[(i * 2) + 1] - _sgbLatchR; + if (ls != 0 && processSound) + { + _blipL.AddDelta(t, ls); + } + if (rs != 0 && processSound) + { + _blipR.AddDelta(t, rs); + } + _sgbLatchL = _sgbsoundbuff[i * 2]; + _sgbLatchR = _sgbsoundbuff[(i * 2) + 1]; + } + } + private void ProcessSoundEnd() { _blipL.EndFrame(_blipAccumulate); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs index 8fe0fbf2ab..85c82d1010 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IStatable.cs @@ -1,4 +1,6 @@ -using System; +//#define USE_UPSTREAM_STATES + +using System; using System.IO; using Newtonsoft.Json; @@ -22,13 +24,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public void SaveStateBinary(BinaryWriter writer) { - if (!LibGambatte.gambatte_newstatesave(GambatteState, _savebuff, _savebuff.Length)) +#if USE_UPSTREAM_STATES + int size = LibGambatte.gambatte_savestate(GambatteState, null, 160, _stateBuf); + if (size != _stateBuf.Length) + { + throw new InvalidOperationException("Savestate buffer size mismatch!"); + } +#else + if (!LibGambatte.gambatte_newstatesave(GambatteState, _stateBuf, _stateBuf.Length)) { throw new Exception($"{nameof(LibGambatte.gambatte_newstatesave)}() returned false"); } +#endif - writer.Write(_savebuff.Length); - writer.Write(_savebuff); + writer.Write(_stateBuf.Length); + writer.Write(_stateBuf); // other variables writer.Write(IsLagFrame); @@ -37,22 +47,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy writer.Write(frameOverflow); writer.Write(_cycleCount); writer.Write(IsCgb); + writer.Write(IsSgb); } public void LoadStateBinary(BinaryReader reader) { int length = reader.ReadInt32(); - if (length != _savebuff.Length) + if (length != _stateBuf.Length) { throw new InvalidOperationException("Savestate buffer size mismatch!"); } - reader.Read(_savebuff, 0, _savebuff.Length); + reader.Read(_stateBuf, 0, _stateBuf.Length); - if (!LibGambatte.gambatte_newstateload(GambatteState, _savebuff, _savebuff.Length)) +#if USE_UPSTREAM_STATES + if (!LibGambatte.gambatte_loadstate(GambatteState, _stateBuf, _stateBuf.Length)) + { + throw new Exception($"{nameof(LibGambatte.gambatte_loadstate)}() returned false"); + } +#else + if (!LibGambatte.gambatte_newstateload(GambatteState, _stateBuf, _stateBuf.Length)) { throw new Exception($"{nameof(LibGambatte.gambatte_newstateload)}() returned false"); } +#endif // other variables IsLagFrame = reader.ReadBoolean(); @@ -61,13 +79,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy frameOverflow = reader.ReadUInt32(); _cycleCount = reader.ReadUInt64(); IsCgb = reader.ReadBoolean(); + IsSgb = reader.ReadBoolean(); } - private byte[] _savebuff; + private byte[] _stateBuf; private void NewSaveCoreSetBuff() { - _savebuff = new byte[LibGambatte.gambatte_newstatelen(GambatteState)]; +#if USE_UPSTREAM_STATES + _stateBuf = new byte[LibGambatte.gambatte_savestate(GambatteState, null, 160, null)]; +#else + _stateBuf = new byte[LibGambatte.gambatte_newstatelen(GambatteState)]; +#endif } private readonly JsonSerializer ser = new JsonSerializer { Formatting = Formatting.Indented }; @@ -81,6 +104,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public ulong _cycleCount; public uint frameOverflow; public bool IsCgb; + public bool IsSgb; } internal TextState SaveState() @@ -95,6 +119,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy s.ExtraData.frameOverflow = frameOverflow; s.ExtraData._cycleCount = _cycleCount; s.ExtraData.IsCgb = IsCgb; + s.ExtraData.IsSgb = IsSgb; return s; } @@ -109,6 +134,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy frameOverflow = s.ExtraData.frameOverflow; _cycleCount = s.ExtraData._cycleCount; IsCgb = s.ExtraData.IsCgb; + IsSgb = s.ExtraData.IsSgb; } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs index 4b828a3b88..17f1ab92bb 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.IVideoProvider.cs @@ -11,7 +11,12 @@ /// stored image of most recent frame /// private readonly int[] VideoBuffer = CreateVideoBuffer(); - + + /// + /// stored image of most recent sgb frame + /// + private readonly int[] SgbVideoBuffer = new int[256 * 244]; + private static int[] CreateVideoBuffer() { var b = new int[160 * 144]; @@ -24,16 +29,16 @@ public int[] GetVideoBuffer() { - return VideoBuffer; + return (IsSgb && _settings.ShowBorder) ? SgbVideoBuffer : VideoBuffer; } - public int VirtualWidth => 160; // only sgb changes this, which we don't emulate here + public int VirtualWidth => (IsSgb && _settings.ShowBorder) ? 256 : 160; - public int VirtualHeight => 144; + public int VirtualHeight => (IsSgb && _settings.ShowBorder) ? 224 : 144; - public int BufferWidth => 160; + public int BufferWidth => (IsSgb && _settings.ShowBorder) ? 256 : 160; - public int BufferHeight => 144; + public int BufferHeight => (IsSgb && _settings.ShowBorder) ? 224 : 144; public int BackgroundColor => 0; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 1782e7f345..819d7abea2 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using BizHawk.Common; using BizHawk.Common.BufferExtensions; using BizHawk.Emulation.Common; @@ -20,6 +21,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { [CoreConstructor("GB")] [CoreConstructor("GBC")] + [CoreConstructor("SGB")] public Gameboy(CoreComm comm, GameInfo game, byte[] file, Gameboy.GambatteSettings settings, Gameboy.GambatteSyncSettings syncSettings, bool deterministic) { var ser = new BasicServiceProvider(this); @@ -66,6 +68,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy break; } + if (game.System == "SGB") + { + flags &= ~(LibGambatte.LoadFlags.CGB_MODE | LibGambatte.LoadFlags.GBA_FLAG); + flags |= LibGambatte.LoadFlags.SGB_MODE; + IsSgb = true; + } + if (_syncSettings.MulticartCompat) { flags |= LibGambatte.LoadFlags.MULTICART_COMPAT; @@ -77,7 +86,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy IsCgb = (flags & LibGambatte.LoadFlags.CGB_MODE) == LibGambatte.LoadFlags.CGB_MODE; biosSystemId = IsCgb ? "GBC" : "GB"; - biosId = ((_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA) && !_syncSettings.PatchBIOS) ? "AGB" : "World"; + biosId = (_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA) && !_syncSettings.PatchBIOS ? "AGB" : "World"; + + if (IsSgb) + { + biosId = "SGB2"; + } if (_syncSettings.EnableBIOS) { @@ -86,15 +100,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy { if (!IsCgb) { - bios[0xFD] ^= 0xFE; // patch from dmg<->mgb + bios[0xFD] ^= 0xFE; // patch from dmg<->mgb, or sgb1<->sgb2 } else if (_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA) { // patch from cgb->agb re bios[0xF3] ^= 0x03; - for (var i = 0xF5; i < 0xFB;) + for (var i = 0xF5; i < 0xFB; i++) { - bios[i] = bios[++i]; + bios[i] = bios[i + 1]; } bios[0xFB] ^= 0x74; } @@ -118,6 +132,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy throw new InvalidOperationException($"{nameof(LibGambatte.gambatte_loadbuf)}() returned non-zero (is this not a gb or gbc rom?)"); } + if (IsSgb) + { + ResetStallTicks = 128 * (2 << 14); + } + else if (_syncSettings.EnableBIOS && (_syncSettings.ConsoleMode is GambatteSyncSettings.ConsoleModeType.GBA)) + { + ResetStallTicks = 485808; // GBA takes 971616 cycles to switch to CGB mode; CGB CPU is inactive during this time. + } + else + { + ResetStallTicks = 0; + } + // set real default colors (before anyone mucks with them at all) PutSettings((GambatteSettings)settings ?? new GambatteSettings()); @@ -196,6 +223,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy _cdCallback = new LibGambatte.CDCallback(CDCallbackProc); + ControllerDefinition = CreateControllerDefinition(IsSgb, _syncSettings.FrameLength is GambatteSyncSettings.FrameLengthType.UserDefinedFrames); + NewSaveCoreSetBuff(); } catch @@ -219,6 +248,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy /// private const uint TICKSPERSECOND = 2097152; + /// + /// number of reset stall ticks + /// + private uint ResetStallTicks { get; set; } = 0; + /// /// keep a copy of the input callback delegate so it doesn't get GCed /// @@ -237,8 +271,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public int LagCount { get; set; } public bool IsLagFrame { get; set; } public bool IsCgb { get; set; } + public bool IsSgb { get; set; } - // all cycle counts are relative to a 2*1024*1024 mhz refclock + // all cycle counts are relative to a 2*1024*1024 hz refclock /// /// total cycles actually executed @@ -253,29 +288,52 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy public long CycleCount => (long)_cycleCount; public double ClockRate => TICKSPERSECOND; - public static readonly ControllerDefinition GbController = new ControllerDefinition + public static ControllerDefinition CreateControllerDefinition(bool sgb, bool sub) { - Name = "Gameboy Controller", - BoolButtons = + var ret = sub + ? new ControllerDefinition { Name = "Subframe Gameboy Controller" }.AddAxis("Input Length", 0.RangeTo(35112), 35112) + : new ControllerDefinition { Name = "Gameboy Controller" }; + if (sgb) { - "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" + for (int i = 0; i < 4; i++) + { + ret.BoolButtons.AddRange( + new[] { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A" } + .Select(s => $"P{i + 1} {s}")); + } + ret.BoolButtons.Add("Power"); } - }; - - public static readonly ControllerDefinition SubGbController = new ControllerDefinition - { - Name = "Subframe Gameboy Controller", - BoolButtons = + else { - "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" + ret.BoolButtons.AddRange(new[] { "Up", "Down", "Left", "Right", "Start", "Select", "B", "A", "Power" }); } - }.AddAxis("Input Length", 0.RangeTo(35112), 35112); + return ret; + } private LibGambatte.Buttons ControllerCallback() { InputCallbacks.Call(); IsLagFrame = false; - return CurrentButtons; + if (IsSgb) + { + int index = LibGambatte.gambatte_getjoypadindex(GambatteState); + uint b = (uint)CurrentButtons; + b >>= index * 8; + b &= 0xFF; + if ((b & 0x30) == 0x30) // snes software side blocks l+r + { + b &= ~0x30u; + } + if ((b & 0xC0) == 0xC0) // same for u+d + { + b &= ~0xC0u; + } + return (LibGambatte.Buttons)b; + } + else + { + return CurrentButtons; + } } /// @@ -310,16 +368,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy } // needs to match the reverse order of Libgambatte's button enum - private static readonly IReadOnlyList BUTTON_ORDER_IN_BITMASK = new[] { "Down", "Up", "Left", "Right", "Start", "Select", "B", "A" }; + private static readonly IReadOnlyList GB_BUTTON_ORDER_IN_BITMASK = new[] { "Down", "Up", "Left", "Right", "Start", "Select", "B", "A" }; + + // input callback assumes buttons are ordered from first player in lsbs to last player in msbs + private static readonly IReadOnlyList SGB_BUTTON_ORDER_IN_BITMASK = new[] { + "P4 Down", "P4 Up", "P4 Left", "P4 Right", "P4 Start", "P4 Select", "P4 B", "P4 A", + "P3 Down", "P3 Up", "P3 Left", "P3 Right", "P3 Start", "P3 Select", "P3 B", "P3 A", + "P2 Down", "P2 Up", "P2 Left", "P2 Right", "P2 Start", "P2 Select", "P2 B", "P2 A", + "P1 Down", "P1 Up", "P1 Left", "P1 Right", "P1 Start", "P1 Select", "P1 B", "P1 A" }; internal void FrameAdvancePrep(IController controller) { // update our local copy of the controller data - byte b = 0; - for (var i = 0; i < 8; i++) + uint b = 0; + if (IsSgb) { - b <<= 1; - if (controller.IsPressed(BUTTON_ORDER_IN_BITMASK[i])) b |= 1; + for (var i = 0; i < 32; i++) + { + b <<= 1; + if (controller.IsPressed(SGB_BUTTON_ORDER_IN_BITMASK[i])) b |= 1; + } + } + else + { + for (var i = 0; i < 8; i++) + { + b <<= 1; + if (controller.IsPressed(GB_BUTTON_ORDER_IN_BITMASK[i])) b |= 1; + } } CurrentButtons = (LibGambatte.Buttons)b; @@ -328,8 +404,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy if (controller.IsPressed("Power")) { - bool stall = _syncSettings.EnableBIOS && (_syncSettings.ConsoleMode is GambatteSyncSettings.ConsoleModeType.GBA); // GBA takes 971616 cycles to switch to CGB mode; CGB CPU is inactive during this time. - LibGambatte.gambatte_reset(GambatteState, stall ? 485808u : 0u); + LibGambatte.gambatte_reset(GambatteState, ResetStallTicks); } if (Tracer.IsEnabled()) @@ -779,8 +854,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy else { LinkConnected = false; - printer.Disconnect(); - printer = null; + if (printer != null) // have no idea how this is ever null??? + { + printer.Disconnect(); + printer = null; + } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs index 985840295c..2a16894ff4 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/GambatteLink.cs @@ -71,8 +71,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy private const int SampPerFrame = 35112; - private readonly SaveController LCont = new SaveController(Gameboy.GbController); - private readonly SaveController RCont = new SaveController(Gameboy.GbController); + private readonly SaveController LCont = new SaveController(Gameboy.CreateControllerDefinition(false, false)); + private readonly SaveController RCont = new SaveController(Gameboy.CreateControllerDefinition(false, false)); public bool IsCGBMode(bool right) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs index d98d31cf57..767099e975 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -93,6 +93,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] public static extern unsafe int gambatte_runfor(IntPtr core, int* videobuf, int pitch, short* soundbuf, ref uint samples); + [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_updatescreenborder(IntPtr core, int[] videobuf, int pitch); + + [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_generatesgbsamples(IntPtr core, short[] soundbuf, out uint samples); + /// /// Reset to initial state. /// Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again. @@ -156,6 +162,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] public static extern void gambatte_setinputgetter(IntPtr core, InputGetter getinput); + /// + /// Gets which SGB controller is in use, 0 indexed. + /// + /// opaque state pointer + [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_getjoypadindex(IntPtr core); + /// /// type of the read\write memory callbacks /// @@ -405,6 +418,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] public static extern bool gambatte_getmemoryarea(IntPtr core, MemoryAreas which, ref IntPtr data, ref int length); + /// + /// Saves emulator state to the buffer given by 'stateBuf'. + /// + /// opaque state pointer + /// 160x144 RGB32 (native endian) video frame buffer or 0. Used for saving a thumbnail. + /// pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf. + /// buffer for savestate + /// size + [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_savestate(IntPtr core, int[] videoBuf, int pitch, byte[] stateBuf); + + /// + /// Loads emulator state from the buffer given by 'stateBuf' of size 'size'. + /// + /// opaque state pointer + /// buffer for savestate + /// size of savestate buffer + /// success + [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] + public static extern bool gambatte_loadstate(IntPtr core, byte[] stateBuf, int size); + /// /// read a single byte from the cpu bus. this includes all ram, rom, mmio, etc, as it is visible to the cpu (including mappers). /// while there is no cycle cost to these reads, there may be other side effects! use at your own risk. diff --git a/submodules/gambatte b/submodules/gambatte index c3e44f8fde..dc09a5882c 160000 --- a/submodules/gambatte +++ b/submodules/gambatte @@ -1 +1 @@ -Subproject commit c3e44f8fde85317219f233d0f2876e2a2fb354ee +Subproject commit dc09a5882c962c426b5c56f4dec541e5fd22335e