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[] { "SNES" },
new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }), new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }),
(new[] { "SGB" }, (new[] { "SGB" },
new[] { CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}), new[] { CoreNames.Gambatte, CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}),
(new[] { "GB", "GBC" }, (new[] { "GB", "GBC" },
new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }), new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }),
(new[] { "DGB" }, (new[] { "DGB" },

View File

@ -2004,6 +2004,7 @@ namespace BizHawk.Client.EmuHawk
case "GB": case "GB":
case "GBC": case "GBC":
case "SGB" when Emulator is Sameboy: case "SGB" when Emulator is Sameboy:
case "SGB" when Emulator is Gameboy:
GBSubMenu.Visible = true; GBSubMenu.Visible = true;
break; break;
case "SNES" when Emulator is LibsnesCore { IsSGB: true }: // doesn't use "SGB" sysID case "SNES" when Emulator is LibsnesCore { IsSGB: true }: // doesn't use "SGB" sysID

View File

@ -28,84 +28,98 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); this.propertyGrid1 = new System.Windows.Forms.PropertyGrid();
this.buttonDefaults = new System.Windows.Forms.Button(); this.buttonDefaults = new System.Windows.Forms.Button();
this.buttonPalette = new System.Windows.Forms.Button(); this.buttonPalette = new System.Windows.Forms.Button();
this.cbRgbdsSyntax = new System.Windows.Forms.CheckBox(); this.cbRgbdsSyntax = new System.Windows.Forms.CheckBox();
this.checkBoxMuted = new System.Windows.Forms.CheckBox(); this.checkBoxMuted = new System.Windows.Forms.CheckBox();
this.SuspendLayout(); this.cbShowBorder = new System.Windows.Forms.CheckBox();
// this.SuspendLayout();
// propertyGrid1 //
// // propertyGrid1
this.propertyGrid1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) //
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.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.propertyGrid1.Location = new System.Drawing.Point(3, 3); this.propertyGrid1.Location = new System.Drawing.Point(3, 3);
this.propertyGrid1.Name = "propertyGrid1"; this.propertyGrid1.Name = "propertyGrid1";
this.propertyGrid1.PropertySort = System.Windows.Forms.PropertySort.NoSort; this.propertyGrid1.PropertySort = System.Windows.Forms.PropertySort.NoSort;
this.propertyGrid1.Size = new System.Drawing.Size(338, 279); this.propertyGrid1.Size = new System.Drawing.Size(402, 368);
this.propertyGrid1.TabIndex = 0; this.propertyGrid1.TabIndex = 0;
this.propertyGrid1.ToolbarVisible = false; this.propertyGrid1.ToolbarVisible = false;
this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.PropertyGrid1_PropertyValueChanged); this.propertyGrid1.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.PropertyGrid1_PropertyValueChanged);
// //
// buttonDefaults // buttonDefaults
// //
this.buttonDefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.Location = new System.Drawing.Point(330, 377);
this.buttonDefaults.Name = "buttonDefaults"; this.buttonDefaults.Name = "buttonDefaults";
this.buttonDefaults.Size = new System.Drawing.Size(75, 23); this.buttonDefaults.Size = new System.Drawing.Size(75, 23);
this.buttonDefaults.TabIndex = 1; this.buttonDefaults.TabIndex = 1;
this.buttonDefaults.Text = "Defaults"; this.buttonDefaults.Text = "Defaults";
this.buttonDefaults.UseVisualStyleBackColor = true; this.buttonDefaults.UseVisualStyleBackColor = true;
this.buttonDefaults.Click += new System.EventHandler(this.ButtonDefaults_Click); this.buttonDefaults.Click += new System.EventHandler(this.ButtonDefaults_Click);
// //
// buttonPalette // buttonPalette
// //
this.buttonPalette.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 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.Location = new System.Drawing.Point(3, 377);
this.buttonPalette.Name = "buttonPalette"; this.buttonPalette.Name = "buttonPalette";
this.buttonPalette.Size = new System.Drawing.Size(75, 23); this.buttonPalette.Size = new System.Drawing.Size(75, 23);
this.buttonPalette.TabIndex = 2; this.buttonPalette.TabIndex = 2;
this.buttonPalette.Text = "Palette..."; this.buttonPalette.Text = "Palette...";
this.buttonPalette.UseVisualStyleBackColor = true; this.buttonPalette.UseVisualStyleBackColor = true;
this.buttonPalette.Click += new System.EventHandler(this.ButtonPalette_Click); this.buttonPalette.Click += new System.EventHandler(this.ButtonPalette_Click);
// //
// checkBoxMuted // cbRgbdsSyntax
// //
this.checkBoxMuted.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.cbRgbdsSyntax.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.checkBoxMuted.AutoSize = true; this.cbRgbdsSyntax.AutoSize = true;
this.checkBoxMuted.Location = new System.Drawing.Point(82, 292); this.cbRgbdsSyntax.Location = new System.Drawing.Point(138, 381);
this.checkBoxMuted.Name = "checkBoxMuted"; this.cbRgbdsSyntax.Name = "cbRgbdsSyntax";
this.checkBoxMuted.Size = new System.Drawing.Size(50, 17); this.cbRgbdsSyntax.Size = new System.Drawing.Size(99, 17);
this.checkBoxMuted.TabIndex = 3; this.cbRgbdsSyntax.TabIndex = 3;
this.checkBoxMuted.Text = "Mute"; this.cbRgbdsSyntax.Text = "RGBDS Syntax";
this.checkBoxMuted.UseVisualStyleBackColor = true; this.cbRgbdsSyntax.UseVisualStyleBackColor = true;
this.checkBoxMuted.CheckedChanged += new System.EventHandler(this.CheckBoxMuted_CheckedChanged); this.cbRgbdsSyntax.CheckedChanged += new System.EventHandler(this.CbRgbdsSyntax_CheckedChanged);
// //
// cbRgbdsSyntax // checkBoxMuted
// //
this.cbRgbdsSyntax.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.checkBoxMuted.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.cbRgbdsSyntax.AutoSize = true; this.checkBoxMuted.AutoSize = true;
this.cbRgbdsSyntax.Location = new System.Drawing.Point(130, 292); this.checkBoxMuted.Location = new System.Drawing.Point(82, 381);
this.cbRgbdsSyntax.Name = "cbRgbdsSyntax"; this.checkBoxMuted.Name = "checkBoxMuted";
this.cbRgbdsSyntax.Size = new System.Drawing.Size(150, 17); this.checkBoxMuted.Size = new System.Drawing.Size(50, 17);
this.cbRgbdsSyntax.TabIndex = 7; this.checkBoxMuted.TabIndex = 4;
this.cbRgbdsSyntax.Text = "RGBDS Syntax"; this.checkBoxMuted.Text = "Mute";
this.cbRgbdsSyntax.UseVisualStyleBackColor = true; this.checkBoxMuted.UseVisualStyleBackColor = true;
this.cbRgbdsSyntax.CheckedChanged += new System.EventHandler(this.CbRgbdsSyntax_CheckedChanged); this.checkBoxMuted.CheckedChanged += new System.EventHandler(this.CheckBoxMuted_CheckedChanged);
// //
// GBPrefControl // cbShowBorder
// //
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Inherit; this.cbShowBorder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.Controls.Add(this.cbRgbdsSyntax); this.cbShowBorder.AutoSize = true;
this.Controls.Add(this.checkBoxMuted); this.cbShowBorder.Location = new System.Drawing.Point(243, 381);
this.Controls.Add(this.buttonPalette); this.cbShowBorder.Name = "cbShowBorder";
this.Controls.Add(this.buttonDefaults); this.cbShowBorder.Size = new System.Drawing.Size(87, 17);
this.Controls.Add(this.propertyGrid1); this.cbShowBorder.TabIndex = 5;
this.Name = "GBPrefControl"; this.cbShowBorder.Text = "Show Border";
this.Size = new System.Drawing.Size(344, 314); this.cbShowBorder.UseVisualStyleBackColor = true;
this.ResumeLayout(false); this.cbShowBorder.CheckedChanged += new System.EventHandler(this.CbShowBorder_CheckedChanged);
this.PerformLayout(); //
// 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.Button buttonPalette;
private System.Windows.Forms.CheckBox cbRgbdsSyntax; private System.Windows.Forms.CheckBox cbRgbdsSyntax;
private System.Windows.Forms.CheckBox checkBoxMuted; 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(); propertyGrid1.Enabled = movieSession.Movie.NotActive();
checkBoxMuted.Checked = _s.Muted; checkBoxMuted.Checked = _s.Muted;
cbRgbdsSyntax.Checked = _s.RgbdsSyntax; cbRgbdsSyntax.Checked = _s.RgbdsSyntax;
cbShowBorder.Checked = _s.ShowBorder;
} }
public void GetSettings(out Gameboy.GambatteSettings s, out Gameboy.GambatteSyncSettings ss) public void GetSettings(out Gameboy.GambatteSettings s, out Gameboy.GambatteSyncSettings ss)
@ -86,5 +87,10 @@ namespace BizHawk.Client.EmuHawk
{ {
_s.RgbdsSyntax = ((CheckBox)sender).Checked; _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> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.buttonOK = new System.Windows.Forms.Button(); this.buttonOK = new System.Windows.Forms.Button();
this.buttonCancel = new System.Windows.Forms.Button(); this.buttonCancel = new System.Windows.Forms.Button();
this.gbPrefControl1 = new GBPrefControl(); this.gbPrefControl1 = new BizHawk.Client.EmuHawk.GBPrefControl();
this.SuspendLayout(); this.SuspendLayout();
// //
// buttonOK // buttonOK
// //
this.buttonOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.DialogResult = System.Windows.Forms.DialogResult.OK;
this.buttonOK.Location = new System.Drawing.Point(280, 363); this.buttonOK.Location = new System.Drawing.Point(280, 363);
this.buttonOK.Name = "buttonOK"; this.buttonOK.Name = "buttonOK";
this.buttonOK.Size = new System.Drawing.Size(75, 23); this.buttonOK.Size = new System.Drawing.Size(75, 23);
this.buttonOK.TabIndex = 1; this.buttonOK.TabIndex = 1;
this.buttonOK.Text = "OK"; this.buttonOK.Text = "OK";
this.buttonOK.UseVisualStyleBackColor = true; this.buttonOK.UseVisualStyleBackColor = true;
// //
// buttonCancel // buttonCancel
// //
this.buttonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.buttonCancel.Location = new System.Drawing.Point(361, 363); this.buttonCancel.Location = new System.Drawing.Point(361, 363);
this.buttonCancel.Name = "buttonCancel"; this.buttonCancel.Name = "buttonCancel";
this.buttonCancel.Size = new System.Drawing.Size(75, 23); this.buttonCancel.Size = new System.Drawing.Size(75, 23);
this.buttonCancel.TabIndex = 2; this.buttonCancel.TabIndex = 2;
this.buttonCancel.Text = "Cancel"; this.buttonCancel.Text = "Cancel";
this.buttonCancel.UseVisualStyleBackColor = true; this.buttonCancel.UseVisualStyleBackColor = true;
// //
// gbPrefControl1 // gbPrefControl1
// //
this.gbPrefControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 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.Left)
| System.Windows.Forms.AnchorStyles.Right))); | System.Windows.Forms.AnchorStyles.Right)));
this.gbPrefControl1.ColorGameBoy = false; this.gbPrefControl1.ColorGameBoy = false;
this.gbPrefControl1.Location = new System.Drawing.Point(12, 12); this.gbPrefControl1.Location = new System.Drawing.Point(12, 12);
this.gbPrefControl1.Name = "gbPrefControl1"; this.gbPrefControl1.Name = "gbPrefControl1";
this.gbPrefControl1.Size = new System.Drawing.Size(424, 345); this.gbPrefControl1.Size = new System.Drawing.Size(424, 345);
this.gbPrefControl1.TabIndex = 0; this.gbPrefControl1.TabIndex = 0;
// //
// GBPrefs // GBPrefs
// //
this.AcceptButton = this.buttonOK; this.AcceptButton = this.buttonOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.buttonCancel; this.CancelButton = this.buttonCancel;
this.ClientSize = new System.Drawing.Size(448, 398); this.ClientSize = new System.Drawing.Size(448, 398);
this.Controls.Add(this.buttonCancel); this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.buttonOK); this.Controls.Add(this.buttonOK);
this.Controls.Add(this.gbPrefControl1); this.Controls.Add(this.gbPrefControl1);
this.Name = "GBPrefs"; this.Name = "GBPrefs";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Game Boy Settings"; this.Text = "Game Boy Settings";
this.ResumeLayout(false); this.ResumeLayout(false);
} }

