Implement HLE SGB Emulation in Gambatte (squashed PR #2917)

* sgb meme

* various sgb fixes, add hard reset support for spc, make frontend provide spc file

* sgb border support, mostly copied from sameboy

* add support for disabling sgb border, also fix dumb when disabling border

* state work, states seem to be broken tho

* fix dumb state issue

* multiplayer

* fix dumb in spc stating

* misc

* pass SGB tests

* oh right I have to fix this too

* and this dumb too

* attempt to fix weird crashes

* or maybe this will fix it?

* wtf is spc doing?

* rebase

* misc state + debugging stuff

* finally fix weird assertion failure

* factor out loading in spc file, also factor out the ipl

* oops

* init special sgb colors for certain games

* slight sgb audio refactor

* this should work better?

* oops

* switch back to master

* super penguin
This commit is contained in:
CasualPokePlayer 2021-09-17 06:35:00 -07:00 committed by GitHub
parent fc0ebf372f
commit fbab7f6291
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 407 additions and 176 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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" },

View File

@ -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

View File

@ -28,84 +28,98 @@
/// </summary>
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;
}
}

View File

@ -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;
}
}
}

View File

@ -28,58 +28,58 @@
/// </summary>
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);
}

View File

@ -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);

View File

@ -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; }

View File

@ -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
/// </summary>
public bool RgbdsSyntax;
/// <summary>
/// true to show sgb border (sgb mode only)
/// </summary>
public bool ShowBorder;
public GambatteSettings()
{
GBPalette = (int[])DefaultPalette.Clone();
CGBColors = GBColors.ColorType.gambatte;
RgbdsSyntax = true;
ShowBorder = true;
}

View File

@ -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);

View File

@ -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<TextStateData> 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;
}
}
}

View File

@ -11,7 +11,12 @@
/// stored image of most recent frame
/// </summary>
private readonly int[] VideoBuffer = CreateVideoBuffer();
/// <summary>
/// stored image of most recent sgb frame
/// </summary>
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;

View File

@ -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
/// </summary>
private const uint TICKSPERSECOND = 2097152;
/// <summary>
/// number of reset stall ticks
/// </summary>
private uint ResetStallTicks { get; set; } = 0;
/// <summary>
/// keep a copy of the input callback delegate so it doesn't get GCed
/// </summary>
@ -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
/// <summary>
/// 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;
}
}
/// <summary>
@ -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<string> BUTTON_ORDER_IN_BITMASK = new[] { "Down", "Up", "Left", "Right", "Start", "Select", "B", "A" };
private static readonly IReadOnlyList<string> 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<string> 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;
}
}
}

View File

@ -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)
{

View File

@ -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);
/// <summary>
/// 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);
/// <summary>
/// Gets which SGB controller is in use, 0 indexed.
/// </summary>
/// <param name="core">opaque state pointer</param>
[DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern int gambatte_getjoypadindex(IntPtr core);
/// <summary>
/// type of the read\write memory callbacks
/// </summary>
@ -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);
/// <summary>
/// Saves emulator state to the buffer given by 'stateBuf'.
/// </summary>
/// <param name="core">opaque state pointer</param>
/// <param name="videoBuf">160x144 RGB32 (native endian) video frame buffer or 0. Used for saving a thumbnail.</param>
/// <param name="pitch">pitch distance in number of pixels (not bytes) from the start of one line to the next in videoBuf.</param>
/// <param name="stateBuf">buffer for savestate</param>
/// <returns>size</returns>
[DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern int gambatte_savestate(IntPtr core, int[] videoBuf, int pitch, byte[] stateBuf);
/// <summary>
/// Loads emulator state from the buffer given by 'stateBuf' of size 'size'.
/// </summary>
/// <param name="core">opaque state pointer</param>
/// <param name="stateBuf">buffer for savestate</param>
/// <param name="size">size of savestate buffer</param>
/// <returns>success</returns>
[DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern bool gambatte_loadstate(IntPtr core, byte[] stateBuf, int size);
/// <summary>
/// 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.

@ -1 +1 @@
Subproject commit c3e44f8fde85317219f233d0f2876e2a2fb354ee
Subproject commit dc09a5882c962c426b5c56f4dec541e5fd22335e