View File

@ -24,7 +24,7 @@ namespace BizHawk.Client.EmuHawk
using var dlg = new GBPrefs(mainForm.DialogController); using var dlg = new GBPrefs(mainForm.DialogController);
dlg.gbPrefControl1.PutSettings(config, game, movieSession, s, ss); 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()) if (mainForm.ShowDialogAsChild(dlg).IsOk())
{ {
dlg.gbPrefControl1.GetSettings(out s, out ss); dlg.gbPrefControl1.GetSettings(out s, out ss);

View File

@ -9,12 +9,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{ {
public IEmulatorServiceProvider ServiceProvider { get; } 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) public bool FrameAdvance(IController controller, bool render, bool rendersound)
{ {
FrameAdvancePrep(controller); FrameAdvancePrep(controller);
uint samplesEmitted; uint samplesEmitted;
uint samplesEmittedInFrame = 0; // for sgb
switch (_syncSettings.FrameLength) switch (_syncSettings.FrameLength)
{ {
case GambatteSyncSettings.FrameLengthType.VBlankDrivenFrames: 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) if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0)
{ {
Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); 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; _cycleCount += samplesEmitted;
samplesEmittedInFrame += samplesEmitted;
frameOverflow = 0; frameOverflow = 0;
if (rendersound && !Muted) if (rendersound && !Muted)
{ {
ProcessSound((int)samplesEmitted); ProcessSound((int)samplesEmitted);
@ -43,10 +53,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0) if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0)
{ {
Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); 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 // account for actual number of samples emitted
_cycleCount += samplesEmitted; _cycleCount += samplesEmitted;
samplesEmittedInFrame += samplesEmitted;
frameOverflow += samplesEmitted; frameOverflow += samplesEmitted;
if (rendersound && !Muted) if (rendersound && !Muted)
@ -76,10 +94,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0) if (LibGambatte.gambatte_runfor(GambatteState, FrameBuffer, 160, _soundbuff, ref samplesEmitted) > 0)
{ {
Array.Copy(FrameBuffer, VideoBuffer, FrameBuffer.Length); 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 // account for actual number of samples emitted
_cycleCount += samplesEmitted; _cycleCount += samplesEmitted;
samplesEmittedInFrame += samplesEmitted;
frameOverflow += samplesEmitted; frameOverflow += samplesEmitted;
if (rendersound && !Muted) if (rendersound && !Muted)
@ -96,6 +122,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
break; break;
} }
if (IsSgb)
{
ProcessSgbSound((int)samplesEmittedInFrame, (rendersound && !Muted));
}
if (rendersound && !Muted) if (rendersound && !Muted)
{ {
ProcessSoundEnd(); ProcessSoundEnd();
@ -108,7 +139,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
public int Frame { get; private set; } public int Frame { get; private set; }
public string SystemId => "GB"; public string SystemId => IsSgb ? "SGB" : "GB";
public string BoardName { get; } public string BoardName { get; }

View File

@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{ {
_settings = o; _settings = o;
_disassembler.UseRGBDSSyntax = _settings.RgbdsSyntax; _disassembler.UseRGBDSSyntax = _settings.RgbdsSyntax;
if (IsCGBMode()) if (IsCGBMode() || IsSgb)
{ {
SetCGBColors(_settings.CGBColors); SetCGBColors(_settings.CGBColors);
} }
@ -77,11 +77,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
/// </summary> /// </summary>
public bool RgbdsSyntax; public bool RgbdsSyntax;
/// <summary>
/// true to show sgb border (sgb mode only)
/// </summary>
public bool ShowBorder;
public GambatteSettings() public GambatteSettings()
{ {
GBPalette = (int[])DefaultPalette.Clone(); GBPalette = (int[])DefaultPalette.Clone();
CGBColors = GBColors.ColorType.gambatte; CGBColors = GBColors.ColorType.gambatte;
RgbdsSyntax = true; RgbdsSyntax = true;
ShowBorder = true;
} }

View File

@ -37,6 +37,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
// sample pairs before resampling // sample pairs before resampling
private readonly short[] _soundbuff = new short[(35112 + 2064) * 2]; private readonly short[] _soundbuff = new short[(35112 + 2064) * 2];
private readonly short[] _sgbsoundbuff = new short[2048 * 2];
private int _soundoutbuffcontains = 0; private int _soundoutbuffcontains = 0;
@ -45,6 +46,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
private int _latchL = 0; private int _latchL = 0;
private int _latchR = 0; private int _latchR = 0;
private int _sgbLatchL = 0;
private int _sgbLatchR = 0;
private BlipBuffer _blipL, _blipR; private BlipBuffer _blipL, _blipR;
private uint _blipAccumulate; 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() private void ProcessSoundEnd()
{ {
_blipL.EndFrame(_blipAccumulate); _blipL.EndFrame(_blipAccumulate);

View File

@ -1,4 +1,6 @@
using System; //#define USE_UPSTREAM_STATES
using System;
using System.IO; using System.IO;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -22,13 +24,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
public void SaveStateBinary(BinaryWriter writer) 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"); throw new Exception($"{nameof(LibGambatte.gambatte_newstatesave)}() returned false");
} }
#endif
writer.Write(_savebuff.Length); writer.Write(_stateBuf.Length);
writer.Write(_savebuff); writer.Write(_stateBuf);
// other variables // other variables
writer.Write(IsLagFrame); writer.Write(IsLagFrame);
@ -37,22 +47,30 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
writer.Write(frameOverflow); writer.Write(frameOverflow);
writer.Write(_cycleCount); writer.Write(_cycleCount);
writer.Write(IsCgb); writer.Write(IsCgb);
writer.Write(IsSgb);
} }
public void LoadStateBinary(BinaryReader reader) public void LoadStateBinary(BinaryReader reader)
{ {
int length = reader.ReadInt32(); int length = reader.ReadInt32();
if (length != _savebuff.Length) if (length != _stateBuf.Length)
{ {
throw new InvalidOperationException("Savestate buffer size mismatch!"); 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"); throw new Exception($"{nameof(LibGambatte.gambatte_newstateload)}() returned false");
} }
#endif
// other variables // other variables
IsLagFrame = reader.ReadBoolean(); IsLagFrame = reader.ReadBoolean();
@ -61,13 +79,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
frameOverflow = reader.ReadUInt32(); frameOverflow = reader.ReadUInt32();
_cycleCount = reader.ReadUInt64(); _cycleCount = reader.ReadUInt64();
IsCgb = reader.ReadBoolean(); IsCgb = reader.ReadBoolean();
IsSgb = reader.ReadBoolean();
} }
private byte[] _savebuff; private byte[] _stateBuf;
private void NewSaveCoreSetBuff() 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 }; private readonly JsonSerializer ser = new JsonSerializer { Formatting = Formatting.Indented };
@ -81,6 +104,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
public ulong _cycleCount; public ulong _cycleCount;
public uint frameOverflow; public uint frameOverflow;
public bool IsCgb; public bool IsCgb;
public bool IsSgb;
} }
internal TextState<TextStateData> SaveState() internal TextState<TextStateData> SaveState()
@ -95,6 +119,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
s.ExtraData.frameOverflow = frameOverflow; s.ExtraData.frameOverflow = frameOverflow;
s.ExtraData._cycleCount = _cycleCount; s.ExtraData._cycleCount = _cycleCount;
s.ExtraData.IsCgb = IsCgb; s.ExtraData.IsCgb = IsCgb;
s.ExtraData.IsSgb = IsSgb;
return s; return s;
} }
@ -109,6 +134,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
frameOverflow = s.ExtraData.frameOverflow; frameOverflow = s.ExtraData.frameOverflow;
_cycleCount = s.ExtraData._cycleCount; _cycleCount = s.ExtraData._cycleCount;
IsCgb = s.ExtraData.IsCgb; IsCgb = s.ExtraData.IsCgb;
IsSgb = s.ExtraData.IsSgb;
} }
} }
} }

View File

@ -11,7 +11,12 @@
/// stored image of most recent frame /// stored image of most recent frame
/// </summary> /// </summary>
private readonly int[] VideoBuffer = CreateVideoBuffer(); 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() private static int[] CreateVideoBuffer()
{ {
var b = new int[160 * 144]; var b = new int[160 * 144];
@ -24,16 +29,16 @@
public int[] GetVideoBuffer() 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; public int BackgroundColor => 0;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using BizHawk.Common; using BizHawk.Common;
using BizHawk.Common.BufferExtensions; using BizHawk.Common.BufferExtensions;
using BizHawk.Emulation.Common; using BizHawk.Emulation.Common;
@ -20,6 +21,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{ {
[CoreConstructor("GB")] [CoreConstructor("GB")]
[CoreConstructor("GBC")] [CoreConstructor("GBC")]
[CoreConstructor("SGB")]
public Gameboy(CoreComm comm, GameInfo game, byte[] file, Gameboy.GambatteSettings settings, Gameboy.GambatteSyncSettings syncSettings, bool deterministic) public Gameboy(CoreComm comm, GameInfo game, byte[] file, Gameboy.GambatteSettings settings, Gameboy.GambatteSyncSettings syncSettings, bool deterministic)
{ {
var ser = new BasicServiceProvider(this); var ser = new BasicServiceProvider(this);
@ -66,6 +68,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
break; break;
} }
if (game.System == "SGB")
{
flags &= ~(LibGambatte.LoadFlags.CGB_MODE | LibGambatte.LoadFlags.GBA_FLAG);
flags |= LibGambatte.LoadFlags.SGB_MODE;
IsSgb = true;
}
if (_syncSettings.MulticartCompat) if (_syncSettings.MulticartCompat)
{ {
flags |= LibGambatte.LoadFlags.MULTICART_COMPAT; 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; IsCgb = (flags & LibGambatte.LoadFlags.CGB_MODE) == LibGambatte.LoadFlags.CGB_MODE;
biosSystemId = IsCgb ? "GBC" : "GB"; 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) if (_syncSettings.EnableBIOS)
{ {
@ -86,15 +100,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
{ {
if (!IsCgb) 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) else if (_syncSettings.ConsoleMode == GambatteSyncSettings.ConsoleModeType.GBA)
{ {
// patch from cgb->agb re // patch from cgb->agb re
bios[0xF3] ^= 0x03; 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; 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?)"); 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) // set real default colors (before anyone mucks with them at all)
PutSettings((GambatteSettings)settings ?? new GambatteSettings()); PutSettings((GambatteSettings)settings ?? new GambatteSettings());
@ -196,6 +223,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
_cdCallback = new LibGambatte.CDCallback(CDCallbackProc); _cdCallback = new LibGambatte.CDCallback(CDCallbackProc);
ControllerDefinition = CreateControllerDefinition(IsSgb, _syncSettings.FrameLength is GambatteSyncSettings.FrameLengthType.UserDefinedFrames);
NewSaveCoreSetBuff(); NewSaveCoreSetBuff();
} }
catch catch
@ -219,6 +248,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
/// </summary> /// </summary>
private const uint TICKSPERSECOND = 2097152; private const uint TICKSPERSECOND = 2097152;
/// <summary>
/// number of reset stall ticks
/// </summary>
private uint ResetStallTicks { get; set; } = 0;
/// <summary> /// <summary>
/// keep a copy of the input callback delegate so it doesn't get GCed /// keep a copy of the input callback delegate so it doesn't get GCed
/// </summary> /// </summary>
@ -237,8 +271,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
public int LagCount { get; set; } public int LagCount { get; set; }
public bool IsLagFrame { get; set; } public bool IsLagFrame { get; set; }
public bool IsCgb { 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> /// <summary>
/// total cycles actually executed /// total cycles actually executed
@ -253,29 +288,52 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
public long CycleCount => (long)_cycleCount; public long CycleCount => (long)_cycleCount;
public double ClockRate => TICKSPERSECOND; public double ClockRate => TICKSPERSECOND;
public static readonly ControllerDefinition GbController = new ControllerDefinition public static ControllerDefinition CreateControllerDefinition(bool sgb, bool sub)
{ {
Name = "Gameboy Controller", var ret = sub
BoolButtons = ? 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");
} }
}; else
public static readonly ControllerDefinition SubGbController = new ControllerDefinition
{
Name = "Subframe Gameboy Controller",
BoolButtons =
{ {
"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() private LibGambatte.Buttons ControllerCallback()
{ {
InputCallbacks.Call(); InputCallbacks.Call();
IsLagFrame = false; 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> /// <summary>
@ -310,16 +368,34 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
} }
// needs to match the reverse order of Libgambatte's button enum // 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) internal void FrameAdvancePrep(IController controller)
{ {
// update our local copy of the controller data // update our local copy of the controller data
byte b = 0; uint b = 0;
for (var i = 0; i < 8; i++) if (IsSgb)
{ {
b <<= 1; for (var i = 0; i < 32; i++)
if (controller.IsPressed(BUTTON_ORDER_IN_BITMASK[i])) b |= 1; {
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; CurrentButtons = (LibGambatte.Buttons)b;
@ -328,8 +404,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
if (controller.IsPressed("Power")) 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, ResetStallTicks);
LibGambatte.gambatte_reset(GambatteState, stall ? 485808u : 0u);
} }
if (Tracer.IsEnabled()) if (Tracer.IsEnabled())
@ -779,8 +854,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
else else
{ {
LinkConnected = false; LinkConnected = false;
printer.Disconnect(); if (printer != null) // have no idea how this is ever null???
printer = null; {
printer.Disconnect();
printer = null;
}
} }
} }

View File

@ -71,8 +71,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
private const int SampPerFrame = 35112; private const int SampPerFrame = 35112;
private readonly SaveController LCont = new SaveController(Gameboy.GbController); private readonly SaveController LCont = new SaveController(Gameboy.CreateControllerDefinition(false, false));
private readonly SaveController RCont = new SaveController(Gameboy.GbController); private readonly SaveController RCont = new SaveController(Gameboy.CreateControllerDefinition(false, false));
public bool IsCGBMode(bool right) public bool IsCGBMode(bool right)
{ {

View File

@ -93,6 +93,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
[DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe int gambatte_runfor(IntPtr core, int* videobuf, int pitch, short* soundbuf, ref uint samples); 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> /// <summary>
/// Reset to initial state. /// Reset to initial state.
/// Equivalent to reloading a ROM image, or turning a Game Boy Color off and on again. /// 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)] [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern void gambatte_setinputgetter(IntPtr core, InputGetter getinput); 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> /// <summary>
/// type of the read\write memory callbacks /// type of the read\write memory callbacks
/// </summary> /// </summary>
@ -405,6 +418,27 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
[DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)] [DllImport("libgambatte", CallingConvention = CallingConvention.Cdecl)]
public static extern bool gambatte_getmemoryarea(IntPtr core, MemoryAreas which, ref IntPtr data, ref int length); 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> /// <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). /// 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. /// 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