diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 9a9e0338ab..d054ab86f6 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -922,6 +922,7 @@ namespace BizHawk.Client.Common { //core = CoreInventory.Instance["GB", "Pizza Boy"]; core = CoreInventory.Instance["GB", "Gambatte"]; + //core = CoreInventory.Instance["GB", "SameBoy"]; } else { @@ -934,7 +935,7 @@ namespace BizHawk.Client.Common } else { - core = CoreInventory.Instance["SGB", "Pizza Boy"]; + core = CoreInventory.Instance["SGB", "SameBoy"]; } } break; diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 290b2429bf..21c72d1312 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -25,6 +25,7 @@ using BizHawk.Client.EmuHawk.ToolExtensions; using BizHawk.Emulation.Cores.Computers.AppleII; using BizHawk.Client.ApiHawk; using BizHawk.Emulation.Cores.Computers.Commodore64; +using BizHawk.Emulation.Cores.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { @@ -2002,7 +2003,14 @@ namespace BizHawk.Client.EmuHawk private void GBCoreSettingsMenuItem_Click(object sender, EventArgs e) { - GBPrefs.DoGBPrefsDialog(this); + if (Global.Emulator is Gameboy) + { + GBPrefs.DoGBPrefsDialog(this); + } + else // sameboy + { + GenericCoreConfig.DoDialog(this, "Gameboy Settings"); + } } private void LoadGbInSgbMenuItem_Click(object sender, EventArgs e) diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 2274b6ce5e..611c693e20 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -33,6 +33,7 @@ using BizHawk.Emulation.Common.Base_Implementations; using BizHawk.Emulation.Cores.Nintendo.SNES9X; using BizHawk.Emulation.Cores.Consoles.SNK; using BizHawk.Emulation.Cores.Consoles.Sega.PicoDrive; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; namespace BizHawk.Client.EmuHawk { @@ -1744,6 +1745,10 @@ namespace BizHawk.Client.EmuHawk { sNESToolStripMenuItem.Visible = true; } + else if (Emulator is Sameboy) + { + GBSubMenu.Visible = true; + } break; case "Coleco": ColecoSubMenu.Visible = true; diff --git a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs index 6151c05677..8dc50567ef 100644 --- a/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs +++ b/BizHawk.Client.EmuHawk/tools/GB/GBGPUView.cs @@ -9,13 +9,15 @@ using BizHawk.Emulation.Cores.Nintendo.Gameboy; using BizHawk.Client.EmuHawk.WinFormExtensions; using System.Collections.Generic; using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy; +using BizHawk.Common; namespace BizHawk.Client.EmuHawk { public partial class GBGPUView : Form, IToolFormAutoConfig { [RequiredService] - public Gameboy Gb { get; private set; } + public IGameboyCommon Gb { get; private set; } // TODO: freeze semantics are a bit weird: details for a mouseover or freeze are taken from the current // state, not the state at the last callback (and so can be quite different when update is set to manual). @@ -32,11 +34,8 @@ namespace BizHawk.Client.EmuHawk // g' = 8.25g // b' = 8.25b - // gambatte doesn't modify these memory locations unless you reconstruct, so we can store - private IntPtr _vram; - private IntPtr _bgpal; - private IntPtr _sppal; - private IntPtr _oam; + + private GPUMemoryAreas _memory; private bool _cgb; // set once at start private int _lcdc; // set at each callback @@ -89,13 +88,9 @@ namespace BizHawk.Client.EmuHawk _cgb = Gb.IsCGBMode(); _lcdc = 0; - // TODO: can this be a required Emulator Service, and let the tool manage the logic of closing? - if (!Gb.GetGPUMemoryAreas(out _vram, out _bgpal, out _sppal, out _oam)) - { - if (Visible) - Close(); - } - tilespal = _bgpal; + _memory = Gb.GetGPU(); + + tilespal = _memory.Bgpal; if (_cgb) label4.Enabled = true; @@ -357,112 +352,120 @@ namespace BizHawk.Client.EmuHawk #endregion - void ScanlineCallback(int lcdc) + void ScanlineCallback(byte lcdc) { - _lcdc = lcdc; - // set alpha on all pixels - unsafe + using (_memory.EnterExit()) { - int* p = (int*)_bgpal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - p = (int*)_sppal; - for (int i = 0; i < 32; i++) - p[i] |= unchecked((int)0xff000000); - int c = Spriteback.ToArgb(); - for (int i = 0; i < 32; i += 4) - p[i] = c; - } + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - // bg maps - if (!_cgb) - { - DrawBGDMG( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + _lcdc = lcdc; + // set alpha on all pixels + // TODO: RE: Spriteback, you can't muck with Sameboy in this way due to how SGB reads stuff...? + /*unsafe + { + int* p = (int*)_bgpal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + p = (int*)_sppal; + for (int i = 0; i < 32; i++) + p[i] |= unchecked((int)0xff000000); + int c = Spriteback.ToArgb(); + for (int i = 0; i < 32; i += 4) + p[i] = c; + }*/ - DrawBGDMG( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - else - { - DrawBGCGB( - bmpViewBG.BMP, - _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), - _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), - !lcdc.Bit(4), - _bgpal); + // bg maps + if (!_cgb) + { + DrawBGDMG( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - DrawBGCGB( - bmpViewWin.BMP, - _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), - _vram + 0x1000, // force win to second tile bank??? - true, - _bgpal); - } - bmpViewBG.Refresh(); - bmpViewWin.Refresh(); + DrawBGDMG( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + else + { + DrawBGCGB( + bmpViewBG.BMP, + _vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + _vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4), + _bgpal); - // tile display - // TODO: user selects palette to use, instead of fixed palette 0 - // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? - DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); - bmpViewTiles1.Refresh(); - if (_cgb) - { - DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); - bmpViewTiles2.Refresh(); - } + DrawBGCGB( + bmpViewWin.BMP, + _vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + _vram + 0x1000, // force win to second tile bank??? + true, + _bgpal); + } + bmpViewBG.Refresh(); + bmpViewWin.Refresh(); - // palettes - if (_cgb) - { - bmpViewBGPal.ChangeBitmapSize(8, 4); - if (bmpViewBGPal.Width != 128) - bmpViewBGPal.Width = 128; - bmpViewSPPal.ChangeBitmapSize(8, 4); - if (bmpViewSPPal.Width != 128) - bmpViewSPPal.Width = 128; - DrawPal(bmpViewBGPal.BMP, _bgpal, 8); - DrawPal(bmpViewSPPal.BMP, _sppal, 8); - } - else - { - bmpViewBGPal.ChangeBitmapSize(1, 4); - if (bmpViewBGPal.Width != 16) - bmpViewBGPal.Width = 16; - bmpViewSPPal.ChangeBitmapSize(2, 4); - if (bmpViewSPPal.Width != 32) - bmpViewSPPal.Width = 32; - DrawPal(bmpViewBGPal.BMP, _bgpal, 1); - DrawPal(bmpViewSPPal.BMP, _sppal, 2); - } - bmpViewBGPal.Refresh(); - bmpViewSPPal.Refresh(); + // tile display + // TODO: user selects palette to use, instead of fixed palette 0 + // or possibly "smart" where, if a tile is in use, it's drawn with one of the palettes actually being used with it? + DrawTiles(bmpViewTiles1.BMP, _vram, tilespal); + bmpViewTiles1.Refresh(); + if (_cgb) + { + DrawTiles(bmpViewTiles2.BMP, _vram + 0x2000, tilespal); + bmpViewTiles2.Refresh(); + } - // oam - if (lcdc.Bit(2)) // 8x16 - { - bmpViewOAM.ChangeBitmapSize(320, 16); - if (bmpViewOAM.Height != 16) - bmpViewOAM.Height = 16; - } - else - { - bmpViewOAM.ChangeBitmapSize(320, 8); - if (bmpViewOAM.Height != 8) - bmpViewOAM.Height = 8; - } - DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); - bmpViewOAM.Refresh(); + // palettes + if (_cgb) + { + bmpViewBGPal.ChangeBitmapSize(8, 4); + if (bmpViewBGPal.Width != 128) + bmpViewBGPal.Width = 128; + bmpViewSPPal.ChangeBitmapSize(8, 4); + if (bmpViewSPPal.Width != 128) + bmpViewSPPal.Width = 128; + DrawPal(bmpViewBGPal.BMP, _bgpal, 8); + DrawPal(bmpViewSPPal.BMP, _sppal, 8); + } + else + { + bmpViewBGPal.ChangeBitmapSize(1, 4); + if (bmpViewBGPal.Width != 16) + bmpViewBGPal.Width = 16; + bmpViewSPPal.ChangeBitmapSize(2, 4); + if (bmpViewSPPal.Width != 32) + bmpViewSPPal.Width = 32; + DrawPal(bmpViewBGPal.BMP, _bgpal, 1); + DrawPal(bmpViewSPPal.BMP, _sppal, 2); + } + bmpViewBGPal.Refresh(); + bmpViewSPPal.Refresh(); + // oam + if (lcdc.Bit(2)) // 8x16 + { + bmpViewOAM.ChangeBitmapSize(320, 16); + if (bmpViewOAM.Height != 16) + bmpViewOAM.Height = 16; + } + else + { + bmpViewOAM.ChangeBitmapSize(320, 8); + if (bmpViewOAM.Height != 8) + bmpViewOAM.Height = 8; + } + DrawOam(bmpViewOAM.BMP, _oam, _vram, _sppal, lcdc.Bit(2), _cgb); + bmpViewOAM.Refresh(); + } // try to run the current mouseover, to refresh if the mouse is being held over a pane while the emulator runs // this doesn't really work well; the update rate seems to be throttled MouseEventArgs e = new MouseEventArgs(MouseButtons.None, 0, Cursor.Position.X, Cursor.Position.Y, 0); @@ -514,7 +517,7 @@ namespace BizHawk.Client.EmuHawk private void buttonRefresh_Click(object sender, EventArgs e) { - if (cbscanline == -2 && Gb != null) + if (cbscanline == -2) Gb.SetScanlineCallback(ScanlineCallback, -2); } @@ -614,145 +617,177 @@ namespace BizHawk.Client.EmuHawk private unsafe void PaletteMouseover(int x, int y, bool sprite) { - bmpViewDetails.ChangeBitmapSize(8, 10); - if (bmpViewDetails.Height != 80) - bmpViewDetails.Height = 80; - var sb = new StringBuilder(); - x /= 16; - y /= 16; - int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; - int color = pal[y]; - - sb.AppendLine(string.Format("Palette {0}", x)); - sb.AppendLine(string.Format("Color {0}", y)); - sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); - - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - int* dest = (int*)lockdata.Scan0; - int pitch = lockdata.Stride / sizeof(int); - - for (int py = 0; py < 10; py++) + using (_memory.EnterExit()) { - for (int px = 0; px < 8; px++) + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; + + bmpViewDetails.ChangeBitmapSize(8, 10); + if (bmpViewDetails.Height != 80) + bmpViewDetails.Height = 80; + var sb = new StringBuilder(); + x /= 16; + y /= 16; + int* pal = (int*)(sprite ? _sppal : _bgpal) + x * 4; + int color = pal[y]; + + sb.AppendLine(string.Format("Palette {0}", x)); + sb.AppendLine(string.Format("Color {0}", y)); + sb.AppendLine(string.Format("(R,G,B) = ({0},{1},{2})", color >> 16 & 255, color >> 8 & 255, color & 255)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 10), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + int* dest = (int*)lockdata.Scan0; + int pitch = lockdata.Stride / sizeof(int); + + for (int py = 0; py < 10; py++) { - if (py < 8) - *dest++ = color; - else - *dest++ = pal[px / 2]; + for (int px = 0; px < 8; px++) + { + if (py < 8) + *dest++ = color; + else + *dest++ = pal[px / 2]; + } + dest -= 8; + dest += pitch; } - dest -= 8; - dest += pitch; + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void TileMouseover(int x, int y, bool secondbank) { - // todo: draw with a specific palette - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - x /= 8; - y /= 8; - int tileindex = y * 16 + x; - int tileoffs = tileindex * 16; - if (_cgb) - sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); - else - sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); + // todo: draw with a specific palette + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + x /= 8; + y /= 8; + int tileindex = y * 16 + x; + int tileoffs = tileindex * 16; + if (_cgb) + sb.AppendLine(string.Format("Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, secondbank ? 1 : 0)); + else + sb.AppendLine(string.Format("Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + DrawTile((byte*)_vram + tileoffs + (secondbank ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)tilespal); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); + } } unsafe void TilemapMouseover(int x, int y, bool win) { - bmpViewDetails.ChangeBitmapSize(8, 8); - if (bmpViewDetails.Height != 64) - bmpViewDetails.Height = 64; - var sb = new StringBuilder(); - bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); - int mapoffs = secondmap ? 0x1c00 : 0x1800; - x /= 8; - y /= 8; - mapoffs += y * 32 + x; - byte* mapbase = (byte*)_vram + mapoffs; - int tileindex = mapbase[0]; - if (win || !_lcdc.Bit(4)) // 0x9000 base - if (tileindex < 128) - tileindex += 256; // compute all if from 0x8000 base - int tileoffs = tileindex * 16; - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (!_cgb) + using (_memory.EnterExit()) { - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); - DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); - } - else - { - int tileext = mapbase[8192]; + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); - sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); - DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + bmpViewDetails.ChangeBitmapSize(8, 8); + if (bmpViewDetails.Height != 64) + bmpViewDetails.Height = 64; + var sb = new StringBuilder(); + bool secondmap = win ? _lcdc.Bit(6) : _lcdc.Bit(3); + int mapoffs = secondmap ? 0x1c00 : 0x1800; + x /= 8; + y /= 8; + mapoffs += y * 32 + x; + byte* mapbase = (byte*)_vram + mapoffs; + int tileindex = mapbase[0]; + if (win || !_lcdc.Bit(4)) // 0x9000 base + if (tileindex < 128) + tileindex += 256; // compute all if from 0x8000 base + int tileoffs = tileindex * 16; + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (!_cgb) + { + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", tileindex, tileoffs + 0x8000)); + DrawTile((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal); + } + else + { + int tileext = mapbase[8192]; + + sb.AppendLine(string.Format("{0} Map ({1},{2}) @{3:x4}", win ? "Win" : "BG", x, y, mapoffs + 0x8000)); + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", tileindex, tileoffs + 0x8000, tileext.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", tileext & 7)); + sb.AppendLine(string.Format(" Flags {0}{1}{2}", tileext.Bit(5) ? 'H' : ' ', tileext.Bit(6) ? 'V' : ' ', tileext.Bit(7) ? 'P' : ' ')); + DrawTileHv((byte*)_vram + tileoffs + (tileext.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_bgpal + 4 * (tileext & 7), tileext.Bit(5), tileext.Bit(6)); + } + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } unsafe void SpriteMouseover(int x, int y) { - bool tall = _lcdc.Bit(2); - x /= 8; - y /= 8; - bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); - if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) - bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; - var sb = new StringBuilder(); + using (_memory.EnterExit()) + { + var _bgpal = _memory.Bgpal; + var _sppal = _memory.Sppal; + var _oam = _memory.Oam; + var _vram = _memory.Vram; - byte* oament = (byte*)_oam + 4 * x; - int sy = oament[0]; - int sx = oament[1]; - int tilenum = oament[2]; - int flags = oament[3]; - bool hflip = flags.Bit(5); - bool vflip = flags.Bit(6); - if (tall) - tilenum = vflip ? tilenum | 1 : tilenum & ~1; - int tileoffs = tilenum * 16; - sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); - sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); - var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - if (_cgb) - { - sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); - sb.AppendLine(string.Format(" Palette {0}", flags & 7)); - DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + bool tall = _lcdc.Bit(2); + x /= 8; + y /= 8; + bmpViewDetails.ChangeBitmapSize(8, tall ? 16 : 8); + if (bmpViewDetails.Height != bmpViewDetails.BMP.Height * 8) + bmpViewDetails.Height = bmpViewDetails.BMP.Height * 8; + var sb = new StringBuilder(); + + byte* oament = (byte*)_oam + 4 * x; + int sy = oament[0]; + int sx = oament[1]; + int tilenum = oament[2]; + int flags = oament[3]; + bool hflip = flags.Bit(5); + bool vflip = flags.Bit(6); if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + tilenum = vflip ? tilenum | 1 : tilenum & ~1; + int tileoffs = tilenum * 16; + sb.AppendLine(string.Format("Sprite #{0} @{1:x4}", x, 4 * x + 0xfe00)); + sb.AppendLine(string.Format(" (x,y) = ({0},{1})", sx, sy)); + var lockdata = bmpViewDetails.BMP.LockBits(new Rectangle(0, 0, 8, tall ? 16 : 8), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + if (_cgb) + { + sb.AppendLine(string.Format(" Tile #{0} @{2}:{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000, flags.Bit(3) ? 1 : 0)); + sb.AppendLine(string.Format(" Palette {0}", flags & 7)); + DrawTileHv((byte*)_vram + tileoffs + (flags.Bit(3) ? 8192 : 0), (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16) + (flags.Bit(3) ? 8192 : 0), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags & 7), hflip, vflip); + } + else + { + sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); + sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); + DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); + if (tall) + DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); + } + sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); + bmpViewDetails.BMP.UnlockBits(lockdata); + labelDetails.Text = sb.ToString(); + bmpViewDetails.Refresh(); } - else - { - sb.AppendLine(string.Format(" Tile #{0} @{1:x4}", y == 1 ? tilenum ^ 1 : tilenum, tileoffs + 0x8000)); - sb.AppendLine(string.Format(" Palette {0}", flags.Bit(4) ? 1 : 0)); - DrawTileHv((byte*)_vram + tileoffs, (int*)lockdata.Scan0, lockdata.Stride / sizeof(int), (int*)_sppal + (flags.Bit(4) ? 4 : 0), hflip, vflip); - if (tall) - DrawTileHv((byte*)_vram + (tileoffs ^ 16), (int*)(lockdata.Scan0 + lockdata.Stride * 8), lockdata.Stride / sizeof(int), (int*)_sppal + 4 * (flags.Bit(4) ? 4 : 0), hflip, vflip); - } - sb.AppendLine(string.Format(" Flags {0}{1}{2}", hflip ? 'H' : ' ', vflip ? 'V' : ' ', flags.Bit(7) ? 'P' : ' ')); - bmpViewDetails.BMP.UnlockBits(lockdata); - labelDetails.Text = sb.ToString(); - bmpViewDetails.Refresh(); } private void bmpViewBG_MouseEnter(object sender, EventArgs e) @@ -882,9 +917,9 @@ namespace BizHawk.Client.EmuHawk else if (e.Button == MouseButtons.Left) { if (sender == bmpViewBGPal) - tilespal = _bgpal + e.X / 16 * 16; + tilespal = _memory.Bgpal + e.X / 16 * 16; else if (sender == bmpViewSPPal) - tilespal = _sppal + e.X / 16 * 16; + tilespal = _memory.Sppal + e.X / 16 * 16; } } diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 64328b3723..a7b4a3b804 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -539,7 +539,9 @@ + + @@ -1310,6 +1312,8 @@ + + diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 9e04730e4a..864cdd856d 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -439,7 +439,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy #region ppudebug - public bool GetGPUMemoryAreas(out IntPtr vram, out IntPtr bgpal, out IntPtr sppal, out IntPtr oam) + public GPUMemoryAreas GetGPU() { IntPtr _vram = IntPtr.Zero; IntPtr _bgpal = IntPtr.Zero; @@ -451,23 +451,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.sppal, ref _sppal, ref unused) || !LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.oam, ref _oam, ref unused)) { - vram = IntPtr.Zero; - bgpal = IntPtr.Zero; - sppal = IntPtr.Zero; - oam = IntPtr.Zero; - return false; + throw new InvalidOperationException("Unexpected error in gambatte_getmemoryarea"); } - vram = _vram; - bgpal = _bgpal; - sppal = _sppal; - oam = _oam; - return true; - } + return new GPUMemoryAreas(_vram, _oam, _sppal, _bgpal); - /// - /// - /// current value of register $ff40 (LCDC) - public delegate void ScanlineCallback(int lcdc); + } /// /// set up callback diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs index fa236b8f47..b874919ad7 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/IGameboyCommon.cs @@ -1,4 +1,6 @@ -using System; +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,8 +8,49 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { - public interface IGameboyCommon + /// + /// + /// current value of register $ff40 (LCDC) + public delegate void ScanlineCallback(byte lcdc); + + public interface IGameboyCommon : ISpecializedEmulatorService { bool IsCGBMode(); + GPUMemoryAreas GetGPU(); + + /// + /// set up callback + /// + /// scanline. -1 = end of frame, -2 = RIGHT NOW + void SetScanlineCallback(ScanlineCallback callback, int line); + } + + public class GPUMemoryAreas : IMonitor + { + public IntPtr Vram { get; } + public IntPtr Oam { get; } + public IntPtr Sppal { get; } + public IntPtr Bgpal { get; } + + private readonly IMonitor _monitor; + + public GPUMemoryAreas(IntPtr vram, IntPtr oam, IntPtr sppal, IntPtr bgpal, IMonitor monitor = null) + { + Vram = vram; + Oam = oam; + Sppal = sppal; + Bgpal = bgpal; + _monitor = monitor; + } + + public void Enter() + { + _monitor?.Enter(); + } + + public void Exit() + { + _monitor?.Exit(); + } } } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs new file mode 100644 index 0000000000..405635404c --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibSameboy.cs @@ -0,0 +1,55 @@ +using BizHawk.Common.BizInvoke; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy +{ + public abstract class LibSameboy : LibWaterboxCore + { + [Flags] + public enum Buttons : uint + { + A = 0x01, + B = 0x02, + SELECT = 0x04, + START = 0x08, + RIGHT = 0x10, + LEFT = 0x20, + UP = 0x40, + DOWN = 0x80 + } + + [StructLayout(LayoutKind.Sequential)] + public new class FrameInfo : LibWaterboxCore.FrameInfo + { + public long Time; + public Buttons Keys; + } + + [BizImport(CC)] + public abstract bool Init(bool cgb, byte[] spc, int spclen); + + [BizImport(CC)] + public abstract void GetGpuMemory(IntPtr[] ptrs); + + [BizImport(CC)] + public abstract void SetScanlineCallback(ScanlineCallback callback, int ly); + + [BizImport(CC)] + public abstract byte GetIoReg(byte port); + + [BizImport(CC)] + public abstract void PutSaveRam(); + + [BizImport(CC)] + public abstract void GetSaveRam(); + + [BizImport(CC)] + public abstract bool HasSaveRam(); + } +} diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs index 52b966647b..90fecb3b99 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Pizza.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy { [Core("Pizza Boy", "Davide Berra", true, true, "c7bc6ee376028b3766de8d7a02e60ab794841f45", "https://github.com/davideberra/emu-pizza/", false)] - public class Pizza : WaterboxCore, IGameboyCommon + public class Pizza : WaterboxCore { private LibPizza _pizza; private readonly bool _sgb; diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs new file mode 100644 index 0000000000..c9d5269710 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Sameboy.cs @@ -0,0 +1,298 @@ +using BizHawk.Common; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Properties; +using BizHawk.Emulation.Cores.Waterbox; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy +{ + [Core("SameBoy", "LIJI32", true, false, "efc11783c7fb6da66e1dd084e41ba6a85c0bd17e", + "https://sameboy.github.io/", false)] + public class Sameboy : WaterboxCore, + IGameboyCommon, ISaveRam, + ISettable + { + /// + /// the nominal length of one frame + /// + private const int TICKSPERFRAME = 35112; + + /// + /// number of ticks per second (GB, CGB) + /// + private const int TICKSPERSECOND = 2097152; + + /// + /// number of ticks per second (SGB) + /// + private const int TICKSPERSECOND_SGB = 2147727; + + private LibSameboy _core; + private bool _cgb; + private bool _sgb; + + [CoreConstructor("SGB")] + public Sameboy(byte[] rom, CoreComm comm, SyncSettings syncSettings, bool deterministic) + : this(rom, comm, true, syncSettings, deterministic) + { } + + [CoreConstructor("GB")] + public Sameboy(CoreComm comm, byte[] rom, SyncSettings syncSettings, bool deterministic) + : this(rom, comm, false, syncSettings, deterministic) + { } + + public Sameboy(byte[] rom, CoreComm comm, bool sgb, SyncSettings syncSettings, bool deterministic) + : base(comm, new Configuration + { + DefaultWidth = sgb ? 256 : 160, + DefaultHeight = sgb ? 224 : 144, + MaxWidth = sgb ? 256 : 160, + MaxHeight = sgb ? 224 : 144, + MaxSamples = 1024, + DefaultFpsNumerator = sgb ? TICKSPERSECOND_SGB : TICKSPERSECOND, + DefaultFpsDenominator = TICKSPERFRAME, + SystemId = sgb ? "SGB" : "GB" + }) + { + _core = PreInit(new PeRunnerOptions + { + Filename = "sameboy.wbx", + SbrkHeapSizeKB = 192, + InvisibleHeapSizeKB = 4, + SealedHeapSizeKB = 9 * 1024, + PlainHeapSizeKB = 4, + MmapHeapSizeKB = 1024 + }); + + _cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb; + _sgb = sgb; + Console.WriteLine("Automaticly detected CGB to " + _cgb); + _syncSettings = syncSettings ?? new SyncSettings(); + + var bios = _syncSettings.UseRealBIOS && !sgb + ? comm.CoreFileProvider.GetFirmware(_cgb ? "GBC" : "GB", "World", true) + : Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot : Resources.SameboyDmgBoot)); + + var spc = sgb + ? Util.DecompressGzipFile(new MemoryStream(Resources.SgbCartPresent_SPC)) + : null; + + _exe.AddReadonlyFile(rom, "game.rom"); + _exe.AddReadonlyFile(bios, "boot.rom"); + + if (!_core.Init(_cgb, spc, spc?.Length ?? 0)) + { + throw new InvalidOperationException("Core rejected the rom!"); + } + + _exe.RemoveReadonlyFile("game.rom"); + _exe.RemoveReadonlyFile("boot.rom"); + + PostInit(); + + var scratch = new IntPtr[4]; + _core.GetGpuMemory(scratch); + _gpuMemory = new GPUMemoryAreas(scratch[0], scratch[1], scratch[3], scratch[2], _exe); + + DeterministicEmulation = deterministic || !_syncSettings.UseRealTime; + InitializeRtc(_syncSettings.InitialTime); + } + + #region Controller + + private static readonly ControllerDefinition _gbDefinition; + private static readonly ControllerDefinition _sgbDefinition; + public override ControllerDefinition ControllerDefinition => _sgb ? _sgbDefinition : _gbDefinition; + + private static ControllerDefinition CreateControllerDefinition(int p) + { + var ret = new ControllerDefinition { Name = "Gameboy Controller" }; + for (int i = 0; i < p; i++) + { + ret.BoolButtons.AddRange( + new[] { "Up", "Down", "Left", "Right", "A", "B", "Select", "Start" } + .Select(s => $"P{i + 1} {s}")); + } + return ret; + } + + static Sameboy() + { + _gbDefinition = CreateControllerDefinition(1); + _sgbDefinition = CreateControllerDefinition(4); + } + + private LibSameboy.Buttons GetButtons(IController c) + { + LibSameboy.Buttons b = 0; + for (int i = _sgb ? 4 : 1; i > 0; i--) + { + if (c.IsPressed($"P{i} Up")) + b |= LibSameboy.Buttons.UP; + if (c.IsPressed($"P{i} Down")) + b |= LibSameboy.Buttons.DOWN; + if (c.IsPressed($"P{i} Left")) + b |= LibSameboy.Buttons.LEFT; + if (c.IsPressed($"P{i} Right")) + b |= LibSameboy.Buttons.RIGHT; + if (c.IsPressed($"P{i} A")) + b |= LibSameboy.Buttons.A; + if (c.IsPressed($"P{i} B")) + b |= LibSameboy.Buttons.B; + if (c.IsPressed($"P{i} Select")) + b |= LibSameboy.Buttons.SELECT; + if (c.IsPressed($"P{i} Start")) + b |= LibSameboy.Buttons.START; + if (i != 1) + b = (LibSameboy.Buttons)((uint)b << 8); + } + return b; + } + + #endregion + + #region ISaveram + + public new bool SaveRamModified => _core.HasSaveRam(); + + public new byte[] CloneSaveRam() + { + _exe.AddTransientFile(null, "save.ram"); + _core.GetSaveRam(); + return _exe.RemoveTransientFile("save.ram"); + } + + public new void StoreSaveRam(byte[] data) + { + _exe.AddReadonlyFile(data, "save.ram"); + _core.PutSaveRam(); + _exe.RemoveReadonlyFile("save.ram"); + } + + #endregion + + #region ISettable + + private SyncSettings _syncSettings; + + public class SyncSettings + { + [DisplayName("Initial Time")] + [Description("Initial time of emulation. Only relevant when UseRealTime is false.")] + [DefaultValue(typeof(DateTime), "2010-01-01")] + public DateTime InitialTime { get; set; } + + [DisplayName("Use RealTime")] + [Description("If true, RTC clock will be based off of real time instead of emulated time. Ignored (set to false) when recording a movie.")] + [DefaultValue(false)] + public bool UseRealTime { get; set; } + + [Description("If true, real BIOS files will be used. Ignored in SGB mode.")] + [DefaultValue(false)] + public bool UseRealBIOS { get; set; } + + public SyncSettings Clone() + { + return (SyncSettings)MemberwiseClone(); + } + + public static bool NeedsReboot(SyncSettings x, SyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } + + public SyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + } + + public object GetSettings() + { + return null; + } + + public SyncSettings GetSyncSettings() + { + return _syncSettings.Clone(); + } + + public bool PutSettings(object o) + { + return false; + } + + public bool PutSyncSettings(SyncSettings o) + { + var ret = SyncSettings.NeedsReboot(_syncSettings, o); + _syncSettings = o; + return ret; + } + + #endregion + + protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound) + { + return new LibSameboy.FrameInfo + { + Time = GetRtcTime(!DeterministicEmulation), + Keys = GetButtons(controller) + }; + } + + protected override void FrameAdvancePost() + { + if (_scanlineCallback != null && _scanlineCallbackLine == -1) + _scanlineCallback(_core.GetIoReg(0x40)); + } + + protected override void LoadStateBinaryInternal(BinaryReader reader) + { + UpdateCoreScanlineCallback(false); + } + + public bool IsCGBMode() => _cgb; + + private GPUMemoryAreas _gpuMemory; + + public GPUMemoryAreas GetGPU() => _gpuMemory; + private ScanlineCallback _scanlineCallback; + private int _scanlineCallbackLine; + + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + _scanlineCallback = callback; + _scanlineCallbackLine = line; + UpdateCoreScanlineCallback(true); + } + + private void UpdateCoreScanlineCallback(bool now) + { + if (_scanlineCallback == null) + { + _core.SetScanlineCallback(null, -1); + } + else + { + if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153) + { + _core.SetScanlineCallback(_scanlineCallback, _scanlineCallbackLine); + } + else + { + _core.SetScanlineCallback(null, -1); + if (_scanlineCallbackLine == -2 && now) + { + _scanlineCallback(_core.GetIoReg(0x40)); + } + } + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs index 59d6602caf..5a3bbcce9a 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs +++ b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs @@ -60,6 +60,26 @@ namespace BizHawk.Emulation.Cores.Properties { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] SameboyCgbBoot { + get { + object obj = ResourceManager.GetObject("SameboyCgbBoot", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] SameboyDmgBoot { + get { + object obj = ResourceManager.GetObject("SameboyDmgBoot", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/BizHawk.Emulation.Cores/Properties/Resources.resx b/BizHawk.Emulation.Cores/Properties/Resources.resx index 1378c9e6e9..bdb07b9a9f 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.resx +++ b/BizHawk.Emulation.Cores/Properties/Resources.resx @@ -118,6 +118,12 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\cgb_boot.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\dmg_boot.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\sgb-cart-present.spc.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz b/BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz new file mode 100644 index 0000000000..ab0eb54bfd Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/cgb_boot.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz b/BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz new file mode 100644 index 0000000000..85f1e8276a Binary files /dev/null and b/BizHawk.Emulation.Cores/Resources/dmg_boot.bin.gz differ diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs index 1db3950b0a..080b91e4b3 100644 --- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs +++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs @@ -308,6 +308,68 @@ namespace BizHawk.Emulation.Cores.Waterbox } } + private class TransientFile : IFileObject + { + private bool _inUse = false; + public string Name { get; } + public Stream Stream { get; } + public bool Close() + { + if (_inUse) + { + _inUse = false; + return true; + } + else + { + return false; + } + } + + public bool Open(FileAccess access) + { + if (_inUse) + { + return false; + } + else + { + // TODO: if access != RW, the resultant handle lets you do those all anyway + _inUse = true; + Stream.Position = 0; + return true; + } + } + + public void LoadStateBinary(BinaryReader br) + { + throw new InvalidOperationException("Internal savestate error!"); + } + + public void SaveStateBinary(BinaryWriter bw) + { + throw new InvalidOperationException("Transient files cannot be savestated!"); + } + + public TransientFile(byte[] data, string name) + { + Stream = new MemoryStream(); + Name = name; + if (data != null) + { + Stream.Write(data, 0, data.Length); + Stream.Position = 0; + } + } + + public byte[] GetContents() + { + if (_inUse) + throw new InvalidOperationException(); + return ((MemoryStream)Stream).ToArray(); + } + } + private readonly List _openFiles = new List(); private readonly Dictionary _availableFiles = new Dictionary(); @@ -697,6 +759,21 @@ namespace BizHawk.Emulation.Cores.Waterbox } } + + private T RemoveFileInternal(string name) + where T : IFileObject + { + IFileObject o; + if (!_availableFiles.TryGetValue(name, out o)) + throw new InvalidOperationException("File was never registered!"); + if (o.GetType() != typeof(T)) + throw new InvalidOperationException("Object was not a the right kind of file"); + if (_openFiles.Contains(o)) + throw new InvalidOperationException("Core never closed the file!"); + _availableFiles.Remove(name); + return (T)o; + } + public void AddReadonlyFile(byte[] data, string name) { _availableFiles.Add(name, new ReadonlyFirmware(data, name)); @@ -704,15 +781,16 @@ namespace BizHawk.Emulation.Cores.Waterbox public void RemoveReadonlyFile(string name) { - IFileObject o; - if (!_availableFiles.TryGetValue(name, out o)) - throw new InvalidOperationException("Firmware was never registered!"); - var f = o as ReadonlyFirmware; - if (f == null) - throw new InvalidOperationException("Object was not a firmware!"); - if (_openFiles.Contains(o)) - throw new InvalidOperationException("Core never closed the firmware!"); - _availableFiles.Remove(name); + RemoveFileInternal(name); + } + + public void AddTransientFile(byte[] data, string name) + { + _availableFiles.Add(name, new TransientFile(data, name)); + } + public byte[] RemoveTransientFile(string name) + { + return RemoveFileInternal(name).GetContents(); } } @@ -1090,6 +1168,24 @@ namespace BizHawk.Emulation.Cores.Waterbox _syscalls.RemoveReadonlyFile(name); } + /// + /// Add a transient file that will appear to the waterbox core's libc. The file will be readable + /// and writable. Any attempt to save state while the file is loaded will fail. + /// + public void AddTransientFile(byte[] data, string name) + { + _syscalls.AddTransientFile(data, name); // don't need to clone data, as it's used at init only + } + + /// + /// Remove a file previously added by AddTransientFile + /// + /// The state of the file when it was removed + public byte[] RemoveTransientFile(string name) + { + return _syscalls.RemoveTransientFile(name); + } + public void SaveStateBinary(BinaryWriter bw) { bw.Write(_createstamp); diff --git a/output/dll/sameboy.wbx.gz b/output/dll/sameboy.wbx.gz new file mode 100644 index 0000000000..c810c4beea Binary files /dev/null and b/output/dll/sameboy.wbx.gz differ diff --git a/waterbox/emulibc/emulibc.h b/waterbox/emulibc/emulibc.h index da0eccc7c8..29d0ff7baa 100644 --- a/waterbox/emulibc/emulibc.h +++ b/waterbox/emulibc/emulibc.h @@ -10,7 +10,11 @@ extern "C" { // mark an entry point or callback pointer #define ECL_ENTRY // mark a visible symbol +#ifdef __cplusplus +#define ECL_EXPORT extern "C" __attribute__((visibility("default"))) +#else #define ECL_EXPORT __attribute__((visibility("default"))) +#endif // allocate memory from the "sealed" pool. this memory can never be freed, // and can only be allocated or written to during the init phase. after that, the host diff --git a/waterbox/sameboy/.vscode/settings.json b/waterbox/sameboy/.vscode/settings.json new file mode 100644 index 0000000000..a5401e1c1f --- /dev/null +++ b/waterbox/sameboy/.vscode/settings.json @@ -0,0 +1,6 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "editor.tabSize": 4, + "editor.insertSpaces": false, + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/waterbox/sameboy/Makefile b/waterbox/sameboy/Makefile new file mode 100644 index 0000000000..e694a321e1 --- /dev/null +++ b/waterbox/sameboy/Makefile @@ -0,0 +1,54 @@ +CC = x86_64-nt64-midipix-gcc +CPP = x86_64-nt64-midipix-g++ + +FLAGS:=-Wall -Werror=pointer-to-int-cast -Werror=int-to-pointer-cast -Werror=implicit-function-declaration \ + -Wno-multichar \ + -fomit-frame-pointer \ + -O3 -flto + +CCFLAGS:=$(FLAGS) \ + -std=gnu99 \ + -DLSB_FIRST -D_GNU_SOURCE -DGB_INTERNAL + +CPPFLAGS:=$(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x -D_GNU_SOURCE -DGB_INTERNAL + +TARGET = sameboy.wbx + +LDFLAGS = -Wl,--dynamicbase,--export-all-symbols + +ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +C_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.c') +CPP_SRCS:=$(shell find $(ROOT_DIR) -type f -name '*.cpp') +SRCS:=$(C_SRCS) $(CPP_SRCS) +OBJ_DIR:=$(ROOT_DIR)/obj + +__OBJS:=$(SRCS:.c=.o) +_OBJS:=$(__OBJS:.cpp=.opp) +OBJS:=$(patsubst $(ROOT_DIR)%,$(OBJ_DIR)%,$(_OBJS)) + +$(OBJ_DIR)/%.o: %.c + @mkdir -p $(@D) + @$(CC) -c -o $@ $< $(CCFLAGS) + +$(OBJ_DIR)/%.opp: %.cpp + @mkdir -p $(@D) + @$(CPP) -c -o $@ $< $(CPPFLAGS) + +all: $(TARGET) + +.PHONY: clean all + +$(TARGET).in: $(OBJS) + @$(CPP) -o $@ $(LDFLAGS) $(FLAGS) $(OBJS) ../emulibc/libemuhost.so + +$(TARGET): $(TARGET).in + strip $< -o $@ -R /4 -R /14 -R /29 -R /41 -R /55 -R /67 -R /78 -R /89 -R /104 +# cp $< $@ + +clean: + rm -rf $(OBJ_DIR) + rm -f $(TARGET).in + rm -f $(TARGET) + +#install: +# $(CP) $(TARGET) $(DEST_$(ARCH)) diff --git a/waterbox/sameboy/apu.c b/waterbox/sameboy/apu.c new file mode 100644 index 0000000000..10b5bf9bea --- /dev/null +++ b/waterbox/sameboy/apu.c @@ -0,0 +1,402 @@ +#include +#include +#include +#include "gb.h" + +#undef max +#define max(a,b) \ +({ __typeof__ (a) _a = (a); \ +__typeof__ (b) _b = (b); \ +_a > _b ? _a : _b; }) + +#undef min +#define min(a,b) \ +({ __typeof__ (a) _a = (a); \ +__typeof__ (b) _b = (b); \ +_a < _b ? _a : _b; }) + +#define APU_FREQUENCY 0x80000 + +static int16_t generate_square(uint64_t phase, uint32_t wave_length, int16_t amplitude, uint8_t duty) +{ + if (!wave_length) return 0; + if (phase % wave_length > wave_length * duty / 8) { + return amplitude; + } + return 0; +} + +static int16_t generate_wave(uint64_t phase, uint32_t wave_length, int16_t amplitude, int8_t *wave, uint8_t shift) +{ + if (!wave_length) wave_length = 1; + phase = phase % wave_length; + return ((wave[(int)(phase * 32 / wave_length)]) >> shift) * (int)amplitude / 0xF; +} + +static int16_t generate_noise(int16_t amplitude, uint16_t lfsr) +{ + if (lfsr & 1) { + return amplitude; + } + return 0; +} + +static int16_t step_lfsr(uint16_t lfsr, bool uses_7_bit) +{ + bool xor = (lfsr & 1) ^ ((lfsr & 2) >> 1); + lfsr >>= 1; + if (xor) { + lfsr |= 0x4000; + } + if (uses_7_bit) { + lfsr &= ~0x40; + if (xor) { + lfsr |= 0x40; + } + } + return lfsr; +} + +/* General Todo: The APU emulation seems to fail many accuracy tests. It might require a rewrite with + these tests in mind. */ + +static void GB_apu_run_internal(GB_gameboy_t *gb) +{ + uint32_t steps = gb->apu.apu_cycles / (CPU_FREQUENCY/APU_FREQUENCY); + if (!steps) + return; + + gb->apu.apu_cycles %= (CPU_FREQUENCY/APU_FREQUENCY); + for (uint8_t i = 0; i < 4; i++) { + /* Phase */ + gb->apu.wave_channels[i].phase += steps; + while (gb->apu.wave_channels[i].wave_length && gb->apu.wave_channels[i].phase >= gb->apu.wave_channels[i].wave_length) { + if (i == 3) { + gb->apu.lfsr = step_lfsr(gb->apu.lfsr, gb->apu.lfsr_7_bit); + } + + gb->apu.wave_channels[i].phase -= gb->apu.wave_channels[i].wave_length; + } + /* Stop on Length */ + if (gb->apu.wave_channels[i].stop_on_length) { + if (gb->apu.wave_channels[i].sound_length > 0) { + gb->apu.wave_channels[i].sound_length -= steps; + } + if (gb->apu.wave_channels[i].sound_length <= 0) { + gb->apu.wave_channels[i].amplitude = 0; + gb->apu.wave_channels[i].is_playing = false; + gb->apu.wave_channels[i].sound_length = i == 2? APU_FREQUENCY : APU_FREQUENCY / 4; + } + } + } + + gb->apu.envelope_step_timer += steps; + while (gb->apu.envelope_step_timer >= APU_FREQUENCY / 64) { + gb->apu.envelope_step_timer -= APU_FREQUENCY / 64; + for (uint8_t i = 0; i < 4; i++) { + if (gb->apu.wave_channels[i].envelope_steps && !--gb->apu.wave_channels[i].cur_envelope_steps) { + gb->apu.wave_channels[i].amplitude = min(max(gb->apu.wave_channels[i].amplitude + gb->apu.wave_channels[i].envelope_direction * CH_STEP, 0), MAX_CH_AMP); + gb->apu.wave_channels[i].cur_envelope_steps = gb->apu.wave_channels[i].envelope_steps; + } + } + } + + gb->apu.sweep_step_timer += steps; + while (gb->apu.sweep_step_timer >= APU_FREQUENCY / 128) { + gb->apu.sweep_step_timer -= APU_FREQUENCY / 128; + if (gb->apu.wave_channels[0].sweep_steps && !--gb->apu.wave_channels[0].cur_sweep_steps) { + + // Convert back to GB format + uint16_t temp = 2048 - gb->apu.wave_channels[0].wave_length / (APU_FREQUENCY / 131072); + + // Apply sweep + temp = temp + gb->apu.wave_channels[0].sweep_direction * + (temp / (1 << gb->apu.wave_channels[0].sweep_shift)); + if (temp > 2047) { + temp = 0; + } + + // Back to frequency + gb->apu.wave_channels[0].wave_length = (2048 - temp) * (APU_FREQUENCY / 131072); + gb->apu.wave_channels[0].cur_sweep_steps = gb->apu.wave_channels[0].sweep_steps; + } + } +} + +void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples) +{ + GB_apu_run_internal(gb); + + samples->left = samples->right = 0; + if (!gb->apu.global_enable) { + return; + } + + gb->io_registers[GB_IO_PCM_12] = 0; + gb->io_registers[GB_IO_PCM_34] = 0; + + { + int16_t sample = generate_square(gb->apu.wave_channels[0].phase, + gb->apu.wave_channels[0].wave_length, + gb->apu.wave_channels[0].amplitude, + gb->apu.wave_channels[0].duty); + if (gb->apu.wave_channels[0].left_on ) samples->left += sample; + if (gb->apu.wave_channels[0].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_square(gb->apu.wave_channels[1].phase, + gb->apu.wave_channels[1].wave_length, + gb->apu.wave_channels[1].amplitude, + gb->apu.wave_channels[1].duty); + if (gb->apu.wave_channels[1].left_on ) samples->left += sample; + if (gb->apu.wave_channels[1].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_12] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + if (gb->apu.wave_channels[2].is_playing) + { + int16_t sample = generate_wave(gb->apu.wave_channels[2].phase, + gb->apu.wave_channels[2].wave_length, + MAX_CH_AMP, + gb->apu.wave_form, + gb->apu.wave_shift); + if (gb->apu.wave_channels[2].left_on ) samples->left += sample; + if (gb->apu.wave_channels[2].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] = ((int)sample) * 0xF / MAX_CH_AMP; + } + + { + int16_t sample = generate_noise(gb->apu.wave_channels[3].amplitude, + gb->apu.lfsr); + if (gb->apu.wave_channels[3].left_on ) samples->left += sample; + if (gb->apu.wave_channels[3].right_on) samples->right += sample; + gb->io_registers[GB_IO_PCM_34] |= (((int)sample) * 0xF / MAX_CH_AMP) << 4; + } + + samples->left = (int) samples->left * gb->apu.left_volume / 7; + samples->right = (int) samples->right * gb->apu.right_volume / 7; +} + +void GB_apu_run(GB_gameboy_t *gb) +{ + GB_sample_t sample; + GB_apu_get_samples_and_update_pcm_regs(gb, &sample); + gb->sample_callback(gb, sample, gb->cycles_since_epoch); +} + +void GB_apu_init(GB_gameboy_t *gb) +{ + memset(&gb->apu, 0, sizeof(gb->apu)); + gb->apu.wave_channels[0].duty = gb->apu.wave_channels[1].duty = 4; + gb->apu.lfsr = 0x7FFF; + gb->apu.left_volume = 7; + gb->apu.right_volume = 7; + for (int i = 0; i < 4; i++) { + gb->apu.wave_channels[i].left_on = gb->apu.wave_channels[i].right_on = 1; + } +} + +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg) +{ + GB_apu_run_internal(gb); + + if (reg == GB_IO_NR52) { + uint8_t value = 0; + for (int i = 0; i < 4; i++) { + value >>= 1; + if (gb->apu.wave_channels[i].is_playing) { + value |= 0x8; + } + } + if (gb->apu.global_enable) { + value |= 0x80; + } + value |= 0x70; + return value; + } + + static const char read_mask[GB_IO_WAV_END - GB_IO_NR10 + 1] = { + /* NRX0 NRX1 NRX2 NRX3 NRX4 */ + 0x80, 0x3F, 0x00, 0xFF, 0xBF, // NR1X + 0xFF, 0x3F, 0x00, 0xFF, 0xBF, // NR2X + 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, // NR3X + 0xFF, 0xFF, 0x00, 0x00, 0xBF, // NR4X + 0x00, 0x00, 0x70, 0xFF, 0xFF, // NR5X + + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Unused + // Wave RAM + 0, /* ... */ + }; + + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END && gb->apu.wave_channels[2].is_playing) { + if (gb->apu.wave_channels[2].wave_length == 0) { + return gb->apu.wave_form[0]; + } + gb->apu.wave_channels[2].phase %= gb->apu.wave_channels[2].wave_length; + return gb->apu.wave_form[(int)(gb->apu.wave_channels[2].phase * 32 / gb->apu.wave_channels[2].wave_length)]; + } + + return gb->io_registers[reg] | read_mask[reg - GB_IO_NR10]; +} + +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value) +{ + GB_apu_run_internal(gb); + + static const uint8_t duties[] = {1, 2, 4, 6}; /* Values are in 1/8 */ + uint8_t channel = 0; + + if (!gb->apu.global_enable && reg != GB_IO_NR52) { + return; + } + + gb->io_registers[reg] = value; + + switch (reg) { + case GB_IO_NR10: + case GB_IO_NR11: + case GB_IO_NR12: + case GB_IO_NR13: + case GB_IO_NR14: + channel = 0; + break; + case GB_IO_NR21: + case GB_IO_NR22: + case GB_IO_NR23: + case GB_IO_NR24: + channel = 1; + break; + case GB_IO_NR33: + case GB_IO_NR34: + channel = 2; + break; + case GB_IO_NR41: + case GB_IO_NR42: + channel = 3; + default: + break; + } + + switch (reg) { + case GB_IO_NR10: + gb->apu.wave_channels[channel].sweep_direction = value & 8? -1 : 1; + gb->apu.wave_channels[channel].cur_sweep_steps = + gb->apu.wave_channels[channel].sweep_steps = (value & 0x70) >> 4; + gb->apu.wave_channels[channel].sweep_shift = value & 7; + break; + case GB_IO_NR11: + case GB_IO_NR21: + case GB_IO_NR41: + gb->apu.wave_channels[channel].duty = duties[value >> 6]; + gb->apu.wave_channels[channel].sound_length = (64 - (value & 0x3F)) * (APU_FREQUENCY / 256); + if (gb->apu.wave_channels[channel].sound_length == 0) { + gb->apu.wave_channels[channel].is_playing = false; + } + break; + case GB_IO_NR12: + case GB_IO_NR22: + case GB_IO_NR42: + gb->apu.wave_channels[channel].start_amplitude = + gb->apu.wave_channels[channel].amplitude = CH_STEP * (value >> 4); + if (value >> 4 == 0) { + gb->apu.wave_channels[channel].is_playing = false; + } + gb->apu.wave_channels[channel].envelope_direction = value & 8? 1 : -1; + gb->apu.wave_channels[channel].cur_envelope_steps = + gb->apu.wave_channels[channel].envelope_steps = value & 7; + break; + case GB_IO_NR13: + case GB_IO_NR23: + case GB_IO_NR33: + gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF00) | value; + gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072); + if (channel == 2) { + gb->apu.wave_channels[channel].wave_length *= 2; + } + break; + case GB_IO_NR14: + case GB_IO_NR24: + case GB_IO_NR34: + gb->apu.wave_channels[channel].stop_on_length = value & 0x40; + if ((value & 0x80) && (channel != 2 || gb->apu.wave_enable)) { + gb->apu.wave_channels[channel].is_playing = true; + gb->apu.wave_channels[channel].phase = 0; + gb->apu.wave_channels[channel].amplitude = gb->apu.wave_channels[channel].start_amplitude; + gb->apu.wave_channels[channel].cur_envelope_steps = gb->apu.wave_channels[channel].envelope_steps; + } + + gb->apu.wave_channels[channel].NRX3_X4_temp = (gb->apu.wave_channels[channel].NRX3_X4_temp & 0xFF) | ((value & 0x7) << 8); + gb->apu.wave_channels[channel].wave_length = (2048 - gb->apu.wave_channels[channel].NRX3_X4_temp) * (APU_FREQUENCY / 131072); + if (channel == 2) { + gb->apu.wave_channels[channel].wave_length *= 2; + } + break; + case GB_IO_NR30: + gb->apu.wave_enable = value & 0x80; + gb->apu.wave_channels[2].is_playing &= gb->apu.wave_enable; + break; + case GB_IO_NR31: + gb->apu.wave_channels[2].sound_length = (256 - value) * (APU_FREQUENCY / 256); + if (gb->apu.wave_channels[2].sound_length == 0) { + gb->apu.wave_channels[2].is_playing = false; + } + break; + case GB_IO_NR32: + gb->apu.wave_shift = ((value >> 5) + 3) & 3; + if (gb->apu.wave_shift == 3) { + gb->apu.wave_shift = 4; + } + break; + case GB_IO_NR43: + { + double r = value & 0x7; + if (r == 0) r = 0.5; + uint8_t s = value >> 4; + gb->apu.wave_channels[3].wave_length = r * (1 << s) * (APU_FREQUENCY / 262144) ; + gb->apu.lfsr_7_bit = value & 0x8; + break; + } + case GB_IO_NR44: + gb->apu.wave_channels[3].stop_on_length = value & 0x40; + if (value & 0x80) { + gb->apu.wave_channels[3].is_playing = true; + gb->apu.lfsr = 0x7FFF; + gb->apu.wave_channels[3].amplitude = gb->apu.wave_channels[3].start_amplitude; + gb->apu.wave_channels[3].cur_envelope_steps = gb->apu.wave_channels[3].envelope_steps; + } + break; + + case GB_IO_NR50: + gb->apu.left_volume = (value & 7); + gb->apu.right_volume = ((value >> 4) & 7); + break; + + case GB_IO_NR51: + for (int i = 0; i < 4; i++) { + gb->apu.wave_channels[i].left_on = value & 1; + gb->apu.wave_channels[i].right_on = value & 0x10; + value >>= 1; + } + break; + case GB_IO_NR52: + + if ((value & 0x80) && !gb->apu.global_enable) { + GB_apu_init(gb); + gb->apu.global_enable = true; + } + else if (!(value & 0x80) && gb->apu.global_enable) { + memset(&gb->apu, 0, sizeof(gb->apu)); + memset(gb->io_registers + GB_IO_NR10, 0, GB_IO_WAV_START - GB_IO_NR10); + } + break; + + default: + if (reg >= GB_IO_WAV_START && reg <= GB_IO_WAV_END) { + gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2] = value >> 4; + gb->apu.wave_form[(reg - GB_IO_WAV_START) * 2 + 1] = value & 0xF; + } + break; + } +} diff --git a/waterbox/sameboy/apu.h b/waterbox/sameboy/apu.h new file mode 100644 index 0000000000..a8eab55fd6 --- /dev/null +++ b/waterbox/sameboy/apu.h @@ -0,0 +1,65 @@ +#ifndef apu_h +#define apu_h +#include +#include +#include "gb_struct_def.h" +/* Divides nicely and never overflows with 4 channels */ +#define MAX_CH_AMP 0x1E00 +#define CH_STEP (0x1E00/0xF) + + +typedef struct +{ + int16_t left; + int16_t right; +} GB_sample_t; + +/* Not all used on all channels */ +/* All lengths are in APU ticks */ +typedef struct +{ + uint32_t phase; + uint32_t wave_length; + int32_t sound_length; + bool stop_on_length; + uint8_t duty; + int16_t amplitude; + int16_t start_amplitude; + uint8_t envelope_steps; + uint8_t cur_envelope_steps; + int8_t envelope_direction; + uint8_t sweep_steps; + uint8_t cur_sweep_steps; + int8_t sweep_direction; + uint8_t sweep_shift; + bool is_playing; + uint16_t NRX3_X4_temp; + bool left_on; + bool right_on; +} GB_apu_channel_t; + +typedef struct +{ + uint16_t apu_cycles; + bool global_enable; + uint32_t envelope_step_timer; + uint32_t sweep_step_timer; + int8_t wave_form[32]; + uint8_t wave_shift; + bool wave_enable; + uint16_t lfsr; + bool lfsr_7_bit; + uint8_t left_volume; + uint8_t right_volume; + GB_apu_channel_t wave_channels[4]; +} GB_apu_t; + +#ifdef GB_INTERNAL +void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value); +uint8_t GB_apu_read(GB_gameboy_t *gb, uint8_t reg); +void GB_apu_get_samples_and_update_pcm_regs(GB_gameboy_t *gb, GB_sample_t *samples); +void GB_apu_init(GB_gameboy_t *gb); +void GB_apu_run(GB_gameboy_t *gb); +#endif + +#endif /* apu_h */ diff --git a/waterbox/sameboy/bizhawk.cpp b/waterbox/sameboy/bizhawk.cpp new file mode 100644 index 0000000000..28c6cda11e --- /dev/null +++ b/waterbox/sameboy/bizhawk.cpp @@ -0,0 +1,251 @@ +#include +#include "../emulibc/emulibc.h" +#include "../emulibc/waterboxcore.h" +#include "blip_buf/blip_buf.h" + +#define _Static_assert static_assert + +extern "C" { +#include "gb.h" +#include "joypad.h" +#include "apu.h" +#include "sgb.h" +} + +static GB_gameboy_t GB; + +static uint32_t GBPixels[160 * 144]; +static uint32_t *CurrentFramebuffer; +static bool sgb; +static void VBlankCallback(GB_gameboy_t *gb) +{ + if (sgb) + { + sgb_take_frame(GBPixels); + sgb_render_frame(CurrentFramebuffer); + } + else + { + memcpy(CurrentFramebuffer, GBPixels, sizeof(GBPixels)); + } +} + +static void LogCallback(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes) +{ + fputs(string, stdout); +} + +static uint32_t RgbEncodeCallback(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b) +{ + return b | g << 8 | r << 16 | 0xff000000; +} + +static void InfraredCallback(GB_gameboy_t *gb, bool on, long cycles_since_last_update) +{ +} + +static void RumbleCallback(GB_gameboy_t *gb, bool rumble_on) +{ +} + +static void SerialStartCallback(GB_gameboy_t *gb, uint8_t byte_to_send) +{ +} + +static uint8_t SerialEndCallback(GB_gameboy_t *gb) +{ + return 0; +} + +static void (*FrontendInputCallback)(); + +static void InputCallback(GB_gameboy_t *gb) +{ + FrontendInputCallback(); +} + +static blip_t *leftblip; +static blip_t *rightblip; +const int SOUND_RATE_GB = 2097152; +const int SOUND_RATE_SGB = 2147727; +static uint64_t sound_start_clock; +static GB_sample_t sample_gb; +static GB_sample_t sample_sgb; + +static void SampleCallback(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock) +{ + int l = sample.left - sample_gb.left; + int r = sample.right - sample_gb.right; + if (l) + blip_add_delta(leftblip, clock - sound_start_clock, l); + if (r) + blip_add_delta(rightblip, clock - sound_start_clock, r); + sample_gb = sample; +} +static void SgbSampleCallback(int16_t sl, int16_t sr, uint64_t clock) +{ + int l = sl - sample_sgb.left; + int r = sr - sample_sgb.right; + if (l) + blip_add_delta(leftblip, clock - sound_start_clock, l); + if (r) + blip_add_delta(rightblip, clock - sound_start_clock, r); + sample_sgb.left = sl; + sample_sgb.right = sr; +} + +ECL_EXPORT bool Init(bool cgb, const uint8_t *spc, int spclen) +{ + if (spc) + { + GB_init_sgb(&GB); + if (!sgb_init(spc, spclen)) + return false; + sgb = true; + } + else if (cgb) + { + GB_init_cgb(&GB); + } + else + { + GB_init(&GB); + } + + if (GB_load_boot_rom(&GB, "boot.rom") != 0) + return false; + if (GB_load_rom(&GB, "game.rom") != 0) + return false; + + GB_set_pixels_output(&GB, GBPixels); + GB_set_vblank_callback(&GB, VBlankCallback); + GB_set_log_callback(&GB, LogCallback); + GB_set_rgb_encode_callback(&GB, RgbEncodeCallback); + GB_set_infrared_callback(&GB, InfraredCallback); + GB_set_rumble_callback(&GB, RumbleCallback); + GB_set_sample_callback(&GB, SampleCallback); + + leftblip = blip_new(1024); + rightblip = blip_new(1024); + blip_set_rates(leftblip, sgb ? SOUND_RATE_SGB : SOUND_RATE_GB, 44100); + blip_set_rates(rightblip, sgb ? SOUND_RATE_SGB : SOUND_RATE_GB, 44100); + + return true; +} + +struct MyFrameInfo : public FrameInfo +{ + int64_t Time; + uint32_t Keys; +}; + +static int FrameOverflow; + +ECL_EXPORT void FrameAdvance(MyFrameInfo &f) +{ + if (sgb) + { + sgb_set_controller_data((uint8_t *)&f.Keys); + } + else + { + GB_set_key_state(&GB, f.Keys & 0xff); + } + sound_start_clock = GB_epoch(&GB); + CurrentFramebuffer = f.VideoBuffer; + GB_set_lagged(&GB, true); + GB.frontend_rtc_time = f.Time; + + uint32_t target = 35112 - FrameOverflow; + f.Cycles = GB_run_cycles(&GB, target); + FrameOverflow = f.Cycles - target; + if (sgb) + { + f.Width = 256; + f.Height = 224; + sgb_render_audio(GB_epoch(&GB), SgbSampleCallback); + } + else + { + f.Width = 160; + f.Height = 144; + } + blip_end_frame(leftblip, f.Cycles); + blip_end_frame(rightblip, f.Cycles); + f.Samples = blip_read_samples(leftblip, f.SoundBuffer, 2048, 1); + blip_read_samples(rightblip, f.SoundBuffer + 1, 2048, 1); + CurrentFramebuffer = NULL; + f.Lagged = GB_get_lagged(&GB); +} + +static void SetMemoryArea(MemoryArea *m, GB_direct_access_t access, const char *name, int32_t flags) +{ + size_t size; + m->Name = name; + m->Data = GB_get_direct_access(&GB, access, &size, nullptr); + m->Size = size; + m->Flags = flags; +} + +ECL_EXPORT void GetMemoryAreas(MemoryArea *m) +{ + // TODO: "System Bus" + SetMemoryArea(m + 0, GB_DIRECT_ACCESS_RAM, "WRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE | MEMORYAREA_FLAGS_PRIMARY); + SetMemoryArea(m + 1, GB_DIRECT_ACCESS_ROM, "ROM", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 2, GB_DIRECT_ACCESS_VRAM, "VRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 3, GB_DIRECT_ACCESS_CART_RAM, "CartRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 4, GB_DIRECT_ACCESS_OAM, "OAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 5, GB_DIRECT_ACCESS_HRAM, "HRAM", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 6, GB_DIRECT_ACCESS_IO, "IO", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 7, GB_DIRECT_ACCESS_BOOTROM, "BOOTROM", MEMORYAREA_FLAGS_WORDSIZE1); + SetMemoryArea(m + 8, GB_DIRECT_ACCESS_BGP, "BGP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); + SetMemoryArea(m + 9, GB_DIRECT_ACCESS_OBP, "OBP", MEMORYAREA_FLAGS_WORDSIZE1 | MEMORYAREA_FLAGS_WRITABLE); +} + +ECL_EXPORT void SetInputCallback(void (*callback)()) +{ + FrontendInputCallback = callback; + GB_set_input_callback(&GB, callback ? InputCallback : nullptr); +} + +ECL_EXPORT void GetGpuMemory(void **p) +{ + p[0] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_VRAM, nullptr, nullptr); + p[1] = GB_get_direct_access(&GB, GB_DIRECT_ACCESS_OAM, nullptr, nullptr); + p[2] = GB.background_palettes_rgb; + p[3] = GB.sprite_palettes_rgb; +} + +ECL_EXPORT void SetScanlineCallback(void (*callback)(uint8_t), int ly) +{ + GB.scanline_callback = callback; + GB.scanline_callback_ly = ly; +} +ECL_EXPORT uint8_t GetIoReg(uint8_t port) +{ + return GB.io_registers[port]; +} + +ECL_EXPORT void PutSaveRam() +{ + GB_load_battery(&GB, "save.ram"); +} + +ECL_EXPORT void GetSaveRam() +{ + GB_save_battery(&GB, "save.ram"); +} + +ECL_EXPORT bool HasSaveRam() +{ + if (!GB.cartridge_type->has_battery) + return false; // Nothing to save. + if (GB.mbc_ram_size == 0 && !GB.cartridge_type->has_rtc) + return false; /* Claims to have battery, but has no RAM or RTC */ + return true; +} + +int main() +{ + return 0; +} diff --git a/waterbox/sameboy/blip_buf/blip_buf.c b/waterbox/sameboy/blip_buf/blip_buf.c new file mode 100644 index 0000000000..1bd3377e92 --- /dev/null +++ b/waterbox/sameboy/blip_buf/blip_buf.c @@ -0,0 +1,344 @@ +/* blip_buf 1.1.0. http://www.slack.net/~ant/ */ + +#include "blip_buf.h" + +#include +#include +#include +#include + +/* Library Copyright (C) 2003-2009 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#if defined (BLARGG_TEST) && BLARGG_TEST + #include "blargg_test.h" +#endif + +/* Equivalent to ULONG_MAX >= 0xFFFFFFFF00000000. +Avoids constants that don't fit in 32 bits. */ +#if ULONG_MAX/0xFFFFFFFF > 0xFFFFFFFF + typedef unsigned long fixed_t; + enum { pre_shift = 32 }; + +#elif defined(ULLONG_MAX) + typedef unsigned long long fixed_t; + enum { pre_shift = 32 }; + +#else + typedef unsigned fixed_t; + enum { pre_shift = 0 }; + +#endif + +enum { time_bits = pre_shift + 20 }; + +static fixed_t const time_unit = (fixed_t) 1 << time_bits; + +enum { bass_shift = 9 }; /* affects high-pass filter breakpoint frequency */ +enum { end_frame_extra = 2 }; /* allows deltas slightly after frame length */ + +enum { half_width = 8 }; +enum { buf_extra = half_width*2 + end_frame_extra }; +enum { phase_bits = 5 }; +enum { phase_count = 1 << phase_bits }; +enum { delta_bits = 15 }; +enum { delta_unit = 1 << delta_bits }; +enum { frac_bits = time_bits - pre_shift }; + +/* We could eliminate avail and encode whole samples in offset, but that would +limit the total buffered samples to blip_max_frame. That could only be +increased by decreasing time_bits, which would reduce resample ratio accuracy. +*/ + +/** Sample buffer that resamples to output rate and accumulates samples +until they're read out */ +struct blip_t +{ + fixed_t factor; + fixed_t offset; + int avail; + int size; + int integrator; +}; + +typedef int buf_t; + +/* probably not totally portable */ +#define SAMPLES( buf ) ((buf_t*) ((buf) + 1)) + +/* Arithmetic (sign-preserving) right shift */ +#define ARITH_SHIFT( n, shift ) \ + ((n) >> (shift)) + +enum { max_sample = +32767 }; +enum { min_sample = -32768 }; + +#define CLAMP( n ) \ + {\ + if ( (short) n != n )\ + n = ARITH_SHIFT( n, 16 ) ^ max_sample;\ + } + +static void check_assumptions( void ) +{ + int n; + + #if INT_MAX < 0x7FFFFFFF || UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" + #endif + + assert( (-3 >> 1) == -2 ); /* right shift must preserve sign */ + + n = max_sample * 2; + CLAMP( n ); + assert( n == max_sample ); + + n = min_sample * 2; + CLAMP( n ); + assert( n == min_sample ); + + assert( blip_max_ratio <= time_unit ); + assert( blip_max_frame <= (fixed_t) -1 >> time_bits ); +} + +blip_t* blip_new( int size ) +{ + blip_t* m; + assert( size >= 0 ); + + m = (blip_t*) malloc( sizeof *m + (size + buf_extra) * sizeof (buf_t) ); + if ( m ) + { + m->factor = time_unit / blip_max_ratio; + m->size = size; + blip_clear( m ); + check_assumptions(); + } + return m; +} + +void blip_delete( blip_t* m ) +{ + if ( m != NULL ) + { + /* Clear fields in case user tries to use after freeing */ + memset( m, 0, sizeof *m ); + free( m ); + } +} + +void blip_set_rates( blip_t* m, double clock_rate, double sample_rate ) +{ + double factor = time_unit * sample_rate / clock_rate; + m->factor = (fixed_t) factor; + + /* Fails if clock_rate exceeds maximum, relative to sample_rate */ + assert( 0 <= factor - m->factor && factor - m->factor < 1 ); + + /* Avoid requiring math.h. Equivalent to + m->factor = (int) ceil( factor ) */ + if ( m->factor < factor ) + m->factor++; + + /* At this point, factor is most likely rounded up, but could still + have been rounded down in the floating-point calculation. */ +} + +void blip_clear( blip_t* m ) +{ + /* We could set offset to 0, factor/2, or factor-1. 0 is suitable if + factor is rounded up. factor-1 is suitable if factor is rounded down. + Since we don't know rounding direction, factor/2 accommodates either, + with the slight loss of showing an error in half the time. Since for + a 64-bit factor this is years, the halving isn't a problem. */ + + m->offset = m->factor / 2; + m->avail = 0; + m->integrator = 0; + memset( SAMPLES( m ), 0, (m->size + buf_extra) * sizeof (buf_t) ); +} + +int blip_clocks_needed( const blip_t* m, int samples ) +{ + fixed_t needed; + + /* Fails if buffer can't hold that many more samples */ + assert( samples >= 0 && m->avail + samples <= m->size ); + + needed = (fixed_t) samples * time_unit; + if ( needed < m->offset ) + return 0; + + return (needed - m->offset + m->factor - 1) / m->factor; +} + +void blip_end_frame( blip_t* m, unsigned t ) +{ + fixed_t off = t * m->factor + m->offset; + m->avail += off >> time_bits; + m->offset = off & (time_unit - 1); + + /* Fails if buffer size was exceeded */ + assert( m->avail <= m->size ); +} + +int blip_samples_avail( const blip_t* m ) +{ + return m->avail; +} + +static void remove_samples( blip_t* m, int count ) +{ + buf_t* buf = SAMPLES( m ); + int remain = m->avail + buf_extra - count; + m->avail -= count; + + memmove( &buf [0], &buf [count], remain * sizeof buf [0] ); + memset( &buf [remain], 0, count * sizeof buf [0] ); +} + +int blip_read_samples( blip_t* m, short out [], int count, int stereo ) +{ + assert( count >= 0 ); + + if ( count > m->avail ) + count = m->avail; + + if ( count ) + { + int const step = stereo ? 2 : 1; + buf_t const* in = SAMPLES( m ); + buf_t const* end = in + count; + int sum = m->integrator; + do + { + /* Eliminate fraction */ + int s = ARITH_SHIFT( sum, delta_bits ); + + sum += *in++; + + CLAMP( s ); + + *out = s; + out += step; + + /* High-pass filter */ + sum -= s << (delta_bits - bass_shift); + } + while ( in != end ); + m->integrator = sum; + + remove_samples( m, count ); + } + + return count; +} + +/* Things that didn't help performance on x86: + __attribute__((aligned(128))) + #define short int + restrict +*/ + +/* Sinc_Generator( 0.9, 0.55, 4.5 ) */ +static short const bl_step [phase_count + 1] [half_width] = +{ +{ 43, -115, 350, -488, 1136, -914, 5861,21022}, +{ 44, -118, 348, -473, 1076, -799, 5274,21001}, +{ 45, -121, 344, -454, 1011, -677, 4706,20936}, +{ 46, -122, 336, -431, 942, -549, 4156,20829}, +{ 47, -123, 327, -404, 868, -418, 3629,20679}, +{ 47, -122, 316, -375, 792, -285, 3124,20488}, +{ 47, -120, 303, -344, 714, -151, 2644,20256}, +{ 46, -117, 289, -310, 634, -17, 2188,19985}, +{ 46, -114, 273, -275, 553, 117, 1758,19675}, +{ 44, -108, 255, -237, 471, 247, 1356,19327}, +{ 43, -103, 237, -199, 390, 373, 981,18944}, +{ 42, -98, 218, -160, 310, 495, 633,18527}, +{ 40, -91, 198, -121, 231, 611, 314,18078}, +{ 38, -84, 178, -81, 153, 722, 22,17599}, +{ 36, -76, 157, -43, 80, 824, -241,17092}, +{ 34, -68, 135, -3, 8, 919, -476,16558}, +{ 32, -61, 115, 34, -60, 1006, -683,16001}, +{ 29, -52, 94, 70, -123, 1083, -862,15422}, +{ 27, -44, 73, 106, -184, 1152,-1015,14824}, +{ 25, -36, 53, 139, -239, 1211,-1142,14210}, +{ 22, -27, 34, 170, -290, 1261,-1244,13582}, +{ 20, -20, 16, 199, -335, 1301,-1322,12942}, +{ 18, -12, -3, 226, -375, 1331,-1376,12293}, +{ 15, -4, -19, 250, -410, 1351,-1408,11638}, +{ 13, 3, -35, 272, -439, 1361,-1419,10979}, +{ 11, 9, -49, 292, -464, 1362,-1410,10319}, +{ 9, 16, -63, 309, -483, 1354,-1383, 9660}, +{ 7, 22, -75, 322, -496, 1337,-1339, 9005}, +{ 6, 26, -85, 333, -504, 1312,-1280, 8355}, +{ 4, 31, -94, 341, -507, 1278,-1205, 7713}, +{ 3, 35, -102, 347, -506, 1238,-1119, 7082}, +{ 1, 40, -110, 350, -499, 1190,-1021, 6464}, +{ 0, 43, -115, 350, -488, 1136, -914, 5861} +}; + +/* Shifting by pre_shift allows calculation using unsigned int rather than +possibly-wider fixed_t. On 32-bit platforms, this is likely more efficient. +And by having pre_shift 32, a 32-bit platform can easily do the shift by +simply ignoring the low half. */ + +void blip_add_delta( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int const phase_shift = frac_bits - phase_bits; + int phase = fixed >> phase_shift & (phase_count - 1); + short const* in = bl_step [phase]; + short const* rev = bl_step [phase_count - phase]; + + int interp = fixed >> (phase_shift - delta_bits) & (delta_unit - 1); + int delta2 = (delta * interp) >> delta_bits; + delta -= delta2; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [0] += in[0]*delta + in[half_width+0]*delta2; + out [1] += in[1]*delta + in[half_width+1]*delta2; + out [2] += in[2]*delta + in[half_width+2]*delta2; + out [3] += in[3]*delta + in[half_width+3]*delta2; + out [4] += in[4]*delta + in[half_width+4]*delta2; + out [5] += in[5]*delta + in[half_width+5]*delta2; + out [6] += in[6]*delta + in[half_width+6]*delta2; + out [7] += in[7]*delta + in[half_width+7]*delta2; + + in = rev; + out [ 8] += in[7]*delta + in[7-half_width]*delta2; + out [ 9] += in[6]*delta + in[6-half_width]*delta2; + out [10] += in[5]*delta + in[5-half_width]*delta2; + out [11] += in[4]*delta + in[4-half_width]*delta2; + out [12] += in[3]*delta + in[3-half_width]*delta2; + out [13] += in[2]*delta + in[2-half_width]*delta2; + out [14] += in[1]*delta + in[1-half_width]*delta2; + out [15] += in[0]*delta + in[0-half_width]*delta2; +} + +void blip_add_delta_fast( blip_t* m, unsigned time, int delta ) +{ + unsigned fixed = (unsigned) ((time * m->factor + m->offset) >> pre_shift); + buf_t* out = SAMPLES( m ) + m->avail + (fixed >> frac_bits); + + int interp = fixed >> (frac_bits - delta_bits) & (delta_unit - 1); + int delta2 = delta * interp; + + /* Fails if buffer size was exceeded */ + assert( out <= &SAMPLES( m ) [m->size + end_frame_extra] ); + + out [7] += delta * delta_unit - delta2; + out [8] += delta2; +} diff --git a/waterbox/sameboy/blip_buf/blip_buf.h b/waterbox/sameboy/blip_buf/blip_buf.h new file mode 100644 index 0000000000..e9a5d4cc3b --- /dev/null +++ b/waterbox/sameboy/blip_buf/blip_buf.h @@ -0,0 +1,72 @@ +/** \file +Sample buffer that resamples from input clock rate to output sample rate */ + +/* blip_buf 1.1.0 */ +#ifndef BLIP_BUF_H +#define BLIP_BUF_H + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is blip_t*, or const blip_t* if nothing +is changed. */ +typedef struct blip_t blip_t; + +/** Creates new buffer that can hold at most sample_count samples. Sets rates +so that there are blip_max_ratio clocks per sample. Returns pointer to new +buffer, or NULL if insufficient memory. */ +blip_t* blip_new( int sample_count ); + +/** Sets approximate input clock rate and output sample rate. For every +clock_rate input clocks, approximately sample_rate samples are generated. */ +void blip_set_rates( blip_t*, double clock_rate, double sample_rate ); + +enum { /** Maximum clock_rate/sample_rate ratio. For a given sample_rate, +clock_rate must not be greater than sample_rate*blip_max_ratio. */ +blip_max_ratio = 1 << 20 }; + +/** Clears entire buffer. Afterwards, blip_samples_avail() == 0. */ +void blip_clear( blip_t* ); + +/** Adds positive/negative delta into buffer at specified clock time. */ +void blip_add_delta( blip_t*, unsigned int clock_time, int delta ); + +/** Same as blip_add_delta(), but uses faster, lower-quality synthesis. */ +void blip_add_delta_fast( blip_t*, unsigned int clock_time, int delta ); + +/** Length of time frame, in clocks, needed to make sample_count additional +samples available. */ +int blip_clocks_needed( const blip_t*, int sample_count ); + +enum { /** Maximum number of samples that can be generated from one time frame. */ +blip_max_frame = 4000 }; + +/** Makes input clocks before clock_duration available for reading as output +samples. Also begins new time frame at clock_duration, so that clock time 0 in +the new time frame specifies the same clock as clock_duration in the old time +frame specified. Deltas can have been added slightly past clock_duration (up to +however many clocks there are in two output samples). */ +void blip_end_frame( blip_t*, unsigned int clock_duration ); + +/** Number of buffered samples available for reading. */ +int blip_samples_avail( const blip_t* ); + +/** Reads and removes at most 'count' samples and writes them to 'out'. If +'stereo' is true, writes output to every other element of 'out', allowing easy +interleaving of two buffers into a stereo sample stream. Outputs 16-bit signed +samples. Returns number of samples actually read. */ +int blip_read_samples( blip_t*, short out [], int count, int stereo ); + +/** Frees buffer. No effect if NULL is passed. */ +void blip_delete( blip_t* ); + + +/* Deprecated */ +typedef blip_t blip_buffer_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/waterbox/sameboy/camera.c b/waterbox/sameboy/camera.c new file mode 100644 index 0000000000..6ce2c0d89e --- /dev/null +++ b/waterbox/sameboy/camera.c @@ -0,0 +1,149 @@ +#include "gb.h" + +static int noise_seed = 0; + +/* This is not a complete emulation of the camera chip. Only the features used by the GameBoy Camera ROMs are supported. + We also do not emulate the timing of the real cart, as it might be actually faster than the webcam. */ + +static uint8_t generate_noise(uint8_t x, uint8_t y) +{ + int value = (x + y * 128 + noise_seed); + uint8_t *data = (uint8_t *) &value; + unsigned hash = 0; + + while ((int *) data != &value + 1) { + hash ^= (*data << 8); + if (hash & 0x8000) { + hash ^= 0x8a00; + hash ^= *data; + } + data++; + hash <<= 1; + } + return (hash >> 8); +} + +static long get_processed_color(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + if (x >= 128) { + x = 0; + } + if (y >= 112) { + y = 0; + } + + long color = gb->camera_get_pixel_callback? gb->camera_get_pixel_callback(gb, x, y) : (generate_noise(x, y)); + + static const double gain_values[] = + {0.8809390, 0.9149149, 0.9457498, 0.9739758, + 1.0000000, 1.0241412, 1.0466537, 1.0677433, + 1.0875793, 1.1240310, 1.1568911, 1.1868043, + 1.2142561, 1.2396208, 1.2743837, 1.3157323, + 1.3525190, 1.3856512, 1.4157897, 1.4434309, + 1.4689574, 1.4926697, 1.5148087, 1.5355703, + 1.5551159, 1.5735801, 1.5910762, 1.6077008, + 1.6235366, 1.6386550, 1.6531183, 1.6669808}; + /* Multiply color by gain value */ + color *= gain_values[gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0x1F]; + + + /* Color is multiplied by the exposure register to simulate exposure. */ + color = color * ((gb->camera_registers[GB_CAMERA_EXPOSURE_HIGH] << 8) + gb->camera_registers[GB_CAMERA_EXPOSURE_LOW]) / 0x1000; + + return color; +} + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) { + /* Forbid reading the image while the camera is busy. */ + return 0xFF; + } + uint8_t tile_x = addr / 0x10 % 0x10; + uint8_t tile_y = addr / 0x10 / 0x10; + + uint8_t y = ((addr >> 1) & 0x7) + tile_y * 8; + uint8_t bit = addr & 1; + + uint8_t ret = 0; + + for (uint8_t x = tile_x * 8; x < tile_x * 8 + 8; x++) { + + long color = get_processed_color(gb, x, y); + + static const double edge_enhancement_ratios[] = {0.5, 0.75, 1, 1.25, 2, 3, 4, 5}; + double edge_enhancement_ratio = edge_enhancement_ratios[(gb->camera_registers[GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE] >> 4) & 0x7]; + if ((gb->camera_registers[GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS] & 0xE0) == 0xE0) { + color += (color * 4) * edge_enhancement_ratio; + color -= get_processed_color(gb, x - 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x + 1, y) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y - 1) * edge_enhancement_ratio; + color -= get_processed_color(gb, x, y + 1) * edge_enhancement_ratio; + } + + + /* The camera's registers are used as a threshold pattern, which defines the dithering */ + uint8_t pattern_base = ((x & 3) + (y & 3) * 4) * 3 + GB_CAMERA_DITHERING_PATTERN_START; + + if (color < gb->camera_registers[pattern_base]) { + color = 3; + } + else if (color < gb->camera_registers[pattern_base + 1]) { + color = 2; + } + else if (color < gb->camera_registers[pattern_base + 2]) { + color = 1; + } + else { + color = 0; + } + + ret <<= 1; + ret |= (color >> bit) & 1; + } + + return ret; +} + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback) +{ + gb->camera_get_pixel_callback = callback; +} + +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback) +{ + gb->camera_update_request_callback = callback; +} + +void GB_camera_updated(GB_gameboy_t *gb) +{ + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] &= ~1; +} + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + addr &= 0x7F; + if (addr == GB_CAMERA_SHOOT_AND_1D_FLAGS) { + value &= 0x7; + noise_seed = 42; // rand(); + if ((value & 1) && !(gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] & 1) && gb->camera_update_request_callback) { + /* If no callback is set, ignore the write as if the camera is instantly done */ + gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS] |= 1; + gb->camera_update_request_callback(gb); + } + } + else { + if (addr >= 0x36) { + GB_log(gb, "Wrote invalid camera register %02x: %2x\n", addr, value); + return; + } + gb->camera_registers[addr] = value; + } +} +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr) +{ + if ((addr & 0x7F) == 0) { + return gb->camera_registers[GB_CAMERA_SHOOT_AND_1D_FLAGS]; + } + return 0; +} diff --git a/waterbox/sameboy/camera.h b/waterbox/sameboy/camera.h new file mode 100644 index 0000000000..21c69b68e6 --- /dev/null +++ b/waterbox/sameboy/camera.h @@ -0,0 +1,29 @@ +#ifndef camera_h +#define camera_h +#include +#include "gb_struct_def.h" + +typedef uint8_t (*GB_camera_get_pixel_callback_t)(GB_gameboy_t *gb, uint8_t x, uint8_t y); +typedef void (*GB_camera_update_request_callback_t)(GB_gameboy_t *gb); + +enum { + GB_CAMERA_SHOOT_AND_1D_FLAGS = 0, + GB_CAMERA_GAIN_AND_EDGE_ENHACEMENT_FLAGS = 1, + GB_CAMERA_EXPOSURE_HIGH = 2, + GB_CAMERA_EXPOSURE_LOW = 3, + GB_CAMERA_EDGE_ENHANCEMENT_INVERT_AND_VOLTAGE = 4, + GB_CAMERA_DITHERING_PATTERN_START = 6, + GB_CAMERA_DITHERING_PATTERN_END = 0x35, +}; + +uint8_t GB_camera_read_image(GB_gameboy_t *gb, uint16_t addr); + +void GB_set_camera_get_pixel_callback(GB_gameboy_t *gb, GB_camera_get_pixel_callback_t callback); +void GB_set_camera_update_request_callback(GB_gameboy_t *gb, GB_camera_update_request_callback_t callback); + +void GB_camera_updated(GB_gameboy_t *gb); + +void GB_camera_write_register(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +uint8_t GB_camera_read_register(GB_gameboy_t *gb, uint16_t addr); + +#endif diff --git a/waterbox/sameboy/display.c b/waterbox/sameboy/display.c new file mode 100644 index 0000000000..28dd5f07ec --- /dev/null +++ b/waterbox/sameboy/display.c @@ -0,0 +1,796 @@ +#include +#include +#include +#include +#include "gb.h" + +/* + Each line is 456 cycles, approximately: + Mode 2 - 80 cycles / OAM Transfer + Mode 3 - 172 cycles / Rendering + Mode 0 - 204 cycles / HBlank + + Mode 1 is VBlank + + Todo: Mode lengths are not constants, see http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt + */ + +#define MODE2_LENGTH (80) +#define MODE3_LENGTH (172) +#define MODE0_LENGTH (204) +#define LINE_LENGTH (MODE2_LENGTH + MODE3_LENGTH + MODE0_LENGTH) // = 456 +#define LINES (144) +#define WIDTH (160) +#define VIRTUAL_LINES (LCDC_PERIOD / LINE_LENGTH) // = 154 + +typedef struct __attribute__((packed)) { + uint8_t y; + uint8_t x; + uint8_t tile; + uint8_t flags; +} GB_sprite_t; + +static bool window_enabled(GB_gameboy_t *gb) +{ + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (!gb->cgb_mode && gb->is_cgb) { + return false; + } + } + return (gb->io_registers[GB_IO_LCDC] & 0x20) && gb->io_registers[GB_IO_WX] < 167; +} + +static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) +{ + /* + Bit 7 - LCD Display Enable (0=Off, 1=On) + Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) + Bit 5 - Window Display Enable (0=Off, 1=On) + Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF) + Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) + Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16) + Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On) + Bit 0 - BG Display (for CGB see below) (0=Off, 1=On) + */ + uint16_t map = 0x1800; + uint8_t tile = 0; + uint8_t attributes = 0; + uint8_t sprite_palette = 0; + uint16_t tile_address = 0; + uint8_t background_pixel = 0, sprite_pixel = 0; + GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + uint8_t sprites_in_line = 0; + bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0; + bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0; + uint8_t lowest_sprite_x = 0xFF; + bool use_obp1 = false, priority = false; + bool in_window = false; + bool bg_enabled = true; + bool bg_behind = false; + if ((gb->io_registers[GB_IO_LCDC] & 0x1) == 0) { + if (gb->cgb_mode) { + bg_behind = true; + } + else { + bg_enabled = false; + } + } + if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] && x + 7 >= gb->io_registers[GB_IO_WX] && gb->current_window_line != 0xFF) { + in_window = true; + } + + if (sprites_enabled) { + // Loop all sprites + for (uint8_t i = 40; i--; sprite++) { + int sprite_y = sprite->y - 16; + int sprite_x = sprite->x - 8; + // Is sprite in our line? + if (sprite_y <= y && sprite_y + (lcd_8_16_mode? 16:8) > y) { + uint8_t tile_x, tile_y, current_sprite_pixel; + uint16_t line_address; + // Limit to 10 sprites in one scan line. + if (++sprites_in_line == 11) break; + // Does not overlap our pixel. + if (sprite_x > x || sprite_x + 8 <= x) continue; + tile_x = x - sprite_x; + tile_y = y - sprite_y; + if (sprite->flags & 0x20) tile_x = 7 - tile_x; + if (sprite->flags & 0x40) tile_y = (lcd_8_16_mode? 15:7) - tile_y; + line_address = (lcd_8_16_mode? sprite->tile & 0xFE : sprite->tile) * 0x10 + tile_y * 2; + if (gb->cgb_mode && (sprite->flags & 0x8)) { + line_address += 0x2000; + } + current_sprite_pixel = (((gb->vram[line_address ] >> ((~tile_x)&7)) & 1 ) | + ((gb->vram[line_address + 1] >> ((~tile_x)&7)) & 1) << 1 ); + /* From Pandocs: + When sprites with different x coordinate values overlap, the one with the smaller x coordinate + (closer to the left) will have priority and appear above any others. This applies in Non CGB Mode + only. When sprites with the same x coordinate values overlap, they have priority according to table + ordering. (i.e. $FE00 - highest, $FE04 - next highest, etc.) In CGB Mode priorities are always + assigned like this. + */ + if (current_sprite_pixel != 0) { + if (!gb->cgb_mode && sprite->x >= lowest_sprite_x) { + break; + } + sprite_pixel = current_sprite_pixel; + lowest_sprite_x = sprite->x; + use_obp1 = (sprite->flags & 0x10) != 0; + sprite_palette = sprite->flags & 7; + priority = (sprite->flags & 0x80) != 0; + if (gb->cgb_mode) { + break; + } + } + } + } + } + + if (in_window) { + x -= gb->io_registers[GB_IO_WX] - 7; // Todo: This value is probably latched + y = gb->current_window_line; + } + else { + x += gb->effective_scx; + y += gb->io_registers[GB_IO_SCY]; + } + if (gb->io_registers[GB_IO_LCDC] & 0x08 && !in_window) { + map = 0x1C00; + } + else if (gb->io_registers[GB_IO_LCDC] & 0x40 && in_window) { + map = 0x1C00; + } + tile = gb->vram[map + x/8 + y/8 * 32]; + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x80) { + priority = !bg_behind && bg_enabled; + } + + if (!priority && sprite_pixel) { + if (!gb->cgb_mode) { + sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3; + sprite_palette = use_obp1; + } + return gb->sprite_palettes_rgb[sprite_palette * 4 + sprite_pixel]; + } + + if (bg_enabled) { + if (gb->io_registers[GB_IO_LCDC] & 0x10) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + if (attributes & 0x8) { + tile_address += 0x2000; + } + + if (attributes & 0x20) { + x = ~x; + } + + if (attributes & 0x40) { + y = ~y; + } + + background_pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1 ); + } + + if (priority && sprite_pixel && !background_pixel) { + if (!gb->cgb_mode) { + sprite_pixel = (gb->io_registers[use_obp1? GB_IO_OBP1:GB_IO_OBP0] >> (sprite_pixel << 1)) & 3; + sprite_palette = use_obp1; + } + return gb->sprite_palettes_rgb[sprite_palette * 4 + sprite_pixel]; + } + + if (!gb->cgb_mode) { + background_pixel = ((gb->io_registers[GB_IO_BGP] >> (background_pixel << 1)) & 3); + } + + return gb->background_palettes_rgb[(attributes & 7) * 4 + background_pixel]; +} + +static void display_vblank(GB_gameboy_t *gb) +{ + if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) { + /* LCD is off, set screen to white */ + uint32_t white = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + for (unsigned i = 0; i < WIDTH * LINES; i++) { + gb ->screen[i] = white; + } + } + + gb->vblank_callback(gb); + + gb->vblank_just_occured = true; +} + +static inline uint8_t scale_channel(uint8_t x) +{ + x &= 0x1f; + return (x << 3) | (x >> 2); +} + +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index) +{ + uint8_t *palette_data = background_palette? gb->background_palettes_data : gb->sprite_palettes_data; + uint16_t color = palette_data[index & ~1] | (palette_data[index | 1] << 8); + + // No need to &, scale channel does that. + uint8_t r = scale_channel(color); + uint8_t g = scale_channel(color >> 5); + uint8_t b = scale_channel(color >> 10); + assert (gb->rgb_encode_callback); + (background_palette? gb->background_palettes_rgb : gb->sprite_palettes_rgb)[index / 2] = gb->rgb_encode_callback(gb, r, g, b); +} + +/* + STAT interrupt is implemented based on this finding: + http://board.byuu.org/phpbb3/viewtopic.php?p=25527#p25531 + + General timing is based on GiiBiiAdvance's documents: + https://github.com/AntonioND/giibiiadvance + + */ + +static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) +{ + uint8_t previous_stat_interrupt_line = gb->stat_interrupt_line; + gb->stat_interrupt_line = false; + + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* LCD is disabled, state is constant */ + + /* When the LCD is off, LY is 0 and STAT mode is 0. + Todo: Verify the LY=LYC flag should be on. */ + gb->io_registers[GB_IO_LY] = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 4; + if (gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + gb->hdma_on = false; + + /* Todo: is this correct? */ + gb->hdma_steps_left = 0xff; + } + + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + + /* Keep sending vblanks to user even if the screen is off */ + gb->display_cycles += cycles; + if (gb->display_cycles >= LCDC_PERIOD) { + /* VBlank! */ + gb->display_cycles -= LCDC_PERIOD; + display_vblank(gb); + } + + /* Reset window rendering state */ + gb->current_window_line = 0xFF; + return; + } + + uint8_t atomic_increase = gb->cgb_double_speed? 2 : 4; + uint8_t stat_delay = gb->cgb_double_speed? 2 : (gb->cgb_mode? 0 : 4); + /* Todo: This is correct for DMG. Is it correct for the 3 CGB modes (DMG/single/double)?*/ + uint8_t scx_delay = ((gb->effective_scx & 7) + atomic_increase - 1) & ~(atomic_increase - 1); + /* Todo: These are correct for DMG, DMG-mode CGB, and single speed CGB. Is is correct for double speed CGB? */ + uint8_t oam_blocking_rush = gb->cgb_double_speed? 2 : 4; + uint8_t vram_blocking_rush = gb->is_cgb? 0 : 4; + + for (; cycles; cycles -= atomic_increase) { + + gb->display_cycles += atomic_increase; + /* The very first line is 2 (4 from the CPU's perseptive) clocks shorter when the LCD turns on. + Todo: Verify on the 3 CGB modes, especially double speed mode. */ + if (gb->first_scanline && gb->display_cycles >= LINE_LENGTH - atomic_increase) { + gb->first_scanline = false; + gb->display_cycles += atomic_increase; + } + bool should_compare_ly = true; + uint8_t ly_for_comparison = gb->io_registers[GB_IO_LY] = gb->display_cycles / LINE_LENGTH; + + if (gb->scanline_callback && gb->display_cycles % LINE_LENGTH == 0 && gb->scanline_callback_ly == ly_for_comparison) { + gb->scanline_callback(gb->io_registers[GB_IO_LCDC]); + } + + /* Handle cycle completion. STAT's initial value depends on model and mode */ + if (gb->display_cycles == LCDC_PERIOD) { + /* VBlank! */ + gb->display_cycles = 0; + gb->io_registers[GB_IO_STAT] &= ~3; + if (gb->is_cgb) { + if (stat_delay) { + gb->io_registers[GB_IO_STAT] |= 1; + } + else { + gb->io_registers[GB_IO_STAT] |= 2; + } + } + ly_for_comparison = gb->io_registers[GB_IO_LY] = 0; + + /* Todo: verify timing */ + gb->oam_read_blocked = true; + gb->vram_read_blocked = false; + gb->oam_write_blocked = true; + gb->vram_write_blocked = false; + + + /* Reset window rendering state */ + gb->current_window_line = 0xFF; + } + + /* Entered VBlank state, update STAT and IF */ + else if (gb->display_cycles == LINES * LINE_LENGTH + stat_delay) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 1; + gb->io_registers[GB_IO_IF] |= 1; + + /* Entering VBlank state triggers the OAM interrupt. In CGB, it happens 4 cycles earlier */ + if (gb->io_registers[GB_IO_STAT] & 0x20 && !gb->is_cgb) { + gb->stat_interrupt_line = true; + } + if (gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON) { + if (!gb->is_cgb) { + display_vblank(gb); + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + } + else { + gb->frame_skip_state = GB_FRAMESKIP_FIRST_FRAME_SKIPPED; + } + } + else { + gb->frame_skip_state = GB_FRAMESKIP_SECOND_FRAME_RENDERED; + display_vblank(gb); + } + } + + + /* Handle line 0 right after turning the LCD on */ + else if (gb->first_scanline) { + /* OAM and VRAM blocking is not rushed in the very first scanline */ + if (gb->display_cycles == atomic_increase) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + else if (gb->display_cycles == MODE2_LENGTH) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->oam_read_blocked = true; + gb->vram_read_blocked = true; + gb->oam_write_blocked = true; + gb->vram_write_blocked = true; + } + else if (gb->display_cycles == MODE2_LENGTH + MODE3_LENGTH) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + } + } + + /* Handle STAT changes for lines 0-143 */ + else if (gb->display_cycles < LINES * LINE_LENGTH) { + unsigned position_in_line = gb->display_cycles % LINE_LENGTH; + + /* Handle OAM and VRAM blocking */ + /* Todo: verify CGB timing for write blocking */ + if (position_in_line == stat_delay - oam_blocking_rush || + // In case stat_delay is 0 + (position_in_line == LINE_LENGTH + stat_delay - oam_blocking_rush && gb->io_registers[GB_IO_LY] != 143)) { + gb->oam_read_blocked = true; + gb->oam_write_blocked = gb->is_cgb; + } + else if (position_in_line == MODE2_LENGTH + stat_delay - vram_blocking_rush) { + gb->vram_read_blocked = true; + gb->vram_write_blocked = gb->is_cgb; + } + + if (position_in_line == stat_delay) { + gb->oam_write_blocked = true; + } + else if (!gb->is_cgb && position_in_line == MODE2_LENGTH + stat_delay - oam_blocking_rush) { + gb->oam_write_blocked = false; + } + else if (position_in_line == MODE2_LENGTH + stat_delay) { + gb->vram_write_blocked = true; + gb->oam_write_blocked = true; + } + + /* Handle everything else */ + if (position_in_line == stat_delay) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 2; + if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH >= gb->io_registers[GB_IO_WY]) { + gb->current_window_line++; + } + } + else if (position_in_line == 0 && gb->display_cycles != 0) { + should_compare_ly = gb->is_cgb; + ly_for_comparison--; + } + else if (position_in_line == MODE2_LENGTH + stat_delay) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->io_registers[GB_IO_STAT] |= 3; + gb->effective_scx = gb->io_registers[GB_IO_SCX]; + gb->previous_lcdc_x = - (gb->effective_scx & 0x7); + + /* Todo: This works on both 007 - The World Is Not Enough and Donkey Kong 94, but should be verified better */ + if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH == gb->io_registers[GB_IO_WY] && gb->current_window_line == 0xFF) { + gb->current_window_line = 0; + } + } + else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) { + gb->io_registers[GB_IO_STAT] &= ~3; + gb->oam_read_blocked = false; + gb->vram_read_blocked = false; + gb->oam_write_blocked = false; + gb->vram_write_blocked = false; + if (gb->hdma_on_hblank) { + gb->hdma_on = true; + gb->hdma_cycles = 0; + } + } + } + + /* Line 153 is special */ + else if (gb->display_cycles >= (VIRTUAL_LINES - 1) * LINE_LENGTH) { + /* DMG */ + if (!gb->is_cgb) { + switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { + case 0: + should_compare_ly = false; + break; + case 4: + gb->io_registers[GB_IO_LY] = 0; + ly_for_comparison = VIRTUAL_LINES - 1; + break; + case 8: + gb->io_registers[GB_IO_LY] = 0; + should_compare_ly = false; + break; + default: + gb->io_registers[GB_IO_LY] = 0; + ly_for_comparison = 0; + } + } + /* CGB in DMG mode */ + else if (!gb->cgb_mode) { + switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { + case 0: + ly_for_comparison = VIRTUAL_LINES - 2; + break; + case 4: + break; + case 8: + gb->io_registers[GB_IO_LY] = 0; + break; + default: + gb->io_registers[GB_IO_LY] = 0; + ly_for_comparison = 0; + } + } + /* Single speed CGB */ + else if (!gb->cgb_double_speed) { + switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { + case 0: + break; + case 4: + gb->io_registers[GB_IO_LY] = 0; + break; + default: + gb->io_registers[GB_IO_LY] = 0; + ly_for_comparison = 0; + } + } + + /* Double speed CGB */ + else { + switch (gb->display_cycles - (VIRTUAL_LINES - 1) * LINE_LENGTH) { + case 0: + ly_for_comparison = VIRTUAL_LINES - 2; + break; + case 2: + case 4: + break; + case 6: + case 8: + gb->io_registers[GB_IO_LY] = 0; + break; + default: + gb->io_registers[GB_IO_LY] = 0; + ly_for_comparison = 0; + } + } + } + + /* Lines 144 - 152 */ + else { + if (stat_delay && gb->display_cycles % LINE_LENGTH == 0) { + should_compare_ly = gb->is_cgb; + ly_for_comparison--; + } + } + + /* Set LY=LYC bit */ + if (should_compare_ly && (ly_for_comparison == gb->io_registers[GB_IO_LYC])) { + gb->io_registers[GB_IO_STAT] |= 4; + } + else { + gb->io_registers[GB_IO_STAT] &= ~4; + } + + if (!gb->stat_interrupt_line) { + switch (gb->io_registers[GB_IO_STAT] & 3) { + case 0: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 8; break; + case 1: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x10; break; + case 2: gb->stat_interrupt_line = gb->io_registers[GB_IO_STAT] & 0x20; break; + } + + /* Use requested a LY=LYC interrupt and the LY=LYC bit is on */ + if ((gb->io_registers[GB_IO_STAT] & 0x44) == 0x44) { + gb->stat_interrupt_line = true; + } + } + } + + /* On the CGB, the last cycle of line 144 triggers an OAM interrupt + Todo: Verify timing for CGB in CGB mode and double speed CGB */ + if (gb->is_cgb && + gb->display_cycles == LINES * LINE_LENGTH + stat_delay - atomic_increase && + (gb->io_registers[GB_IO_STAT] & 0x20)) { + gb->stat_interrupt_line = true; + } + + if (gb->stat_interrupt_line && !previous_stat_interrupt_line) { + gb->io_registers[GB_IO_IF] |= 2; + } + + /* The value of LY is glitched in the last cycle of every line in CGB mode CGB in single speed + This is based on GiiBiiAdvance's docs */ + if (gb->cgb_mode && !gb->cgb_double_speed && + gb->display_cycles % LINE_LENGTH == LINE_LENGTH - 4) { + uint8_t glitch_pattern[] = {0, 0, 2, 0, 4, 4, 6, 0, 8}; + if ((gb->io_registers[GB_IO_LY] & 0xF) == 0xF) { + gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] >> 4] << 4; + } + else { + gb->io_registers[GB_IO_LY] = glitch_pattern[gb->io_registers[GB_IO_LY] & 7] | (gb->io_registers[GB_IO_LY] & 0xF8); + } + } +} + +void GB_display_run(GB_gameboy_t *gb, uint8_t cycles) +{ + update_display_state(gb, cycles); + if (gb->disable_rendering) { + return; + } + + /* + Display controller bug: For some reason, the OAM STAT interrupt is called, as expected, for LY = 0..143. + However, it is also called from LY = 144. + + See http://forums.nesdev.com/viewtopic.php?f=20&t=13727 + */ + + if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* LCD is disabled, do nothing */ + return; + } + if (gb->display_cycles >= LINE_LENGTH * 144) { /* VBlank */ + return; + } + + uint8_t effective_ly = gb->display_cycles / LINE_LENGTH; + + + if (gb->display_cycles % LINE_LENGTH < MODE2_LENGTH) { /* Mode 2 */ + return; + } + + + /* Render */ + /* Todo: it appears that the actual rendering starts 4 cycles after mode 3 starts. Is this correct? */ + int16_t current_lcdc_x = gb->display_cycles % LINE_LENGTH - MODE2_LENGTH - (gb->effective_scx & 0x7) - 4; + + for (;gb->previous_lcdc_x < current_lcdc_x; gb->previous_lcdc_x++) { + if (gb->previous_lcdc_x >= WIDTH) { + continue; + } + if (gb->previous_lcdc_x < 0) { + continue; + } + gb->screen[effective_ly * WIDTH + gb->previous_lcdc_x] = + get_pixel(gb, gb->previous_lcdc_x, effective_ly); + } +} + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + default: + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + break; + } + + for (unsigned y = 0; y < 192; y++) { + for (unsigned x = 0; x < 256; x++) { + if (x >= 128 && !gb->is_cgb) { + *(dest++) = gb->background_palettes_rgb[0]; + continue; + } + uint16_t tile = (x % 128) / 8 + y / 8 * 16; + uint16_t tile_address = tile * 0x10 + (x >= 128? 0x2000 : 0); + uint8_t pixel = (((gb->vram[tile_address + (y & 7) * 2 ] >> ((~x)&7)) & 1 ) | + ((gb->vram[tile_address + (y & 7) * 2 + 1] >> ((~x)&7)) & 1) << 1); + + if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_BACKGROUND) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + else if (!gb->cgb_mode) { + if (palette_type == GB_PALETTE_OAM) { + pixel = ((gb->io_registers[palette_index == 0? GB_IO_OBP0 : GB_IO_OBP1] >> (pixel << 1)) & 3); + } + } + } + + + *(dest++) = palette[pixel]; + } + } +} + +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type) +{ + uint32_t none_palette[4]; + uint32_t *palette = NULL; + uint16_t map = 0x1800; + + switch (gb->is_cgb? palette_type : GB_PALETTE_NONE) { + case GB_PALETTE_NONE: + none_palette[0] = gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + none_palette[1] = gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + none_palette[2] = gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + none_palette[3] = gb->rgb_encode_callback(gb, 0, 0, 0 ); + palette = none_palette; + break; + case GB_PALETTE_BACKGROUND: + palette = gb->background_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_OAM: + palette = gb->sprite_palettes_rgb + (4 * (palette_index & 7)); + break; + case GB_PALETTE_AUTO: + break; + } + + if (map_type == GB_MAP_9C00 || (map_type == GB_MAP_AUTO && gb->io_registers[GB_IO_LCDC] & 0x08)) { + map = 0x1c00; + } + + if (tileset_type == GB_TILESET_AUTO) { + tileset_type = (gb->io_registers[GB_IO_LCDC] & 0x10)? GB_TILESET_8800 : GB_TILESET_8000; + } + + for (unsigned y = 0; y < 256; y++) { + for (unsigned x = 0; x < 256; x++) { + uint8_t tile = gb->vram[map + x/8 + y/8 * 32]; + uint16_t tile_address; + uint8_t attributes = 0; + + if (tileset_type == GB_TILESET_8800) { + tile_address = tile * 0x10; + } + else { + tile_address = (int8_t) tile * 0x10 + 0x1000; + } + + if (gb->cgb_mode) { + attributes = gb->vram[map + x/8 + y/8 * 32 + 0x2000]; + } + + if (attributes & 0x8) { + tile_address += 0x2000; + } + + uint8_t pixel = (((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 ] >> (((attributes & 0x20)? x : ~x)&7)) & 1 ) | + ((gb->vram[tile_address + (((attributes & 0x40)? ~y : y) & 7) * 2 + 1] >> (((attributes & 0x20)? x : ~x)&7)) & 1) << 1); + + if (!gb->cgb_mode && (palette_type == GB_PALETTE_BACKGROUND || palette_type == GB_PALETTE_AUTO)) { + pixel = ((gb->io_registers[GB_IO_BGP] >> (pixel << 1)) & 3); + } + + if (palette) { + *(dest++) = palette[pixel]; + } + else { + *(dest++) = gb->background_palettes_rgb[(attributes & 7) * 4 + pixel]; + } + } + } +} + +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height) +{ + uint8_t count = 0; + *sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8; + uint8_t oam_to_dest_index[40] = {0,}; + for (unsigned y = 0; y < LINES; y++) { + GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam; + uint8_t sprites_in_line = 0; + for (uint8_t i = 0; i < 40; i++, sprite++) { + int sprite_y = sprite->y - 16; + bool obscured = false; + // Is sprite not in this line? + if (sprite_y > y || sprite_y + *sprite_height <= y) continue; + if (++sprites_in_line == 11) obscured = true; + + GB_oam_info_t *info = NULL; + if (!oam_to_dest_index[i]) { + info = dest + count; + oam_to_dest_index[i] = ++count; + info->x = sprite->x; + info->y = sprite->y; + info->tile = *sprite_height == 16? sprite->tile & 0xFE : sprite->tile; + info->flags = sprite->flags; + info->obscured_by_line_limit = false; + info->oam_addr = 0xFE00 + i * sizeof(*sprite); + } + else { + info = dest + oam_to_dest_index[i] - 1; + } + info->obscured_by_line_limit |= obscured; + } + } + + + for (unsigned i = 0; i < count; i++) { + uint16_t vram_address = dest[i].tile * 0x10; + uint8_t flags = dest[i].flags; + uint8_t palette = gb->cgb_mode? (flags & 7) : ((flags & 0x10)? 1 : 0); + if (gb->is_cgb && (flags & 0x8)) { + vram_address += 0x2000; + } + + for (unsigned y = 0; y < *sprite_height; y++) { + for (unsigned x = 0; x < 8; x++) { + uint8_t color = (((gb->vram[vram_address ] >> ((~x)&7)) & 1 ) | + ((gb->vram[vram_address + 1] >> ((~x)&7)) & 1) << 1 ); + + if (!gb->cgb_mode) { + color = (gb->io_registers[palette? GB_IO_OBP1:GB_IO_OBP0] >> (color << 1)) & 3; + } + dest[i].image[((flags & 0x20)?7-x:x) + ((flags & 0x40)?*sprite_height - 1 -y:y) * 8] = gb->sprite_palettes_rgb[palette * 4 + color]; + } + vram_address += 2; + } + } + return count; +} diff --git a/waterbox/sameboy/display.h b/waterbox/sameboy/display.h new file mode 100644 index 0000000000..3967787237 --- /dev/null +++ b/waterbox/sameboy/display.h @@ -0,0 +1,40 @@ +#ifndef display_h +#define display_h + +#include "gb.h" +#ifdef GB_INTERNAL +void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); +void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); +#endif + +typedef enum { + GB_PALETTE_NONE, + GB_PALETTE_BACKGROUND, + GB_PALETTE_OAM, + GB_PALETTE_AUTO, +} GB_palette_type_t; + +typedef enum { + GB_MAP_AUTO, + GB_MAP_9800, + GB_MAP_9C00, +} GB_map_type_t; + +typedef enum { + GB_TILESET_AUTO, + GB_TILESET_8800, + GB_TILESET_8000, +} GB_tileset_type_t; + +typedef struct { + uint32_t image[128]; + uint8_t x, y, tile, flags; + uint16_t oam_addr; + bool obscured_by_line_limit; +} GB_oam_info_t; + +void GB_draw_tileset(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index); +void GB_draw_tilemap(GB_gameboy_t *gb, uint32_t *dest, GB_palette_type_t palette_type, uint8_t palette_index, GB_map_type_t map_type, GB_tileset_type_t tileset_type); +uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_height); + +#endif /* display_h */ diff --git a/waterbox/sameboy/gb.c b/waterbox/sameboy/gb.c new file mode 100644 index 0000000000..ce14dc647c --- /dev/null +++ b/waterbox/sameboy/gb.c @@ -0,0 +1,473 @@ +#include +#include +#include +#include +#include +#include +#include +#include "gb.h" +#include "../emulibc/emulibc.h" + +void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args) +{ + char *string = NULL; + vasprintf(&string, fmt, args); + if (string) { + if (gb->log_callback) { + gb->log_callback(gb, string, attributes); + } + else { + /* Todo: Add ANSI escape sequences for attributed text */ + printf("%s", string); + } + } + free(string); +} + +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, attributes, fmt, args); + va_end(args); +} + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + GB_attributed_logv(gb, 0, fmt, args); + va_end(args); +} + +void GB_init(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + + GB_reset(gb); +} + +void GB_init_sgb(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->ram = malloc(gb->ram_size = 0x2000); + gb->vram = malloc(gb->vram_size = 0x2000); + gb->is_sgb = true; + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + + GB_reset(gb); +} + +void GB_init_cgb(GB_gameboy_t *gb) +{ + memset(gb, 0, sizeof(*gb)); + gb->ram = malloc(gb->ram_size = 0x2000 * 8); + gb->vram = malloc(gb->vram_size = 0x2000 * 2); + gb->is_cgb = true; + gb->cartridge_type = &GB_cart_defs[0]; // Default cartridge type + + GB_reset(gb); +} + +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open boot ROM: %s.\n", strerror(errno)); + return errno; + } + fread(gb->boot_rom, sizeof(gb->boot_rom), 1, f); + fclose(f); + return 0; +} + +int GB_load_rom(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + GB_log(gb, "Could not open ROM: %s.\n", strerror(errno)); + return errno; + } + fseek(f, 0, SEEK_END); + gb->rom_size = (ftell(f) + 0x3FFF) & ~0x3FFF; /* Round to bank */ + /* And then round to a power of two */ + while (gb->rom_size & (gb->rom_size - 1)) { + /* I promise this works. */ + gb->rom_size |= gb->rom_size >> 1; + gb->rom_size++; + } + fseek(f, 0, SEEK_SET); + if (gb->rom) { + free(gb->rom); + } + gb->rom = alloc_sealed(gb->rom_size); + memset(gb->rom, 0xFF, gb->rom_size); /* Pad with 0xFFs */ + fread(gb->rom, gb->rom_size, 1, f); + fclose(f); + GB_configure_cart(gb); + + return 0; +} + +int GB_save_battery(GB_gameboy_t *gb, const char *path) +{ + if (!gb->cartridge_type->has_battery) return 0; // Nothing to save. + if (gb->mbc_ram_size == 0 && !gb->cartridge_type->has_rtc) return 0; /* Claims to have battery, but has no RAM or RTC */ + FILE *f = fopen(path, "wb"); + if (!f) { + GB_log(gb, "Could not open battery save: %s.\n", strerror(errno)); + return errno; + } + + if (fwrite(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + fclose(f); + return EIO; + } + if (gb->cartridge_type->has_rtc) { + if (fwrite(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { + fclose(f); + return EIO; + } + + if (fwrite(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) { + fclose(f); + return EIO; + } + } + + errno = 0; + fclose(f); + return errno; +} + +/* Loading will silently stop if the format is incomplete */ +void GB_load_battery(GB_gameboy_t *gb, const char *path) +{ + FILE *f = fopen(path, "rb"); + if (!f) { + return; + } + + if (fread(gb->mbc_ram, 1, gb->mbc_ram_size, f) != gb->mbc_ram_size) { + goto reset_rtc; + } + + if (fread(&gb->rtc_real, 1, sizeof(gb->rtc_real), f) != sizeof(gb->rtc_real)) { + goto reset_rtc; + } + + if (fread(&gb->last_rtc_second, 1, sizeof(gb->last_rtc_second), f) != sizeof(gb->last_rtc_second)) { + goto reset_rtc; + } + + if (gb->last_rtc_second > time(NULL)) { + /* We must reset RTC here, or it will not advance. */ + goto reset_rtc; + } + + if (gb->last_rtc_second < 852076800) { /* 1/1/97. There weren't any RTC games that time, + so if the value we read is lower it means it wasn't + really RTC data. */ + goto reset_rtc; + } + goto exit; +reset_rtc: + gb->last_rtc_second = time(NULL); + gb->rtc_real.high |= 0x80; /* This gives the game a hint that the clock should be reset. */ +exit: + fclose(f); + return; +} + +void GB_run(GB_gameboy_t *gb) +{ + GB_cpu_run(gb); + if (gb->vblank_just_occured) { + GB_update_joyp(gb); + GB_rtc_run(gb); + } +} + +uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles) +{ + GB_update_joyp(gb); + GB_rtc_run(gb); + uint64_t start = gb->cycles_since_epoch; + uint64_t target = start + cycles; + while (gb->cycles_since_epoch < target) { + GB_run(gb); + } + return gb->cycles_since_epoch - start; +} + +uint64_t GB_epoch(GB_gameboy_t *gb) +{ + return gb->cycles_since_epoch; +} + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output) +{ + gb->screen = output; +} + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback) +{ + gb->vblank_callback = callback; +} + +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback) +{ + gb->log_callback = callback; +} + +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) +{ + gb->input_callback = callback; +} + +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback) +{ + if (!gb->rgb_encode_callback && !gb->is_cgb) { + gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = + callback(gb, 0xFF, 0xFF, 0xFF); + gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = + callback(gb, 0xAA, 0xAA, 0xAA); + gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = + callback(gb, 0x55, 0x55, 0x55); + gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = + callback(gb, 0, 0, 0); + } + gb->rgb_encode_callback = callback; +} + +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback) +{ + gb->infrared_callback = callback; +} + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state) +{ + gb->infrared_input = state; + gb->cycles_since_input_ir_change = 0; + gb->ir_queue_length = 0; +} + +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change) +{ + if (gb->ir_queue_length == GB_MAX_IR_QUEUE) { + GB_log(gb, "IR Queue is full\n"); + return; + } + gb->ir_queue[gb->ir_queue_length++] = (GB_ir_queue_item_t){state, cycles_after_previous_change}; +} + +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback) +{ + gb->rumble_callback = callback; +} + +void GB_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback) +{ + gb->sample_callback = callback; +} + +void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback) +{ + gb->serial_transfer_start_callback = callback; +} + +void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback) +{ + gb->serial_transfer_end_callback = callback; +} + +uint8_t GB_serial_get_data(GB_gameboy_t *gb) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial read request while using internal clock. \n"); + return 0xFF; + } + return gb->io_registers[GB_IO_SB]; +} +void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data) +{ + if (gb->io_registers[GB_IO_SC] & 1) { + /* Internal Clock */ + GB_log(gb, "Serial write request while using internal clock. \n"); + return; + } + gb->io_registers[GB_IO_SB] = data; + gb->io_registers[GB_IO_IF] |= 8; +} + +void GB_disconnect_serial(GB_gameboy_t *gb) +{ + gb->serial_transfer_start_callback = NULL; + gb->serial_transfer_end_callback = NULL; + + /* Reset any internally-emulated device. Currently, only the printer. */ + memset(&gb->printer, 0, sizeof(gb->printer)); +} + +bool GB_is_inited(GB_gameboy_t *gb) +{ + return gb->magic == 'SAME'; +} + +bool GB_is_cgb(GB_gameboy_t *gb) +{ + return gb->is_cgb; +} + +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled) +{ + gb->disable_rendering = disabled; +} + +void *GB_get_user_data(GB_gameboy_t *gb) +{ + return gb->user_data; +} + +void GB_set_user_data(GB_gameboy_t *gb, void *data) +{ + gb->user_data = data; +} + +void GB_reset(GB_gameboy_t *gb) +{ + uint32_t mbc_ram_size = gb->mbc_ram_size; + bool cgb = gb->is_cgb; + // memset(gb, 0, (size_t)GB_GET_SECTION((GB_gameboy_t *) 0, unsaved)); + gb->version = GB_STRUCT_VERSION; + + gb->mbc_rom_bank = 1; + gb->last_rtc_second = time(NULL); + gb->cgb_ram_bank = 1; + gb->io_registers[GB_IO_JOYP] = 0xF; + gb->mbc_ram_size = mbc_ram_size; + if (cgb) { + gb->ram_size = 0x2000 * 8; + memset(gb->ram, 0, gb->ram_size); + gb->vram_size = 0x2000 * 2; + memset(gb->vram, 0, gb->vram_size); + + gb->is_cgb = true; + gb->cgb_mode = true; + gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0x00; + } + else { + gb->ram_size = 0x2000; + memset(gb->ram, 0, gb->ram_size); + gb->vram_size = 0x2000; + memset(gb->vram, 0, gb->vram_size); + + if (gb->rgb_encode_callback) { + gb->sprite_palettes_rgb[4] = gb->sprite_palettes_rgb[0] = gb->background_palettes_rgb[0] = + gb->rgb_encode_callback(gb, 0xFF, 0xFF, 0xFF); + gb->sprite_palettes_rgb[5] = gb->sprite_palettes_rgb[1] = gb->background_palettes_rgb[1] = + gb->rgb_encode_callback(gb, 0xAA, 0xAA, 0xAA); + gb->sprite_palettes_rgb[6] = gb->sprite_palettes_rgb[2] = gb->background_palettes_rgb[2] = + gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55); + gb->sprite_palettes_rgb[7] = gb->sprite_palettes_rgb[3] = gb->background_palettes_rgb[3] = + gb->rgb_encode_callback(gb, 0, 0, 0); + } + gb->io_registers[GB_IO_OBP0] = gb->io_registers[GB_IO_OBP1] = 0xFF; + } + /* The serial interrupt always occur on the 0xF8th cycle of every 0x100 cycle since boot. */ + gb->serial_cycles = 0x100 - 0xF8; + gb->io_registers[GB_IO_SC] = 0x7E; + gb->magic = (uintptr_t)'SAME'; +} + +void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb) +{ + if (is_cgb) { + gb->ram = realloc(gb->ram, gb->ram_size = 0x2000 * 8); + gb->vram = realloc(gb->vram, gb->vram_size = 0x2000 * 2); + } + else { + gb->ram = realloc(gb->ram, gb->ram_size = 0x2000); + gb->vram = realloc(gb->vram, gb->vram_size = 0x2000); + } + gb->is_cgb = is_cgb; + GB_reset(gb); + +} + +void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank) +{ + /* Set size and bank to dummy pointers if not set */ + size_t dummy_size; + uint16_t dummy_bank; + if (!size) { + size = &dummy_size; + } + + if (!bank) { + bank = &dummy_bank; + } + + + switch (access) { + case GB_DIRECT_ACCESS_ROM: + *size = gb->rom_size; + *bank = gb->mbc_rom_bank; + return gb->rom; + case GB_DIRECT_ACCESS_RAM: + *size = gb->ram_size; + *bank = gb->cgb_ram_bank; + return gb->ram; + case GB_DIRECT_ACCESS_CART_RAM: + *size = gb->mbc_ram_size; + *bank = gb->mbc_ram_bank; + return gb->mbc_ram; + case GB_DIRECT_ACCESS_VRAM: + *size = gb->vram_size; + *bank = gb->cgb_vram_bank; + return gb->vram; + case GB_DIRECT_ACCESS_HRAM: + *size = sizeof(gb->hram); + *bank = 0; + return &gb->hram; + case GB_DIRECT_ACCESS_IO: + *size = sizeof(gb->io_registers); + *bank = 0; + return &gb->io_registers; + case GB_DIRECT_ACCESS_BOOTROM: + *size = gb->is_cgb? sizeof(gb->boot_rom) : 0x100; + *bank = 0; + return &gb->boot_rom; + case GB_DIRECT_ACCESS_OAM: + *size = sizeof(gb->oam); + *bank = 0; + return &gb->oam; + case GB_DIRECT_ACCESS_BGP: + *size = sizeof(gb->background_palettes_data); + *bank = 0; + return &gb->background_palettes_data; + case GB_DIRECT_ACCESS_OBP: + *size = sizeof(gb->sprite_palettes_data); + *bank = 0; + return &gb->sprite_palettes_data; + default: + *size = 0; + *bank = 0; + return NULL; + } +} + +void GB_set_lagged(GB_gameboy_t *gb, bool lagged) +{ + gb->lagged = lagged; +} + +bool GB_get_lagged(GB_gameboy_t *gb) +{ + return gb->lagged; +} + diff --git a/waterbox/sameboy/gb.h b/waterbox/sameboy/gb.h new file mode 100644 index 0000000000..098a3d2ea3 --- /dev/null +++ b/waterbox/sameboy/gb.h @@ -0,0 +1,523 @@ +#ifndef GB_h +#define GB_h +#include +#include +#include +#include +#include + +#include "gb_struct_def.h" + +#include "apu.h" +#include "camera.h" +#include "display.h" +#include "joypad.h" +#include "mbc.h" +#include "memory.h" +#include "printer.h" +#include "timing.h" +#include "z80_cpu.h" + +#define GB_STRUCT_VERSION 11 + +enum { + GB_REGISTER_AF, + GB_REGISTER_BC, + GB_REGISTER_DE, + GB_REGISTER_HL, + GB_REGISTER_SP, + GB_REGISTERS_16_BIT /* Count */ +}; + +/* Todo: Actually use these! */ +enum { + GB_CARRY_FLAG = 16, + GB_HALF_CARRY_FLAG = 32, + GB_SUBSTRACT_FLAG = 64, + GB_ZERO_FLAG = 128, +}; + +#define GB_MAX_IR_QUEUE 256 + +enum { + /* Joypad and Serial */ + GB_IO_JOYP = 0x00, // Joypad (R/W) + GB_IO_SB = 0x01, // Serial transfer data (R/W) + GB_IO_SC = 0x02, // Serial Transfer Control (R/W) + + /* Missing */ + + /* Timers */ + GB_IO_DIV = 0x04, // Divider Register (R/W) + GB_IO_TIMA = 0x05, // Timer counter (R/W) + GB_IO_TMA = 0x06, // Timer Modulo (R/W) + GB_IO_TAC = 0x07, // Timer Control (R/W) + + /* Missing */ + + GB_IO_IF = 0x0f, // Interrupt Flag (R/W) + + /* Sound */ + GB_IO_NR10 = 0x10, // Channel 1 Sweep register (R/W) + GB_IO_NR11 = 0x11, // Channel 1 Sound length/Wave pattern duty (R/W) + GB_IO_NR12 = 0x12, // Channel 1 Volume Envelope (R/W) + GB_IO_NR13 = 0x13, // Channel 1 Frequency lo (Write Only) + GB_IO_NR14 = 0x14, // Channel 1 Frequency hi (R/W) + GB_IO_NR21 = 0x16, // Channel 2 Sound Length/Wave Pattern Duty (R/W) + GB_IO_NR22 = 0x17, // Channel 2 Volume Envelope (R/W) + GB_IO_NR23 = 0x18, // Channel 2 Frequency lo data (W) + GB_IO_NR24 = 0x19, // Channel 2 Frequency hi data (R/W) + GB_IO_NR30 = 0x1a, // Channel 3 Sound on/off (R/W) + GB_IO_NR31 = 0x1b, // Channel 3 Sound Length + GB_IO_NR32 = 0x1c, // Channel 3 Select output level (R/W) + GB_IO_NR33 = 0x1d, // Channel 3 Frequency's lower data (W) + GB_IO_NR34 = 0x1e, // Channel 3 Frequency's higher data (R/W) + + /* Missing */ + + GB_IO_NR41 = 0x20, // Channel 4 Sound Length (R/W) + GB_IO_NR42 = 0x21, // Channel 4 Volume Envelope (R/W) + GB_IO_NR43 = 0x22, // Channel 4 Polynomial Counter (R/W) + GB_IO_NR44 = 0x23, // Channel 4 Counter/consecutive, Inital (R/W) + GB_IO_NR50 = 0x24, // Channel control / ON-OFF / Volume (R/W) + GB_IO_NR51 = 0x25, // Selection of Sound output terminal (R/W) + GB_IO_NR52 = 0x26, // Sound on/off + + /* Missing */ + + GB_IO_WAV_START = 0x30, // Wave pattern start + GB_IO_WAV_END = 0x3f, // Wave pattern end + + /* Graphics */ + GB_IO_LCDC = 0x40, // LCD Control (R/W) + GB_IO_STAT = 0x41, // LCDC Status (R/W) + GB_IO_SCY = 0x42, // Scroll Y (R/W) + GB_IO_SCX = 0x43, // Scroll X (R/W) + GB_IO_LY = 0x44, // LCDC Y-Coordinate (R) + GB_IO_LYC = 0x45, // LY Compare (R/W) + GB_IO_DMA = 0x46, // DMA Transfer and Start Address (W) + GB_IO_BGP = 0x47, // BG Palette Data (R/W) - Non CGB Mode Only + GB_IO_OBP0 = 0x48, // Object Palette 0 Data (R/W) - Non CGB Mode Only + GB_IO_OBP1 = 0x49, // Object Palette 1 Data (R/W) - Non CGB Mode Only + GB_IO_WY = 0x4a, // Window Y Position (R/W) + GB_IO_WX = 0x4b, // Window X Position minus 7 (R/W) + // Has some undocumented compatibility flags written at boot. + // Unfortunately it is not readable or writable after boot has finished, so research of this + // register is quite limited. The value written to this register, however, can be controlled + // in some cases. + GB_IO_DMG_EMULATION = 0x4c, + + /* General CGB features */ + GB_IO_KEY1 = 0x4d, // CGB Mode Only - Prepare Speed Switch + + /* Missing */ + + GB_IO_VBK = 0x4f, // CGB Mode Only - VRAM Bank + GB_IO_BIOS = 0x50, // Write to disable the BIOS mapping + + /* CGB DMA */ + GB_IO_HDMA1 = 0x51, // CGB Mode Only - New DMA Source, High + GB_IO_HDMA2 = 0x52, // CGB Mode Only - New DMA Source, Low + GB_IO_HDMA3 = 0x53, // CGB Mode Only - New DMA Destination, High + GB_IO_HDMA4 = 0x54, // CGB Mode Only - New DMA Destination, Low + GB_IO_HDMA5 = 0x55, // CGB Mode Only - New DMA Length/Mode/Start + + /* IR */ + GB_IO_RP = 0x56, // CGB Mode Only - Infrared Communications Port + + /* Missing */ + + /* CGB Paletts */ + GB_IO_BGPI = 0x68, // CGB Mode Only - Background Palette Index + GB_IO_BGPD = 0x69, // CGB Mode Only - Background Palette Data + GB_IO_OBPI = 0x6a, // CGB Mode Only - Sprite Palette Index + GB_IO_OBPD = 0x6b, // CGB Mode Only - Sprite Palette Data + + // 1 is written for DMG ROMs on a CGB. Does not appear to have an effect. + GB_IO_DMG_EMULATION_INDICATION = 0x6c, // (FEh) Bit 0 (Read/Write) + + /* Missing */ + + GB_IO_SVBK = 0x70, // CGB Mode Only - WRAM Bank + GB_IO_UNKNOWN2 = 0x72, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN3 = 0x73, // (00h) - Bit 0-7 (Read/Write) + GB_IO_UNKNOWN4 = 0x74, // (00h) - Bit 0-7 (Read/Write) - CGB Mode Only + GB_IO_UNKNOWN5 = 0x75, // (8Fh) - Bit 4-6 (Read/Write) + GB_IO_PCM_12 = 0x76, // Channels 1 and 2 amplitudes + GB_IO_PCM_34 = 0x77, // Channels 3 and 4 amplitudes + GB_IO_UNKNOWN8 = 0x7F, // Unknown, write only +}; + +typedef enum { + GB_LOG_BOLD = 1, + GB_LOG_DASHED_UNDERLINE = 2, + GB_LOG_UNDERLINE = 4, + GB_LOG_UNDERLINE_MASK = GB_LOG_DASHED_UNDERLINE | GB_LOG_UNDERLINE +} GB_log_attributes; + +#ifdef GB_INTERNAL +#define LCDC_PERIOD 70224 +#define CPU_FREQUENCY 0x400000 +#define DIV_CYCLES (0x100) +#define INTERNAL_DIV_CYCLES (0x40000) +#define FRAME_LENGTH 16742706 // in nanoseconds +#endif + +typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); +typedef void (*GB_input_callback_t)(GB_gameboy_t *gb); +typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); +typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update); +typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on); +typedef void (*GB_serial_transfer_start_callback_t)(GB_gameboy_t *gb, uint8_t byte_to_send); +typedef uint8_t (*GB_serial_transfer_end_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_sample_callback_t)(GB_gameboy_t *gb, GB_sample_t sample, uint64_t clock); +typedef void (*GB_scanline_callback_t)(uint8_t lcdc); + +typedef struct { + bool state; + long delay; +} GB_ir_queue_item_t; + +/* When state saving, each section is dumped independently of other sections. + This allows adding data to the end of the section without worrying about future compatibility. + Some other changes might be "safe" as well. + This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64 + bit platforms. */ + +/* We make sure bool is 1 for cross-platform save state compatibility. */ +/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */ +_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1"); + +#ifdef GB_INTERNAL +struct GB_gameboy_s { +#else +struct GB_gameboy_internal_s { +#endif + /* The magic makes sure a state file is: + - Indeed a SameBoy state file. + - Has the same endianess has the current platform. */ + volatile uint32_t magic; + /* The version field makes sure we don't load save state files with a completely different structure. + This happens when struct fields are removed/resized in an backward incompatible manner. */ + uint32_t version; + + /* Registers */ + uint16_t pc; + union { + uint16_t registers[GB_REGISTERS_16_BIT]; + struct { + uint16_t af, + bc, + de, + hl, + sp; + }; + struct { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t a, f, + b, c, + d, e, + h, l; +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint8_t f, a, + c, b, + e, d, + l, h; +#else +#error Unable to detect endianess +#endif + }; + + }; + uint8_t ime; + uint8_t interrupt_enable; + uint8_t cgb_ram_bank; + + /* CPU and General Hardware Flags*/ + bool is_sgb; + bool cgb_mode; + bool is_cgb; + bool cgb_double_speed; + bool halted; + bool stopped; + bool boot_rom_finished; + bool ime_toggle; /* ei (and di in CGB) have delayed effects.*/ + bool halt_bug; + + /* Misc state */ + bool infrared_input; + GB_printer_t printer; + + /* DMA and HDMA */ + bool hdma_on; + bool hdma_on_hblank; + uint8_t hdma_steps_left; + uint16_t hdma_cycles; + uint16_t hdma_current_src, hdma_current_dest; + + uint8_t dma_steps_left; + uint8_t dma_current_dest; + uint16_t dma_current_src; + int16_t dma_cycles; + bool is_dma_restarting; + + /* MBC */ + uint16_t mbc_rom_bank; + uint8_t mbc_ram_bank; + uint32_t mbc_ram_size; + bool mbc_ram_enable; + union { + struct { + uint8_t bank_low:5; + uint8_t bank_high:2; + uint8_t padding:1; // Save state compatibility with 0.9 + uint8_t mode:1; + } mbc1; + + struct { + uint8_t rom_bank:4; + } mbc2; + + struct { + uint8_t rom_bank:7; + uint8_t padding:1; + uint8_t ram_bank:4; + } mbc3; + + struct { + uint8_t rom_bank_low; + uint8_t rom_bank_high:1; + uint8_t ram_bank:4; + } mbc5; + + struct { + uint8_t bank_low:6; + uint8_t bank_high:3; + uint8_t mode:1; + } huc1; + + struct { + uint8_t rom_bank; + uint8_t ram_bank; + } huc3; + }; + uint16_t mbc_rom0_bank; /* For some MBC1 wirings. */ + bool camera_registers_mapped; + uint8_t camera_registers[0x36]; + bool rumble_state; + + + /* HRAM and HW Registers */ + uint8_t hram[0xFFFF - 0xFF80]; + uint8_t io_registers[0x80]; + + /* Timing */ + uint32_t display_cycles; + uint32_t div_cycles; + uint8_t tima_reload_state; /* After TIMA overflows, it becomes 0 for 4 cycles before actually reloading. */ + uint16_t serial_cycles; /* This field changed its meaning in v0.10 */ + uint16_t serial_length; + + /* APU */ + GB_apu_t apu; + + /* RTC */ + union { + struct { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t days; + uint8_t high; + }; + uint8_t data[5]; + } rtc_real, rtc_latched; + time_t last_rtc_second; + bool rtc_latch; + + /* Video Display */ + uint32_t vram_size; // Different between CGB and DMG + uint8_t cgb_vram_bank; + uint8_t oam[0xA0]; + uint8_t background_palettes_data[0x40]; + uint8_t sprite_palettes_data[0x40]; + uint32_t background_palettes_rgb[0x20]; + uint32_t sprite_palettes_rgb[0x20]; + int16_t previous_lcdc_x; + bool stat_interrupt_line; + uint8_t effective_scx; + uint8_t current_window_line; + /* The LCDC will skip the first frame it renders after turning it on. + On the CGB, a frame is not skipped if the previous frame was skipped as well. + See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ + enum { + GB_FRAMESKIP_LCD_TURNED_ON, // On a DMG, the LCD renders a blank screen during this state, + // on a CGB, the previous frame is repeated (which might be + // blank if the LCD was off for more than a few cycles) + GB_FRAMESKIP_FIRST_FRAME_SKIPPED, // This state is 'skipped' when emulating a DMG + GB_FRAMESKIP_SECOND_FRAME_RENDERED, + } frame_skip_state; + bool first_scanline; // The very first scan line after turning the LCD behaves differently. + bool oam_read_blocked; + bool vram_read_blocked; + bool oam_write_blocked; + bool vram_write_blocked; + + /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ + /* This data is reserved on reset and must come last in the struct */ + /* ROM */ + uint8_t *rom; + uint32_t rom_size; + const GB_cartridge_t *cartridge_type; + enum { + GB_STANDARD_MBC1_WIRING, + GB_MBC1M_WIRING, + } mbc1_wiring; + + /* Various RAMs */ + uint8_t *ram; + uint8_t *vram; + uint8_t *mbc_ram; + + /* I/O */ + uint32_t *screen; + int keys; + bool lagged; + + /* Timing */ + uint64_t cycles_since_epoch; + + /* Callbacks */ + void *user_data; + GB_log_callback_t log_callback; + GB_input_callback_t input_callback; + GB_rgb_encode_callback_t rgb_encode_callback; + GB_vblank_callback_t vblank_callback; + GB_infrared_callback_t infrared_callback; + GB_camera_get_pixel_callback_t camera_get_pixel_callback; + GB_camera_update_request_callback_t camera_update_request_callback; + GB_rumble_callback_t rumble_callback; + GB_serial_transfer_start_callback_t serial_transfer_start_callback; + GB_serial_transfer_end_callback_t serial_transfer_end_callback; + GB_sample_callback_t sample_callback; + + GB_scanline_callback_t scanline_callback; + int scanline_callback_ly; + + /* IR */ + long cycles_since_ir_change; + long cycles_since_input_ir_change; + GB_ir_queue_item_t ir_queue[GB_MAX_IR_QUEUE]; + size_t ir_queue_length; + + /* Breakpoints */ + uint16_t n_breakpoints; + struct GB_breakpoint_s *breakpoints; + + /* SLD (Todo: merge with backtrace) */ + bool stack_leak_detection; + int debug_call_depth; + uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */ + uint16_t addr_for_call_depth[0x200]; + + /* Backtrace */ + unsigned int backtrace_size; + uint16_t backtrace_sps[0x200]; + struct { + uint16_t bank; + uint16_t addr; + } backtrace_returns[0x200]; + + /* Misc */ + bool disable_rendering; + uint32_t ram_size; // Different between CGB and DMG + uint8_t boot_rom[0x900]; + bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank + + int64_t frontend_rtc_time; +}; + +#ifndef GB_INTERNAL +struct GB_gameboy_s { + char __internal[sizeof(struct GB_gameboy_internal_s)]; +}; +#endif + + +#ifndef __printflike +/* Missing from Linux headers. */ +#define __printflike(fmtarg, firstvararg) \ +__attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#endif + +void GB_init(GB_gameboy_t *gb); +void GB_init_sgb(GB_gameboy_t *gb); +void GB_init_cgb(GB_gameboy_t *gb); +bool GB_is_inited(GB_gameboy_t *gb); +bool GB_is_cgb(GB_gameboy_t *gb); +void GB_free(GB_gameboy_t *gb); +void GB_reset(GB_gameboy_t *gb); +void GB_switch_model_and_reset(GB_gameboy_t *gb, bool is_cgb); +void GB_run(GB_gameboy_t *gb); +uint64_t GB_run_cycles(GB_gameboy_t *gb, uint32_t cycles); +uint64_t GB_epoch(GB_gameboy_t *gb); + +typedef enum { + GB_DIRECT_ACCESS_ROM, + GB_DIRECT_ACCESS_RAM, + GB_DIRECT_ACCESS_CART_RAM, + GB_DIRECT_ACCESS_VRAM, + GB_DIRECT_ACCESS_HRAM, + GB_DIRECT_ACCESS_IO, /* Warning: Some registers can only be read/written correctly via GB_memory_read/write. */ + GB_DIRECT_ACCESS_BOOTROM, + GB_DIRECT_ACCESS_OAM, + GB_DIRECT_ACCESS_BGP, + GB_DIRECT_ACCESS_OBP, +} GB_direct_access_t; + +/* Returns a mutable pointer to various hardware memories. If that memory is banked, the current bank + is returned at *bank, even if only a portion of the memory is banked. */ +void *GB_get_direct_access(GB_gameboy_t *gb, GB_direct_access_t access, size_t *size, uint16_t *bank); + +void *GB_get_user_data(GB_gameboy_t *gb); +void GB_set_user_data(GB_gameboy_t *gb, void *data); + +int GB_load_boot_rom(GB_gameboy_t *gb, const char *path); +int GB_load_rom(GB_gameboy_t *gb, const char *path); + +int GB_save_battery(GB_gameboy_t *gb, const char *path); +void GB_load_battery(GB_gameboy_t *gb, const char *path); + +void GB_set_rendering_disabled(GB_gameboy_t *gb, bool disabled); + +void GB_log(GB_gameboy_t *gb, const char *fmt, ...) __printflike(2, 3); +void GB_attributed_log(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, ...) __printflike(3, 4); + +void GB_set_pixels_output(GB_gameboy_t *gb, uint32_t *output); + +void GB_set_infrared_input(GB_gameboy_t *gb, bool state); +void GB_queue_infrared_input(GB_gameboy_t *gb, bool state, long cycles_after_previous_change); + +void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); +void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); +void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); +void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); +void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); +void GB_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback); + +/* These APIs are used when using internal clock */ +void GB_set_serial_transfer_start_callback(GB_gameboy_t *gb, GB_serial_transfer_start_callback_t callback); +void GB_set_serial_transfer_end_callback(GB_gameboy_t *gb, GB_serial_transfer_end_callback_t callback); + +/* These APIs are used when using external clock */ +uint8_t GB_serial_get_data(GB_gameboy_t *gb); +void GB_serial_set_data(GB_gameboy_t *gb, uint8_t data); + +void GB_disconnect_serial(GB_gameboy_t *gb); + +void GB_set_lagged(GB_gameboy_t *gb, bool lagged); +bool GB_get_lagged(GB_gameboy_t *gb); + +#endif /* GB_h */ diff --git a/waterbox/sameboy/gb_struct_def.h b/waterbox/sameboy/gb_struct_def.h new file mode 100644 index 0000000000..0e0ebd12ee --- /dev/null +++ b/waterbox/sameboy/gb_struct_def.h @@ -0,0 +1,5 @@ +#ifndef gb_struct_def_h +#define gb_struct_def_h +struct GB_gameboy_s; +typedef struct GB_gameboy_s GB_gameboy_t; +#endif diff --git a/waterbox/sameboy/joypad.c b/waterbox/sameboy/joypad.c new file mode 100644 index 0000000000..0cad7ab084 --- /dev/null +++ b/waterbox/sameboy/joypad.c @@ -0,0 +1,48 @@ +#include +#include "gb.h" +#include + +void GB_update_joyp(GB_gameboy_t *gb) +{ + uint8_t key_selection = 0; + uint8_t previous_state = 0; + + /* Todo: add delay to key selection */ + previous_state = gb->io_registers[GB_IO_JOYP] & 0xF; + key_selection = gb->io_registers[GB_IO_JOYP] >> 4 & 3; + gb->io_registers[GB_IO_JOYP] &= 0xF0; + switch (key_selection) { + case 3: + /* Nothing is wired, all up */ + gb->io_registers[GB_IO_JOYP] |= 0x0F; + break; + + case 2: + /* Direction keys */ + gb->io_registers[GB_IO_JOYP] |= ~gb->keys >> 4 & 0xf; + break; + + case 1: + /* Other keys */ + gb->io_registers[GB_IO_JOYP] |= ~gb->keys & 0xf; + break; + + case 0: + /* Todo: verifiy this is correct */ + gb->io_registers[GB_IO_JOYP] |= ~(gb->keys >> 4 & gb->keys) & 0xf; + break; + + default: + break; + } + if (previous_state != (gb->io_registers[GB_IO_JOYP] & 0xF)) { + /* Todo: disable when emulating CGB */ + gb->io_registers[GB_IO_IF] |= 0x10; + } + gb->io_registers[GB_IO_JOYP] |= 0xC0; // No SGB support +} + +void GB_set_key_state(GB_gameboy_t *gb, int keys) +{ + gb->keys = keys; +} diff --git a/waterbox/sameboy/joypad.h b/waterbox/sameboy/joypad.h new file mode 100644 index 0000000000..f7ffdfdec4 --- /dev/null +++ b/waterbox/sameboy/joypad.h @@ -0,0 +1,10 @@ +#ifndef joypad_h +#define joypad_h +#include "gb_struct_def.h" + +void GB_set_key_state(GB_gameboy_t *gb, int keys); + +#ifdef GB_INTERNAL +void GB_update_joyp(GB_gameboy_t *gb); +#endif +#endif /* joypad_h */ diff --git a/waterbox/sameboy/mbc.c b/waterbox/sameboy/mbc.c new file mode 100644 index 0000000000..d3791a180f --- /dev/null +++ b/waterbox/sameboy/mbc.c @@ -0,0 +1,154 @@ +#include +#include +#include +#include "gb.h" + +const GB_cartridge_t GB_cart_defs[256] = { + // From http://gbdev.gg8.se/wiki/articles/The_Cartridge_Header#0147_-_Cartridge_Type + /* MBC SUBTYPE RAM BAT. RTC RUMB. */ + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 00h ROM ONLY + { GB_MBC1 , GB_STANDARD_MBC, false, false, false, false}, // 01h MBC1 + { GB_MBC1 , GB_STANDARD_MBC, true , false, false, false}, // 02h MBC1+RAM + { GB_MBC1 , GB_STANDARD_MBC, true , true , false, false}, // 03h MBC1+RAM+BATTERY + [5] = + { GB_MBC2 , GB_STANDARD_MBC, true , false, false, false}, // 05h MBC2 + { GB_MBC2 , GB_STANDARD_MBC, true , true , false, false}, // 06h MBC2+BATTERY + [8] = + { GB_NO_MBC, GB_STANDARD_MBC, true , false, false, false}, // 08h ROM+RAM + { GB_NO_MBC, GB_STANDARD_MBC, true , true , false, false}, // 09h ROM+RAM+BATTERY + [0xB] = + /* Todo: Not supported yet */ + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Bh MMM01 + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Ch MMM01+RAM + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // 0Dh MMM01+RAM+BATTERY + [0xF] = + { GB_MBC3 , GB_STANDARD_MBC, false, true, true , false}, // 0Fh MBC3+TIMER+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, true , true, true , false}, // 10h MBC3+TIMER+RAM+BATTERY + { GB_MBC3 , GB_STANDARD_MBC, false, false, false, false}, // 11h MBC3 + { GB_MBC3 , GB_STANDARD_MBC, true , false, false, false}, // 12h MBC3+RAM + { GB_MBC3 , GB_STANDARD_MBC, true , true , false, false}, // 13h MBC3+RAM+BATTERY + [0x19] = + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, false}, // 19h MBC5 + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, false}, // 1Ah MBC5+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, false}, // 1Bh MBC5+RAM+BATTERY + { GB_MBC5 , GB_STANDARD_MBC, false, false, false, true }, // 1Ch MBC5+RUMBLE + { GB_MBC5 , GB_STANDARD_MBC, true , false, false, true }, // 1Dh MBC5+RUMBLE+RAM + { GB_MBC5 , GB_STANDARD_MBC, true , true , false, true }, // 1Eh MBC5+RUMBLE+RAM+BATTERY + [0xFC] = + { GB_MBC5 , GB_CAMERA , true , true , false, false}, // FCh POCKET CAMERA + { GB_NO_MBC, GB_STANDARD_MBC, false, false, false, false}, // FDh BANDAI TAMA5 (Todo: Not supported) + { GB_HUC3 , GB_STANDARD_MBC, true , true , false, false}, // FEh HuC3 (Todo: Mapper support only) + { GB_HUC1 , GB_STANDARD_MBC, true , true , false, false}, // FFh HuC1+RAM+BATTERY (Todo: No IR bindings) +}; + +void GB_update_mbc_mappings(GB_gameboy_t *gb) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (gb->mbc1_wiring) { + case GB_STANDARD_MBC1_WIRING: + gb->mbc_rom_bank = gb->mbc1.bank_low | (gb->mbc1.bank_high << 5); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_ram_bank = gb->mbc1.bank_high; + gb->mbc_rom0_bank = gb->mbc1.bank_high << 5; + } + if ((gb->mbc_rom_bank & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + case GB_MBC1M_WIRING: + gb->mbc_rom_bank = (gb->mbc1.bank_low & 0xF) | (gb->mbc1.bank_high << 4); + if (gb->mbc1.mode == 0) { + gb->mbc_ram_bank = 0; + gb->mbc_rom0_bank = 0; + } + else { + gb->mbc_rom0_bank = gb->mbc1.bank_high << 4; + gb->mbc_ram_bank = 0; + } + if ((gb->mbc1.bank_low & 0x1F) == 0) { + gb->mbc_rom_bank++; + } + break; + } + break; + case GB_MBC2: + gb->mbc_rom_bank = gb->mbc2.rom_bank; + if ((gb->mbc_rom_bank & 0xF) == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC3: + gb->mbc_rom_bank = gb->mbc3.rom_bank; + gb->mbc_ram_bank = gb->mbc3.ram_bank; + if (gb->mbc_rom_bank == 0) { + gb->mbc_rom_bank = 1; + } + break; + case GB_MBC5: + gb->mbc_rom_bank = gb->mbc5.rom_bank_low | (gb->mbc5.rom_bank_high << 8); + gb->mbc_ram_bank = gb->mbc5.ram_bank; + break; + case GB_HUC1: + if (gb->huc1.mode == 0) { + gb->mbc_rom_bank = gb->huc1.bank_low | (gb->mbc1.bank_high << 6); + gb->mbc_ram_bank = 0; + } + else { + gb->mbc_rom_bank = gb->huc1.bank_low; + gb->mbc_ram_bank = gb->huc1.bank_high; + } + break; + case GB_HUC3: + gb->mbc_rom_bank = gb->huc3.rom_bank; + gb->mbc_ram_bank = gb->huc3.ram_bank; + break; + } +} + +void GB_configure_cart(GB_gameboy_t *gb) +{ + gb->cartridge_type = &GB_cart_defs[gb->rom[0x147]]; + + if (gb->rom[0x147] == 0 && gb->rom_size > 0x8000) { + GB_log(gb, "ROM header reports no MBC, but file size is over 32Kb. Assuming cartridge uses MBC3.\n"); + gb->cartridge_type = &GB_cart_defs[0x11]; + } + else if (gb->rom[0x147] != 0 && memcmp(gb->cartridge_type, &GB_cart_defs[0], sizeof(GB_cart_defs[0])) == 0) { + GB_log(gb, "Cartridge type %02x is not yet supported.\n", gb->rom[0x147]); + } + + if (gb->cartridge_type->has_ram) { + if (gb->cartridge_type->mbc_type == GB_MBC2) { + gb->mbc_ram_size = 0x200; + } + else { + static const int ram_sizes[256] = {0, 0x800, 0x2000, 0x8000, 0x20000, 0x10000}; + gb->mbc_ram_size = ram_sizes[gb->rom[0x149]]; + } + gb->mbc_ram = malloc(gb->mbc_ram_size); + + /* Todo: Some games assume unintialized MBC RAM is 0xFF. It this true for all cartridges types? */ + memset(gb->mbc_ram, 0xFF, gb->mbc_ram_size); + } + + /* MBC1 has at least 3 types of wiring (We currently support two (Standard and 4bit-MBC1M) of these). + See http://forums.nesdev.com/viewtopic.php?f=20&t=14099 */ + + /* Attempt to "guess" wiring */ + if (gb->cartridge_type->mbc_type == GB_MBC1) { + if (gb->rom_size >= 0x44000 && memcmp(gb->rom + 0x104, gb->rom + 0x40104, 0x30) == 0) { + gb->mbc1_wiring = GB_MBC1M_WIRING; + } + } + + /* Set MBC5's bank to 1 correctly */ + if (gb->cartridge_type->mbc_type == GB_MBC5) { + gb->mbc5.rom_bank_low = 1; + } +} diff --git a/waterbox/sameboy/mbc.h b/waterbox/sameboy/mbc.h new file mode 100644 index 0000000000..e7260f5643 --- /dev/null +++ b/waterbox/sameboy/mbc.h @@ -0,0 +1,31 @@ +#ifndef MBC_h +#define MBC_h +#include "gb_struct_def.h" + +typedef struct { + enum { + GB_NO_MBC, + GB_MBC1, + GB_MBC2, + GB_MBC3, + GB_MBC5, + GB_HUC1, /* Todo: HUC1 features are not emulated. Should be unified with the CGB IR sensor API. */ + GB_HUC3, + } mbc_type; + enum { + GB_STANDARD_MBC, + GB_CAMERA, + } mbc_subtype; + bool has_ram; + bool has_battery; + bool has_rtc; + bool has_rumble; +} GB_cartridge_t; + +#ifdef GB_INTERNAL +extern const GB_cartridge_t GB_cart_defs[256]; +void GB_update_mbc_mappings(GB_gameboy_t *gb); +void GB_configure_cart(GB_gameboy_t *gb); +#endif + +#endif /* MBC_h */ diff --git a/waterbox/sameboy/memory.c b/waterbox/sameboy/memory.c new file mode 100644 index 0000000000..f53aac608d --- /dev/null +++ b/waterbox/sameboy/memory.c @@ -0,0 +1,731 @@ +#include +#include +#include "gb.h" +#include "sgb.h" + +typedef uint8_t GB_read_function_t(GB_gameboy_t *gb, uint16_t addr); +typedef void GB_write_function_t(GB_gameboy_t *gb, uint16_t addr, uint8_t value); + +typedef enum { + GB_BUS_MAIN, /* In DMG: Cart and RAM. In CGB: Cart only */ + GB_BUS_RAM, /* In CGB only. */ + GB_BUS_VRAM, + GB_BUS_INTERNAL, /* Anything in highram. Might not be the most correct name. */ +} GB_bus_t; + +static GB_bus_t bus_for_addr(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x8000) { + return GB_BUS_MAIN; + } + if (addr < 0xA000) { + return GB_BUS_VRAM; + } + if (addr < 0xC000) { + return GB_BUS_MAIN; + } + if (addr < 0xFE00) { + return gb->is_cgb? GB_BUS_RAM : GB_BUS_MAIN; + } + return GB_BUS_INTERNAL; +} + +static bool is_addr_in_dma_use(GB_gameboy_t *gb, uint16_t addr) +{ + if (!gb->dma_steps_left || (gb->dma_cycles < 0 && !gb->is_dma_restarting)) return false; + return bus_for_addr(gb, addr) == bus_for_addr(gb, gb->dma_current_src); +} + +static uint8_t read_rom(GB_gameboy_t *gb, uint16_t addr) +{ + if (addr < 0x100 && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (addr >= 0x200 && addr < 0x900 && gb->is_cgb && !gb->boot_rom_finished) { + return gb->boot_rom[addr]; + } + + if (!gb->rom_size) { + return 0xFF; + } + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom0_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_mbc_rom(GB_gameboy_t *gb, uint16_t addr) +{ + unsigned int effective_address = (addr & 0x3FFF) + gb->mbc_rom_bank * 0x4000; + return gb->rom[effective_address & (gb->rom_size - 1)]; +} + +static uint8_t read_vram(GB_gameboy_t *gb, uint16_t addr) +{ + if (gb->vram_read_blocked) { + return 0xFF; + } + return gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000]; +} + +static uint8_t read_mbc_ram(GB_gameboy_t *gb, uint16_t addr) +{ + if ((!gb->mbc_ram_enable || !gb->mbc_ram_size) && + gb->cartridge_type->mbc_subtype != GB_CAMERA && + gb->cartridge_type->mbc_type != GB_HUC1) return 0xFF; + + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC read */ + gb->rtc_latched.high |= ~0xC1; /* Not all bytes in RTC high are used. */ + return gb->rtc_latched.data[gb->mbc_ram_bank - 8]; + } + + if (gb->camera_registers_mapped) { + return GB_camera_read_register(gb, addr); + } + + if (!gb->mbc_ram) { + return 0xFF; + } + + if (gb->cartridge_type->mbc_subtype == GB_CAMERA && gb->mbc_ram_bank == 0 && addr >= 0xa100 && addr < 0xaf00) { + return GB_camera_read_image(gb, addr - 0xa100); + } + + uint8_t ret = gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)]; + if (gb->cartridge_type->mbc_type == GB_MBC2) { + ret |= 0xF0; + } + return ret; +} + +static uint8_t read_ram(GB_gameboy_t *gb, uint16_t addr) +{ + return gb->ram[addr & 0x0FFF]; +} + +static uint8_t read_banked_ram(GB_gameboy_t *gb, uint16_t addr) +{ + return gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000]; +} + +static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr) +{ + + if (addr < 0xFE00) { + return gb->ram[addr & 0x0FFF]; + } + + if (addr < 0xFEA0) { + if (gb->oam_read_blocked || (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + return 0xFF; + } + return gb->oam[addr & 0xFF]; + } + + if (addr < 0xFF00) { + /* Unusable. CGB results are verified, but DMG results were tested on a SGB2 */ + if ((gb->io_registers[GB_IO_STAT] & 0x3) >= 2) { /* Seems to be disabled in Modes 2 and 3 */ + return 0xFF; + } + if (gb->is_cgb) { + return (addr & 0xF0) | ((addr >> 4) & 0xF); + } + return 0; + + } + + if (addr < 0xFF80) { + if (addr == 0xff00 && gb->is_sgb) + { + if (gb->input_callback) + gb->input_callback(gb); + gb->lagged = false; + return sgb_read_ff00(gb->cycles_since_epoch); + } + + switch (addr & 0xFF) { + case GB_IO_IF: + return gb->io_registers[GB_IO_IF] | 0xE0; + case GB_IO_TAC: + return gb->io_registers[GB_IO_TAC] | 0xF8; + case GB_IO_STAT: + return gb->io_registers[GB_IO_STAT] | 0x80; + case GB_IO_DMG_EMULATION_INDICATION: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->io_registers[GB_IO_DMG_EMULATION_INDICATION] | 0xFE; + + case GB_IO_PCM_12: + case GB_IO_PCM_34: + { + if (!gb->is_cgb) return 0xFF; + GB_sample_t dummy; + GB_apu_get_samples_and_update_pcm_regs(gb, &dummy); + } + /* Fall through */ + case GB_IO_JOYP: + case GB_IO_TMA: + case GB_IO_LCDC: + case GB_IO_SCY: + case GB_IO_SCX: + case GB_IO_LY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_SC: + case GB_IO_SB: + return gb->io_registers[addr & 0xFF]; + case GB_IO_TIMA: + if (gb->tima_reload_state == GB_TIMA_RELOADING) { + return 0; + } + return gb->io_registers[GB_IO_TIMA]; + case GB_IO_DIV: + return gb->div_cycles >> 8; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return 0xFF; + return ((gb->hdma_on || gb->hdma_on_hblank)? 0 : 0x80) | ((gb->hdma_steps_left - 1) & 0x7F); + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return 0xFF; + } + return gb->cgb_ram_bank | ~0x7; + case GB_IO_VBK: + if (!gb->is_cgb) { + return 0xFF; + } + return gb->cgb_vram_bank | ~0x1; + + /* Todo: It seems that a CGB in DMG mode can access BGPI and OBPI, but not BGPD and OBPD? */ + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!gb->is_cgb) { + return 0xFF; + } + return gb->io_registers[addr & 0xFF] | 0x40; + + case GB_IO_BGPD: + case GB_IO_OBPD: + { + if (!gb->cgb_mode && gb->boot_rom_finished) { + return 0xFF; + } + uint8_t index_reg = (addr & 0xFF) - 1; + return ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F]; + } + + case GB_IO_KEY1: + if (!gb->cgb_mode) { + return 0xFF; + } + return (gb->io_registers[GB_IO_KEY1] & 0x7F) | (gb->cgb_double_speed? 0xFE : 0x7E); + + case GB_IO_RP: { + if (!gb->cgb_mode) return 0xFF; + /* You will read your own IR LED if it's on. */ + bool read_value = gb->infrared_input || (gb->io_registers[GB_IO_RP] & 1); + uint8_t ret = (gb->io_registers[GB_IO_RP] & 0xC1) | 0x3C; + if ((gb->io_registers[GB_IO_RP] & 0xC0) == 0xC0 && read_value) { + ret |= 2; + } + return ret; + } + case GB_IO_DMA: + /* Todo: is this documented? */ + return gb->is_cgb? 0x00 : 0xFF; + case GB_IO_UNKNOWN2: + case GB_IO_UNKNOWN3: + return gb->is_cgb? gb->io_registers[addr & 0xFF] : 0xFF; + case GB_IO_UNKNOWN4: + return gb->cgb_mode? gb->io_registers[addr & 0xFF] : 0xFF; + case GB_IO_UNKNOWN5: + return gb->is_cgb? gb->io_registers[addr & 0xFF] | 0x8F : 0xFF; + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + return GB_apu_read(gb, addr & 0xFF); + } + return 0xFF; + } + /* Hardware registers */ + return 0; + } + + if (addr == 0xFFFF) { + /* Interrupt Mask */ + return gb->interrupt_enable; + } + + /* HRAM */ + return gb->hram[addr - 0xFF80]; +} + +static GB_read_function_t * const read_map[] = +{ + read_rom, read_rom, read_rom, read_rom, /* 0XXX, 1XXX, 2XXX, 3XXX */ + read_mbc_rom, read_mbc_rom, read_mbc_rom, read_mbc_rom, /* 4XXX, 5XXX, 6XXX, 7XXX */ + read_vram, read_vram, /* 8XXX, 9XXX */ + read_mbc_ram, read_mbc_ram, /* AXXX, BXXX */ + read_ram, read_banked_ram, /* CXXX, DXXX */ + read_high_memory, read_high_memory, /* EXXX FXXX */ +}; + +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) +{ + if (is_addr_in_dma_use(gb, addr)) { + addr = gb->dma_current_src; + } + return read_map[addr >> 12](gb, addr); +} + +static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + switch (gb->cartridge_type->mbc_type) { + case GB_NO_MBC: return; + case GB_MBC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->mbc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->mbc1.mode = value; break; + } + break; + case GB_MBC2: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: if (!(addr & 0x100)) gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: if ( addr & 0x100) gb->mbc2.rom_bank = value; break; + } + break; + case GB_MBC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->mbc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->mbc3.ram_bank = value; break; + case 0x6000: case 0x7000: + if (!gb->rtc_latch && (value & 1)) { /* Todo: verify condition is correct */ + memcpy(&gb->rtc_latched, &gb->rtc_real, sizeof(gb->rtc_real)); + } + gb->rtc_latch = value & 1; + break; + } + break; + case GB_MBC5: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: gb->mbc5.rom_bank_low = value; break; + case 0x3000: gb->mbc5.rom_bank_high = value; break; + case 0x4000: case 0x5000: + if (gb->cartridge_type->has_rumble) { + if (!!(value & 8) != gb->rumble_state) { + gb->rumble_state = !gb->rumble_state; + if (gb->rumble_callback) { + gb->rumble_callback(gb, gb->rumble_state); + } + } + value &= 7; + } + gb->mbc5.ram_bank = value; + gb->camera_registers_mapped = (value & 0x10) && gb->cartridge_type->mbc_subtype == GB_CAMERA; + break; + } + break; + case GB_HUC1: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->huc1.bank_low = value; break; + case 0x4000: case 0x5000: gb->huc1.bank_high = value; break; + case 0x6000: case 0x7000: gb->huc1.mode = value; break; + } + break; + case GB_HUC3: + switch (addr & 0xF000) { + case 0x0000: case 0x1000: gb->mbc_ram_enable = (value & 0xF) == 0xA; break; + case 0x2000: case 0x3000: gb->huc3.rom_bank = value; break; + case 0x4000: case 0x5000: gb->huc3.ram_bank = value; break; + } + break; + } + GB_update_mbc_mappings(gb); +} + +static void write_vram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->vram_write_blocked) { + //GB_log(gb, "Wrote %02x to %04x (VRAM) during mode 3\n", value, addr); + return; + } + gb->vram[(addr & 0x1FFF) + (uint16_t) gb->cgb_vram_bank * 0x2000] = value; +} + +static void write_mbc_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (gb->camera_registers_mapped) { + GB_camera_write_register(gb, addr, value); + return; + } + + if (!gb->mbc_ram_enable || !gb->mbc_ram_size) return; + + if (gb->cartridge_type->has_rtc && gb->mbc_ram_bank >= 8 && gb->mbc_ram_bank <= 0xC) { + /* RTC read */ + gb->rtc_latched.data[gb->mbc_ram_bank - 8] = gb->rtc_real.data[gb->mbc_ram_bank - 8] = value; /* Todo: does it really write both? */ + } + + if (!gb->mbc_ram) { + return; + } + + gb->mbc_ram[((addr & 0x1FFF) + gb->mbc_ram_bank * 0x2000) & (gb->mbc_ram_size - 1)] = value; +} + +static void write_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[addr & 0x0FFF] = value; +} + +static void write_banked_ram(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + gb->ram[(addr & 0x0FFF) + gb->cgb_ram_bank * 0x1000] = value; +} + +static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (addr < 0xFE00) { + GB_log(gb, "Wrote %02x to %04x (RAM Mirror)\n", value, addr); + gb->ram[addr & 0x0FFF] = value; + return; + } + + if (addr < 0xFEA0) { + if (gb->oam_write_blocked|| (gb->dma_steps_left && (gb->dma_cycles > 0 || gb->is_dma_restarting))) { + return; + } + gb->oam[addr & 0xFF] = value; + return; + } + + if (addr < 0xFF00) { + GB_log(gb, "Wrote %02x to %04x (Unused)\n", value, addr); + return; + } + + if (addr < 0xFF80) { + if (addr == 0xff00 && gb->is_sgb) + { + sgb_write_ff00(value, gb->cycles_since_epoch); + return; + } + + /* Hardware registers */ + switch (addr & 0xFF) { + + case GB_IO_SCX: + case GB_IO_IF: + case GB_IO_SCY: + case GB_IO_LYC: + case GB_IO_BGP: + case GB_IO_OBP0: + case GB_IO_OBP1: + case GB_IO_WY: + case GB_IO_WX: + case GB_IO_SB: + case GB_IO_DMG_EMULATION_INDICATION: + case GB_IO_UNKNOWN2: + case GB_IO_UNKNOWN3: + case GB_IO_UNKNOWN4: + case GB_IO_UNKNOWN5: + gb->io_registers[addr & 0xFF] = value; + return; + + case GB_IO_TIMA: + if (gb->tima_reload_state != GB_TIMA_RELOADED) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TMA: + gb->io_registers[GB_IO_TMA] = value; + if (gb->tima_reload_state != GB_TIMA_RUNNING) { + gb->io_registers[GB_IO_TIMA] = value; + } + return; + + case GB_IO_TAC: + GB_emulate_timer_glitch(gb, gb->io_registers[GB_IO_TAC], value); + gb->io_registers[GB_IO_TAC] = value; + return; + + + case GB_IO_LCDC: + if ((value & 0x80) && !(gb->io_registers[GB_IO_LCDC] & 0x80)) { + /* It appears that there's a slight delay after enabling the screen? */ + /* Todo: verify this. */ + gb->display_cycles = 0; + gb->first_scanline = true; + if (gb->frame_skip_state == GB_FRAMESKIP_SECOND_FRAME_RENDERED) { + gb->frame_skip_state = GB_FRAMESKIP_LCD_TURNED_ON; + } + } + gb->io_registers[GB_IO_LCDC] = value; + return; + + case GB_IO_STAT: + /* A DMG bug: http://www.devrs.com/gb/files/faqs.html#GBBugs */ + if (!gb->is_cgb && !gb->stat_interrupt_line && + (gb->io_registers[GB_IO_STAT] & 0x3) < 2 && (gb->io_registers[GB_IO_LCDC] & 0x80)) { + gb->io_registers[GB_IO_IF] |= 2; + } + /* Delete previous R/W bits */ + gb->io_registers[GB_IO_STAT] &= 7; + /* Set them by value */ + gb->io_registers[GB_IO_STAT] |= value & ~7; + /* Set unused bit to 1 */ + gb->io_registers[GB_IO_STAT] |= 0x80; + return; + + case GB_IO_DIV: + GB_set_internal_div_counter(gb, 0); + return; + + case GB_IO_JOYP: + gb->io_registers[GB_IO_JOYP] &= 0x0F; + gb->io_registers[GB_IO_JOYP] |= value & 0xF0; + GB_update_joyp(gb); + return; + + case GB_IO_BIOS: + gb->boot_rom_finished = true; + return; + + case GB_IO_DMG_EMULATION: + if (gb->is_cgb && !gb->boot_rom_finished) { + gb->cgb_mode = value != 4; /* The real "contents" of this register aren't quite known yet. */ + } + return; + + case GB_IO_DMA: + if (value <= 0xE0) { + if (gb->dma_steps_left) { + /* This is not correct emulation, since we're not really delaying the second DMA. + One write that should have happened in the first DMA will not happen. However, + since that byte will be overwritten by the second DMA before it can actually be + read, it doesn't actually matter. */ + gb->is_dma_restarting = true; + } + gb->dma_cycles = -7; + gb->dma_current_dest = 0; + gb->dma_current_src = value << 8; + gb->dma_steps_left = 0xa0; + } + /* else { what? } */ + + return; + case GB_IO_SVBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_ram_bank = value & 0x7; + if (!gb->cgb_ram_bank) { + gb->cgb_ram_bank++; + } + return; + case GB_IO_VBK: + if (!gb->cgb_mode) { + return; + } + gb->cgb_vram_bank = value & 0x1; + return; + + case GB_IO_BGPI: + case GB_IO_OBPI: + if (!gb->is_cgb) { + return; + } + gb->io_registers[addr & 0xFF] = value; + return; + case GB_IO_BGPD: + case GB_IO_OBPD: + if (!gb->cgb_mode && gb->boot_rom_finished) { + /* Todo: Due to the behavior of a broken Game & Watch Gallery 2 ROM on a real CGB. A proper test ROM + is required. */ + return; + } + uint8_t index_reg = (addr & 0xFF) - 1; + ((addr & 0xFF) == GB_IO_BGPD? + gb->background_palettes_data : + gb->sprite_palettes_data)[gb->io_registers[index_reg] & 0x3F] = value; + GB_palette_changed(gb, (addr & 0xFF) == GB_IO_BGPD, gb->io_registers[index_reg] & 0x3F); + if (gb->io_registers[index_reg] & 0x80) { + gb->io_registers[index_reg]++; + gb->io_registers[index_reg] |= 0x80; + } + return; + case GB_IO_KEY1: + if (!gb->is_cgb) { + return; + } + gb->io_registers[GB_IO_KEY1] = value; + return; + case GB_IO_HDMA1: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xF0; + gb->hdma_current_src |= value << 8; + } + return; + case GB_IO_HDMA2: + if (gb->cgb_mode) { + gb->hdma_current_src &= 0xFF00; + gb->hdma_current_src |= value & 0xF0; + } + return; + case GB_IO_HDMA3: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0xF0; + gb->hdma_current_dest |= value << 8; + } + return; + case GB_IO_HDMA4: + if (gb->cgb_mode) { + gb->hdma_current_dest &= 0x1F00; + gb->hdma_current_dest |= value & 0xF0; + } + return; + case GB_IO_HDMA5: + if (!gb->cgb_mode) return; + if ((value & 0x80) == 0 && gb->hdma_on_hblank) { + gb->hdma_on_hblank = false; + return; + } + gb->hdma_on = (value & 0x80) == 0; + gb->hdma_on_hblank = (value & 0x80) != 0; + if (gb->hdma_on_hblank && (gb->io_registers[GB_IO_STAT] & 3) == 0) { + gb->hdma_on = true; + gb->hdma_cycles = 0; + } + gb->io_registers[GB_IO_HDMA5] = value; + gb->hdma_steps_left = (gb->io_registers[GB_IO_HDMA5] & 0x7F) + 1; + /* Todo: Verify this. Gambatte's DMA tests require this. */ + if (gb->hdma_current_dest + (gb->hdma_steps_left << 4) > 0xFFFF) { + gb->hdma_steps_left = (0x10000 - gb->hdma_current_dest) >> 4; + } + gb->hdma_cycles = 0; + return; + + /* Todo: what happens when starting a transfer during a transfer? + What happens when starting a transfer during external clock? + */ + case GB_IO_SC: + if (!gb->cgb_mode) { + value |= 2; + } + gb->io_registers[GB_IO_SC] = value | (~0x83); + if ((value & 0x80) && (value & 0x1) ) { + gb->serial_length = gb->cgb_mode && (value & 2)? 128 : 4096; + /* Todo: This is probably incorrect for CGB's faster clock mode. */ + gb->serial_cycles &= 0xFF; + if (gb->serial_transfer_start_callback) { + gb->serial_transfer_start_callback(gb, gb->io_registers[GB_IO_SB]); + } + } + else { + gb->serial_length = 0; + } + return; + + case GB_IO_RP: { + if (!gb->is_cgb) { + return; + } + if ((value & 1) != (gb->io_registers[GB_IO_RP] & 1)) { + if (gb->infrared_callback) { + gb->infrared_callback(gb, value & 1, gb->cycles_since_ir_change); + gb->cycles_since_ir_change = 0; + } + } + gb->io_registers[GB_IO_RP] = value; + return; + } + + default: + if ((addr & 0xFF) >= GB_IO_NR10 && (addr & 0xFF) <= GB_IO_WAV_END) { + GB_apu_write(gb, addr & 0xFF, value); + return; + } + GB_log(gb, "Wrote %02x to %04x (HW Register)\n", value, addr); + return; + } + } + + if (addr == 0xFFFF) { + /* Interrupt mask */ + gb->interrupt_enable = value; + return; + } + + /* HRAM */ + gb->hram[addr - 0xFF80] = value; +} + + + +static GB_write_function_t * const write_map[] = +{ + write_mbc, write_mbc, write_mbc, write_mbc, /* 0XXX, 1XXX, 2XXX, 3XXX */ + write_mbc, write_mbc, write_mbc, write_mbc, /* 4XXX, 5XXX, 6XXX, 7XXX */ + write_vram, write_vram, /* 8XXX, 9XXX */ + write_mbc_ram, write_mbc_ram, /* AXXX, BXXX */ + write_ram, write_banked_ram, /* CXXX, DXXX */ + write_high_memory, write_high_memory, /* EXXX FXXX */ +}; + +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) +{ + if (is_addr_in_dma_use(gb, addr)) { + /* Todo: What should happen? Will this affect DMA? Will data be written? What and where? */ + return; + } + write_map[addr >> 12](gb, addr, value); +} + +void GB_dma_run(GB_gameboy_t *gb) +{ + while (gb->dma_cycles >= 4 && gb->dma_steps_left) { + /* Todo: measure this value */ + gb->dma_cycles -= 4; + gb->dma_steps_left--; + gb->oam[gb->dma_current_dest++] = GB_read_memory(gb, gb->dma_current_src); + /* dma_current_src must be the correct value during GB_read_memory */ + gb->dma_current_src++; + if (!gb->dma_steps_left) { + gb->is_dma_restarting = false; + } + } +} + +void GB_hdma_run(GB_gameboy_t *gb) +{ + if (!gb->hdma_on) return; + while (gb->hdma_cycles >= 8) { + gb->hdma_cycles -= 8; + + for (uint8_t i = 0; i < 0x10; i++) { + GB_write_memory(gb, 0x8000 | (gb->hdma_current_dest++ & 0x1FFF), GB_read_memory(gb, (gb->hdma_current_src++))); + } + + if(--gb->hdma_steps_left == 0){ + gb->hdma_on = false; + gb->hdma_on_hblank = false; + gb->io_registers[GB_IO_HDMA5] &= 0x7F; + break; + } + if (gb->hdma_on_hblank) { + gb->hdma_on = false; + break; + } + } +} diff --git a/waterbox/sameboy/memory.h b/waterbox/sameboy/memory.h new file mode 100644 index 0000000000..16b3b9220f --- /dev/null +++ b/waterbox/sameboy/memory.h @@ -0,0 +1,12 @@ +#ifndef memory_h +#define memory_h +#include "gb.h" + +uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); +void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); +#ifdef GB_INTERNAL +void GB_dma_run(GB_gameboy_t *gb); +void GB_hdma_run(GB_gameboy_t *gb); +#endif + +#endif /* memory_h */ diff --git a/waterbox/sameboy/printer.c b/waterbox/sameboy/printer.c new file mode 100644 index 0000000000..bc1d45ab47 --- /dev/null +++ b/waterbox/sameboy/printer.c @@ -0,0 +1,201 @@ +#include "gb.h" + +/* TODO: Emulation is VERY basic and assumes the ROM correctly uses the printer's interface. + Incorrect usage is not correctly emulated, as it's not well documented, nor do I + have my own GB Printer to figure it out myself. + + It also does not currently emulate communication timeout, which means that a bug + might prevent the printer operation until the GameBoy is restarted. + + Also, field mask values are assumed. */ + +static void handle_command(GB_gameboy_t *gb) +{ + + switch (gb->printer.command_id) { + case GB_PRINTER_INIT_COMMAND: + gb->printer.status = 0; + gb->printer.image_offset = 0; + break; + + case GB_PRINTER_START_COMMAND: + if (gb->printer.command_length == 4) { + gb->printer.status = 6; /* Printing */ + uint32_t image[gb->printer.image_offset]; + uint8_t palette = gb->printer.command_data[2]; + uint32_t colors[4] = {gb->rgb_encode_callback(gb, 0xff, 0xff, 0xff), + gb->rgb_encode_callback(gb, 0xaa, 0xaa, 0xaa), + gb->rgb_encode_callback(gb, 0x55, 0x55, 0x55), + gb->rgb_encode_callback(gb, 0x00, 0x00, 0x00)}; + for (unsigned i = 0; i < gb->printer.image_offset; i++) { + image[i] = colors[(palette >> (gb->printer.image[i] * 2)) & 3]; + } + + if (gb->printer.callback) { + gb->printer.callback(gb, image, gb->printer.image_offset / 160, + gb->printer.command_data[1] >> 4, gb->printer.command_data[1] & 7, + gb->printer.command_data[3] & 0x7F); + } + + gb->printer.image_offset = 0; + } + break; + + case GB_PRINTER_DATA_COMMAND: + if (gb->printer.command_length == GB_PRINTER_DATA_SIZE) { + gb->printer.image_offset %= sizeof(gb->printer.image); + gb->printer.status = 8; /* Received 0x280 bytes */ + + uint8_t *byte = gb->printer.command_data; + + for (unsigned row = 2; row--; ) { + for (unsigned tile_x = 0; tile_x < 160 / 8; tile_x++) { + for (unsigned y = 0; y < 8; y++, byte += 2) { + for (unsigned x_pixel = 0; x_pixel < 8; x_pixel++) { + gb->printer.image[gb->printer.image_offset + tile_x * 8 + x_pixel + y * 160] = + ((*byte) >> 7) | (((*(byte + 1)) >> 7) << 1); + (*byte) <<= 1; + (*(byte + 1)) <<= 1; + } + } + } + + gb->printer.image_offset += 8 * 160; + } + } + + case GB_PRINTER_NOP_COMMAND: + default: + break; + } +} + +static void serial_start(GB_gameboy_t *gb, uint8_t byte_received) +{ + gb->printer.byte_to_send = 0; + switch (gb->printer.command_state) { + case GB_PRINTER_COMMAND_MAGIC1: + if (byte_received != 0x88) { + return; + } + gb->printer.status &= ~1; + gb->printer.command_length = 0; + gb->printer.checksum = 0; + break; + + case GB_PRINTER_COMMAND_MAGIC2: + if (byte_received != 0x33) { + if (byte_received != 0x88) { + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + } + return; + } + break; + + case GB_PRINTER_COMMAND_ID: + gb->printer.command_id = byte_received & 0xF; + break; + + case GB_PRINTER_COMMAND_COMPRESSION: + gb->printer.compression = byte_received & 1; + break; + + case GB_PRINTER_COMMAND_LENGTH_LOW: + gb->printer.length_left = byte_received; + break; + + case GB_PRINTER_COMMAND_LENGTH_HIGH: + gb->printer.length_left |= (byte_received & 3) << 8; + break; + + case GB_PRINTER_COMMAND_DATA: + if (gb->printer.command_length != GB_PRINTER_MAX_COMMAND_LENGTH) { + if (gb->printer.compression) { + if (!gb->printer.compression_run_lenth) { + gb->printer.compression_run_is_compressed = byte_received & 0x80; + gb->printer.compression_run_lenth = (byte_received & 0x7F) + 1 + gb->printer.compression_run_is_compressed; + } + else if (gb->printer.compression_run_is_compressed) { + while (gb->printer.compression_run_lenth) { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + if (gb->printer.command_length == GB_PRINTER_MAX_COMMAND_LENGTH) { + gb->printer.compression_run_lenth = 0; + } + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + gb->printer.compression_run_lenth--; + } + } + else { + gb->printer.command_data[gb->printer.command_length++] = byte_received; + } + } + gb->printer.length_left--; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_LOW: + gb->printer.checksum ^= byte_received; + break; + + case GB_PRINTER_COMMAND_CHECKSUM_HIGH: + gb->printer.checksum ^= byte_received << 8; + if (gb->printer.checksum) { + gb->printer.status |= 1; /* Checksum error*/ + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + return; + } + break; + case GB_PRINTER_COMMAND_ACTIVE: + gb->printer.byte_to_send = 0x81; + break; + case GB_PRINTER_COMMAND_STATUS: + + if ((gb->printer.command_id & 0xF) == GB_PRINTER_INIT_COMMAND) { + /* Games expect INIT commands to return 0? */ + gb->printer.byte_to_send = 0; + } + else { + gb->printer.byte_to_send = gb->printer.status; + } + + /* Printing is done instantly, but let the game recieve a 6 (Printing) status at least once, for compatibility */ + if (gb->printer.status == 6) { + gb->printer.status = 4; /* Done */ + } + + gb->printer.command_state = GB_PRINTER_COMMAND_MAGIC1; + handle_command(gb); + return; + } + + if (gb->printer.command_state >= GB_PRINTER_COMMAND_ID && gb->printer.command_state < GB_PRINTER_COMMAND_CHECKSUM_LOW) { + gb->printer.checksum += byte_received; + } + + if (gb->printer.command_state != GB_PRINTER_COMMAND_DATA) { + gb->printer.command_state++; + } + + if (gb->printer.command_state == GB_PRINTER_COMMAND_DATA) { + if (gb->printer.length_left == 0) { + gb->printer.command_state++; + } + } + +} + +static uint8_t serial_end(GB_gameboy_t *gb) +{ + return gb->printer.byte_to_send; +} + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback) +{ + memset(&gb->printer, 0, sizeof(gb->printer)); + GB_set_serial_transfer_start_callback(gb, serial_start); + GB_set_serial_transfer_end_callback(gb, serial_end); + gb->printer.callback = callback; +} \ No newline at end of file diff --git a/waterbox/sameboy/printer.h b/waterbox/sameboy/printer.h new file mode 100644 index 0000000000..e5d9036a08 --- /dev/null +++ b/waterbox/sameboy/printer.h @@ -0,0 +1,59 @@ +#ifndef printer_h +#define printer_h +#include +#include +#include "gb_struct_def.h" +#define GB_PRINTER_MAX_COMMAND_LENGTH 0x280 +#define GB_PRINTER_DATA_SIZE 0x280 + +typedef void (*GB_print_image_callback_t)(GB_gameboy_t *gb, + uint32_t *image, + uint8_t height, + uint8_t top_margin, + uint8_t bottom_margin, + uint8_t exposure); + + +typedef struct +{ + /* Communication state machine */ + + enum { + GB_PRINTER_COMMAND_MAGIC1, + GB_PRINTER_COMMAND_MAGIC2, + GB_PRINTER_COMMAND_ID, + GB_PRINTER_COMMAND_COMPRESSION, + GB_PRINTER_COMMAND_LENGTH_LOW, + GB_PRINTER_COMMAND_LENGTH_HIGH, + GB_PRINTER_COMMAND_DATA, + GB_PRINTER_COMMAND_CHECKSUM_LOW, + GB_PRINTER_COMMAND_CHECKSUM_HIGH, + GB_PRINTER_COMMAND_ACTIVE, + GB_PRINTER_COMMAND_STATUS, + } command_state : 8; + enum { + GB_PRINTER_INIT_COMMAND = 1, + GB_PRINTER_START_COMMAND = 2, + GB_PRINTER_DATA_COMMAND = 4, + GB_PRINTER_NOP_COMMAND = 0xF, + } command_id : 8; + bool compression; + uint16_t length_left; + uint8_t command_data[GB_PRINTER_MAX_COMMAND_LENGTH]; + uint16_t command_length; + uint16_t checksum; + uint8_t status; + uint8_t byte_to_send; + + uint8_t image[160 * 200]; + uint16_t image_offset; + + GB_print_image_callback_t callback; + + uint8_t compression_run_lenth; + bool compression_run_is_compressed; +} GB_printer_t; + + +void GB_connect_printer(GB_gameboy_t *gb, GB_print_image_callback_t callback); +#endif diff --git a/waterbox/sameboy/sgb.c b/waterbox/sameboy/sgb.c new file mode 100644 index 0000000000..42c0ba7b9f --- /dev/null +++ b/waterbox/sameboy/sgb.c @@ -0,0 +1,1001 @@ +#include "sgb.h" +#include +#include +#include +#include "snes_spc/spc.h" + +#define utils_log printf + +const uint8_t iplrom[64] = { + /*ffc0*/ 0xcd, 0xef, //mov x,#$ef + /*ffc2*/ 0xbd, //mov sp,x + /*ffc3*/ 0xe8, 0x00, //mov a,#$00 + /*ffc5*/ 0xc6, //mov (x),a + /*ffc6*/ 0x1d, //dec x + /*ffc7*/ 0xd0, 0xfc, //bne $ffc5 + /*ffc9*/ 0x8f, 0xaa, 0xf4, //mov $f4,#$aa + /*ffcc*/ 0x8f, 0xbb, 0xf5, //mov $f5,#$bb + /*ffcf*/ 0x78, 0xcc, 0xf4, //cmp $f4,#$cc + /*ffd2*/ 0xd0, 0xfb, //bne $ffcf + /*ffd4*/ 0x2f, 0x19, //bra $ffef + /*ffd6*/ 0xeb, 0xf4, //mov y,$f4 + /*ffd8*/ 0xd0, 0xfc, //bne $ffd6 + /*ffda*/ 0x7e, 0xf4, //cmp y,$f4 + /*ffdc*/ 0xd0, 0x0b, //bne $ffe9 + /*ffde*/ 0xe4, 0xf5, //mov a,$f5 + /*ffe0*/ 0xcb, 0xf4, //mov $f4,y + /*ffe2*/ 0xd7, 0x00, //mov ($00)+y,a + /*ffe4*/ 0xfc, //inc y + /*ffe5*/ 0xd0, 0xf3, //bne $ffda + /*ffe7*/ 0xab, 0x01, //inc $01 + /*ffe9*/ 0x10, 0xef, //bpl $ffda + /*ffeb*/ 0x7e, 0xf4, //cmp y,$f4 + /*ffed*/ 0x10, 0xeb, //bpl $ffda + /*ffef*/ 0xba, 0xf6, //movw ya,$f6 + /*fff1*/ 0xda, 0x00, //movw $00,ya + /*fff3*/ 0xba, 0xf4, //movw ya,$f4 + /*fff5*/ 0xc4, 0xf4, //mov $f4,a + /*fff7*/ 0xdd, //mov a,y + /*fff8*/ 0x5d, //mov x,a + /*fff9*/ 0xd0, 0xdb, //bne $ffd6 + /*fffb*/ 0x1f, 0x00, 0x00, //jmp ($0000+x) + /*fffe*/ 0xc0, 0xff //reset vector location ($ffc0) +}; + +// the "reference clock" is tied to the GB cpu. 35112 of these should equal one GB LCD frame. +// it is always increasing and never resets/rebases + +const int refclocks_per_spc_sample = 67; // ~32055hz + +typedef struct +{ + // writes to FF00 + uint64_t last_write_time; // last write time relative to reference clock + uint8_t last_write_value; + + // recv packets + uint8_t read_index; // 0-127, index of the next bit read. if 255, not currently reading + uint8_t packet[16]; // a packet in the process of being formed + + uint8_t command[16 * 7]; // a command in the process of being formed + uint8_t expected_packets; // total number of packets expected for a command + uint8_t next_packet; // index of the next packet to be read + + // joypad reading + uint8_t joypad_index; // index of currently reading joypad + uint8_t num_joypads; // number of currently selected joypads (MLT_REQ) + uint8_t joypad_data[4]; // data for each joypad + uint8_t joypad_has_been_read; // state for advancing joypad_index. extermely weird; logic lifted from VBA and probably wrong + + // palettes + uint32_t palette[8][16]; + uint32_t auxpalette[512][4]; + + // border + uint8_t tiles[256][64]; // tiles stored in packed form + uint16_t tilemap[32 * 32]; + + // frame data + uint8_t frame[160 * 144]; // the most recent obtained full frame + uint32_t frozenframe[256 * 224]; // the most recent saved full frame (MASK_EN) + uint8_t attr[20 * 18]; // current attr map for the GB screen + uint8_t auxattr[45][20 * 18]; // 45 attr files + + // MASK_EN + uint8_t active_mask; // true if mask is currently being used + + // audio + SNES_SPC *spc; + uint64_t frame_start; // when the current audio frame started relative to reference clock + uint32_t clock_remainder; // number of reference clocks not sent to the SPC last frame + uint8_t sound_control[4]; // TODO... + + // transfers + uint32_t waiting_transfer; +#define TRN_NONE 0 +#define TRN_SOUND 1 +#define TRN_PAL 2 +#define TRN_CHR_LOW 3 +#define TRN_CHR_HI 4 +#define TRN_PCT 5 +#define TRN_ATTR 6 + int32_t transfer_countdown; // number of frames until transfer. not entirely accurate +} sgb_t; + +static sgb_t sgb; + +static uint32_t makecol(uint16_t c) +{ + return c >> 7 & 0xf8 | c >> 12 & 0x07 | c << 6 & 0xf800 | c << 1 & 0x0700 | c << 19 & 0xf80000 | c << 14 & 0x070000 | 0xff000000; +} + +static void cmd_trn(uint32_t which) +{ + if ((sgb.command[0] & 7) == 1) + { + if (sgb.waiting_transfer == TRN_NONE) + { + sgb.waiting_transfer = which; + sgb.transfer_countdown = 4; + } + else + { + utils_log("SGB: TRN already queued!\n"); + } + } + else + { + utils_log("SGB: cmd_trn bad length\n"); + } +} + +static void cmd_pal(int a, int b) +{ + if ((sgb.command[0] & 7) == 1) + { + uint32_t c[7]; + for (int i = 0; i < 7; i++) + c[i] = makecol(sgb.command[i * 2 + 1] | sgb.command[i * 2 + 2] << 8); + sgb.palette[0][0] = c[0]; + sgb.palette[1][0] = c[0]; + sgb.palette[2][0] = c[0]; + sgb.palette[3][0] = c[0]; + sgb.palette[a][1] = c[1]; + sgb.palette[a][2] = c[2]; + sgb.palette[a][3] = c[3]; + sgb.palette[b][1] = c[4]; + sgb.palette[b][2] = c[5]; + sgb.palette[b][3] = c[6]; + } + else + { + utils_log("SGB: cmd_pal bad length\n"); + } +} + +static void cmd_pal_set(void) +{ + if ((sgb.command[0] & 7) == 1) + { + int p0 = sgb.command[1] | sgb.command[2] << 8 & 0x100; + for (int i = 0; i < 4; i++) + { + int p = sgb.command[i * 2 + 1] | sgb.command[i * 2 + 2] << 8 & 0x100; + sgb.palette[i][0] = sgb.auxpalette[p0][0]; + sgb.palette[i][1] = sgb.auxpalette[p][1]; + sgb.palette[i][2] = sgb.auxpalette[p][2]; + sgb.palette[i][3] = sgb.auxpalette[p][3]; + } + if (sgb.command[9] & 0x80) // change attribute + { + int attr = sgb.command[9] & 0x3f; + if (attr >= 45) + attr = 44; + memcpy(sgb.attr, sgb.auxattr[attr], sizeof(sgb.attr)); + } + if (sgb.command[9] & 0x40) // cancel mask + { + sgb.active_mask = 0; + } + } + else + { + utils_log("SGB: cmd_pal bad length\n"); + } +} + +static void cmd_attr_blk() +{ + int nset = sgb.command[1]; + if (nset <= 0 || nset >= 19) + { + utils_log("SGB: cmd_attr_blk bad nset\n"); + return; + } + int npacket = (nset * 6 + 16) / 16; + if ((sgb.command[0] & 7) != npacket) + { + utils_log("SGB: cmd_attr_blk bad length\n"); + return; + } + for (int i = 0; i < nset; i++) + { + int ctrl = sgb.command[i * 6 + 2] & 7; + int pals = sgb.command[i * 6 + 3]; + int x1 = sgb.command[i * 6 + 4]; + int y1 = sgb.command[i * 6 + 5]; + int x2 = sgb.command[i * 6 + 6]; + int y2 = sgb.command[i * 6 + 7]; + int inside = ctrl & 1; + int line = ctrl & 2; + int outside = ctrl & 4; + int insidepal = pals & 3; + int linepal = pals >> 2 & 3; + int outsidepal = pals >> 4 & 3; + if (ctrl == 1) + { + ctrl = 3; + linepal = insidepal; + } + else if (ctrl == 4) + { + ctrl = 6; + linepal = outsidepal; + } + uint8_t *dst = sgb.attr; + for (int y = 0; y < 18; y++) + { + for (int x = 0; x < 20; x++) + { + if (outside && (x < x1 || x > x2 || y < y1 || y > y2)) + *dst = outsidepal; + else if (inside && x > x1 && x < x2 && y > y1 && y < y2) + *dst = insidepal; + else if (line) + *dst = linepal; + dst++; + } + } + } +} + +static void cmd_attr_lin() +{ + int nset = sgb.command[1]; + if (nset <= 0 || nset >= 111) + { + utils_log("SGB: cmd_attr_lin bad nset\n"); + return; + } + int npacket = (nset + 17) / 16; + if ((sgb.command[0] & 7) != npacket) + { + utils_log("SGB: cmd_attr_lin bad length\n"); + return; + } + for (int i = 0; i < nset; i++) + { + uint8_t v = sgb.command[i + 2]; + int line = v & 31; + int a = v >> 5 & 3; + if (v & 0x80) // horizontal + { + if (line > 17) + line = 17; + memset(sgb.attr + line * 20, a, 20); + } + else // vertical + { + if (line > 19) + line = 19; + uint8_t *dst = sgb.attr + line; + for (int i = 0; i < 18; i++, dst += 20) + dst[0] = a; + } + } +} + +static void cmd_attr_div() +{ + if ((sgb.command[0] & 7) == 1) + { + uint8_t v = sgb.command[1]; + + int c = v & 3; + int a = v >> 2 & 3; + int b = v >> 4 & 3; + + int pos = sgb.command[2]; + uint8_t *dst = sgb.attr; + if (v & 0x40) // horizontal + { + if (pos > 17) + pos = 17; + int i; + for (i = 0; i < pos; i++, dst += 20) + memset(dst, a, 20); + memset(dst, b, 20); + i++, dst += 20; + for (; i < 18; i++, dst += 20) + memset(dst, c, 20); + } + else // vertical + { + if (pos > 19) + pos = 19; + for (int j = 0; j < 18; j++) + { + int i; + for (i = 0; i < pos; i++) + *dst++ = a; + *dst++ = b; + i++; + for (; i < 20; i++) + *dst++ = c; + } + } + } + else + { + utils_log("SGB: cmd_attr_div bad length\n"); + } +} + +static void cmd_attr_chr() +{ + int x = sgb.command[1]; + int y = sgb.command[2]; + int n = sgb.command[3] | sgb.command[4] << 8; + if (n > 360) + { + utils_log("SGB: cmd_attr_chr bad n\n"); + return; + } + int npacket = (n + 87) / 64; + if ((sgb.command[0] & 7) < npacket) + { + utils_log("SGB: cmd_attr_chr bad length\n"); + return; + } + uint8_t *dst = sgb.attr; + if (x > 19) + x = 19; + if (y > 17) + y = 17; + int vertical = sgb.command[5]; + for (int i = 0; i < n; i++) + { + uint8_t v = sgb.command[i / 4 + 6]; + int a = v >> (2 * (3 - (i & 3))) & 3; + dst[y * 20 + x] = a; + if (vertical) + { + y++; + if (y == 18) + { + y = 0; + x++; + if (x == 20) + return; + } + } + else + { + x++; + if (x == 20) + { + x = 0; + y++; + if (y == 18) + return; + } + } + } +} + +static void cmd_attr_set() +{ + if ((sgb.command[0] & 7) == 1) + { + int attr = sgb.command[1] & 0x3f; + if (attr >= 45) + attr = 44; + memcpy(sgb.attr, sgb.auxattr[attr], sizeof(sgb.attr)); + if (sgb.command[1] & 0x40) + { + sgb.active_mask = 0; + } + } + else + { + utils_log("SGB: cmd_attr_set bad length\n"); + } +} + +static void cmd_mlt_req(void) +{ + if ((sgb.command[0] & 7) == 1) + { + switch (sgb.command[1] & 3) + { + case 0: + case 2: + sgb.num_joypads = 1; + sgb.joypad_index = 0; + break; + case 1: + sgb.num_joypads = 2; + sgb.joypad_index = 1; + break; + case 3: + sgb.num_joypads = 4; + sgb.joypad_index = 1; + break; + } + utils_log("SGB: %u joypads\n", sgb.num_joypads); + } + else + { + utils_log("SGB: cmd_mlt_req bad length\n"); + } +} + +static void cmd_mask(void) +{ + if ((sgb.command[0] & 7) == 1) + { + switch (sgb.command[1] & 3) + { + case 0: + sgb.active_mask = 0; + break; + case 1: + sgb.active_mask = 1; + break; + case 2: + case 3: + sgb.active_mask = 1; + for (int i = 0; i < 256 * 224; i++) + sgb.frozenframe[i] = sgb.palette[0][0]; + break; + } + } + else + { + utils_log("SGB: cmd_mask bad length\n"); + } +} + +static void cmd_sound(void) +{ + if ((sgb.command[0] & 7) == 1) + { + sgb.sound_control[1] = sgb.command[1]; + sgb.sound_control[2] = sgb.command[2]; + sgb.sound_control[3] = sgb.command[3]; + sgb.sound_control[0] = sgb.command[4]; + } + else + { + utils_log("SGB: cmd_sound bad length\n"); + } +} + +static void do_command(void) +{ + const int command = sgb.command[0] >> 3; + switch (command) + { + default: + utils_log("SGB: Unknown or unimplemented command %02xh\n", command); + break; + + case 0x00: // PAL01 + utils_log("SGB: PAL01\n"); + cmd_pal(0, 1); + break; + case 0x01: // PAL23 + utils_log("SGB: PAL23\n"); + cmd_pal(2, 3); + break; + case 0x02: // PAL03 + utils_log("SGB: PAL03\n"); + cmd_pal(0, 3); + break; + case 0x03: // PAL12 + utils_log("SGB: PAL12\n"); + cmd_pal(1, 2); + break; + case 0x0a: // PAL_SET + utils_log("SGB: PAL_SET\n"); + cmd_pal_set(); + break; + + case 0x04: // ATTR_BLK + utils_log("SGB: ATTR_BLK\n"); + cmd_attr_blk(); + break; + case 0x05: // ATTR_LIN + utils_log("SGB: ATTR_LIN\n"); + cmd_attr_lin(); + break; + case 0x06: // ATTR_DIV + utils_log("SGB: ATTR_DIV\n"); + cmd_attr_div(); + break; + case 0x07: // ATTR_CHR + utils_log("SGB: ATTR_CHR\n"); + cmd_attr_chr(); + break; + case 0x16: // ATTR_SET + utils_log("SGB: ATTR_SET\n"); + cmd_attr_set(); + break; + + case 0x17: // MASK_EN + utils_log("SGB: MASK_EN\n"); + cmd_mask(); + break; + + // unknown functions + case 0x0c: // ATRC_EN + utils_log("SGB: ATRC_EN??\n"); + break; + case 0x0d: // TEST_EN + utils_log("SGB: TEST_EN??\n"); + break; + case 0x0e: // ICON_EN + utils_log("SGB: ICON_EN??\n"); + break; + case 0x18: // OBJ_TRN + // no game used this + utils_log("SGB: OBJ_TRN??\n"); + break; + + // unimplementable functions + case 0x0f: // DATA_SND + // TODO: Is it possible for this (and DATA_TRN) to write data to + // memory areas used for the attribute file, etc? + // If so, do games do this? + utils_log("SGB: DATA_SND!! %02x:%02x%02x [%02x]\n", sgb.command[3], sgb.command[2], sgb.command[1], sgb.command[4]); + break; + case 0x10: // DATA_TRN + utils_log("SGB: DATA_TRN!!\n"); + break; + case 0x12: // JUMP + utils_log("SGB: JUMP!!\n"); + break; + + // joypad + case 0x11: // MLT_REQ + utils_log("SGB: MLT_REQ\n"); + cmd_mlt_req(); + break; + + // sound + case 0x08: // SOUND + utils_log("SGB: SOUND %02x %02x %02x %02x\n", sgb.command[1], sgb.command[2], sgb.command[3], sgb.command[4]); + cmd_sound(); + break; + + // all vram transfers + case 0x09: // SOU_TRN + utils_log("SGB: SOU_TRN\n"); + cmd_trn(TRN_SOUND); + break; + case 0x0b: // PAL_TRN + utils_log("SGB: PAL_TRN\n"); + cmd_trn(TRN_PAL); + break; + case 0x13: // CHR_TRN + utils_log("SGB: CHR_TRN\n"); + cmd_trn(sgb.command[1] & 1 ? TRN_CHR_HI : TRN_CHR_LOW); + break; + case 0x14: // PCT_TRN + utils_log("SGB: PCT_TRN\n"); + cmd_trn(TRN_PCT); + break; + case 0x15: // ATTR_TRN + utils_log("SGB: ATTR_TRN\n"); + cmd_trn(TRN_ATTR); + break; + } +} + +static void do_packet(void) +{ + memcpy(sgb.command + sgb.next_packet * 16, sgb.packet, sizeof(sgb.packet)); + sgb.next_packet++; + + if (sgb.expected_packets == 0) // not in the middle of a command + sgb.expected_packets = sgb.command[0] & 7; + + if (sgb.expected_packets == 0) // huh? + { + utils_log("SGB: zero packet command\n"); + sgb.expected_packets = 0; + sgb.next_packet = 0; + } + else if (sgb.next_packet == sgb.expected_packets) + { + do_command(); + sgb.expected_packets = 0; + sgb.next_packet = 0; + } +} + +int sgb_init(const uint8_t *spc, int length) +{ + memset(&sgb, 0, sizeof(sgb)); + sgb.read_index = 255; + sgb.num_joypads = 1; + sgb.palette[0][0] = 0xffffffff; + sgb.palette[0][1] = 0xffaaaaaa; + sgb.palette[0][2] = 0xff555555; + sgb.palette[0][3] = 0xff000000; + + sgb.spc = spc_new(); + spc_init_rom(sgb.spc, iplrom); + spc_reset(sgb.spc); + if (spc_load_spc(sgb.spc, spc, length) != NULL) + { + utils_log("SGB: Failed to load SPC\n"); + return 0; + } + + return 1; +} + +void sgb_write_ff00(uint8_t val, uint64_t time) +{ + val &= 0x30; + + //utils_log("ZZ: %02x, %llu", val, time); + const int p14_fell = (val & 0x10) < (sgb.last_write_value & 0x10); + const int p15_fell = (val & 0x20) < (sgb.last_write_value & 0x20); + const int p14_rose = (val & 0x10) > (sgb.last_write_value & 0x10); + const int p15_rose = (val & 0x20) > (sgb.last_write_value & 0x20); + + if (val == 0) // reset command processing + { + sgb.read_index = 0; + memset(sgb.packet, 0, sizeof(sgb.packet)); + } + else if (sgb.read_index != 255) // currently reading a packet + { + if (p14_fell || p15_fell) + { + if (sgb.read_index == 128) // end of packet + { + if (p14_fell) + do_packet(); + else + utils_log("SGB: Stop bit not present\n"); + sgb.read_index = 255; + } + else + { + if (p15_fell) + { + int byte = sgb.read_index >> 3; + int bit = sgb.read_index & 7; + sgb.packet[byte] |= 1 << bit; + } + sgb.read_index++; + } + } + } + else // joypad processing + { + if (val == 0x10) + sgb.joypad_has_been_read |= 2; // reading P15 + if (val == 0x20) + sgb.joypad_has_been_read |= 1; // reading P14 + if (val == 0x30 && (p14_rose || p15_rose)) + { + if (sgb.joypad_has_been_read == 7) + { + sgb.joypad_has_been_read = 0; + sgb.joypad_index++; + sgb.joypad_index &= sgb.num_joypads - 1; + //utils_log("SGB: joypad index to %u", sgb.joypad_index); + } + else + { + sgb.joypad_has_been_read &= 3; // the other line must be asserted and a read must happen before joypad_index inc?? + } + } + } + + sgb.last_write_value = val; + sgb.last_write_time = time; +} + +uint8_t sgb_read_ff00(uint64_t time) +{ + uint8_t ret = sgb.last_write_value & 0xf0 | 0xc0; + const int p14 = !(ret & 0x10); + const int p15 = !(ret & 0x20); + const int ji = sgb.joypad_index; + + // TODO: is this "reset" correct? + sgb.joypad_has_been_read |= 4; // read occured + sgb.read_index = 255; + sgb.next_packet = 0; + sgb.expected_packets = 0; + + if (!p14 && !p15) + { + //utils_log("SGB: SCAN%u", ji); + // scan id + return ret | (15 - ji); + } + else + { + // get data + const uint8_t j = sgb.joypad_data[ji]; + if (p14) + ret |= j >> 4; + if (p15) + ret |= j & 0x0f; + //utils_log("SGB: READ%u %02x", ji, ret ^ 0x0f); + return ret ^ 0x0f; + } +} + +// for each of 4 joypads: +// 7......0 +// DULRSsBA +void sgb_set_controller_data(const uint8_t *buttons) +{ + memcpy(sgb.joypad_data, buttons, sizeof(sgb.joypad_data)); +} + +static void trn_sound(const uint8_t* data) +{ + int len = data[0] | data[1] << 8; + int addr = data[2] | data[3] << 8; + utils_log("TRN_SOUND %04x %04x\n", addr, len); + uint8_t* dst = spc_get_ram(sgb.spc); + + if (len > 0xffc) + { + utils_log("TRN_SOUND src overflow\n"); + return; + } + if (len + addr >= 0x10000) + { + utils_log("TRN_SOUND dst overflow\n"); + return; + } + memcpy(dst + addr, data + 4, len); +} + +static void trn_pal(const uint8_t *data) +{ + const uint16_t *src = (const uint16_t *)data; + uint32_t *dst = sgb.auxpalette[0]; + for (int i = 0; i < 2048; i++) + dst[i] = makecol(src[i]); +} + +static void trn_attr(const uint8_t *data) +{ + uint8_t *dst = sgb.auxattr[0]; + for (int n = 0; n < 45 * 90; n++) + { + uint8_t s = *data++; + *dst++ = s >> 6 & 3; + *dst++ = s >> 4 & 3; + *dst++ = s >> 2 & 3; + *dst++ = s >> 0 & 3; + } +} + +static void trn_pct(const uint8_t *data) +{ + memcpy(sgb.tilemap, data, sizeof(sgb.tilemap)); + const uint16_t *palettes = (const uint16_t *)(data + sizeof(sgb.tilemap)); + uint32_t *dst = sgb.palette[4]; + for (int i = 0; i < 64; i++) + dst[i] = makecol(palettes[i]); +} + +static void trn_chr(const uint8_t *data, int bank) +{ + uint8_t *dst = sgb.tiles[128 * bank]; + for (int n = 0; n < 128; n++) + { + for (int y = 0; y < 8; y++) + { + int a = data[0]; + int b = data[1] << 1; + int c = data[16] << 2; + int d = data[17] << 3; + for (int x = 7; x >= 0; x--) + { + dst[x] = a & 1 | b & 2 | c & 4 | d & 8; + a >>= 1; + b >>= 1; + c >>= 1; + d >>= 1; + } + dst += 8; + data += 2; + } + data += 16; + } +} + +static void do_vram_transfer(void) +{ + uint8_t vram[4096]; + for (int tilenum = 0; tilenum < 256; tilenum++) + { + const int ty = tilenum / 20; + const int tx = tilenum % 20; + const uint8_t *src = sgb.frame + ty * 8 * 160 + tx * 8; + uint8_t *dst = vram + 16 * tilenum; + for (int j = 0; j < 8; j++) + { + uint32_t a = 0, b = 0; + a |= (src[7] & 1) << 0; + a |= (src[6] & 1) << 1; + a |= (src[5] & 1) << 2; + a |= (src[4] & 1) << 3; + a |= (src[3] & 1) << 4; + a |= (src[2] & 1) << 5; + a |= (src[1] & 1) << 6; + a |= (src[0] & 1) << 7; + + b |= (src[7] & 2) >> 1; + b |= (src[6] & 2) << 0; + b |= (src[5] & 2) << 1; + b |= (src[4] & 2) << 2; + b |= (src[3] & 2) << 3; + b |= (src[2] & 2) << 4; + b |= (src[1] & 2) << 5; + b |= (src[0] & 2) << 6; + *dst++ = a; + *dst++ = b; + src += 160; + } + } + + switch (sgb.waiting_transfer) + { + case TRN_SOUND: + trn_sound(vram); + break; + case TRN_PAL: + trn_pal(vram); + break; + case TRN_CHR_LOW: + trn_chr(vram, 0); + break; + case TRN_CHR_HI: + trn_chr(vram, 1); + break; + case TRN_PCT: + trn_pct(vram); + break; + case TRN_ATTR: + trn_attr(vram); + break; + } +} + +static void sgb_render_frame_gb(uint32_t *vbuff) +{ + const uint8_t *attr = sgb.attr; + const uint8_t *src = sgb.frame; + uint32_t *dst = vbuff + ((224 - 144) / 2 * 256 + (256 - 160) / 2); + + for (int j = 0; j < 144; j++) + { + const uint8_t *attr_line = attr + j / 8 * 20; + for (int i = 0; i < 160; i++) + { + const int attr_index = i / 8; + *dst++ = sgb.palette[attr_line[attr_index]][*src++]; + } + dst += 256 - 160; + } +} + +static void draw_tile(uint16_t entry, uint32_t *dest) +{ + const uint8_t *tile = sgb.tiles[entry & 0xff]; + const uint32_t *palette = sgb.palette[entry >> 10 & 7]; + int hflip = entry & 0x4000; + int vflip = entry & 0x8000; + int hinc, vinc; + if (hflip) + { + hinc = -1; + dest += 7; + } + else + { + hinc = 1; + } + if (vflip) + { + vinc = -256; + dest += 7 * 256; + } + else + { + vinc = 256; + } + vinc -= 8 * hinc; + for (int y = 0; y < 8; y++, dest += vinc) + { + for (int x = 0; x < 8; x++, dest += hinc) + { + int c = *tile++; + if (c) + *dest = palette[c]; + } + } +} + +static void sgb_render_border(uint32_t *vbuff) +{ + const uint16_t *tilemap = sgb.tilemap; + for (int n = 0; n < 32 * 28; n++) + { + draw_tile(*tilemap++, vbuff); + vbuff += 8; + if ((n & 31) == 31) + vbuff += 256 * 7; + } +} + +// 160x144 32bpp pixel data +// assumed to contain exact pixel values 00, 55, aa, ff +void sgb_take_frame(uint32_t *vbuff) +{ + for (int i = 0; i < 160 * 144; i++) + { + sgb.frame[i] = 3 - (vbuff[i] >> 6 & 3); // 0, 1, 2, or 3 for each pixel + } + if (sgb.waiting_transfer != TRN_NONE) + { + if (!--sgb.transfer_countdown) + { + do_vram_transfer(); + sgb.waiting_transfer = TRN_NONE; + } + } + if (!sgb.active_mask) + { + // render the frame now + for (int i = 0; i < 256 * 224; i++) + sgb.frozenframe[i] = sgb.palette[0][0]; + sgb_render_frame_gb(sgb.frozenframe); + sgb_render_border(sgb.frozenframe); + } +} + +void sgb_render_frame(uint32_t *vbuff) +{ + memcpy(vbuff, sgb.frozenframe, sizeof(sgb.frozenframe)); +} + +void sgb_render_audio(uint64_t time, void (*callback)(int16_t l, int16_t r, uint64_t time)) +{ + int16_t sound_buffer[4096]; + uint32_t diff = time - sgb.frame_start + sgb.clock_remainder; + //utils_log("%ul", diff); + + uint32_t samples = diff / refclocks_per_spc_sample; + uint32_t new_remainder = diff % refclocks_per_spc_sample; + + spc_set_output(sgb.spc, sound_buffer, sizeof(sound_buffer) / sizeof(sound_buffer[0])); + int p; + for (p = 0; p < 4; p++) + { + if (spc_read_port(sgb.spc, 0, p) != sgb.sound_control[p]) + break; + } + if (p == 4) // recived + { + sgb.sound_control[0] = 0; + sgb.sound_control[1] = 0; + sgb.sound_control[2] = 0; + } + for (p = 0; p < 4; p++) + { + spc_write_port(sgb.spc, 0, p, sgb.sound_control[p]); + } + + spc_end_frame(sgb.spc, samples * 32); + + uint64_t t = sgb.frame_start + refclocks_per_spc_sample - sgb.clock_remainder; + for (int i = 0; i < samples; i++, t += refclocks_per_spc_sample) + callback(sound_buffer[i * 2], sound_buffer[i * 2] + 1, t); + + sgb.frame_start = time; + sgb.clock_remainder = new_remainder; +} diff --git a/waterbox/sameboy/sgb.h b/waterbox/sameboy/sgb.h new file mode 100644 index 0000000000..35e76dcad9 --- /dev/null +++ b/waterbox/sameboy/sgb.h @@ -0,0 +1,36 @@ +#pragma once +#include + +// whenever a time is asked for, it is relative to a clock that ticks 35112 times +// per nominal frame on the GB lcd, starts at 0 when emulation begins, and never resets/rebases + +// write to MMIO ff00. only bits 4 and 5 are used +void sgb_write_ff00(uint8_t val, uint64_t time); + +// read from MMIO ff00. supplies data for all 8 bits +uint8_t sgb_read_ff00(uint64_t time); + +// set controller data to be used by subsequent controller reads +// buttons[0] = controller 1, buttons[3] = controller 4 +// 7......0 +// DULRSsBA +void sgb_set_controller_data(const uint8_t* buttons); + +// initialize the SGB module. pass an SPC file that results from the real S-CPU initialization, +// and the length of that file +int sgb_init(const uint8_t* spc, int length); + +// call whenever the gameboy has finished producing a video frame +// data is 32bpp 160x144 screen data. for each pixel: +//31 7 0 +// xxxxxxxx xxxxxxxx xxxxxxxx DDxxxxxx -- DD = 0, 1, 2, or 3. x = don't care +void sgb_take_frame(uint32_t* vbuff); + +// copy the finished video frame to an output buffer. pixel format is 32bpp xrgb +// can be called at any time, including right after sgb_take_frame +void sgb_render_frame(uint32_t* vbuff); + +// call to finish a frame's worth of audio. should be called once every 35112 time units (some jitter is OK) +// callback will be called with L and R sample values for various time points +// between the last time sgb_render_audio was called and now +void sgb_render_audio(uint64_t time, void(*callback)(int16_t l, int16_t r, uint64_t time)); diff --git a/waterbox/sameboy/snes_spc/SNES_SPC.cpp b/waterbox/sameboy/snes_spc/SNES_SPC.cpp new file mode 100644 index 0000000000..fb3b147ae8 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC.cpp @@ -0,0 +1,564 @@ +// Core SPC emulation: CPU, timers, SMP registers, memory + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +// (n ? n : 256) +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + +// Note: SPC_MORE_ACCURACY exists mainly so I can run my validation tests, which +// do crazy echo buffer accesses. +#ifndef SPC_MORE_ACCURACY + #define SPC_MORE_ACCURACY 0 +#endif + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + + +//// Timers + +#if SPC_DISABLE_TEMPO + #define TIMER_DIV( t, n ) ((n) >> t->prescaler) + #define TIMER_MUL( t, n ) ((n) << t->prescaler) +#else + #define TIMER_DIV( t, n ) ((n) / t->prescaler) + #define TIMER_MUL( t, n ) ((n) * t->prescaler) +#endif + +SNES_SPC::Timer* SNES_SPC::run_timer_( Timer* t, rel_time_t time ) +{ + int elapsed = TIMER_DIV( t, time - t->next_time ) + 1; + t->next_time += TIMER_MUL( t, elapsed ); + + if ( t->enabled ) + { + int remain = IF_0_THEN_256( t->period - t->divider ); + int divider = t->divider + elapsed; + int over = elapsed - remain; + if ( over >= 0 ) + { + int n = over / t->period; + t->counter = (t->counter + 1 + n) & 0x0F; + divider = over - n * t->period; + } + t->divider = (uint8_t) divider; + } + return t; +} + +inline SNES_SPC::Timer* SNES_SPC::run_timer( Timer* t, rel_time_t time ) +{ + if ( time >= t->next_time ) + t = run_timer_( t, time ); + return t; +} + + +//// ROM + +void SNES_SPC::enable_rom( int enable ) +{ + if ( m.rom_enabled != enable ) + { + m.rom_enabled = enable; + if ( enable ) + memcpy( m.hi_ram, &RAM [rom_addr], sizeof m.hi_ram ); + memcpy( &RAM [rom_addr], (enable ? m.rom : m.hi_ram), rom_size ); + // TODO: ROM can still get overwritten when DSP writes to echo buffer + } +} + + +//// DSP + +#if SPC_LESS_ACCURATE + int const max_reg_time = 29; + + signed char const SNES_SPC::reg_times_ [256] = + { + -1, 0,-11,-10,-15,-11, -2, -2, 4, 3, 14, 14, 26, 26, 14, 22, + 2, 3, 0, 1,-12, 0, 1, 1, 7, 6, 14, 14, 27, 14, 14, 23, + 5, 6, 3, 4, -1, 3, 4, 4, 10, 9, 14, 14, 26, -5, 14, 23, + 8, 9, 6, 7, 2, 6, 7, 7, 13, 12, 14, 14, 27, -4, 14, 24, + 11, 12, 9, 10, 5, 9, 10, 10, 16, 15, 14, 14, -2, -4, 14, 24, + 14, 15, 12, 13, 8, 12, 13, 13, 19, 18, 14, 14, -2,-36, 14, 24, + 17, 18, 15, 16, 11, 15, 16, 16, 22, 21, 14, 14, 28, -3, 14, 25, + 20, 21, 18, 19, 14, 18, 19, 19, 25, 24, 14, 14, 14, 29, 14, 25, + + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, + }; + + #define RUN_DSP( time, offset ) \ + int count = (time) - (offset) - m.dsp_time;\ + if ( count >= 0 )\ + {\ + int clock_count = (count & ~(clocks_per_sample - 1)) + clocks_per_sample;\ + m.dsp_time += clock_count;\ + dsp.run( clock_count );\ + } +#else + #define RUN_DSP( time, offset ) \ + {\ + int count = (time) - m.dsp_time;\ + if ( !SPC_MORE_ACCURACY || count )\ + {\ + assert( count > 0 );\ + m.dsp_time = (time);\ + dsp.run( count );\ + }\ + } +#endif + +int SNES_SPC::dsp_read( rel_time_t time ) +{ + RUN_DSP( time, reg_times [REGS [r_dspaddr] & 0x7F] ); + + int result = dsp.read( REGS [r_dspaddr] & 0x7F ); + + #ifdef SPC_DSP_READ_HOOK + SPC_DSP_READ_HOOK( spc_time + time, (REGS [r_dspaddr] & 0x7F), result ); + #endif + + return result; +} + +inline void SNES_SPC::dsp_write( int data, rel_time_t time ) +{ + RUN_DSP( time, reg_times [REGS [r_dspaddr]] ) + #if SPC_LESS_ACCURATE + else if ( m.dsp_time == skipping_time ) + { + int r = REGS [r_dspaddr]; + if ( r == SPC_DSP::r_kon ) + m.skipped_kon |= data & ~dsp.read( SPC_DSP::r_koff ); + + if ( r == SPC_DSP::r_koff ) + { + m.skipped_koff |= data; + m.skipped_kon &= ~data; + } + } + #endif + + #ifdef SPC_DSP_WRITE_HOOK + SPC_DSP_WRITE_HOOK( m.spc_time + time, REGS [r_dspaddr], (uint8_t) data ); + #endif + + if ( REGS [r_dspaddr] <= 0x7F ) + dsp.write( REGS [r_dspaddr], data ); + else if ( !SPC_MORE_ACCURACY ) + dprintf( "SPC wrote to DSP register > $7F\n" ); +} + + +//// Memory access extras + +#if SPC_MORE_ACCURACY + #define MEM_ACCESS( time, addr ) \ + {\ + if ( time >= m.dsp_time )\ + {\ + RUN_DSP( time, max_reg_time );\ + }\ + } +#elif !defined (NDEBUG) + // Debug-only check for read/write within echo buffer, since this might result in + // inaccurate emulation due to the DSP not being caught up to the present. + + bool SNES_SPC::check_echo_access( int addr ) + { + if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + { + int start = 0x100 * dsp.read( SPC_DSP::r_esa ); + int size = 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + int end = start + (size ? size : 4); + if ( start <= addr && addr < end ) + { + if ( !m.echo_accessed ) + { + m.echo_accessed = 1; + return true; + } + } + } + return false; + } + + #define MEM_ACCESS( time, addr ) check( !check_echo_access( (uint16_t) addr ) ); +#else + #define MEM_ACCESS( time, addr ) +#endif + + +//// CPU write + +#if SPC_MORE_ACCURACY +static unsigned char const glitch_probs [3] [256] = +{ + 0xC3,0x92,0x5B,0x1C,0xD1,0x92,0x5B,0x1C,0xDB,0x9C,0x72,0x18,0xCD,0x5C,0x38,0x0B, + 0xE1,0x9C,0x74,0x17,0xCF,0x75,0x45,0x0C,0xCF,0x6E,0x4A,0x0D,0xA3,0x3A,0x1D,0x08, + 0xDB,0xA0,0x82,0x19,0xD9,0x73,0x3C,0x0E,0xCB,0x76,0x52,0x0B,0xA5,0x46,0x1D,0x09, + 0xDA,0x74,0x55,0x0F,0xA2,0x3F,0x21,0x05,0x9A,0x40,0x20,0x07,0x63,0x1E,0x10,0x01, + 0xDF,0xA9,0x85,0x1D,0xD3,0x84,0x4B,0x0E,0xCF,0x6F,0x49,0x0F,0xB3,0x48,0x1E,0x05, + 0xD8,0x77,0x52,0x12,0xB7,0x49,0x23,0x06,0xAA,0x45,0x28,0x07,0x7D,0x28,0x0F,0x07, + 0xCC,0x7B,0x4A,0x0E,0xB2,0x4F,0x24,0x07,0xAD,0x43,0x2C,0x06,0x86,0x29,0x11,0x07, + 0xAE,0x48,0x1F,0x0A,0x76,0x21,0x19,0x05,0x76,0x21,0x14,0x05,0x44,0x11,0x0B,0x01, + 0xE7,0xAD,0x96,0x23,0xDC,0x86,0x59,0x0E,0xDC,0x7C,0x5F,0x15,0xBB,0x53,0x2E,0x09, + 0xD6,0x7C,0x4A,0x16,0xBB,0x4A,0x25,0x08,0xB3,0x4F,0x28,0x0B,0x8E,0x23,0x15,0x08, + 0xCF,0x7F,0x57,0x11,0xB5,0x4A,0x23,0x0A,0xAA,0x42,0x28,0x05,0x7D,0x22,0x12,0x03, + 0xA6,0x49,0x28,0x09,0x82,0x2B,0x0D,0x04,0x7A,0x20,0x0F,0x04,0x3D,0x0F,0x09,0x03, + 0xD1,0x7C,0x4C,0x0F,0xAF,0x4E,0x21,0x09,0xA8,0x46,0x2A,0x07,0x85,0x1F,0x0E,0x07, + 0xA6,0x3F,0x26,0x07,0x7C,0x24,0x14,0x07,0x78,0x22,0x16,0x04,0x46,0x12,0x0A,0x02, + 0xA6,0x41,0x2C,0x0A,0x7E,0x28,0x11,0x05,0x73,0x1B,0x14,0x05,0x3D,0x11,0x0A,0x02, + 0x70,0x22,0x17,0x05,0x48,0x13,0x08,0x03,0x3C,0x07,0x0D,0x07,0x26,0x07,0x06,0x01, + + 0xE0,0x9F,0xDA,0x7C,0x4F,0x18,0x28,0x0D,0xE9,0x9F,0xDA,0x7C,0x4F,0x18,0x1F,0x07, + 0xE6,0x97,0xD8,0x72,0x64,0x13,0x26,0x09,0xDC,0x67,0xA9,0x38,0x21,0x07,0x15,0x06, + 0xE9,0x91,0xD2,0x6B,0x63,0x14,0x2B,0x0E,0xD6,0x61,0xB7,0x41,0x2B,0x0E,0x10,0x09, + 0xCF,0x59,0xB0,0x2F,0x35,0x08,0x0F,0x07,0xB6,0x30,0x7A,0x21,0x17,0x07,0x09,0x03, + 0xE7,0xA3,0xE5,0x6B,0x65,0x1F,0x34,0x09,0xD8,0x6B,0xBE,0x45,0x27,0x07,0x10,0x07, + 0xDA,0x54,0xB1,0x39,0x2E,0x0E,0x17,0x08,0xA9,0x3C,0x86,0x22,0x16,0x06,0x07,0x03, + 0xD4,0x51,0xBC,0x3D,0x38,0x0A,0x13,0x06,0xB2,0x37,0x79,0x1C,0x17,0x05,0x0E,0x06, + 0xA7,0x31,0x74,0x1C,0x11,0x06,0x0C,0x02,0x6D,0x1A,0x38,0x10,0x0B,0x05,0x06,0x03, + 0xEB,0x9A,0xE1,0x7A,0x6F,0x13,0x34,0x0E,0xE6,0x75,0xC5,0x45,0x3E,0x0B,0x1A,0x05, + 0xD8,0x63,0xC1,0x40,0x3C,0x1B,0x19,0x06,0xB3,0x42,0x83,0x29,0x18,0x0A,0x08,0x04, + 0xD4,0x58,0xBA,0x43,0x3F,0x0A,0x1F,0x09,0xB1,0x33,0x8A,0x1F,0x1F,0x06,0x0D,0x05, + 0xAF,0x3C,0x7A,0x1F,0x16,0x08,0x0A,0x01,0x72,0x1B,0x52,0x0D,0x0B,0x09,0x06,0x01, + 0xCF,0x63,0xB7,0x47,0x40,0x10,0x14,0x06,0xC0,0x41,0x96,0x20,0x1C,0x09,0x10,0x05, + 0xA6,0x35,0x82,0x1A,0x20,0x0C,0x0E,0x04,0x80,0x1F,0x53,0x0F,0x0B,0x02,0x06,0x01, + 0xA6,0x31,0x81,0x1B,0x1D,0x01,0x08,0x08,0x7B,0x20,0x4D,0x19,0x0E,0x05,0x07,0x03, + 0x6B,0x17,0x49,0x07,0x0E,0x03,0x0A,0x05,0x37,0x0B,0x1F,0x06,0x04,0x02,0x07,0x01, + + 0xF0,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x47,0x1E,0x6E,0x1B,0x32,0x0A, + 0xF0,0xD6,0xEA,0xA4,0xED,0xC4,0xDE,0x82,0x98,0x1F,0x50,0x13,0x52,0x15,0x2A,0x0A, + 0xF1,0xD1,0xEB,0xA2,0xEB,0xB7,0xD8,0x69,0xA2,0x1F,0x5B,0x18,0x55,0x18,0x2C,0x0A, + 0xED,0xB5,0xDE,0x7E,0xE6,0x85,0xD3,0x59,0x59,0x0F,0x2C,0x09,0x24,0x07,0x15,0x09, + 0xF1,0xD6,0xEA,0xA0,0xEC,0xBB,0xDA,0x77,0xA9,0x23,0x58,0x14,0x5D,0x12,0x2F,0x09, + 0xF1,0xC1,0xE3,0x86,0xE4,0x87,0xD2,0x4E,0x68,0x15,0x26,0x0B,0x27,0x09,0x15,0x02, + 0xEE,0xA6,0xE0,0x5C,0xE0,0x77,0xC3,0x41,0x67,0x1B,0x3C,0x07,0x2A,0x06,0x19,0x07, + 0xE4,0x75,0xC6,0x43,0xCC,0x50,0x95,0x23,0x35,0x09,0x14,0x04,0x15,0x05,0x0B,0x04, + 0xEE,0xD6,0xED,0xAD,0xEC,0xB1,0xEB,0x79,0xAC,0x22,0x56,0x14,0x5A,0x12,0x26,0x0A, + 0xEE,0xBB,0xE7,0x7E,0xE9,0x8D,0xCB,0x49,0x67,0x11,0x34,0x07,0x2B,0x0B,0x14,0x07, + 0xED,0xA7,0xE5,0x76,0xE3,0x7E,0xC4,0x4B,0x77,0x14,0x34,0x08,0x27,0x07,0x14,0x04, + 0xE7,0x8B,0xD2,0x4C,0xCA,0x56,0x9E,0x31,0x36,0x0C,0x11,0x07,0x14,0x04,0x0A,0x02, + 0xF0,0x9B,0xEA,0x6F,0xE5,0x81,0xC4,0x43,0x74,0x10,0x30,0x0B,0x2D,0x08,0x1B,0x06, + 0xE6,0x83,0xCA,0x48,0xD9,0x56,0xA7,0x23,0x3B,0x09,0x12,0x09,0x15,0x07,0x0A,0x03, + 0xE5,0x5F,0xCB,0x3C,0xCF,0x48,0x91,0x22,0x31,0x0A,0x17,0x08,0x15,0x04,0x0D,0x02, + 0xD1,0x43,0x91,0x20,0xA9,0x2D,0x54,0x12,0x17,0x07,0x09,0x02,0x0C,0x04,0x05,0x03, +}; +#endif + +// divided into multiple functions to keep rarely-used functionality separate +// so often-used functionality can be optimized better by compiler + +// If write isn't preceded by read, data has this added to it +int const no_read_before_write = 0x2000; + +void SNES_SPC::cpu_write_smp_reg_( int data, rel_time_t time, int addr ) +{ + switch ( addr ) + { + case r_t0target: + case r_t1target: + case r_t2target: { + Timer* t = &m.timers [addr - r_t0target]; + int period = IF_0_THEN_256( data ); + if ( t->period != period ) + { + t = run_timer( t, time ); + #if SPC_MORE_ACCURACY + // Insane behavior when target is written just after counter is + // clocked and counter matches new period and new period isn't 1, 2, 4, or 8 + if ( t->divider == (period & 0xFF) && + t->next_time == time + TIMER_MUL( t, 1 ) && + ((period - 1) | ~0x0F) & period ) + { + //dprintf( "SPC pathological timer target write\n" ); + + // If the period is 3, 5, or 9, there's a probability this behavior won't occur, + // based on the previous period + int prob = 0xFF; + int old_period = t->period & 0xFF; + if ( period == 3 ) prob = glitch_probs [0] [old_period]; + if ( period == 5 ) prob = glitch_probs [1] [old_period]; + if ( period == 9 ) prob = glitch_probs [2] [old_period]; + + // The glitch suppresses incrementing of one of the counter bits, based on + // the lowest set bit in the new period + int b = 1; + while ( !(period & b) ) + b <<= 1; + + if ( (rand() >> 4 & 0xFF) <= prob ) + t->divider = (t->divider - b) & 0xFF; + } + #endif + t->period = period; + } + break; + } + + case r_t0out: + case r_t1out: + case r_t2out: + if ( !SPC_MORE_ACCURACY ) + dprintf( "SPC wrote to counter %d\n", (int) addr - r_t0out ); + + if ( data < no_read_before_write / 2 ) + run_timer( &m.timers [addr - r_t0out], time - 1 )->counter = 0; + break; + + // Registers that act like RAM + case 0x8: + case 0x9: + REGS_IN [addr] = (uint8_t) data; + break; + + case r_test: + if ( (uint8_t) data != 0x0A ) + dprintf( "SPC wrote to test register\n" ); + break; + + case r_control: + // port clears + if ( data & 0x10 ) + { + REGS_IN [r_cpuio0] = 0; + REGS_IN [r_cpuio1] = 0; + } + if ( data & 0x20 ) + { + REGS_IN [r_cpuio2] = 0; + REGS_IN [r_cpuio3] = 0; + } + + // timers + { + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + int enabled = data >> i & 1; + if ( t->enabled != enabled ) + { + t = run_timer( t, time ); + t->enabled = enabled; + if ( enabled ) + { + t->divider = 0; + t->counter = 0; + } + } + } + } + enable_rom( data & 0x80 ); + break; + } +} + +void SNES_SPC::cpu_write_smp_reg( int data, rel_time_t time, int addr ) +{ + if ( addr == r_dspdata ) // 99% + dsp_write( data, time ); + else + cpu_write_smp_reg_( data, time, addr ); +} + +void SNES_SPC::cpu_write_high( int data, int i, rel_time_t time ) +{ + if ( i < rom_size ) + { + m.hi_ram [i] = (uint8_t) data; + if ( m.rom_enabled ) + RAM [i + rom_addr] = m.rom [i]; // restore overwritten ROM + } + else + { + assert( RAM [i + rom_addr] == (uint8_t) data ); + RAM [i + rom_addr] = cpu_pad_fill; // restore overwritten padding + cpu_write( data, i + rom_addr - 0x10000, time ); + } +} + +int const bits_in_int = CHAR_BIT * sizeof (int); + +void SNES_SPC::cpu_write( int data, int addr, rel_time_t time ) +{ + MEM_ACCESS( time, addr ) + + // RAM + RAM [addr] = (uint8_t) data; + int reg = addr - 0xF0; + if ( reg >= 0 ) // 64% + { + // $F0-$FF + if ( reg < reg_count ) // 87% + { + REGS [reg] = (uint8_t) data; + + // Ports + #ifdef SPC_PORT_WRITE_HOOK + if ( (unsigned) (reg - r_cpuio0) < port_count ) + SPC_PORT_WRITE_HOOK( m.spc_time + time, (reg - r_cpuio0), + (uint8_t) data, ®S [r_cpuio0] ); + #endif + + // Registers other than $F2 and $F4-$F7 + //if ( reg != 2 && reg != 4 && reg != 5 && reg != 6 && reg != 7 ) + // TODO: this is a bit on the fragile side + if ( ((~0x2F00 << (bits_in_int - 16)) << reg) < 0 ) // 36% + cpu_write_smp_reg( data, time, reg ); + } + // High mem/address wrap-around + else + { + reg -= rom_addr - 0xF0; + if ( reg >= 0 ) // 1% in IPL ROM area or address wrapped around + cpu_write_high( data, reg, time ); + } + } +} + + +//// CPU read + +inline int SNES_SPC::cpu_read_smp_reg( int reg, rel_time_t time ) +{ + int result = REGS_IN [reg]; + reg -= r_dspaddr; + // DSP addr and data + if ( (unsigned) reg <= 1 ) // 4% 0xF2 and 0xF3 + { + result = REGS [r_dspaddr]; + if ( (unsigned) reg == 1 ) + result = dsp_read( time ); // 0xF3 + } + return result; +} + +int SNES_SPC::cpu_read( int addr, rel_time_t time ) +{ + MEM_ACCESS( time, addr ) + + // RAM + int result = RAM [addr]; + int reg = addr - 0xF0; + if ( reg >= 0 ) // 40% + { + reg -= 0x10; + if ( (unsigned) reg >= 0xFF00 ) // 21% + { + reg += 0x10 - r_t0out; + + // Timers + if ( (unsigned) reg < timer_count ) // 90% + { + Timer* t = &m.timers [reg]; + if ( time >= t->next_time ) + t = run_timer_( t, time ); + result = t->counter; + t->counter = 0; + } + // Other registers + else if ( reg < 0 ) // 10% + { + result = cpu_read_smp_reg( reg + r_t0out, time ); + } + else // 1% + { + assert( reg + (r_t0out + 0xF0 - 0x10000) < 0x100 ); + result = cpu_read( reg + (r_t0out + 0xF0 - 0x10000), time ); + } + } + } + + return result; +} + + +//// Run + +// Prefix and suffix for CPU emulator function +#define SPC_CPU_RUN_FUNC \ +BOOST::uint8_t* SNES_SPC::run_until_( time_t end_time )\ +{\ + rel_time_t rel_time = m.spc_time - end_time;\ + assert( rel_time <= 0 );\ + m.spc_time = end_time;\ + m.dsp_time += rel_time;\ + m.timers [0].next_time += rel_time;\ + m.timers [1].next_time += rel_time;\ + m.timers [2].next_time += rel_time; + +#define SPC_CPU_RUN_FUNC_END \ + m.spc_time += rel_time;\ + m.dsp_time -= rel_time;\ + m.timers [0].next_time -= rel_time;\ + m.timers [1].next_time -= rel_time;\ + m.timers [2].next_time -= rel_time;\ + assert( m.spc_time <= end_time );\ + return ®S [r_cpuio0];\ +} + +int const cpu_lag_max = 12 - 1; // DIV YA,X takes 12 clocks + +void SNES_SPC::end_frame( time_t end_time ) +{ + // Catch CPU up to as close to end as possible. If final instruction + // would exceed end, does NOT execute it and leaves m.spc_time < end. + if ( end_time > m.spc_time ) + run_until_( end_time ); + + m.spc_time -= end_time; + m.extra_clocks += end_time; + + // Greatest number of clocks early that emulation can stop early due to + // not being able to execute current instruction without going over + // allowed time. + assert( -cpu_lag_max <= m.spc_time && m.spc_time <= 0 ); + + // Catch timers up to CPU + for ( int i = 0; i < timer_count; i++ ) + run_timer( &m.timers [i], 0 ); + + // Catch DSP up to CPU + if ( m.dsp_time < 0 ) + { + RUN_DSP( 0, max_reg_time ); + } + + // Save any extra samples beyond what should be generated + if ( m.buf_begin ) + save_extra(); +} + +// Inclusion here allows static memory access functions and better optimization +#include "SPC_CPU.h" diff --git a/waterbox/sameboy/snes_spc/SNES_SPC.h b/waterbox/sameboy/snes_spc/SNES_SPC.h new file mode 100644 index 0000000000..fb1ad18a45 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC.h @@ -0,0 +1,284 @@ +// SNES SPC-700 APU emulator + +// snes_spc 0.9.0 +#ifndef SNES_SPC_H +#define SNES_SPC_H + +#include "SPC_DSP.h" +#include "blargg_endian.h" +#include + +struct SNES_SPC { +public: + typedef BOOST::uint8_t uint8_t; + + // Must be called once before using + blargg_err_t init(); + + // Sample pairs generated per second + enum { sample_rate = 32000 }; + +// Emulator use + + // Sets IPL ROM data. Library does not include ROM data. Most SPC music files + // don't need ROM, but a full emulator must provide this. + enum { rom_size = 0x40 }; + void init_rom( uint8_t const rom [rom_size] ); + + // Sets destination for output samples + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since last set + int sample_count() const; + + // Resets SPC to power-on state. This resets your output buffer, so you must + // call set_output() after this. + void reset(); + + // Emulates pressing reset switch on SNES. This resets your output buffer, so + // you must call set_output() after this. + void soft_reset(); + + // 1024000 SPC clocks per second, sample pair every 32 clocks + typedef int time_t; + enum { clock_rate = 1024000 }; + enum { clocks_per_sample = 32 }; + + // Emulated port read/write at specified time + enum { port_count = 4 }; + int read_port ( time_t, int port ); + void write_port( time_t, int port, int data ); + + // Runs SPC to end_time and starts a new time frame at 0 + void end_frame( time_t end_time ); + + uint8_t* get_ram(); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + + // If true, prevents channels and global volumes from being phase-negated. + // Only supported by fast DSP. + void disable_surround( bool disable = true ); + + // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. + enum { tempo_unit = 0x100 }; + void set_tempo( int ); + +// SPC music files + + // Loads SPC data into emulator + enum { spc_min_file_size = 0x10180 }; + enum { spc_file_size = 0x10200 }; + blargg_err_t load_spc( void const* in, long size ); + + // Clears echo region. Useful after loading an SPC as many have garbage in echo. + void clear_echo(); + + // Plays for count samples and write samples to out. Discards samples if out + // is NULL. Count must be a multiple of 2 since output is stereo. + blargg_err_t play( int count, sample_t* out ); + + // Skips count samples. Several times faster than play() when using fast DSP. + blargg_err_t skip( int count ); + +// State save/load (only available with accurate DSP) + +#if !SPC_NO_COPY_STATE_FUNCS + // Saves/loads state + enum { state_size = 67 * 1024L }; // maximum space needed when saving + typedef SPC_DSP::copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Writes minimal header to spc_out + static void init_header( void* spc_out ); + + // Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. + // Does not set up SPC header; use init_header() for that. + void save_spc( void* spc_out ); + + // Returns true if new key-on events occurred since last check. Useful for + // trimming silence while saving an SPC. + bool check_kon(); +#endif + +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::uint16_t uint16_t; + + // Time relative to m_spc_time. Speeds up code a bit by eliminating need to + // constantly add m_spc_time to time from CPU. CPU uses time that ends at + // 0 to eliminate reloading end time every instruction. It pays off. + typedef int rel_time_t; + + struct Timer + { + rel_time_t next_time; // time of next event + int prescaler; + int period; + int divider; + int enabled; + int counter; + }; + enum { reg_count = 0x10 }; + enum { timer_count = 3 }; + enum { extra_size = SPC_DSP::extra_size }; + + enum { signature_size = 35 }; + +private: + SPC_DSP dsp; + + #if SPC_LESS_ACCURATE + static signed char const reg_times_ [256]; + signed char reg_times [256]; + #endif + + struct state_t + { + Timer timers [timer_count]; + + uint8_t smp_regs [2] [reg_count]; + + struct + { + int pc; + int a; + int x; + int y; + int psw; + int sp; + } cpu_regs; + + rel_time_t dsp_time; + time_t spc_time; + bool echo_accessed; + + int tempo; + int skipped_kon; + int skipped_koff; + const char* cpu_error; + + int extra_clocks; + sample_t* buf_begin; + sample_t const* buf_end; + sample_t* extra_pos; + sample_t extra_buf [extra_size]; + + int rom_enabled; + uint8_t rom [rom_size]; + uint8_t hi_ram [rom_size]; + + unsigned char cycle_table [256]; + + struct + { + // padding to neutralize address overflow + union { + uint8_t padding1 [0x100]; + uint16_t align; // makes compiler align data for 16-bit access + } padding1 [1]; + uint8_t ram [0x10000]; + uint8_t padding2 [0x100]; + } ram; + }; + state_t m; + + enum { rom_addr = 0xFFC0 }; + + enum { skipping_time = 127 }; + + // Value that padding should be filled with + enum { cpu_pad_fill = 0xFF }; + + enum { + r_test = 0x0, r_control = 0x1, + r_dspaddr = 0x2, r_dspdata = 0x3, + r_cpuio0 = 0x4, r_cpuio1 = 0x5, + r_cpuio2 = 0x6, r_cpuio3 = 0x7, + r_f8 = 0x8, r_f9 = 0x9, + r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC, + r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF + }; + + void timers_loaded(); + void enable_rom( int enable ); + void reset_buf(); + void save_extra(); + void load_regs( uint8_t const in [reg_count] ); + void ram_loaded(); + void regs_loaded(); + void reset_time_regs(); + void reset_common( int timer_counter_init ); + + Timer* run_timer_ ( Timer* t, rel_time_t ); + Timer* run_timer ( Timer* t, rel_time_t ); + int dsp_read ( rel_time_t ); + void dsp_write ( int data, rel_time_t ); + void cpu_write_smp_reg_( int data, rel_time_t, int addr ); + void cpu_write_smp_reg ( int data, rel_time_t, int addr ); + void cpu_write_high ( int data, int i, rel_time_t ); + void cpu_write ( int data, int addr, rel_time_t ); + int cpu_read_smp_reg ( int i, rel_time_t ); + int cpu_read ( int addr, rel_time_t ); + unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t ); + + bool check_echo_access ( int addr ); + uint8_t* run_until_( time_t end_time ); + + struct spc_file_t + { + char signature [signature_size]; + uint8_t has_id666; + uint8_t version; + uint8_t pcl, pch; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t psw; + uint8_t sp; + char text [212]; + uint8_t ram [0x10000]; + uint8_t dsp [128]; + uint8_t unused [0x40]; + uint8_t ipl_rom [0x40]; + }; + + static char const signature [signature_size + 1]; + + void save_regs( uint8_t out [reg_count] ); +}; + +#include + +inline uint8_t* SNES_SPC::get_ram() { return m.ram.ram; } + +inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; } + +inline int SNES_SPC::read_port( time_t t, int port ) +{ + assert( (unsigned) port < port_count ); + return run_until_( t ) [port]; +} + +inline void SNES_SPC::write_port( time_t t, int port, int data ) +{ + assert( (unsigned) port < port_count ); + run_until_( t ) [0x10 + port] = data; +} + +inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); } + +inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); } + +#if !SPC_NO_COPY_STATE_FUNCS +inline bool SNES_SPC::check_kon() { return dsp.check_kon(); } +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp b/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp new file mode 100644 index 0000000000..87288ab8ee --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC_misc.cpp @@ -0,0 +1,380 @@ +// SPC emulation support: init, sample buffering, reset, SPC loading + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +// (n ? n : 256) +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + + +//// Init + +blargg_err_t SNES_SPC::init() +{ + memset( &m, 0, sizeof m ); + dsp.init( RAM ); + + m.tempo = tempo_unit; + + // Most SPC music doesn't need ROM, and almost all the rest only rely + // on these two bytes + m.rom [0x3E] = 0xFF; + m.rom [0x3F] = 0xC0; + + static unsigned char const cycle_table [128] = + {// 01 23 45 67 89 AB CD EF + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x68, // 0 + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x46, // 1 + 0x28,0x47,0x34,0x36,0x26,0x54,0x54,0x74, // 2 + 0x48,0x47,0x45,0x56,0x55,0x65,0x22,0x38, // 3 + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x66, // 4 + 0x48,0x47,0x45,0x56,0x55,0x45,0x22,0x43, // 5 + 0x28,0x47,0x34,0x36,0x26,0x44,0x54,0x75, // 6 + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x36, // 7 + 0x28,0x47,0x34,0x36,0x26,0x54,0x52,0x45, // 8 + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0xC5, // 9 + 0x38,0x47,0x34,0x36,0x26,0x44,0x52,0x44, // A + 0x48,0x47,0x45,0x56,0x55,0x55,0x22,0x34, // B + 0x38,0x47,0x45,0x47,0x25,0x64,0x52,0x49, // C + 0x48,0x47,0x56,0x67,0x45,0x55,0x22,0x83, // D + 0x28,0x47,0x34,0x36,0x24,0x53,0x43,0x40, // E + 0x48,0x47,0x45,0x56,0x34,0x54,0x22,0x60, // F + }; + + // unpack cycle table + for ( int i = 0; i < 128; i++ ) + { + int n = cycle_table [i]; + m.cycle_table [i * 2 + 0] = n >> 4; + m.cycle_table [i * 2 + 1] = n & 0x0F; + } + + #if SPC_LESS_ACCURATE + memcpy( reg_times, reg_times_, sizeof reg_times ); + #endif + + reset(); + return 0; +} + +void SNES_SPC::init_rom( uint8_t const in [rom_size] ) +{ + memcpy( m.rom, in, sizeof m.rom ); +} + +void SNES_SPC::set_tempo( int t ) +{ + m.tempo = t; + int const timer2_shift = 4; // 64 kHz + int const other_shift = 3; // 8 kHz + + #if SPC_DISABLE_TEMPO + m.timers [2].prescaler = timer2_shift; + m.timers [1].prescaler = timer2_shift + other_shift; + m.timers [0].prescaler = timer2_shift + other_shift; + #else + if ( !t ) + t = 1; + int const timer2_rate = 1 << timer2_shift; + int rate = (timer2_rate * tempo_unit + (t >> 1)) / t; + if ( rate < timer2_rate / 4 ) + rate = timer2_rate / 4; // max 4x tempo + m.timers [2].prescaler = rate; + m.timers [1].prescaler = rate << other_shift; + m.timers [0].prescaler = rate << other_shift; + #endif +} + +// Timer registers have been loaded. Applies these to the timers. Does not +// reset timer prescalers or dividers. +void SNES_SPC::timers_loaded() +{ + int i; + for ( i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + t->period = IF_0_THEN_256( REGS [r_t0target + i] ); + t->enabled = REGS [r_control] >> i & 1; + t->counter = REGS_IN [r_t0out + i] & 0x0F; + } + + set_tempo( m.tempo ); +} + +// Loads registers from unified 16-byte format +void SNES_SPC::load_regs( uint8_t const in [reg_count] ) +{ + memcpy( REGS, in, reg_count ); + memcpy( REGS_IN, REGS, reg_count ); + + // These always read back as 0 + REGS_IN [r_test ] = 0; + REGS_IN [r_control ] = 0; + REGS_IN [r_t0target] = 0; + REGS_IN [r_t1target] = 0; + REGS_IN [r_t2target] = 0; +} + +// RAM was just loaded from SPC, with $F0-$FF containing SMP registers +// and timer counts. Copies these to proper registers. +void SNES_SPC::ram_loaded() +{ + m.rom_enabled = 0; + load_regs( &RAM [0xF0] ); + + // Put STOP instruction around memory to catch PC underflow/overflow + memset( m.ram.padding1, cpu_pad_fill, sizeof m.ram.padding1 ); + memset( m.ram.padding2, cpu_pad_fill, sizeof m.ram.padding2 ); +} + +// Registers were just loaded. Applies these new values. +void SNES_SPC::regs_loaded() +{ + enable_rom( REGS [r_control] & 0x80 ); + timers_loaded(); +} + +void SNES_SPC::reset_time_regs() +{ + m.cpu_error = 0; + m.echo_accessed = 0; + m.spc_time = 0; + m.dsp_time = 0; + #if SPC_LESS_ACCURATE + m.dsp_time = clocks_per_sample + 1; + #endif + + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + t->next_time = 1; + t->divider = 0; + } + + regs_loaded(); + + m.extra_clocks = 0; + reset_buf(); +} + +void SNES_SPC::reset_common( int timer_counter_init ) +{ + int i; + for ( i = 0; i < timer_count; i++ ) + REGS_IN [r_t0out + i] = timer_counter_init; + + // Run IPL ROM + memset( &m.cpu_regs, 0, sizeof m.cpu_regs ); + m.cpu_regs.pc = rom_addr; + + REGS [r_test ] = 0x0A; + REGS [r_control] = 0xB0; // ROM enabled, clear ports + for ( i = 0; i < port_count; i++ ) + REGS_IN [r_cpuio0 + i] = 0; + + reset_time_regs(); +} + +void SNES_SPC::soft_reset() +{ + reset_common( 0 ); + dsp.soft_reset(); +} + +void SNES_SPC::reset() +{ + memset( RAM, 0xFF, 0x10000 ); + ram_loaded(); + reset_common( 0x0F ); + dsp.reset(); +} + +char const SNES_SPC::signature [signature_size + 1] = + "SNES-SPC700 Sound File Data v0.30\x1A\x1A"; + +blargg_err_t SNES_SPC::load_spc( void const* data, long size ) +{ + spc_file_t const* const spc = (spc_file_t const*) data; + + // be sure compiler didn't insert any padding into fle_t + assert( sizeof (spc_file_t) == spc_min_file_size + 0x80 ); + + // Check signature and file size + if ( size < signature_size || memcmp( spc, signature, 27 ) ) + return "Not an SPC file"; + + if ( size < spc_min_file_size ) + return "Corrupt SPC file"; + + // CPU registers + m.cpu_regs.pc = spc->pch * 0x100 + spc->pcl; + m.cpu_regs.a = spc->a; + m.cpu_regs.x = spc->x; + m.cpu_regs.y = spc->y; + m.cpu_regs.psw = spc->psw; + m.cpu_regs.sp = spc->sp; + + // RAM and registers + memcpy( RAM, spc->ram, 0x10000 ); + ram_loaded(); + + // DSP registers + dsp.load( spc->dsp ); + + reset_time_regs(); + + return 0; +} + +void SNES_SPC::clear_echo() +{ + if ( !(dsp.read( SPC_DSP::r_flg ) & 0x20) ) + { + int addr = 0x100 * dsp.read( SPC_DSP::r_esa ); + int end = addr + 0x800 * (dsp.read( SPC_DSP::r_edl ) & 0x0F); + if ( end > 0x10000 ) + end = 0x10000; + memset( &RAM [addr], 0xFF, end - addr ); + } +} + + +//// Sample output + +void SNES_SPC::reset_buf() +{ + // Start with half extra buffer of silence + sample_t* out = m.extra_buf; + while ( out < &m.extra_buf [extra_size / 2] ) + *out++ = 0; + + m.extra_pos = out; + m.buf_begin = 0; + + dsp.set_output( 0, 0 ); +} + +void SNES_SPC::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // size must be even + + m.extra_clocks &= clocks_per_sample - 1; + if ( out ) + { + sample_t const* out_end = out + size; + m.buf_begin = out; + m.buf_end = out_end; + + // Copy extra to output + sample_t const* in = m.extra_buf; + while ( in < m.extra_pos && out < out_end ) + *out++ = *in++; + + // Handle output being full already + if ( out >= out_end ) + { + // Have DSP write to remaining extra space + out = dsp.extra(); + out_end = &dsp.extra() [extra_size]; + + // Copy any remaining extra samples as if DSP wrote them + while ( in < m.extra_pos ) + *out++ = *in++; + assert( out <= out_end ); + } + + dsp.set_output( out, out_end - out ); + } + else + { + reset_buf(); + } +} + +void SNES_SPC::save_extra() +{ + // Get end pointers + sample_t const* main_end = m.buf_end; // end of data written to buf + sample_t const* dsp_end = dsp.out_pos(); // end of data written to dsp.extra() + if ( m.buf_begin <= dsp_end && dsp_end <= main_end ) + { + main_end = dsp_end; + dsp_end = dsp.extra(); // nothing in DSP's extra + } + + // Copy any extra samples at these ends into extra_buf + sample_t* out = m.extra_buf; + sample_t const* in; + for ( in = m.buf_begin + sample_count(); in < main_end; in++ ) + *out++ = *in; + for ( in = dsp.extra(); in < dsp_end ; in++ ) + *out++ = *in; + + m.extra_pos = out; + assert( out <= &m.extra_buf [extra_size] ); +} + +blargg_err_t SNES_SPC::play( int count, sample_t* out ) +{ + require( (count & 1) == 0 ); // must be even + if ( count ) + { + set_output( out, count ); + end_frame( count * (clocks_per_sample / 2) ); + } + + const char* err = m.cpu_error; + m.cpu_error = 0; + return err; +} + +blargg_err_t SNES_SPC::skip( int count ) +{ + #if SPC_LESS_ACCURATE + if ( count > 2 * sample_rate * 2 ) + { + set_output( 0, 0 ); + + // Skip a multiple of 4 samples + time_t end = count; + count = (count & 3) + 1 * sample_rate * 2; + end = (end - count) * (clocks_per_sample / 2); + + m.skipped_kon = 0; + m.skipped_koff = 0; + + // Preserve DSP and timer synchronization + // TODO: verify that this really preserves it + int old_dsp_time = m.dsp_time + m.spc_time; + m.dsp_time = end - m.spc_time + skipping_time; + end_frame( end ); + m.dsp_time = m.dsp_time - skipping_time + old_dsp_time; + + dsp.write( SPC_DSP::r_koff, m.skipped_koff & ~m.skipped_kon ); + dsp.write( SPC_DSP::r_kon , m.skipped_kon ); + clear_echo(); + } + #endif + + return play( count, 0 ); +} diff --git a/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp b/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp new file mode 100644 index 0000000000..a8052b6587 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SNES_SPC_state.cpp @@ -0,0 +1,129 @@ +// SPC emulation state save/load: copy_state(), save_spc() +// Separate file to avoid linking in unless needed + +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SNES_SPC.h" + +#if !SPC_NO_COPY_STATE_FUNCS + +#include + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define RAM (m.ram.ram) +#define REGS (m.smp_regs [0]) +#define REGS_IN (m.smp_regs [1]) + +void SNES_SPC::save_regs( uint8_t out [reg_count] ) +{ + // Use current timer counter values + for ( int i = 0; i < timer_count; i++ ) + out [r_t0out + i] = m.timers [i].counter; + + // Last written values + memcpy( out, REGS, r_t0out ); +} + +void SNES_SPC::init_header( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + spc->has_id666 = 26; // has none + spc->version = 30; + memcpy( spc, signature, sizeof spc->signature ); + memset( spc->text, 0, sizeof spc->text ); +} + +void SNES_SPC::save_spc( void* spc_out ) +{ + spc_file_t* const spc = (spc_file_t*) spc_out; + + // CPU + spc->pcl = (uint8_t) (m.cpu_regs.pc >> 0); + spc->pch = (uint8_t) (m.cpu_regs.pc >> 8); + spc->a = m.cpu_regs.a; + spc->x = m.cpu_regs.x; + spc->y = m.cpu_regs.y; + spc->psw = m.cpu_regs.psw; + spc->sp = m.cpu_regs.sp; + + // RAM, ROM + memcpy( spc->ram, RAM, sizeof spc->ram ); + if ( m.rom_enabled ) + memcpy( spc->ram + rom_addr, m.hi_ram, sizeof m.hi_ram ); + memset( spc->unused, 0, sizeof spc->unused ); + memcpy( spc->ipl_rom, m.rom, sizeof spc->ipl_rom ); + + // SMP registers + save_regs( &spc->ram [0xF0] ); + int i; + for ( i = 0; i < port_count; i++ ) + spc->ram [0xF0 + r_cpuio0 + i] = REGS_IN [r_cpuio0 + i]; + + // DSP registers + for ( i = 0; i < SPC_DSP::register_count; i++ ) + spc->dsp [i] = dsp.read( i ); +} + +void SNES_SPC::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // Make state data more readable by putting 64K RAM, 16 SMP registers, + // then DSP (with its 128 registers) first + + // RAM + enable_rom( 0 ); // will get re-enabled if necessary in regs_loaded() below + copier.copy( RAM, 0x10000 ); + + { + // SMP registers + uint8_t out_ports [port_count]; + uint8_t regs [reg_count]; + memcpy( out_ports, ®S [r_cpuio0], sizeof out_ports ); + save_regs( regs ); + copier.copy( regs, sizeof regs ); + copier.copy( out_ports, sizeof out_ports ); + load_regs( regs ); + regs_loaded(); + memcpy( ®S [r_cpuio0], out_ports, sizeof out_ports ); + } + + // CPU registers + SPC_COPY( uint16_t, m.cpu_regs.pc ); + SPC_COPY( uint8_t, m.cpu_regs.a ); + SPC_COPY( uint8_t, m.cpu_regs.x ); + SPC_COPY( uint8_t, m.cpu_regs.y ); + SPC_COPY( uint8_t, m.cpu_regs.psw ); + SPC_COPY( uint8_t, m.cpu_regs.sp ); + copier.extra(); + + SPC_COPY( int16_t, m.spc_time ); + SPC_COPY( int16_t, m.dsp_time ); + + // DSP + dsp.copy_state( io, copy ); + + // Timers + for ( int i = 0; i < timer_count; i++ ) + { + Timer* t = &m.timers [i]; + SPC_COPY( int16_t, t->next_time ); + SPC_COPY( uint8_t, t->divider ); + copier.extra(); + } + copier.extra(); +} +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_CPU.h b/waterbox/sameboy/snes_spc/SPC_CPU.h new file mode 100644 index 0000000000..664fc4886e --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_CPU.h @@ -0,0 +1,1220 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +//// Memory access + +#if SPC_MORE_ACCURACY + #define SUSPICIOUS_OPCODE( name ) ((void) 0) +#else + #define SUSPICIOUS_OPCODE( name ) dprintf( "SPC: suspicious opcode: " name "\n" ) +#endif + +#define CPU_READ( time, offset, addr )\ + cpu_read( addr, time + offset ) + +#define CPU_WRITE( time, offset, addr, data )\ + cpu_write( data, addr, time + offset ) + +#if SPC_MORE_ACCURACY + #define CPU_READ_TIMER( time, offset, addr, out )\ + { out = CPU_READ( time, offset, addr ); } + +#else + // timers are by far the most common thing read from dp + #define CPU_READ_TIMER( time, offset, addr_, out )\ + {\ + rel_time_t adj_time = time + offset;\ + int dp_addr = addr_;\ + int ti = dp_addr - (r_t0out + 0xF0);\ + if ( (unsigned) ti < timer_count )\ + {\ + Timer* t = &m.timers [ti];\ + if ( adj_time >= t->next_time )\ + t = run_timer_( t, adj_time );\ + out = t->counter;\ + t->counter = 0;\ + }\ + else\ + {\ + out = ram [dp_addr];\ + int i = dp_addr - 0xF0;\ + if ( (unsigned) i < 0x10 )\ + out = cpu_read_smp_reg( i, adj_time );\ + }\ + } +#endif + +#define TIME_ADJ( n ) (n) + +#define READ_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), (addr), out ) +#define READ( time, addr ) CPU_READ ( rel_time, TIME_ADJ(time), (addr) ) +#define WRITE( time, addr, data ) CPU_WRITE( rel_time, TIME_ADJ(time), (addr), (data) ) + +#define DP_ADDR( addr ) (dp + (addr)) + +#define READ_DP_TIMER( time, addr, out ) CPU_READ_TIMER( rel_time, TIME_ADJ(time), DP_ADDR( addr ), out ) +#define READ_DP( time, addr ) READ ( time, DP_ADDR( addr ) ) +#define WRITE_DP( time, addr, data ) WRITE( time, DP_ADDR( addr ), data ) + +#define READ_PROG16( addr ) GET_LE16( ram + (addr) ) + +#define SET_PC( n ) (pc = ram + (n)) +#define GET_PC() (pc - ram) +#define READ_PC( pc ) (*(pc)) +#define READ_PC16( pc ) GET_LE16( pc ) + +// TODO: remove non-wrapping versions? +#define SPC_NO_SP_WRAPAROUND 0 + +#define SET_SP( v ) (sp = ram + 0x101 + (v)) +#define GET_SP() (sp - 0x101 - ram) + +#if SPC_NO_SP_WRAPAROUND +#define PUSH16( v ) (sp -= 2, SET_LE16( sp, v )) +#define PUSH( v ) (void) (*--sp = (uint8_t) (v)) +#define POP( out ) (void) ((out) = *sp++) + +#else +#define PUSH16( data )\ +{\ + int addr = (sp -= 2) - ram;\ + if ( addr > 0x100 )\ + {\ + SET_LE16( sp, data );\ + }\ + else\ + {\ + ram [(uint8_t) addr + 0x100] = (uint8_t) data;\ + sp [1] = (uint8_t) (data >> 8);\ + sp += 0x100;\ + }\ +} + +#define PUSH( data )\ +{\ + *--sp = (uint8_t) (data);\ + if ( sp - ram == 0x100 )\ + sp += 0x100;\ +} + +#define POP( out )\ +{\ + out = *sp++;\ + if ( sp - ram == 0x201 )\ + {\ + out = sp [-0x101];\ + sp -= 0x100;\ + }\ +} + +#endif + +#define MEM_BIT( rel ) CPU_mem_bit( pc, rel_time + rel ) + +unsigned SNES_SPC::CPU_mem_bit( uint8_t const* pc, rel_time_t rel_time ) +{ + unsigned addr = READ_PC16( pc ); + unsigned t = READ( 0, addr & 0x1FFF ) >> (addr >> 13); + return t << 8 & 0x100; +} + +//// Status flag handling + +// Hex value in name to clarify code and bit shifting. +// Flag stored in indicated variable during emulation +int const n80 = 0x80; // nz +int const v40 = 0x40; // psw +int const p20 = 0x20; // dp +int const b10 = 0x10; // psw +int const h08 = 0x08; // psw +int const i04 = 0x04; // psw +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +int const nz_neg_mask = 0x880; // either bit set indicates N flag set + +#define GET_PSW( out )\ +{\ + out = psw & ~(n80 | p20 | z02 | c01);\ + out |= c >> 8 & c01;\ + out |= dp >> 3 & p20;\ + out |= ((nz >> 4) | nz) & n80;\ + if ( !(uint8_t) nz ) out |= z02;\ +} + +#define SET_PSW( in )\ +{\ + psw = in;\ + c = in << 8;\ + dp = in << 3 & 0x100;\ + nz = (in << 4 & 0x800) | (~in & z02);\ +} + +SPC_CPU_RUN_FUNC +{ + uint8_t* const ram = RAM; + int a = m.cpu_regs.a; + int x = m.cpu_regs.x; + int y = m.cpu_regs.y; + uint8_t const* pc; + uint8_t* sp; + int psw; + int c; + int nz; + int dp; + + SET_PC( m.cpu_regs.pc ); + SET_SP( m.cpu_regs.sp ); + SET_PSW( m.cpu_regs.psw ); + + goto loop; + + + // Main loop + +cbranch_taken_loop: + pc += *(BOOST::int8_t const*) pc; +inc_pc_loop: + pc++; +loop: +{ + unsigned opcode; + unsigned data; + + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + opcode = *pc; + if ( (rel_time += m.cycle_table [opcode]) > 0 ) + goto out_of_time; + + #ifdef SPC_CPU_OPCODE_HOOK + SPC_CPU_OPCODE_HOOK( GET_PC(), opcode ); + #endif + /* + //SUB_CASE_COUNTER( 1 ); + #define PROFILE_TIMER_LOOP( op, addr, len )\ + if ( opcode == op )\ + {\ + int cond = (unsigned) ((addr) - 0xFD) < 3 &&\ + pc [len] == 0xF0 && pc [len+1] == 0xFE - len;\ + SUB_CASE_COUNTER( op && cond );\ + } + + PROFILE_TIMER_LOOP( 0xEC, GET_LE16( pc + 1 ), 3 ); + PROFILE_TIMER_LOOP( 0xEB, pc [1], 2 ); + PROFILE_TIMER_LOOP( 0xE4, pc [1], 2 ); + */ + + // TODO: if PC is at end of memory, this will get wrong operand (very obscure) + data = *++pc; + switch ( opcode ) + { + +// Common instructions + +#define BRANCH( cond )\ +{\ + pc++;\ + pc += (BOOST::int8_t) data;\ + if ( cond )\ + goto loop;\ + pc -= (BOOST::int8_t) data;\ + rel_time -= 2;\ + goto loop;\ +} + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ) // 89% taken + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ) + + case 0x3F:{// CALL + int old_addr = GET_PC() + 2; + SET_PC( READ_PC16( pc ) ); + PUSH16( old_addr ); + goto loop; + } + + case 0x6F:// RET + #if SPC_NO_SP_WRAPAROUND + { + SET_PC( GET_LE16( sp ) ); + sp += 2; + } + #else + { + int addr = sp - ram; + SET_PC( GET_LE16( sp ) ); + sp += 2; + if ( addr < 0x1FF ) + goto loop; + + SET_PC( sp [-0x101] * 0x100 + ram [(uint8_t) addr + 0x100] ); + sp -= 0x100; + } + #endif + goto loop; + + case 0xE4: // MOV a,dp + ++pc; + // 80% from timer + READ_DP_TIMER( 0, data, a = nz ); + goto loop; + + case 0xFA:{// MOV dp,dp + int temp; + READ_DP_TIMER( -2, data, temp ); + data = temp + no_read_before_write ; + } + // fall through + case 0x8F:{// MOV dp,#imm + int temp = READ_PC( pc + 1 ); + pc += 2; + + #if !SPC_MORE_ACCURACY + { + int i = dp + temp; + ram [i] = (uint8_t) data; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 76% + { + REGS [i] = (uint8_t) data; + + // Registers other than $F2 and $F4-$F7 + //if ( i != 2 && i != 4 && i != 5 && i != 6 && i != 7 ) + if ( ((~0x2F00 << (bits_in_int - 16)) << i) < 0 ) // 12% + cpu_write_smp_reg( data, rel_time, i ); + } + } + #else + WRITE_DP( 0, temp, data ); + #endif + goto loop; + } + + case 0xC4: // MOV dp,a + ++pc; + #if !SPC_MORE_ACCURACY + { + int i = dp + data; + ram [i] = (uint8_t) a; + i -= 0xF0; + if ( (unsigned) i < 0x10 ) // 39% + { + unsigned sel = i - 2; + REGS [i] = (uint8_t) a; + + if ( sel == 1 ) // 51% $F3 + dsp_write( a, rel_time ); + else if ( sel > 1 ) // 1% not $F2 or $F3 + cpu_write_smp_reg_( a, rel_time, i ); + } + } + #else + WRITE_DP( 0, data, a ); + #endif + goto loop; + +#define CASE( n ) case n: + +// Define common address modes based on opcode for immediate mode. Execution +// ends with data set to the address of the operand. +#define ADDR_MODES_( op )\ + CASE( op - 0x02 ) /* (X) */\ + data = x + dp;\ + pc--;\ + goto end_##op;\ + CASE( op + 0x0F ) /* (dp)+Y */\ + data = READ_PROG16( data + dp ) + y;\ + goto end_##op;\ + CASE( op - 0x01 ) /* (dp+X) */\ + data = READ_PROG16( ((uint8_t) (data + x)) + dp );\ + goto end_##op;\ + CASE( op + 0x0E ) /* abs+Y */\ + data += y;\ + goto abs_##op;\ + CASE( op + 0x0D ) /* abs+X */\ + data += x;\ + CASE( op - 0x03 ) /* abs */\ + abs_##op:\ + data += 0x100 * READ_PC( ++pc );\ + goto end_##op;\ + CASE( op + 0x0C ) /* dp+X */\ + data = (uint8_t) (data + x); + +#define ADDR_MODES_NO_DP( op )\ + ADDR_MODES_( op )\ + data += dp;\ + end_##op: + +#define ADDR_MODES( op )\ + ADDR_MODES_( op )\ + CASE( op - 0x04 ) /* dp */\ + data += dp;\ + end_##op: + +// 1. 8-bit Data Transmission Commands. Group I + + ADDR_MODES_NO_DP( 0xE8 ) // MOV A,addr + a = nz = READ( 0, data ); + goto inc_pc_loop; + + case 0xBF:{// MOV A,(X)+ + int temp = x + dp; + x = (uint8_t) (x + 1); + a = nz = READ( -1, temp ); + goto loop; + } + + case 0xE8: // MOV A,imm + a = data; + nz = data; + goto inc_pc_loop; + + case 0xF9: // MOV X,dp+Y + data = (uint8_t) (data + y); + case 0xF8: // MOV X,dp + READ_DP_TIMER( 0, data, x = nz ); + goto inc_pc_loop; + + case 0xE9: // MOV X,abs + data = READ_PC16( pc ); + ++pc; + data = READ( 0, data ); + case 0xCD: // MOV X,imm + x = data; + nz = data; + goto inc_pc_loop; + + case 0xFB: // MOV Y,dp+X + data = (uint8_t) (data + x); + case 0xEB: // MOV Y,dp + // 70% from timer + pc++; + READ_DP_TIMER( 0, data, y = nz ); + goto loop; + + case 0xEC:{// MOV Y,abs + int temp = READ_PC16( pc ); + pc += 2; + READ_TIMER( 0, temp, y = nz ); + //y = nz = READ( 0, temp ); + goto loop; + } + + case 0x8D: // MOV Y,imm + y = data; + nz = data; + goto inc_pc_loop; + +// 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 + + ADDR_MODES_NO_DP( 0xC8 ) // MOV addr,A + WRITE( 0, data, a ); + goto inc_pc_loop; + + { + int temp; + case 0xCC: // MOV abs,Y + temp = y; + goto mov_abs_temp; + case 0xC9: // MOV abs,X + temp = x; + mov_abs_temp: + WRITE( 0, READ_PC16( pc ), temp ); + pc += 2; + goto loop; + } + + case 0xD9: // MOV dp+Y,X + data = (uint8_t) (data + y); + case 0xD8: // MOV dp,X + WRITE( 0, data + dp, x ); + goto inc_pc_loop; + + case 0xDB: // MOV dp+X,Y + data = (uint8_t) (data + x); + case 0xCB: // MOV dp,Y + WRITE( 0, data + dp, y ); + goto inc_pc_loop; + +// 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. + + case 0x7D: // MOV A,X + a = x; + nz = x; + goto loop; + + case 0xDD: // MOV A,Y + a = y; + nz = y; + goto loop; + + case 0x5D: // MOV X,A + x = a; + nz = a; + goto loop; + + case 0xFD: // MOV Y,A + y = a; + nz = a; + goto loop; + + case 0x9D: // MOV X,SP + x = nz = GET_SP(); + goto loop; + + case 0xBD: // MOV SP,X + SET_SP( x ); + goto loop; + + //case 0xC6: // MOV (X),A (handled by MOV addr,A in group 2) + + case 0xAF: // MOV (X)+,A + WRITE_DP( 0, x, a + no_read_before_write ); + x++; + goto loop; + +// 5. 8-BIT LOGIC OPERATION COMMANDS + +#define LOGICAL_OP( op, func )\ + ADDR_MODES( op ) /* addr */\ + data = READ( 0, data );\ + case op: /* imm */\ + nz = a func##= data;\ + goto inc_pc_loop;\ + { unsigned addr;\ + case op + 0x11: /* X,Y */\ + data = READ_DP( -2, y );\ + addr = x + dp;\ + goto addr_##op;\ + case op + 0x01: /* dp,dp */\ + data = READ_DP( -3, data );\ + case op + 0x10:{/*dp,imm*/\ + uint8_t const* addr2 = pc + 1;\ + pc += 2;\ + addr = READ_PC( addr2 ) + dp;\ + }\ + addr_##op:\ + nz = data func READ( -1, addr );\ + WRITE( 0, addr, nz );\ + goto loop;\ + } + + LOGICAL_OP( 0x28, & ); // AND + + LOGICAL_OP( 0x08, | ); // OR + + LOGICAL_OP( 0x48, ^ ); // EOR + +// 4. 8-BIT ARITHMETIC OPERATION COMMANDS + + ADDR_MODES( 0x68 ) // CMP addr + data = READ( 0, data ); + case 0x68: // CMP imm + nz = a - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x79: // CMP (X),(Y) + data = READ_DP( -2, y ); + nz = READ_DP( -1, x ) - data; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x69: // CMP dp,dp + data = READ_DP( -3, data ); + case 0x78: // CMP dp,imm + nz = READ_DP( -1, READ_PC( ++pc ) ) - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x3E: // CMP X,dp + data += dp; + goto cmp_x_addr; + case 0x1E: // CMP X,abs + data = READ_PC16( pc ); + pc++; + cmp_x_addr: + data = READ( 0, data ); + case 0xC8: // CMP X,imm + nz = x - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + case 0x7E: // CMP Y,dp + data += dp; + goto cmp_y_addr; + case 0x5E: // CMP Y,abs + data = READ_PC16( pc ); + pc++; + cmp_y_addr: + data = READ( 0, data ); + case 0xAD: // CMP Y,imm + nz = y - data; + c = ~nz; + nz &= 0xFF; + goto inc_pc_loop; + + { + int addr; + case 0xB9: // SBC (x),(y) + case 0x99: // ADC (x),(y) + pc--; // compensate for inc later + data = READ_DP( -2, y ); + addr = x + dp; + goto adc_addr; + case 0xA9: // SBC dp,dp + case 0x89: // ADC dp,dp + data = READ_DP( -3, data ); + case 0xB8: // SBC dp,imm + case 0x98: // ADC dp,imm + addr = READ_PC( ++pc ) + dp; + adc_addr: + nz = READ( -1, addr ); + goto adc_data; + +// catch ADC and SBC together, then decode later based on operand +#undef CASE +#define CASE( n ) case n: case (n) + 0x20: + ADDR_MODES( 0x88 ) // ADC/SBC addr + data = READ( 0, data ); + case 0xA8: // SBC imm + case 0x88: // ADC imm + addr = -1; // A + nz = a; + adc_data: { + int flags; + if ( opcode >= 0xA0 ) // SBC + data ^= 0xFF; + + flags = data ^ nz; + nz += data + (c >> 8 & 1); + flags ^= nz; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = nz; + if ( addr < 0 ) + { + a = (uint8_t) nz; + goto inc_pc_loop; + } + WRITE( 0, addr, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + } + + } + +// 6. ADDITION & SUBTRACTION COMMANDS + +#define INC_DEC_REG( reg, op )\ + nz = reg op;\ + reg = (uint8_t) nz;\ + goto loop; + + case 0xBC: INC_DEC_REG( a, + 1 ) // INC A + case 0x3D: INC_DEC_REG( x, + 1 ) // INC X + case 0xFC: INC_DEC_REG( y, + 1 ) // INC Y + + case 0x9C: INC_DEC_REG( a, - 1 ) // DEC A + case 0x1D: INC_DEC_REG( x, - 1 ) // DEC X + case 0xDC: INC_DEC_REG( y, - 1 ) // DEC Y + + case 0x9B: // DEC dp+X + case 0xBB: // INC dp+X + data = (uint8_t) (data + x); + case 0x8B: // DEC dp + case 0xAB: // INC dp + data += dp; + goto inc_abs; + case 0x8C: // DEC abs + case 0xAC: // INC abs + data = READ_PC16( pc ); + pc++; + inc_abs: + nz = (opcode >> 4 & 2) - 1; + nz += READ( -1, data ); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + +// 7. SHIFT, ROTATION COMMANDS + + case 0x5C: // LSR A + c = 0; + case 0x7C:{// ROR A + nz = (c >> 1 & 0x80) | (a >> 1); + c = a << 8; + a = nz; + goto loop; + } + + case 0x1C: // ASL A + c = 0; + case 0x3C:{// ROL A + int temp = c >> 8 & 1; + c = a << 1; + nz = c | temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x0B: // ASL dp + c = 0; + data += dp; + goto rol_mem; + case 0x1B: // ASL dp+X + c = 0; + case 0x3B: // ROL dp+X + data = (uint8_t) (data + x); + case 0x2B: // ROL dp + data += dp; + goto rol_mem; + case 0x0C: // ASL abs + c = 0; + case 0x2C: // ROL abs + data = READ_PC16( pc ); + pc++; + rol_mem: + nz = c >> 8 & 1; + nz |= (c = READ( -1, data ) << 1); + WRITE( 0, data, /*(uint8_t)*/ nz ); + goto inc_pc_loop; + + case 0x4B: // LSR dp + c = 0; + data += dp; + goto ror_mem; + case 0x5B: // LSR dp+X + c = 0; + case 0x7B: // ROR dp+X + data = (uint8_t) (data + x); + case 0x6B: // ROR dp + data += dp; + goto ror_mem; + case 0x4C: // LSR abs + c = 0; + case 0x6C: // ROR abs + data = READ_PC16( pc ); + pc++; + ror_mem: { + int temp = READ( -1, data ); + nz = (c >> 1 & 0x80) | (temp >> 1); + c = temp << 8; + WRITE( 0, data, nz ); + goto inc_pc_loop; + } + + case 0x9F: // XCN + nz = a = (a >> 4) | (uint8_t) (a << 4); + goto loop; + +// 8. 16-BIT TRANSMISION COMMANDS + + case 0xBA: // MOVW YA,dp + a = READ_DP( -2, data ); + nz = (a & 0x7F) | (a >> 1); + y = READ_DP( 0, (uint8_t) (data + 1) ); + nz |= y; + goto inc_pc_loop; + + case 0xDA: // MOVW dp,YA + WRITE_DP( -1, data, a ); + WRITE_DP( 0, (uint8_t) (data + 1), y + no_read_before_write ); + goto inc_pc_loop; + +// 9. 16-BIT OPERATION COMMANDS + + case 0x3A: // INCW dp + case 0x1A:{// DECW dp + int temp; + // low byte + data += dp; + temp = READ( -3, data ); + temp += (opcode >> 4 & 2) - 1; // +1 for INCW, -1 for DECW + nz = ((temp >> 1) | temp) & 0x7F; + WRITE( -2, data, /*(uint8_t)*/ temp ); + + // high byte + data = (uint8_t) (data + 1) + dp; + temp = (uint8_t) ((temp >> 8) + READ( -1, data )); + nz |= temp; + WRITE( 0, data, temp ); + + goto inc_pc_loop; + } + + case 0x7A: // ADDW YA,dp + case 0x9A:{// SUBW YA,dp + int lo = READ_DP( -2, data ); + int hi = READ_DP( 0, (uint8_t) (data + 1) ); + int result; + int flags; + + if ( opcode == 0x9A ) // SUBW + { + lo = (lo ^ 0xFF) + 1; + hi ^= 0xFF; + } + + lo += a; + result = y + hi + (lo >> 8); + flags = hi ^ y ^ result; + + psw = (psw & ~(v40 | h08)) | + (flags >> 1 & h08) | + ((flags + 0x80) >> 2 & v40); + c = result; + a = (uint8_t) lo; + result = (uint8_t) result; + y = result; + nz = (((lo >> 1) | lo) & 0x7F) | result; + + goto inc_pc_loop; + } + + case 0x5A: { // CMPW YA,dp + int temp = a - READ_DP( -1, data ); + nz = ((temp >> 1) | temp) & 0x7F; + temp = y + (temp >> 8); + temp -= READ_DP( 0, (uint8_t) (data + 1) ); + nz |= temp; + c = ~temp; + nz &= 0xFF; + goto inc_pc_loop; + } + +// 10. MULTIPLICATION & DIVISON COMMANDS + + case 0xCF: { // MUL YA + unsigned temp = y * a; + a = (uint8_t) temp; + nz = ((temp >> 1) | temp) & 0x7F; + y = temp >> 8; + nz |= y; + goto loop; + } + + case 0x9E: // DIV YA,X + { + unsigned ya = y * 0x100 + a; + + psw &= ~(h08 | v40); + + if ( y >= x ) + psw |= v40; + + if ( (y & 15) >= (x & 15) ) + psw |= h08; + + if ( y < x * 2 ) + { + a = ya / x; + y = ya - a * x; + } + else + { + a = 255 - (ya - x * 0x200) / (256 - x); + y = x + (ya - x * 0x200) % (256 - x); + } + + nz = (uint8_t) a; + a = (uint8_t) a; + + goto loop; + } + +// 11. DECIMAL COMPENSATION COMMANDS + + case 0xDF: // DAA + SUSPICIOUS_OPCODE( "DAA" ); + if ( a > 0x99 || c & 0x100 ) + { + a += 0x60; + c = 0x100; + } + + if ( (a & 0x0F) > 9 || psw & h08 ) + a += 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + + case 0xBE: // DAS + SUSPICIOUS_OPCODE( "DAS" ); + if ( a > 0x99 || !(c & 0x100) ) + { + a -= 0x60; + c = 0; + } + + if ( (a & 0x0F) > 9 || !(psw & h08) ) + a -= 0x06; + + nz = a; + a = (uint8_t) a; + goto loop; + +// 12. BRANCHING COMMANDS + + case 0x2F: // BRA rel + pc += (BOOST::int8_t) data; + goto inc_pc_loop; + + case 0x30: // BMI + BRANCH( (nz & nz_neg_mask) ) + + case 0x10: // BPL + BRANCH( !(nz & nz_neg_mask) ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x70: // BVS + BRANCH( psw & v40 ) + + case 0x50: // BVC + BRANCH( !(psw & v40) ) + + #define CBRANCH( cond )\ + {\ + pc++;\ + if ( cond )\ + goto cbranch_taken_loop;\ + rel_time -= 2;\ + goto inc_pc_loop;\ + } + + case 0x03: // BBS dp.bit,rel + case 0x23: + case 0x43: + case 0x63: + case 0x83: + case 0xA3: + case 0xC3: + case 0xE3: + CBRANCH( READ_DP( -4, data ) >> (opcode >> 5) & 1 ) + + case 0x13: // BBC dp.bit,rel + case 0x33: + case 0x53: + case 0x73: + case 0x93: + case 0xB3: + case 0xD3: + case 0xF3: + CBRANCH( !(READ_DP( -4, data ) >> (opcode >> 5) & 1) ) + + case 0xDE: // CBNE dp+X,rel + data = (uint8_t) (data + x); + // fall through + case 0x2E:{// CBNE dp,rel + int temp; + // 61% from timer + READ_DP_TIMER( -4, data, temp ); + CBRANCH( temp != a ) + } + + case 0x6E: { // DBNZ dp,rel + unsigned temp = READ_DP( -4, data ) - 1; + WRITE_DP( -3, (uint8_t) data, /*(uint8_t)*/ temp + no_read_before_write ); + CBRANCH( temp ) + } + + case 0xFE: // DBNZ Y,rel + y = (uint8_t) (y - 1); + BRANCH( y ) + + case 0x1F: // JMP [abs+X] + SET_PC( READ_PC16( pc ) + x ); + // fall through + case 0x5F: // JMP abs + SET_PC( READ_PC16( pc ) ); + goto loop; + +// 13. SUB-ROUTINE CALL RETURN COMMANDS + + case 0x0F:{// BRK + int temp; + int ret_addr = GET_PC(); + SUSPICIOUS_OPCODE( "BRK" ); + SET_PC( READ_PROG16( 0xFFDE ) ); // vector address verified + PUSH16( ret_addr ); + GET_PSW( temp ); + psw = (psw | b10) & ~i04; + PUSH( temp ); + goto loop; + } + + case 0x4F:{// PCALL offset + int ret_addr = GET_PC() + 1; + SET_PC( 0xFF00 | data ); + PUSH16( ret_addr ); + goto loop; + } + + case 0x01: // TCALL n + case 0x11: + case 0x21: + case 0x31: + case 0x41: + case 0x51: + case 0x61: + case 0x71: + case 0x81: + case 0x91: + case 0xA1: + case 0xB1: + case 0xC1: + case 0xD1: + case 0xE1: + case 0xF1: { + int ret_addr = GET_PC(); + SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) ); + PUSH16( ret_addr ); + goto loop; + } + +// 14. STACK OPERATION COMMANDS + + { + int temp; + case 0x7F: // RET1 + temp = *sp; + SET_PC( GET_LE16( sp + 1 ) ); + sp += 3; + goto set_psw; + case 0x8E: // POP PSW + POP( temp ); + set_psw: + SET_PSW( temp ); + goto loop; + } + + case 0x0D: { // PUSH PSW + int temp; + GET_PSW( temp ); + PUSH( temp ); + goto loop; + } + + case 0x2D: // PUSH A + PUSH( a ); + goto loop; + + case 0x4D: // PUSH X + PUSH( x ); + goto loop; + + case 0x6D: // PUSH Y + PUSH( y ); + goto loop; + + case 0xAE: // POP A + POP( a ); + goto loop; + + case 0xCE: // POP X + POP( x ); + goto loop; + + case 0xEE: // POP Y + POP( y ); + goto loop; + +// 15. BIT OPERATION COMMANDS + + case 0x02: // SET1 + case 0x22: + case 0x42: + case 0x62: + case 0x82: + case 0xA2: + case 0xC2: + case 0xE2: + case 0x12: // CLR1 + case 0x32: + case 0x52: + case 0x72: + case 0x92: + case 0xB2: + case 0xD2: + case 0xF2: { + int bit = 1 << (opcode >> 5); + int mask = ~bit; + if ( opcode & 0x10 ) + bit = 0; + data += dp; + WRITE( 0, data, (READ( -1, data ) & mask) | bit ); + goto inc_pc_loop; + } + + case 0x0E: // TSET1 abs + case 0x4E: // TCLR1 abs + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data ); + nz = (uint8_t) (a - temp); + temp &= ~a; + if ( opcode == 0x0E ) + temp |= a; + WRITE( 0, data, temp ); + } + goto loop; + + case 0x4A: // AND1 C,mem.bit + c &= MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x6A: // AND1 C,/mem.bit + c &= ~MEM_BIT( 0 ); + pc += 2; + goto loop; + + case 0x0A: // OR1 C,mem.bit + c |= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x2A: // OR1 C,/mem.bit + c |= ~MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0x8A: // EOR1 C,mem.bit + c ^= MEM_BIT( -1 ); + pc += 2; + goto loop; + + case 0xEA: // NOT1 mem.bit + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -1, data & 0x1FFF ); + temp ^= 1 << (data >> 13); + WRITE( 0, data & 0x1FFF, temp ); + } + goto loop; + + case 0xCA: // MOV1 mem.bit,C + data = READ_PC16( pc ); + pc += 2; + { + unsigned temp = READ( -2, data & 0x1FFF ); + unsigned bit = data >> 13; + temp = (temp & ~(1 << bit)) | ((c >> 8 & 1) << bit); + WRITE( 0, data & 0x1FFF, temp + no_read_before_write ); + } + goto loop; + + case 0xAA: // MOV1 C,mem.bit + c = MEM_BIT( 0 ); + pc += 2; + goto loop; + +// 16. PROGRAM PSW FLAG OPERATION COMMANDS + + case 0x60: // CLRC + c = 0; + goto loop; + + case 0x80: // SETC + c = ~0; + goto loop; + + case 0xED: // NOTC + c ^= 0x100; + goto loop; + + case 0xE0: // CLRV + psw &= ~(v40 | h08); + goto loop; + + case 0x20: // CLRP + dp = 0; + goto loop; + + case 0x40: // SETP + dp = 0x100; + goto loop; + + case 0xA0: // EI + SUSPICIOUS_OPCODE( "EI" ); + psw |= i04; + goto loop; + + case 0xC0: // DI + SUSPICIOUS_OPCODE( "DI" ); + psw &= ~i04; + goto loop; + +// 17. OTHER COMMANDS + + case 0x00: // NOP + goto loop; + + case 0xFF:{// STOP + // handle PC wrap-around + unsigned addr = GET_PC() - 1; + if ( addr >= 0x10000 ) + { + addr &= 0xFFFF; + SET_PC( addr ); + dprintf( "SPC: PC wrapped around\n" ); + goto loop; + } + } + // fall through + case 0xEF: // SLEEP + SUSPICIOUS_OPCODE( "STOP/SLEEP" ); + --pc; + rel_time = 0; + m.cpu_error = "SPC emulation error"; + goto stop; + } // switch + + assert( 0 ); // catch any unhandled instructions +} +out_of_time: + rel_time -= m.cycle_table [*pc]; // undo partial execution of opcode +stop: + + // Uncache registers + if ( GET_PC() >= 0x10000 ) + dprintf( "SPC: PC wrapped around\n" ); + m.cpu_regs.pc = (uint16_t) GET_PC(); + m.cpu_regs.sp = ( uint8_t) GET_SP(); + m.cpu_regs.a = ( uint8_t) a; + m.cpu_regs.x = ( uint8_t) x; + m.cpu_regs.y = ( uint8_t) y; + { + int temp; + GET_PSW( temp ); + m.cpu_regs.psw = (uint8_t) temp; + } +} +SPC_CPU_RUN_FUNC_END diff --git a/waterbox/sameboy/snes_spc/SPC_DSP.cpp b/waterbox/sameboy/snes_spc/SPC_DSP.cpp new file mode 100644 index 0000000000..dd180506fc --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_DSP.cpp @@ -0,0 +1,1018 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_DSP.h" + +#include "blargg_endian.h" +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#if INT_MAX < 0x7FFFFFFF + #error "Requires that int type have at least 32 bits" +#endif + +// TODO: add to blargg_endian.h +#define GET_LE16SA( addr ) ((BOOST::int16_t) GET_LE16( addr )) +#define GET_LE16A( addr ) GET_LE16( addr ) +#define SET_LE16A( addr, data ) SET_LE16( addr, data ) + +static BOOST::uint8_t const initial_regs [SPC_DSP::register_count] = +{ + 0x45,0x8B,0x5A,0x9A,0xE4,0x82,0x1B,0x78,0x00,0x00,0xAA,0x96,0x89,0x0E,0xE0,0x80, + 0x2A,0x49,0x3D,0xBA,0x14,0xA0,0xAC,0xC5,0x00,0x00,0x51,0xBB,0x9C,0x4E,0x7B,0xFF, + 0xF4,0xFD,0x57,0x32,0x37,0xD9,0x42,0x22,0x00,0x00,0x5B,0x3C,0x9F,0x1B,0x87,0x9A, + 0x6F,0x27,0xAF,0x7B,0xE5,0x68,0x0A,0xD9,0x00,0x00,0x9A,0xC5,0x9C,0x4E,0x7B,0xFF, + 0xEA,0x21,0x78,0x4F,0xDD,0xED,0x24,0x14,0x00,0x00,0x77,0xB1,0xD1,0x36,0xC1,0x67, + 0x52,0x57,0x46,0x3D,0x59,0xF4,0x87,0xA4,0x00,0x00,0x7E,0x44,0x9C,0x4E,0x7B,0xFF, + 0x75,0xF5,0x06,0x97,0x10,0xC3,0x24,0xBB,0x00,0x00,0x7B,0x7A,0xE0,0x60,0x12,0x0F, + 0xF7,0x74,0x1C,0xE5,0x39,0x3D,0x73,0xC1,0x00,0x00,0x7A,0xB3,0xFF,0x4E,0x7B,0xFF +}; + +// if ( io < -32768 ) io = -32768; +// if ( io > 32767 ) io = 32767; +#define CLAMP16( io )\ +{\ + if ( (int16_t) io != io )\ + io = (io >> 31) ^ 0x7FFF;\ +} + +// Access global DSP register +#define REG(n) m.regs [r_##n] + +// Access voice DSP register +#define VREG(r,n) r [v_##n] + +#define WRITE_SAMPLES( l, r, out ) \ +{\ + out [0] = l;\ + out [1] = r;\ + out += 2;\ + if ( out >= m.out_end )\ + {\ + check( out == m.out_end );\ + check( m.out_end != &m.extra [extra_size] || \ + (m.extra <= m.out_begin && m.extra < &m.extra [extra_size]) );\ + out = m.extra;\ + m.out_end = &m.extra [extra_size];\ + }\ +}\ + +void SPC_DSP::set_output( sample_t* out, int size ) +{ + require( (size & 1) == 0 ); // must be even + if ( !out ) + { + out = m.extra; + size = extra_size; + } + m.out_begin = out; + m.out = out; + m.out_end = out + size; +} + +// Volume registers and efb are signed! Easy to forget int8_t cast. +// Prefixes are to avoid accidental use of locals with same names. + +// Gaussian interpolation + +static short const gauss [512] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, + 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, + 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 36, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 58, 59, 60, 61, 62, 64, 65, 66, 67, 69, 70, 71, 73, 74, 76, 77, + 78, 80, 81, 83, 84, 86, 87, 89, 90, 92, 94, 95, 97, 99, 100, 102, + 104, 106, 107, 109, 111, 113, 115, 117, 118, 120, 122, 124, 126, 128, 130, 132, + 134, 137, 139, 141, 143, 145, 147, 150, 152, 154, 156, 159, 161, 163, 166, 168, + 171, 173, 175, 178, 180, 183, 186, 188, 191, 193, 196, 199, 201, 204, 207, 210, + 212, 215, 218, 221, 224, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 257, + 260, 263, 267, 270, 273, 276, 280, 283, 286, 290, 293, 297, 300, 304, 307, 311, + 314, 318, 321, 325, 328, 332, 336, 339, 343, 347, 351, 354, 358, 362, 366, 370, + 374, 378, 381, 385, 389, 393, 397, 401, 405, 410, 414, 418, 422, 426, 430, 434, + 439, 443, 447, 451, 456, 460, 464, 469, 473, 477, 482, 486, 491, 495, 499, 504, + 508, 513, 517, 522, 527, 531, 536, 540, 545, 550, 554, 559, 563, 568, 573, 577, + 582, 587, 592, 596, 601, 606, 611, 615, 620, 625, 630, 635, 640, 644, 649, 654, + 659, 664, 669, 674, 678, 683, 688, 693, 698, 703, 708, 713, 718, 723, 728, 732, + 737, 742, 747, 752, 757, 762, 767, 772, 777, 782, 787, 792, 797, 802, 806, 811, + 816, 821, 826, 831, 836, 841, 846, 851, 855, 860, 865, 870, 875, 880, 884, 889, + 894, 899, 904, 908, 913, 918, 923, 927, 932, 937, 941, 946, 951, 955, 960, 965, + 969, 974, 978, 983, 988, 992, 997,1001,1005,1010,1014,1019,1023,1027,1032,1036, +1040,1045,1049,1053,1057,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102, +1106,1109,1113,1117,1121,1125,1128,1132,1136,1139,1143,1146,1150,1153,1157,1160, +1164,1167,1170,1174,1177,1180,1183,1186,1190,1193,1196,1199,1202,1205,1207,1210, +1213,1216,1219,1221,1224,1227,1229,1232,1234,1237,1239,1241,1244,1246,1248,1251, +1253,1255,1257,1259,1261,1263,1265,1267,1269,1270,1272,1274,1275,1277,1279,1280, +1282,1283,1284,1286,1287,1288,1290,1291,1292,1293,1294,1295,1296,1297,1297,1298, +1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305, +}; + +inline int SPC_DSP::interpolate( voice_t const* v ) +{ + // Make pointers into gaussian based on fractional position between samples + int offset = v->interp_pos >> 4 & 0xFF; + short const* fwd = gauss + 255 - offset; + short const* rev = gauss + offset; // mirror left half of gaussian + + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; + int out; + out = (fwd [ 0] * in [0]) >> 11; + out += (fwd [256] * in [1]) >> 11; + out += (rev [256] * in [2]) >> 11; + out = (int16_t) out; + out += (rev [ 0] * in [3]) >> 11; + + CLAMP16( out ); + out &= ~1; + return out; +} + + +//// Counters + +int const simple_counter_range = 2048 * 5 * 3; // 30720 + +static unsigned const counter_rates [32] = +{ + simple_counter_range + 1, // never fires + 2048, 1536, + 1280, 1024, 768, + 640, 512, 384, + 320, 256, 192, + 160, 128, 96, + 80, 64, 48, + 40, 32, 24, + 20, 16, 12, + 10, 8, 6, + 5, 4, 3, + 2, + 1 +}; + +static unsigned const counter_offsets [32] = +{ + 1, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 536, 0, 1040, + 0, + 0 +}; + +inline void SPC_DSP::init_counter() +{ + m.counter = 0; +} + +inline void SPC_DSP::run_counters() +{ + if ( --m.counter < 0 ) + m.counter = simple_counter_range - 1; +} + +inline unsigned SPC_DSP::read_counter( int rate ) +{ + return ((unsigned) m.counter + counter_offsets [rate]) % counter_rates [rate]; +} + + +//// Envelope + +inline void SPC_DSP::run_envelope( voice_t* const v ) +{ + int env = v->env; + if ( v->env_mode == env_release ) // 60% + { + if ( (env -= 0x8) < 0 ) + env = 0; + v->env = env; + } + else + { + int rate; + int env_data = VREG(v->regs,adsr1); + if ( m.t_adsr0 & 0x80 ) // 99% ADSR + { + if ( v->env_mode >= env_decay ) // 99% + { + env--; + env -= env >> 8; + rate = env_data & 0x1F; + if ( v->env_mode == env_decay ) // 1% + rate = (m.t_adsr0 >> 3 & 0x0E) + 0x10; + } + else // env_attack + { + rate = (m.t_adsr0 & 0x0F) * 2 + 1; + env += rate < 31 ? 0x20 : 0x400; + } + } + else // GAIN + { + int mode; + env_data = VREG(v->regs,gain); + mode = env_data >> 5; + if ( mode < 4 ) // direct + { + env = env_data * 0x10; + rate = 31; + } + else + { + rate = env_data & 0x1F; + if ( mode == 4 ) // 4: linear decrease + { + env -= 0x20; + } + else if ( mode < 6 ) // 5: exponential decrease + { + env--; + env -= env >> 8; + } + else // 6,7: linear increase + { + env += 0x20; + if ( mode > 6 && (unsigned) v->hidden_env >= 0x600 ) + env += 0x8 - 0x20; // 7: two-slope linear increase + } + } + } + + // Sustain level + if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) + v->env_mode = env_sustain; + + v->hidden_env = env; + + // unsigned cast because linear decrease going negative also triggers this + if ( (unsigned) env > 0x7FF ) + { + env = (env < 0 ? 0 : 0x7FF); + if ( v->env_mode == env_attack ) + v->env_mode = env_decay; + } + + if ( !read_counter( rate ) ) + v->env = env; // nothing else is controlled by the counter + } +} + + +//// BRR Decoding + +inline void SPC_DSP::decode_brr( voice_t* v ) +{ + // Arrange the four input nybbles in 0xABCD order for easy decoding + int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; + + int const header = m.t_brr_header; + + // Write to next four samples in circular buffer + int* pos = &v->buf [v->buf_pos]; + int* end; + if ( (v->buf_pos += 4) >= brr_buf_size ) + v->buf_pos = 0; + + // Decode four samples + for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) + { + // Extract nybble and sign-extend + int s = (int16_t) nybbles >> 12; + + // Shift sample based on header + int const shift = header >> 4; + s = (s << shift) >> 1; + if ( shift >= 0xD ) // handle invalid range + s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) + + // Apply IIR filter (8 is the most commonly used) + int const filter = header & 0x0C; + int const p1 = pos [brr_buf_size - 1]; + int const p2 = pos [brr_buf_size - 2] >> 1; + if ( filter >= 8 ) + { + s += p1; + s -= p2; + if ( filter == 8 ) // s += p1 * 0.953125 - p2 * 0.46875 + { + s += p2 >> 4; + s += (p1 * -3) >> 6; + } + else // s += p1 * 0.8984375 - p2 * 0.40625 + { + s += (p1 * -13) >> 7; + s += (p2 * 3) >> 4; + } + } + else if ( filter ) // s += p1 * 0.46875 + { + s += p1 >> 1; + s += (-p1) >> 5; + } + + // Adjust and write sample + CLAMP16( s ); + s = (int16_t) (s * 2); + pos [brr_buf_size] = pos [0] = s; // second copy simplifies wrap-around + } +} + + +//// Misc + +#define MISC_CLOCK( n ) inline void SPC_DSP::misc_##n() + +MISC_CLOCK( 27 ) +{ + m.t_pmon = REG(pmon) & 0xFE; // voice 0 doesn't support PMON +} +MISC_CLOCK( 28 ) +{ + m.t_non = REG(non); + m.t_eon = REG(eon); + m.t_dir = REG(dir); +} +MISC_CLOCK( 29 ) +{ + if ( (m.every_other_sample ^= 1) != 0 ) + m.new_kon &= ~m.kon; // clears KON 63 clocks after it was last read +} +MISC_CLOCK( 30 ) +{ + if ( m.every_other_sample ) + { + m.kon = m.new_kon; + m.t_koff = REG(koff) | m.mute_mask; + } + + run_counters(); + + // Noise + if ( !read_counter( REG(flg) & 0x1F ) ) + { + int feedback = (m.noise << 13) ^ (m.noise << 14); + m.noise = (feedback & 0x4000) ^ (m.noise >> 1); + } +} + + +//// Voices + +#define VOICE_CLOCK( n ) void SPC_DSP::voice_##n( voice_t* const v ) + +inline VOICE_CLOCK( V1 ) +{ + m.t_dir_addr = m.t_dir * 0x100 + m.t_srcn * 4; + m.t_srcn = VREG(v->regs,srcn); +} +inline VOICE_CLOCK( V2 ) +{ + // Read sample pointer (ignored if not needed) + uint8_t const* entry = &m.ram [m.t_dir_addr]; + if ( !v->kon_delay ) + entry += 2; + m.t_brr_next_addr = GET_LE16A( entry ); + + m.t_adsr0 = VREG(v->regs,adsr0); + + // Read pitch, spread over two clocks + m.t_pitch = VREG(v->regs,pitchl); +} +inline VOICE_CLOCK( V3a ) +{ + m.t_pitch += (VREG(v->regs,pitchh) & 0x3F) << 8; +} +inline VOICE_CLOCK( V3b ) +{ + // Read BRR header and byte + m.t_brr_byte = m.ram [(v->brr_addr + v->brr_offset) & 0xFFFF]; + m.t_brr_header = m.ram [v->brr_addr]; // brr_addr doesn't need masking +} +VOICE_CLOCK( V3c ) +{ + // Pitch modulation using previous voice's output + if ( m.t_pmon & v->vbit ) + m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; + + if ( v->kon_delay ) + { + // Get ready to start BRR decoding on next sample + if ( v->kon_delay == 5 ) + { + v->brr_addr = m.t_brr_next_addr; + v->brr_offset = 1; + v->buf_pos = 0; + m.t_brr_header = 0; // header is ignored on this sample + m.kon_check = true; + } + + // Envelope is never run during KON + v->env = 0; + v->hidden_env = 0; + + // Disable BRR decoding until last three samples + v->interp_pos = 0; + if ( --v->kon_delay & 3 ) + v->interp_pos = 0x4000; + + // Pitch is never added during KON + m.t_pitch = 0; + } + + // Gaussian interpolation + { + int output = interpolate( v ); + + // Noise + if ( m.t_non & v->vbit ) + output = (int16_t) (m.noise * 2); + + // Apply envelope + m.t_output = (output * v->env) >> 11 & ~1; + v->t_envx_out = (uint8_t) (v->env >> 4); + } + + // Immediate silence due to end of sample or soft reset + if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) + { + v->env_mode = env_release; + v->env = 0; + } + + if ( m.every_other_sample ) + { + // KOFF + if ( m.t_koff & v->vbit ) + v->env_mode = env_release; + + // KON + if ( m.kon & v->vbit ) + { + v->kon_delay = 5; + v->env_mode = env_attack; + } + } + + // Run envelope for next sample + if ( !v->kon_delay ) + run_envelope( v ); +} +inline void SPC_DSP::voice_output( voice_t const* v, int ch ) +{ + // Apply left/right volume + int amp = (m.t_output * (int8_t) VREG(v->regs,voll + ch)) >> 7; + + // Add to output total + m.t_main_out [ch] += amp; + CLAMP16( m.t_main_out [ch] ); + + // Optionally add to echo total + if ( m.t_eon & v->vbit ) + { + m.t_echo_out [ch] += amp; + CLAMP16( m.t_echo_out [ch] ); + } +} +VOICE_CLOCK( V4 ) +{ + // Decode BRR + m.t_looped = 0; + if ( v->interp_pos >= 0x4000 ) + { + decode_brr( v ); + + if ( (v->brr_offset += 2) >= brr_block_size ) + { + // Start decoding next BRR block + assert( v->brr_offset == brr_block_size ); + v->brr_addr = (v->brr_addr + brr_block_size) & 0xFFFF; + if ( m.t_brr_header & 1 ) + { + v->brr_addr = m.t_brr_next_addr; + m.t_looped = v->vbit; + } + v->brr_offset = 1; + } + } + + // Apply pitch + v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; + + // Keep from getting too far ahead (when using pitch modulation) + if ( v->interp_pos > 0x7FFF ) + v->interp_pos = 0x7FFF; + + // Output left + voice_output( v, 0 ); +} +inline VOICE_CLOCK( V5 ) +{ + // Output right + voice_output( v, 1 ); + + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier + int endx_buf = REG(endx) | m.t_looped; + + // Clear bit in ENDX if KON just began + if ( v->kon_delay == 5 ) + endx_buf &= ~v->vbit; + m.endx_buf = (uint8_t) endx_buf; +} +inline VOICE_CLOCK( V6 ) +{ + (void) v; // avoid compiler warning about unused v + m.outx_buf = (uint8_t) (m.t_output >> 8); +} +inline VOICE_CLOCK( V7 ) +{ + // Update ENDX + REG(endx) = m.endx_buf; + + m.envx_buf = v->t_envx_out; +} +inline VOICE_CLOCK( V8 ) +{ + // Update OUTX + VREG(v->regs,outx) = m.outx_buf; +} +inline VOICE_CLOCK( V9 ) +{ + // Update ENVX + VREG(v->regs,envx) = m.envx_buf; +} + +// Most voices do all these in one clock, so make a handy composite +inline VOICE_CLOCK( V3 ) +{ + voice_V3a( v ); + voice_V3b( v ); + voice_V3c( v ); +} + +// Common combinations of voice steps on different voices. This greatly reduces +// code size and allows everything to be inlined in these functions. +VOICE_CLOCK(V7_V4_V1) { voice_V7(v); voice_V1(v+3); voice_V4(v+1); } +VOICE_CLOCK(V8_V5_V2) { voice_V8(v); voice_V5(v+1); voice_V2(v+2); } +VOICE_CLOCK(V9_V6_V3) { voice_V9(v); voice_V6(v+1); voice_V3(v+2); } + + +//// Echo + +// Current echo buffer pointer for left/right channel +#define ECHO_PTR( ch ) (&m.ram [m.t_echo_ptr + ch * 2]) + +// Sample in echo history buffer, where 0 is the oldest +#define ECHO_FIR( i ) (m.echo_hist_pos [i]) + +// Calculate FIR point for left/right channel +#define CALC_FIR( i, ch ) ((ECHO_FIR( i + 1 ) [ch] * (int8_t) REG(fir + i * 0x10)) >> 6) + +#define ECHO_CLOCK( n ) inline void SPC_DSP::echo_##n() + +inline void SPC_DSP::echo_read( int ch ) +{ + int s = GET_LE16SA( ECHO_PTR( ch ) ); + // second copy simplifies wrap-around handling + ECHO_FIR( 0 ) [ch] = ECHO_FIR( 8 ) [ch] = s >> 1; +} + +ECHO_CLOCK( 22 ) +{ + // History + if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) + m.echo_hist_pos = m.echo_hist; + + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; + echo_read( 0 ); + + // FIR (using l and r temporaries below helps compiler optimize) + int l = CALC_FIR( 0, 0 ); + int r = CALC_FIR( 0, 1 ); + + m.t_echo_in [0] = l; + m.t_echo_in [1] = r; +} +ECHO_CLOCK( 23 ) +{ + int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); + int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; + + echo_read( 1 ); +} +ECHO_CLOCK( 24 ) +{ + int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); + int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); + + m.t_echo_in [0] += l; + m.t_echo_in [1] += r; +} +ECHO_CLOCK( 25 ) +{ + int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); + int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); + + l = (int16_t) l; + r = (int16_t) r; + + l += (int16_t) CALC_FIR( 7, 0 ); + r += (int16_t) CALC_FIR( 7, 1 ); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_in [0] = l & ~1; + m.t_echo_in [1] = r & ~1; +} +inline int SPC_DSP::echo_output( int ch ) +{ + int out = (int16_t) ((m.t_main_out [ch] * (int8_t) REG(mvoll + ch * 0x10)) >> 7) + + (int16_t) ((m.t_echo_in [ch] * (int8_t) REG(evoll + ch * 0x10)) >> 7); + CLAMP16( out ); + return out; +} +ECHO_CLOCK( 26 ) +{ + // Left output volumes + // (save sample for next clock so we can output both together) + m.t_main_out [0] = echo_output( 0 ); + + // Echo feedback + int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); + int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); + + CLAMP16( l ); + CLAMP16( r ); + + m.t_echo_out [0] = l & ~1; + m.t_echo_out [1] = r & ~1; +} +ECHO_CLOCK( 27 ) +{ + // Output + int l = m.t_main_out [0]; + int r = echo_output( 1 ); + m.t_main_out [0] = 0; + m.t_main_out [1] = 0; + + // TODO: global muting isn't this simple (turns DAC on and off + // or something, causing small ~37-sample pulse when first muted) + if ( REG(flg) & 0x40 ) + { + l = 0; + r = 0; + } + + // Output sample to DAC + #ifdef SPC_DSP_OUT_HOOK + SPC_DSP_OUT_HOOK( l, r ); + #else + sample_t* out = m.out; + WRITE_SAMPLES( l, r, out ); + m.out = out; + #endif +} +ECHO_CLOCK( 28 ) +{ + m.t_echo_enabled = REG(flg); +} +inline void SPC_DSP::echo_write( int ch ) +{ + if ( !(m.t_echo_enabled & 0x20) ) + SET_LE16A( ECHO_PTR( ch ), m.t_echo_out [ch] ); + m.t_echo_out [ch] = 0; +} +ECHO_CLOCK( 29 ) +{ + m.t_esa = REG(esa); + + if ( !m.echo_offset ) + m.echo_length = (REG(edl) & 0x0F) * 0x800; + + m.echo_offset += 4; + if ( m.echo_offset >= m.echo_length ) + m.echo_offset = 0; + + // Write left echo + echo_write( 0 ); + + m.t_echo_enabled = REG(flg); +} +ECHO_CLOCK( 30 ) +{ + // Write right echo + echo_write( 1 ); +} + + +//// Timing + +// Execute clock for a particular voice +#define V( clock, voice ) voice_##clock( &m.voices [voice] ); + +/* The most common sequence of clocks uses composite operations +for efficiency. For example, the following are equivalent to the +individual steps on the right: + +V(V7_V4_V1,2) -> V(V7,2) V(V4,3) V(V1,5) +V(V8_V5_V2,2) -> V(V8,2) V(V5,3) V(V2,4) +V(V9_V6_V3,2) -> V(V9,2) V(V6,3) V(V3,4) */ + +// Voice 0 1 2 3 4 5 6 7 +#define GEN_DSP_TIMING \ +PHASE( 0) V(V5,0)V(V2,1)\ +PHASE( 1) V(V6,0)V(V3,1)\ +PHASE( 2) V(V7_V4_V1,0)\ +PHASE( 3) V(V8_V5_V2,0)\ +PHASE( 4) V(V9_V6_V3,0)\ +PHASE( 5) V(V7_V4_V1,1)\ +PHASE( 6) V(V8_V5_V2,1)\ +PHASE( 7) V(V9_V6_V3,1)\ +PHASE( 8) V(V7_V4_V1,2)\ +PHASE( 9) V(V8_V5_V2,2)\ +PHASE(10) V(V9_V6_V3,2)\ +PHASE(11) V(V7_V4_V1,3)\ +PHASE(12) V(V8_V5_V2,3)\ +PHASE(13) V(V9_V6_V3,3)\ +PHASE(14) V(V7_V4_V1,4)\ +PHASE(15) V(V8_V5_V2,4)\ +PHASE(16) V(V9_V6_V3,4)\ +PHASE(17) V(V1,0) V(V7,5)V(V4,6)\ +PHASE(18) V(V8_V5_V2,5)\ +PHASE(19) V(V9_V6_V3,5)\ +PHASE(20) V(V1,1) V(V7,6)V(V4,7)\ +PHASE(21) V(V8,6)V(V5,7) V(V2,0) /* t_brr_next_addr order dependency */\ +PHASE(22) V(V3a,0) V(V9,6)V(V6,7) echo_22();\ +PHASE(23) V(V7,7) echo_23();\ +PHASE(24) V(V8,7) echo_24();\ +PHASE(25) V(V3b,0) V(V9,7) echo_25();\ +PHASE(26) echo_26();\ +PHASE(27) misc_27(); echo_27();\ +PHASE(28) misc_28(); echo_28();\ +PHASE(29) misc_29(); echo_29();\ +PHASE(30) misc_30();V(V3c,0) echo_30();\ +PHASE(31) V(V4,0) V(V1,2)\ + +#if !SPC_DSP_CUSTOM_RUN + +void SPC_DSP::run( int clocks_remain ) +{ + require( clocks_remain > 0 ); + + int const phase = m.phase; + m.phase = (phase + clocks_remain) & 31; + switch ( phase ) + { + loop: + + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: + GEN_DSP_TIMING + #undef PHASE + + if ( --clocks_remain ) + goto loop; + } +} + +#endif + + +//// Setup + +void SPC_DSP::init( void* ram_64k ) +{ + m.ram = (uint8_t*) ram_64k; + mute_voices( 0 ); + disable_surround( false ); + set_output( 0, 0 ); + reset(); + + #ifndef NDEBUG + // be sure this sign-extends + assert( (int16_t) 0x8000 == -0x8000 ); + + // be sure right shift preserves sign + assert( (-1 >> 1) == -1 ); + + // check clamp macro + int i; + i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); + i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); + + blargg_verify_byte_order(); + #endif +} + +void SPC_DSP::soft_reset_common() +{ + require( m.ram ); // init() must have been called already + + m.noise = 0x4000; + m.echo_hist_pos = m.echo_hist; + m.every_other_sample = 1; + m.echo_offset = 0; + m.phase = 0; + + init_counter(); +} + +void SPC_DSP::soft_reset() +{ + REG(flg) = 0xE0; + soft_reset_common(); +} + +void SPC_DSP::load( uint8_t const regs [register_count] ) +{ + memcpy( m.regs, regs, sizeof m.regs ); + memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); + + // Internal state + for ( int i = voice_count; --i >= 0; ) + { + voice_t* v = &m.voices [i]; + v->brr_offset = 1; + v->vbit = 1 << i; + v->regs = &m.regs [i * 0x10]; + } + m.new_kon = REG(kon); + m.t_dir = REG(dir); + m.t_esa = REG(esa); + + soft_reset_common(); +} + +void SPC_DSP::reset() { load( initial_regs ); } + + +//// State save/load + +#if !SPC_NO_COPY_STATE_FUNCS + +void SPC_State_Copier::copy( void* state, size_t size ) +{ + func( buf, state, size ); +} + +int SPC_State_Copier::copy_int( int state, int size ) +{ + BOOST::uint8_t s [2]; + SET_LE16( s, state ); + func( buf, &s, size ); + return GET_LE16( s ); +} + +void SPC_State_Copier::skip( int count ) +{ + if ( count > 0 ) + { + char temp [64]; + memset( temp, 0, sizeof temp ); + do + { + int n = sizeof temp; + if ( n > count ) + n = count; + count -= n; + func( buf, temp, n ); + } + while ( count ); + } +} + +void SPC_State_Copier::extra() +{ + int n = 0; + SPC_State_Copier& copier = *this; + SPC_COPY( uint8_t, n ); + skip( n ); +} + +void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) +{ + SPC_State_Copier copier( io, copy ); + + // DSP registers + copier.copy( m.regs, register_count ); + + // Internal state + + // Voices + int i; + for ( i = 0; i < voice_count; i++ ) + { + voice_t* v = &m.voices [i]; + + // BRR buffer + int i; + for ( i = 0; i < brr_buf_size; i++ ) + { + int s = v->buf [i]; + SPC_COPY( int16_t, s ); + v->buf [i] = v->buf [i + brr_buf_size] = s; + } + + SPC_COPY( uint16_t, v->interp_pos ); + SPC_COPY( uint16_t, v->brr_addr ); + SPC_COPY( uint16_t, v->env ); + SPC_COPY( int16_t, v->hidden_env ); + SPC_COPY( uint8_t, v->buf_pos ); + SPC_COPY( uint8_t, v->brr_offset ); + SPC_COPY( uint8_t, v->kon_delay ); + { + int m = v->env_mode; + SPC_COPY( uint8_t, m ); + v->env_mode = (enum env_mode_t) m; + } + SPC_COPY( uint8_t, v->t_envx_out ); + + copier.extra(); + } + + // Echo history + for ( i = 0; i < echo_hist_size; i++ ) + { + int j; + for ( j = 0; j < 2; j++ ) + { + int s = m.echo_hist_pos [i] [j]; + SPC_COPY( int16_t, s ); + m.echo_hist [i] [j] = s; // write back at offset 0 + } + } + m.echo_hist_pos = m.echo_hist; + memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); + + // Misc + SPC_COPY( uint8_t, m.every_other_sample ); + SPC_COPY( uint8_t, m.kon ); + + SPC_COPY( uint16_t, m.noise ); + SPC_COPY( uint16_t, m.counter ); + SPC_COPY( uint16_t, m.echo_offset ); + SPC_COPY( uint16_t, m.echo_length ); + SPC_COPY( uint8_t, m.phase ); + + SPC_COPY( uint8_t, m.new_kon ); + SPC_COPY( uint8_t, m.endx_buf ); + SPC_COPY( uint8_t, m.envx_buf ); + SPC_COPY( uint8_t, m.outx_buf ); + + SPC_COPY( uint8_t, m.t_pmon ); + SPC_COPY( uint8_t, m.t_non ); + SPC_COPY( uint8_t, m.t_eon ); + SPC_COPY( uint8_t, m.t_dir ); + SPC_COPY( uint8_t, m.t_koff ); + + SPC_COPY( uint16_t, m.t_brr_next_addr ); + SPC_COPY( uint8_t, m.t_adsr0 ); + SPC_COPY( uint8_t, m.t_brr_header ); + SPC_COPY( uint8_t, m.t_brr_byte ); + SPC_COPY( uint8_t, m.t_srcn ); + SPC_COPY( uint8_t, m.t_esa ); + SPC_COPY( uint8_t, m.t_echo_enabled ); + + SPC_COPY( int16_t, m.t_main_out [0] ); + SPC_COPY( int16_t, m.t_main_out [1] ); + SPC_COPY( int16_t, m.t_echo_out [0] ); + SPC_COPY( int16_t, m.t_echo_out [1] ); + SPC_COPY( int16_t, m.t_echo_in [0] ); + SPC_COPY( int16_t, m.t_echo_in [1] ); + + SPC_COPY( uint16_t, m.t_dir_addr ); + SPC_COPY( uint16_t, m.t_pitch ); + SPC_COPY( int16_t, m.t_output ); + SPC_COPY( uint16_t, m.t_echo_ptr ); + SPC_COPY( uint8_t, m.t_looped ); + + copier.extra(); +} +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_DSP.h b/waterbox/sameboy/snes_spc/SPC_DSP.h new file mode 100644 index 0000000000..4522ace915 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_DSP.h @@ -0,0 +1,304 @@ +// Highly accurate SNES SPC-700 DSP emulator + +// snes_spc 0.9.0 +#ifndef SPC_DSP_H +#define SPC_DSP_H + +#include "blargg_common.h" + +extern "C" { typedef void (*dsp_copy_func_t)( unsigned char** io, void* state, size_t ); } + +class SPC_DSP { +public: + typedef BOOST::uint8_t uint8_t; + +// Setup + + // Initializes DSP and has it use the 64K RAM provided + void init( void* ram_64k ); + + // Sets destination for output samples. If out is NULL or out_size is 0, + // doesn't generate any. + typedef short sample_t; + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since it was last set, always + // a multiple of 2. Undefined if more samples were generated than + // output buffer could hold. + int sample_count() const; + +// Emulation + + // Resets DSP to power-on state + void reset(); + + // Emulates pressing reset switch on SNES + void soft_reset(); + + // Reads/writes DSP registers. For accuracy, you must first call run() + // to catch the DSP up to present. + int read ( int addr ) const; + void write( int addr, int data ); + + // Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks + // a pair of samples is be generated. + void run( int clock_count ); + +// Sound control + + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; + void mute_voices( int mask ); + +// State + + // Resets DSP and uses supplied values to initialize registers + enum { register_count = 128 }; + void load( uint8_t const regs [register_count] ); + + // Saves/loads exact emulator state + enum { state_size = 640 }; // maximum space needed when saving + typedef dsp_copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Returns non-zero if new key-on events occurred since last call + bool check_kon(); + +// DSP register addresses + + // Global registers + enum { + r_mvoll = 0x0C, r_mvolr = 0x1C, + r_evoll = 0x2C, r_evolr = 0x3C, + r_kon = 0x4C, r_koff = 0x5C, + r_flg = 0x6C, r_endx = 0x7C, + r_efb = 0x0D, r_pmon = 0x2D, + r_non = 0x3D, r_eon = 0x4D, + r_dir = 0x5D, r_esa = 0x6D, + r_edl = 0x7D, + r_fir = 0x0F // 8 coefficients at 0x0F, 0x1F ... 0x7F + }; + + // Voice registers + enum { + v_voll = 0x00, v_volr = 0x01, + v_pitchl = 0x02, v_pitchh = 0x03, + v_srcn = 0x04, v_adsr0 = 0x05, + v_adsr1 = 0x06, v_gain = 0x07, + v_envx = 0x08, v_outx = 0x09 + }; + +public: + enum { extra_size = 16 }; + sample_t* extra() { return m.extra; } + sample_t const* out_pos() const { return m.out; } + void disable_surround( bool ) { } // not supported +public: + BLARGG_DISABLE_NOTHROW + + typedef BOOST::int8_t int8_t; + typedef BOOST::int16_t int16_t; + + enum { echo_hist_size = 8 }; + + enum env_mode_t { env_release, env_attack, env_decay, env_sustain }; + enum { brr_buf_size = 12 }; + struct voice_t + { + int buf [brr_buf_size*2];// decoded samples (twice the size to simplify wrap handling) + int buf_pos; // place in buffer where next samples will be decoded + int interp_pos; // relative fractional position in sample (0x1000 = 1.0) + int brr_addr; // address of current BRR block + int brr_offset; // current decoding offset in BRR block + uint8_t* regs; // pointer to voice's DSP registers + int vbit; // bitmask for voice: 0x01 for voice 0, 0x02 for voice 1, etc. + int kon_delay; // KON delay/current setup phase + env_mode_t env_mode; + int env; // current envelope level + int hidden_env; // used by GAIN mode 7, very obscure quirk + uint8_t t_envx_out; + }; +private: + enum { brr_block_size = 9 }; + + struct state_t + { + uint8_t regs [register_count]; + + // Echo history keeps most recent 8 samples (twice the size to simplify wrap handling) + int echo_hist [echo_hist_size * 2] [2]; + int (*echo_hist_pos) [2]; // &echo_hist [0 to 7] + + int every_other_sample; // toggles every sample + int kon; // KON value when last checked + int noise; + int counter; + int echo_offset; // offset from ESA in echo buffer + int echo_length; // number of bytes that echo_offset will stop at + int phase; // next clock cycle to run (0-31) + bool kon_check; // set when a new KON occurs + + // Hidden registers also written to when main register is written to + int new_kon; + uint8_t endx_buf; + uint8_t envx_buf; + uint8_t outx_buf; + + // Temporary state between clocks + + // read once per sample + int t_pmon; + int t_non; + int t_eon; + int t_dir; + int t_koff; + + // read a few clocks ahead then used + int t_brr_next_addr; + int t_adsr0; + int t_brr_header; + int t_brr_byte; + int t_srcn; + int t_esa; + int t_echo_enabled; + + // internal state that is recalculated every sample + int t_dir_addr; + int t_pitch; + int t_output; + int t_looped; + int t_echo_ptr; + + // left/right sums + int t_main_out [2]; + int t_echo_out [2]; + int t_echo_in [2]; + + voice_t voices [voice_count]; + + // non-emulation state + uint8_t* ram; // 64K shared RAM between DSP and SMP + int mute_mask; + sample_t* out; + sample_t* out_end; + sample_t* out_begin; + sample_t extra [extra_size]; + }; + state_t m; + + void init_counter(); + void run_counters(); + unsigned read_counter( int rate ); + + int interpolate( voice_t const* v ); + void run_envelope( voice_t* const v ); + void decode_brr( voice_t* v ); + + void misc_27(); + void misc_28(); + void misc_29(); + void misc_30(); + + void voice_output( voice_t const* v, int ch ); + void voice_V1( voice_t* const ); + void voice_V2( voice_t* const ); + void voice_V3( voice_t* const ); + void voice_V3a( voice_t* const ); + void voice_V3b( voice_t* const ); + void voice_V3c( voice_t* const ); + void voice_V4( voice_t* const ); + void voice_V5( voice_t* const ); + void voice_V6( voice_t* const ); + void voice_V7( voice_t* const ); + void voice_V8( voice_t* const ); + void voice_V9( voice_t* const ); + void voice_V7_V4_V1( voice_t* const ); + void voice_V8_V5_V2( voice_t* const ); + void voice_V9_V6_V3( voice_t* const ); + + void echo_read( int ch ); + int echo_output( int ch ); + void echo_write( int ch ); + void echo_22(); + void echo_23(); + void echo_24(); + void echo_25(); + void echo_26(); + void echo_27(); + void echo_28(); + void echo_29(); + void echo_30(); + + void soft_reset_common(); +}; + +#include + +inline int SPC_DSP::sample_count() const { return m.out - m.out_begin; } + +inline int SPC_DSP::read( int addr ) const +{ + assert( (unsigned) addr < register_count ); + return m.regs [addr]; +} + +inline void SPC_DSP::write( int addr, int data ) +{ + assert( (unsigned) addr < register_count ); + + m.regs [addr] = (uint8_t) data; + switch ( addr & 0x0F ) + { + case v_envx: + m.envx_buf = (uint8_t) data; + break; + + case v_outx: + m.outx_buf = (uint8_t) data; + break; + + case 0x0C: + if ( addr == r_kon ) + m.new_kon = (uint8_t) data; + + if ( addr == r_endx ) // always cleared, regardless of data written + { + m.endx_buf = 0; + m.regs [r_endx] = 0; + } + break; + } +} + +inline void SPC_DSP::mute_voices( int mask ) { m.mute_mask = mask; } + +inline bool SPC_DSP::check_kon() +{ + bool old = m.kon_check; + m.kon_check = 0; + return old; +} + +#if !SPC_NO_COPY_STATE_FUNCS + +class SPC_State_Copier { + SPC_DSP::copy_func_t func; + unsigned char** buf; +public: + SPC_State_Copier( unsigned char** p, SPC_DSP::copy_func_t f ) { func = f; buf = p; } + void copy( void* state, size_t size ); + int copy_int( int state, int size ); + void skip( int count ); + void extra(); +}; + +#define SPC_COPY( type, state )\ +{\ + state = (BOOST::type) copier.copy_int( state, sizeof (BOOST::type) );\ + assert( (BOOST::type) state == state );\ +} + +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/SPC_Filter.cpp b/waterbox/sameboy/snes_spc/SPC_Filter.cpp new file mode 100644 index 0000000000..b3d5770822 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_Filter.cpp @@ -0,0 +1,68 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "SPC_Filter.h" + +#include + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void SPC_Filter::clear() { memset( ch, 0, sizeof ch ); } + +SPC_Filter::SPC_Filter() +{ + gain = gain_unit; + bass = bass_norm; + clear(); +} + +void SPC_Filter::run( short* io, int count ) +{ + require( (count & 1) == 0 ); // must be even + + int const gain = this->gain; + int const bass = this->bass; + chan_t* c = &ch [2]; + do + { + // cache in registers + int sum = (--c)->sum; + int pp1 = c->pp1; + int p1 = c->p1; + + for ( int i = 0; i < count; i += 2 ) + { + // Low-pass filter (two point FIR with coeffs 0.25, 0.75) + int f = io [i] + p1; + p1 = io [i] * 3; + + // High-pass filter ("leaky integrator") + int delta = f - pp1; + pp1 = f; + int s = sum >> (gain_bits + 2); + sum += (delta * gain) - (sum >> bass); + + // Clamp to 16 bits + if ( (short) s != s ) + s = (s >> 31) ^ 0x7FFF; + + io [i] = (short) s; + } + + c->p1 = p1; + c->pp1 = pp1; + c->sum = sum; + ++io; + } + while ( c != ch ); +} diff --git a/waterbox/sameboy/snes_spc/SPC_Filter.h b/waterbox/sameboy/snes_spc/SPC_Filter.h new file mode 100644 index 0000000000..d5c83cb8f1 --- /dev/null +++ b/waterbox/sameboy/snes_spc/SPC_Filter.h @@ -0,0 +1,47 @@ +// Simple low-pass and high-pass filter to better match sound output of a SNES + +// snes_spc 0.9.0 +#ifndef SPC_FILTER_H +#define SPC_FILTER_H + +#include "blargg_common.h" + +struct SPC_Filter { +public: + + // Filters count samples of stereo sound in place. Count must be a multiple of 2. + typedef short sample_t; + void run( sample_t* io, int count ); + +// Optional features + + // Clears filter to silence + void clear(); + + // Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit + // are fine, since output is clamped to 16-bit sample range. + enum { gain_unit = 0x100 }; + void set_gain( int gain ); + + // Sets amount of bass (logarithmic scale) + enum { bass_none = 0 }; + enum { bass_norm = 8 }; // normal amount + enum { bass_max = 31 }; + void set_bass( int bass ); + +public: + SPC_Filter(); + BLARGG_DISABLE_NOTHROW +private: + enum { gain_bits = 8 }; + int gain; + int bass; + struct chan_t { int p1, pp1, sum; }; + chan_t ch [2]; +}; + +inline void SPC_Filter::set_gain( int g ) { gain = g; } + +inline void SPC_Filter::set_bass( int b ) { bass = b; } + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_common.h b/waterbox/sameboy/snes_spc/blargg_common.h new file mode 100644 index 0000000000..75edff3914 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_common.h @@ -0,0 +1,186 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// snes_spc 0.9.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +#undef BLARGG_COMMON_H +// allow blargg_config.h to #include blargg_common.h +#include "blargg_config.h" +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +// BLARGG_RESTRICT: equivalent to restrict, where supported +#if defined (__GNUC__) || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +// STATIC_CAST(T,expr): Used in place of static_cast (expr) +#ifndef STATIC_CAST + #define STATIC_CAST(T,expr) ((T) (expr)) +#endif + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// blargg_vector - very lightweight vector of POD types (no constructor/destructor) +template +class blargg_vector { + T* begin_; + size_t size_; +public: + blargg_vector() : begin_( 0 ), size_( 0 ) { } + ~blargg_vector() { free( begin_ ); } + size_t size() const { return size_; } + T* begin() const { return begin_; } + T* end() const { return begin_ + size_; } + blargg_err_t resize( size_t n ) + { + // TODO: blargg_common.cpp to hold this as an outline function, ugh + void* p = realloc( begin_, n * sizeof (T) ); + if ( p ) + begin_ = (T*) p; + else if ( n > size_ ) // realloc failure only a problem if expanding + return "Out of memory"; + size_ = n; + return 0; + } + void clear() { void* p = begin_; begin_ = 0; size_ = 0; free( p ); } + T& operator [] ( size_t n ) const + { + assert( n <= size_ ); // <= to allow past-the-end value + return begin_ [n]; + } +}; + +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if operator new can return NULL + #if __cplusplus >= 199711 || defined (__GNUC__) + #define BLARGG_THROWS( spec ) throw spec + #else + #define BLARGG_THROWS( spec ) + #endif + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS(()) { return malloc( s ); }\ + void operator delete ( void* p ) { free( p ); } + #define BLARGG_NEW new +#else + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +// BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant) +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000L + (b&0xFF)*0x10000L + (c&0xFF)*0x100L + (d&0xFF)) + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, +// compiler is assumed to support bool. If undefined, availability is determined. +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + // If you get errors here, modify your blargg_config.h file + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// blargg_long/blargg_ulong = at least 32 bits, int if it's big enough + +#if INT_MAX < 0x7FFFFFFF || LONG_MAX == 0x7FFFFFFF + typedef long blargg_long; +#else + typedef int blargg_long; +#endif + +#if UINT_MAX < 0xFFFFFFFF || ULONG_MAX == 0xFFFFFFFF + typedef unsigned long blargg_ulong; +#else + typedef unsigned blargg_ulong; +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#endif +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_config.h b/waterbox/sameboy/snes_spc/blargg_config.h new file mode 100644 index 0000000000..9dc69db836 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_config.h @@ -0,0 +1,24 @@ +// snes_spc 0.9.0 user configuration file. Don't replace when updating library. + +// snes_spc 0.9.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to disable debugging checks +//#define NDEBUG 1 + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations +//#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_endian.h b/waterbox/sameboy/snes_spc/blargg_endian.h new file mode 100644 index 0000000000..f2daca6416 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// snes_spc 0.9.0 +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline blargg_ulong get_le32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [3] << 24 | + (blargg_ulong) ((unsigned char const*) p) [2] << 16 | + (blargg_ulong) ((unsigned char const*) p) [1] << 8 | + (blargg_ulong) ((unsigned char const*) p) [0]; +} + +inline blargg_ulong get_be32( void const* p ) +{ + return (blargg_ulong) ((unsigned char const*) p) [0] << 24 | + (blargg_ulong) ((unsigned char const*) p) [1] << 16 | + (blargg_ulong) ((unsigned char const*) p) [2] << 8 | + (blargg_ulong) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, blargg_ulong n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned ppc_lhbrx_; asm( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr), "0" (ppc_lhbrx_) ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned ppc_lwbrx_; asm( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr), "0" (ppc_lwbrx_) ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({asm( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #define SET_LE32( addr, in ) ({asm( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, blargg_ulong n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, blargg_ulong n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline blargg_ulong get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline blargg_ulong get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif diff --git a/waterbox/sameboy/snes_spc/blargg_source.h b/waterbox/sameboy/snes_spc/blargg_source.h new file mode 100644 index 0000000000..5e45c4fb42 --- /dev/null +++ b/waterbox/sameboy/snes_spc/blargg_source.h @@ -0,0 +1,100 @@ +/* Included at the beginning of library source files, after all other #include lines. +Sets up helpful macros and services used in my source code. They don't need +module an annoying module prefix on their names since they are defined after +all other #include lines. */ + +// snes_spc 0.9.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +static inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +#define DEF_MIN_MAX( type ) \ + static inline type min( type x, type y ) { if ( x < y ) return x; return y; }\ + static inline type max( type x, type y ) { if ( y < x ) return x; return y; } + +DEF_MIN_MAX( int ) +DEF_MIN_MAX( unsigned ) +DEF_MIN_MAX( long ) +DEF_MIN_MAX( unsigned long ) +DEF_MIN_MAX( float ) +DEF_MIN_MAX( double ) + +#undef DEF_MIN_MAX + +/* +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +// TODO: remove +inline int min( int x, int y ) +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} +*/ + +// TODO: good idea? bad idea? +#undef byte +#define byte byte_ +typedef unsigned char byte; + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/changes.txt b/waterbox/sameboy/snes_spc/changes.txt new file mode 100644 index 0000000000..33661832b4 --- /dev/null +++ b/waterbox/sameboy/snes_spc/changes.txt @@ -0,0 +1,107 @@ +snes_spc Change Log +------------------- + +snes_spc 0.9.0 +-------------- +- Improved documentation + +- SPC: Added spc_skip() function for quickly seeking in an SPC music +file. Runs 3-4x faster than normal playback using the fast DSP (or about +43-60X real-time on my 400 MHz Mac). + +- SPC: Added spc_set_tempo() to change tempo of SPC music playback. + +- SPC: Sample generation is now corrected to generate exactly one pair +of samples every 32 clocks without exception. Before it could generate a +few samples more or less depending on how far ahead or behind DSP was at +the moment. + +- SPC: Changed spc_reset() and spc_soft_reset() to also reset output +buffer (see spc.h). + +- SPC: Fixed minor timer counting bug. + +- SPC: Stack pointer wrap-around is now emulated (and without any +noticeable performance hit). + +- SPC: Runs about 5% faster due to various optimizations. + +- SPC: Found way to make fast DSP register accesses cycle-accurate in +most cases, without reducing performance. Allows fast DSP to pass most +of my validation tests. + +- DSP: Added surround disable support to fast DSP again. + +- DSP: Improved voice un-muting to take effect immediately on fast DSP. + +- DSP: Noise shift register now starts at 0x4000 instead of 0x4001 as it +incorrectly did before. + +- Converted library to C++ code internally. A C interface is still +included in spc.h and dsp.h. Note that these are different than the +previous interface, so your code will require minor changes: + + Old SPC code New SPC code + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #include "spc/spc.h" #include "snes_spc/spc.h" + + snes_spc_t* spc; SNES_SPC* spc; + spc = malloc( sizeof (snes_spc_t) ); spc = spc_new(); + spc_init( spc ); + + spc_end_frame( time ); spc_end_frame( spc, time ); + /* etc. */ + + /* done using SPC */ spc_delete( spc ); + + + Old DSP code New DSP code + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + #include "spc/spc_dsp.h" #include "snes_spc/dsp.h" + + spc_dsp_init( ram ); SPC_DSP* dsp; + dsp = spc_dsp_new(); + spc_dsp_init( dsp, ram ); + + spc_dsp_run( count ); spc_dsp_run( dsp, count ); + /* etc. */ + + /* done using DSP */ spc_dsp_delete( dsp ); + + +snes_spc 0.8.0 +-------------- +- Added several demos + +- Added high-pass/low-pass filter to better match SNES sound + +- Added save state functionality for SPC and accurate DSP (but not fast +DSP) + +- Added emulation of reset switch on NES (soft reset) + +- Made source more compatible with pre-C99 compilers by eliminating +mid-block declarations + +- SPC: Many S-SMP accuracy improvements, mostly in memory access times + +- SPC: S-SMP speed improvements + +- SPC: Added SPC load/save functions and KON checking to help trim +silence from beginning + +- SPC: Changed spc_init() to have you allocate most of the memory used +by the library so you have more control over it + +- DSP: New highly accurate DSP and faster version derived from same code + +- DSP: Changed prefix from dsp_ to spc_dsp_. Your DSP code will require +changes. + +- DSP: Removed surround disable and gain. Gain can now be done with the +dsp_filter module, and surround disable will probably only be +implemented in the fast DSP at some point. + +- DSP: Changed interface to work in clocks rather than samples, +necessary for the new accurate DSP. Sample output is now done with +separate functions. Your DSP code will require changes. diff --git a/waterbox/sameboy/snes_spc/dsp.cpp b/waterbox/sameboy/snes_spc/dsp.cpp new file mode 100644 index 0000000000..99d0cf5c5e --- /dev/null +++ b/waterbox/sameboy/snes_spc/dsp.cpp @@ -0,0 +1,48 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "dsp.h" + +#include "SPC_DSP.h" + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +SPC_DSP* spc_dsp_new( void ) +{ + // be sure constants match + assert( spc_dsp_voice_count == (int) SPC_DSP::voice_count ); + assert( spc_dsp_register_count == (int) SPC_DSP::register_count ); + #if !SPC_NO_COPY_STATE_FUNCS + assert( spc_dsp_state_size == (int) SPC_DSP::state_size ); + #endif + + return new SPC_DSP; +} + +void spc_dsp_delete ( SPC_DSP* s ) { delete s; } +void spc_dsp_init ( SPC_DSP* s, void* ram_64k ) { s->init( ram_64k ); } +void spc_dsp_set_output ( SPC_DSP* s, spc_dsp_sample_t* p, int n ) { s->set_output( p, n ); } +int spc_dsp_sample_count( SPC_DSP const* s ) { return s->sample_count(); } +void spc_dsp_reset ( SPC_DSP* s ) { s->reset(); } +void spc_dsp_soft_reset ( SPC_DSP* s ) { s->soft_reset(); } +int spc_dsp_read ( SPC_DSP const* s, int addr ) { return s->read( addr ); } +void spc_dsp_write ( SPC_DSP* s, int addr, int data ) { s->write( addr, data ); } +void spc_dsp_run ( SPC_DSP* s, int clock_count ) { s->run( clock_count ); } +void spc_dsp_mute_voices ( SPC_DSP* s, int mask ) { s->mute_voices( mask ); } +void spc_dsp_disable_surround( SPC_DSP* s, int disable ) { s->disable_surround( disable ); } +void spc_dsp_load ( SPC_DSP* s, unsigned char const regs [spc_dsp_register_count] ) { s->load( regs ); } + +#if !SPC_NO_COPY_STATE_FUNCS +void spc_dsp_copy_state ( SPC_DSP* s, unsigned char** p, spc_dsp_copy_func_t f ) { s->copy_state( p, f ); } +int spc_dsp_check_kon ( SPC_DSP* s ) { return s->check_kon(); } +#endif diff --git a/waterbox/sameboy/snes_spc/dsp.h b/waterbox/sameboy/snes_spc/dsp.h new file mode 100644 index 0000000000..59867d92f9 --- /dev/null +++ b/waterbox/sameboy/snes_spc/dsp.h @@ -0,0 +1,83 @@ +/* SNES SPC-700 DSP emulator C interface (also usable from C++) */ + +/* snes_spc 0.9.0 */ +#ifndef DSP_H +#define DSP_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +typedef struct SPC_DSP SPC_DSP; + +/* Creates new DSP emulator. NULL if out of memory. */ +SPC_DSP* spc_dsp_new( void ); + +/* Frees DSP emulator */ +void spc_dsp_delete( SPC_DSP* ); + +/* Initializes DSP and has it use the 64K RAM provided */ +void spc_dsp_init( SPC_DSP*, void* ram_64k ); + +/* Sets destination for output samples. If out is NULL or out_size is 0, +doesn't generate any. */ +typedef short spc_dsp_sample_t; +void spc_dsp_set_output( SPC_DSP*, spc_dsp_sample_t* out, int out_size ); + +/* Number of samples written to output since it was last set, always +a multiple of 2. Undefined if more samples were generated than +output buffer could hold. */ +int spc_dsp_sample_count( SPC_DSP const* ); + + +/**** Emulation *****/ + +/* Resets DSP to power-on state */ +void spc_dsp_reset( SPC_DSP* ); + +/* Emulates pressing reset switch on SNES */ +void spc_dsp_soft_reset( SPC_DSP* ); + +/* Reads/writes DSP registers. For accuracy, you must first call spc_dsp_run() */ +/* to catch the DSP up to present. */ +int spc_dsp_read ( SPC_DSP const*, int addr ); +void spc_dsp_write( SPC_DSP*, int addr, int data ); + +/* Runs DSP for specified number of clocks (~1024000 per second). Every 32 clocks */ +/* a pair of samples is be generated. */ +void spc_dsp_run( SPC_DSP*, int clock_count ); + + +/**** Sound control *****/ + +/* Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ +enum { spc_dsp_voice_count = 8 }; +void spc_dsp_mute_voices( SPC_DSP*, int mask ); + +/* If true, prevents channels and global volumes from being phase-negated. +Only supported by fast DSP; has no effect on accurate DSP. */ +void spc_dsp_disable_surround( SPC_DSP*, int disable ); + + +/**** State save/load *****/ + +/* Resets DSP and uses supplied values to initialize registers */ +enum { spc_dsp_register_count = 128 }; +void spc_dsp_load( SPC_DSP*, unsigned char const regs [spc_dsp_register_count] ); + +/* Saves/loads exact emulator state (accurate DSP only) */ +enum { spc_dsp_state_size = 640 }; /* maximum space needed when saving */ +typedef void (*spc_dsp_copy_func_t)( unsigned char** io, void* state, size_t ); +void spc_dsp_copy_state( SPC_DSP*, unsigned char** io, spc_dsp_copy_func_t ); + +/* Returns non-zero if new key-on events occurred since last call (accurate DSP only) */ +int spc_dsp_check_kon( SPC_DSP* ); + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/waterbox/sameboy/snes_spc/license.txt b/waterbox/sameboy/snes_spc/license.txt new file mode 100644 index 0000000000..5faba9d48c --- /dev/null +++ b/waterbox/sameboy/snes_spc/license.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/waterbox/sameboy/snes_spc/readme.txt b/waterbox/sameboy/snes_spc/readme.txt new file mode 100644 index 0000000000..979913f0ac --- /dev/null +++ b/waterbox/sameboy/snes_spc/readme.txt @@ -0,0 +1,86 @@ +snes_spc 0.9.0: SNES SPC-700 APU Emulator +----------------------------------------- +This library includes a full SPC emulator and an S-DSP emulator that can +be used on its own. Two S-DSP emulators are available: a highly accurate +one for use in a SNES emulator, and a 3x faster one for use in an SPC +music player or a resource-limited SNES emulator. + +* Can be used from C and C++ code +* Full SPC-700 APU emulator with cycle accuracy in most cases +* Loads, plays, and saves SPC music files +* Can save and load exact full emulator state +* DSP voice muting, surround sound disable, and song tempo adjustment +* Uses 7% CPU average on 400 MHz Mac to play an SPC using fast DSP + +The accurate DSP emulator is based on past research by others and +hundreds of hours of recent research by me. It passes over a hundred +strenuous timing and behavior validation tests that were also run on the +SNES. As far as I know, it's the first DSP emulator with cycle accuracy, +properly emulating every DSP register and memory access at the exact SPC +cycle it occurs at, whereas previous DSP emulators emulated these only +to the nearest sample (which occurs every 32 clocks). + +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + + +Getting Started +--------------- +Build a program consisting of demo/play_spc.c, demo/demo_util.c, +demo/wave_writer.c, and all source files in snes_spc/. Put an SPC music +file in the same directory and name it "test.spc". Running the program +should generate the recording "out.wav". + +Read snes_spc.txt for more information. Post to the discussion forum for +assistance. + + +Files +----- +snes_spc.txt Documentation +changes.txt Change log +license.txt GNU LGPL license + +demo/ + play_spc.c Records SPC file to wave sound file + benchmark.c Finds how fast emulator runs on your computer + trim_spc.c Trims silence off beginning of an SPC file + save_state.c Saves/loads exact emulator state to/from file + comm.c Communicates with SPC how SNES would + demo_util.h General utility functions used by demos + demo_util.c + wave_writer.h WAVE sound file writer used for demo output + wave_writer.c + +fast_dsp/ Optional standalone fast DSP emulator + SPC_DSP.h To use with full SPC emulator, move into + SPC_DSP.cpp snes_spc/ and replace original files + +snes_spc/ Library sources + blargg_config.h Configuration (modify as necessary) + + spc.h C interface to SPC emulator and sound filter + spc.cpp + + SPC_Filter.h Optional filter to make sound more authentic + SPC_Filter.cpp + + SNES_SPC.h Full SPC emulator + SNES_SPC.cpp + SNES_SPC_misc.cpp + SNES_SPC_state.cpp + SPC_CPU.h + + dsp.h C interface to DSP emulator + dsp.cpp + + SPC_DSP.h Standalone accurate DSP emulator + SPC_DSP.cpp + blargg_common.h + blargg_endian.h + blargg_source.h + +-- +Shay Green diff --git a/waterbox/sameboy/snes_spc/snes_spc.txt b/waterbox/sameboy/snes_spc/snes_spc.txt new file mode 100644 index 0000000000..d37b34347f --- /dev/null +++ b/waterbox/sameboy/snes_spc/snes_spc.txt @@ -0,0 +1,318 @@ +snes_spc 0.9.0: SNES SPC-700 APU Emulator +----------------------------------------- +Author : Shay Green +Website: http://www.slack.net/~ant/ +Forum : http://groups.google.com/group/blargg-sound-libs +License: GNU Lesser General Public License (LGPL) + + +Contents +-------- +* C and C++ Interfaces +* Overview +* Full SPC Emulation +* DSP Emulation +* SPC Music Playback +* State Copying +* Library Compilation +* Error handling +* Solving Problems +* Accurate S-DSP Limitations +* Fast S-DSP Limitations +* S-SMP Limitations +* To Do +* Thanks + + +C and C++ Interfaces +-------------------- +The library includes a C interface in spc.h and dsp.h, which can be used +from C and C++. This C interface is referred to in the following +documentation. If you're building this as a shared library (rather than +linking statically), you should use the C interface since it will change +less in future versions. + +The native C++ interface is in the header files SNES_SPC.h, SPC_DSP.h, +and SPC_Filter.h, and the two interfaces can be freely mixed in C++ +code. Conversion between the two interfaces generally follows a pattern: + + C interface C++ interface + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + SNES_SPC* spc; SNES_SPC* spc; + + spc = spc_new(); spc = new SNES_SPC; + + spc_play( spc, count, buf ); spc->play( count, buf ); + + spc_sample_rate SNES_SPC::sample_rate + + spc_delete( spc ); delete spc; + + +Overview +-------- +There are three main roles for this library: +* Full SPC emulation in a SNES emulator +* DSP emulation in a SNES emulator (where you emulate the SMP CPU) +* SPC playback in an SPC music file player + +Each of these uses are described separately below. + + +Full SPC Emulation +------------------ +See spc.h for full function reference (SNES_SPC.h if using C++). + +* Create SPC emulator with spc_new() and check for NULL. + +* Call spc_init_rom() with a pointer to the 64-byte IPL ROM dump (not +included with library). + +* When your emulated SNES is powered on, call spc_reset(). When the +reset switch is pressed, call spc_soft_reset(). + +* Call spc_set_output() with your output buffer, then do emulation. + +* When the SNES CPU accesses APU ports, call spc_read_port() and +spc_write_port(). + +* When your emulator's timebase is going back to 0, call +spc_end_frame(), usually at the end of a video frame or scanline. + +* Periodically play samples from your buffer. Use spc_sample_count() to +find out how many samples have been written, then spc_set_output() after +you've made more space in your buffer. + +* Save/load full emulator state with spc_copy_state(). + +* You can save as an SPC music file with spc_save_spc(). + +* When done, use spc_delete() to free memory. + + +DSP Emulation +------------- +See dsp.h for full function reference (SPC_DSP.h if using C++). + +* Create DSP emulator with spc_dsp_new() and check for NULL. + +* Let the DSP know where your 64K RAM is with spc_dsp_init(). + +* When your emulated SNES is powered on, call spc_dsp_reset(). When the +reset switch is pressed, call spc_dsp_soft_reset(). + +* Call spc_dsp_set_output() with your output buffer, then do emulation. + +* Use spc_dsp_run() to run DSP for specified number of clocks (1024000 +per second). Every 32 clocks a pair of samples is added to your output +buffer. + +* Use spc_dsp_read() and spc_dsp_write() to handle DSP reads/writes from +the S-SMP. Before calling these always catch the DSP up to present time +with spc_dsp_run(). + +* Periodically play samples from your buffer. Use spc_dsp_sample_count() +to find out how many samples have been written, then +spc_dsp_set_output() after you've made more space in your buffer. + +* Use spc_dsp_copy_state() to save/load full DSP state. + +* When done, use spc_delete() to free memory. + + +SPC Music Playback +------------------ +See spc.h for full function reference (SNES_SPC.h if using C++). + +* Create SPC emulator with spc_new() and check for NULL. + +* Load SPC with spc_load_spc() and check for error. + +* Optionally cear echo buffer with spc_clear_echo(). Many SPCs have +garbage in echo buffer, which causes noise at the beginning. + +* Generate samples as needed with spc_play(). + +* When done, use spc_delete() to free memory. + +* For a more complete game music playback library, use Game_Music_Emu + + +State Copying +------------- +The SPC and DSP modules include state save/load functions. They take a +pointer to a pointer to a buffer to store state, and a copy function. +This copy function can either copy data to the buffer or from it, +allowing state save and restore with the same library function. The +internal save state format allows for future expansion without making +previous save states unusable. + +The SPC save state format puts the most important parts first to make it +easier to manually examine. It's organized as follows: + +Offset Size Data +- - - - - - - - - - - - - - - - - - + 0 $10000 SPC RAM +$10000 $10 SMP $F0-$FF registers +$10010 4 SMP $F4-$F8 output registers +$10014 2 PC +$10016 1 A +$10017 1 X +$10018 1 Y +$10019 1 PSW +$1001A 1 SP +$1001B 5 internal +$10020 $80 DSP registers +$100A0 ... internal + + +Library Compilation +------------------- +While this library is in C++, it has been written to easily link in a C +program *without* needing the standard C++ library. It doesn't use +exception handling or run-time type information (RTTI), so you can +disable these in your C++ compiler to increase efficiency. + +If you're building a shared library (DLL), I recommend only exporting +the C interfaces in spc.h and dsp.h, as the C++ interfaces expose +implementation details that will break link compatibility across +versions. + +If you're using C and compiling with GCC, I recommend the following +command-line options when compiling the library source, otherwise GCC +will insert calls to the standard C++ library and require that it be +linked in: + + -fno-rtti -fno-exceptions + +For maximum optimization, see the NDEBUG and BLARGG_NONPORTABLE options +in blargg_config. If using GCC, you can enable these by adding the +following command-line options when you invoke gcc. If you encounter +problems, try without -DBLARGG_NONPORTABLE; if that works, contact me so +I can figure out why BLARGG_NONPORTABLE was causing it to fail. + + -O3 -DNDEBUG -DBLARGG_NONPORTABLE -fno-rtti -fno-exceptions + + + +Error handling +-------------- +Functions which can fail have a return type of spc_err_t (blargg_err_t +in the C++ interfaces), which is a pointer to an error string (const +char*). If a function is successful it returns NULL. Errors that you can +easily avoid are checked with debug assertions; spc_err_t return values +are only used for genuine run-time errors that can't be easily predicted +in advance (out of memory, I/O errors, incompatible file data). Your +code should check all error values. + +To improve usability for C programmers, C++ programmers unfamiliar with +exceptions, and compatibility with older C++ compilers, the library does +*not* throw any C++ exceptions and uses malloc() instead of the standard +operator new. This means that you *must* check for NULL when creating a +library object with the new operator. + + +Solving Problems +---------------- +If you're having problems, try the following: + +* If you're getting garbled sound, try this simple siren generator in +place of your call to play(). This will quickly tell whether the problem +is in the library or in your code. + + static void play_siren( long count, short* out ) + { + static double a, a2; + while ( count-- ) + *out++ = 0x2000 * sin( a += .1 + .05*sin( a2+=.00005 ) ); + } + +* Enable debugging support in your environment. This enables assertions +and other run-time checks. + +* Turn the compiler's optimizer is off. Sometimes an optimizer generates +bad code. + +* If multiple threads are being used, ensure that only one at a time is +accessing a given set of objects from the library. This library is not +in general thread-safe, though independent objects can be used in +separate threads. + +* If all else fails, see if the demos work. + + +Accurate S-DSP Limitations +-------------------------- +* Power-up and soft reset behavior might have slight inaccuracies. + +* Muting (FLG bit 6) behavior when toggling bit very rapidly is not +emulated properly. + +* No other known inaccuracies. Has passed 100+ strenuous tests. + + +Fast S-DSP Limitations +---------------------- +* Uses faster sample calculations except in cases where exact value is +actually important (BRR decoding, and gaussian interpolation combined +with pitch modulation). + +* Stops decoding BRR data when a voice's envelope has released to +silence. + +* Emulates 32 clocks at a time, so DSP register and memory accesses are +all done in a bunch rather than spread out. Even though, some clever +code makes register accesses separated by 40 or so clocks occur with +cycle-accurate timing. + + +S-SMP Limitations +----------------- +* Opcode fetches and indirect pointers are always read directly from +memory, even for the $F0-$FF region, and the DSP is not caught up for +these fetches. + +* Attempts to perversely execute data in registers or an area being +modified by echo will not be emulated properly. + +* Has not been thoroughly tested. + +* Test register ($F0) is not implemented. + +* Echo buffer can overwrite IPL ROM area, and does not correctly update +extra RAM there. + + +To Do +----- +* I'd like feedback on the interface and any ways to improve it. In +particular, the differing features between the accurate and fast DSP +emulators might make it harder to cleanly switch between them without +modifying source code. + +* Finish thorough tests on SMP memory access times. + +* Finish thorough tests on SMP instruction behavior (flags, registers). + +* Finish thorough tests on SMP timers. + +* Finish power-up and reset behavior testing. + +* Come up with best starting conditions to play an SPC and implement in +hardware SNES SPC player for verification. + + +Thanks +------ +Thanks to Anti-Resonance's SPC2ROM and help getting SPCs playing on my +SNES in the first place, then Brad Martin's openspc and Chris Moeller's +openspc++ C++ adaptation, giving me a good SPC emulator to start with +several years ago. Thanks to Richard Bannister, Mahendra Tallur, Shazz, +nenolod, theHobbit, Johan Samuelsson, nes6502, and Micket for helping +test my Game_Music_Emu library. Thanks to hcs for help in converting to +C for the Rockbox port. Thanks to byuu (bsnes author) and pagefault and +Nach (zsnes team) for testing and using my new rewritten DSP in their +emulators. Thanks to anomie for his good SNES documentation and +discussions with me to keep it up to date with my latest findings. +-- +Shay Green diff --git a/waterbox/sameboy/snes_spc/spc.cpp b/waterbox/sameboy/snes_spc/spc.cpp new file mode 100644 index 0000000000..4e5a77ef15 --- /dev/null +++ b/waterbox/sameboy/snes_spc/spc.cpp @@ -0,0 +1,74 @@ +// snes_spc 0.9.0. http://www.slack.net/~ant/ + +#include "spc.h" + +#include "SNES_SPC.h" +#include "SPC_Filter.h" + +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +SNES_SPC* spc_new( void ) +{ + // be sure constants match + assert( spc_sample_rate == (int) SNES_SPC::sample_rate ); + assert( spc_rom_size == (int) SNES_SPC::rom_size ); + assert( spc_clock_rate == (int) SNES_SPC::clock_rate ); + assert( spc_clocks_per_sample == (int) SNES_SPC::clocks_per_sample ); + assert( spc_port_count == (int) SNES_SPC::port_count ); + assert( spc_voice_count == (int) SNES_SPC::voice_count ); + assert( spc_tempo_unit == (int) SNES_SPC::tempo_unit ); + assert( spc_file_size == (int) SNES_SPC::spc_file_size ); + #if !SPC_NO_COPY_STATE_FUNCS + assert( spc_state_size == (int) SNES_SPC::state_size ); + #endif + + SNES_SPC* s = new SNES_SPC; + if ( s && s->init() ) + { + delete s; + s = 0; + } + return s; +} + +void spc_delete ( SNES_SPC* s ) { delete s; } +void spc_init_rom ( SNES_SPC* s, unsigned char const r [64] ) { s->init_rom( r ); } +void spc_set_output ( SNES_SPC* s, spc_sample_t* p, int n ) { s->set_output( p, n ); } +int spc_sample_count ( SNES_SPC const* s ) { return s->sample_count(); } +void spc_reset ( SNES_SPC* s ) { s->reset(); } +void spc_soft_reset ( SNES_SPC* s ) { s->soft_reset(); } +int spc_read_port ( SNES_SPC* s, spc_time_t t, int p ) { return s->read_port( t, p ); } +void spc_write_port ( SNES_SPC* s, spc_time_t t, int p, int d ) { s->write_port( t, p, d ); } +void spc_end_frame ( SNES_SPC* s, spc_time_t t ) { s->end_frame( t ); } +void spc_mute_voices ( SNES_SPC* s, int mask ) { s->mute_voices( mask ); } +void spc_disable_surround( SNES_SPC* s, int disable ) { s->disable_surround( disable ); } +void spc_set_tempo ( SNES_SPC* s, int tempo ) { s->set_tempo( tempo ); } +uint8_t* spc_get_ram(SNES_SPC* s) { return s->get_ram(); } +spc_err_t spc_load_spc ( SNES_SPC* s, void const* p, long n ) { return s->load_spc( p, n ); } +void spc_clear_echo ( SNES_SPC* s ) { s->clear_echo(); } +spc_err_t spc_play ( SNES_SPC* s, int count, short* out ) { return s->play( count, out ); } +spc_err_t spc_skip ( SNES_SPC* s, int count ) { return s->skip( count ); } +#if !SPC_NO_COPY_STATE_FUNCS +void spc_copy_state ( SNES_SPC* s, unsigned char** p, spc_copy_func_t f ) { s->copy_state( p, f ); } +void spc_init_header ( void* spc_out ) { SNES_SPC::init_header( spc_out ); } +void spc_save_spc ( SNES_SPC* s, void* spc_out ) { s->save_spc( spc_out ); } +int spc_check_kon ( SNES_SPC* s ) { return s->check_kon(); } +#endif + +SPC_Filter* spc_filter_new( void ) { return new SPC_Filter; } +void spc_filter_delete( SPC_Filter* f ) { delete f; } +void spc_filter_run( SPC_Filter* f, spc_sample_t* p, int s ) { f->run( p, s ); } +void spc_filter_clear( SPC_Filter* f ) { f->clear(); } +void spc_filter_set_gain( SPC_Filter* f, int gain ) { f->set_gain( gain ); } +void spc_filter_set_bass( SPC_Filter* f, int bass ) { f->set_bass( bass ); } diff --git a/waterbox/sameboy/snes_spc/spc.h b/waterbox/sameboy/snes_spc/spc.h new file mode 100644 index 0000000000..cf23f3c945 --- /dev/null +++ b/waterbox/sameboy/snes_spc/spc.h @@ -0,0 +1,149 @@ +/* SNES SPC-700 APU emulator C interface (also usable from C++) */ + +/* snes_spc 0.9.0 */ +#ifndef SPC_H +#define SPC_H + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Error string return. NULL if success, otherwise error message. */ +typedef const char* spc_err_t; + +typedef struct SNES_SPC SNES_SPC; + +/* Creates new SPC emulator. NULL if out of memory. */ +SNES_SPC* spc_new( void ); + +/* Frees SPC emulator */ +void spc_delete( SNES_SPC* ); + +/* Sample pairs generated per second */ +enum { spc_sample_rate = 32000 }; + + +/**** Emulator use ****/ + +/* Sets IPL ROM data. Library does not include ROM data. Most SPC music files +don't need ROM, but a full emulator must provide this. */ +enum { spc_rom_size = 0x40 }; +void spc_init_rom( SNES_SPC*, unsigned char const rom [spc_rom_size] ); + +/* Sets destination for output samples */ +typedef short spc_sample_t; +void spc_set_output( SNES_SPC*, spc_sample_t* out, int out_size ); + +/* Number of samples written to output since last set */ +int spc_sample_count( SNES_SPC const* ); + +/* Resets SPC to power-on state. This resets your output buffer, so you must +call spc_set_output() after this. */ +void spc_reset( SNES_SPC* ); + +/* Emulates pressing reset switch on SNES. This resets your output buffer, so +you must call spc_set_output() after this. */ +void spc_soft_reset( SNES_SPC* ); + +/* 1024000 SPC clocks per second, sample pair every 32 clocks */ +typedef int spc_time_t; +enum { spc_clock_rate = 1024000 }; +enum { spc_clocks_per_sample = 32 }; + +/* Reads/writes port at specified time */ +enum { spc_port_count = 4 }; +int spc_read_port ( SNES_SPC*, spc_time_t, int port ); +void spc_write_port( SNES_SPC*, spc_time_t, int port, int data ); + +/* Runs SPC to end_time and starts a new time frame at 0 */ +void spc_end_frame( SNES_SPC*, spc_time_t end_time ); + +uint8_t* spc_get_ram(SNES_SPC*); + +/**** Sound control ****/ + +/*Mutes voices corresponding to non-zero bits in mask. Reduces emulation accuracy. */ +enum { spc_voice_count = 8 }; +void spc_mute_voices( SNES_SPC*, int mask ); + +/* If true, prevents channels and global volumes from being phase-negated. +Only supported by fast DSP; has no effect on accurate DSP. */ +void spc_disable_surround( SNES_SPC*, int disable ); + +/* Sets tempo, where spc_tempo_unit = normal, spc_tempo_unit / 2 = half speed, etc. */ +enum { spc_tempo_unit = 0x100 }; +void spc_set_tempo( SNES_SPC*, int ); + + +/**** SPC music playback *****/ + +/* Loads SPC data into emulator. Returns NULL on success, otherwise error string. */ +spc_err_t spc_load_spc( SNES_SPC*, void const* spc_in, long size ); + +/* Clears echo region. Useful after loading an SPC as many have garbage in echo. */ +void spc_clear_echo( SNES_SPC* ); + +/* Plays for count samples and write samples to out. Discards samples if out +is NULL. Count must be a multiple of 2 since output is stereo. */ +spc_err_t spc_play( SNES_SPC*, int count, short* out ); + +/* Skips count samples. Several times faster than spc_play(). */ +spc_err_t spc_skip( SNES_SPC*, int count ); + + +/**** State save/load (only available with accurate DSP) ****/ + +/* Saves/loads exact emulator state */ +enum { spc_state_size = 67 * 1024L }; /* maximum space needed when saving */ +typedef void (*spc_copy_func_t)( unsigned char** io, void* state, size_t ); +void spc_copy_state( SNES_SPC*, unsigned char** io, spc_copy_func_t ); + +/* Writes minimal SPC file header to spc_out */ +void spc_init_header( void* spc_out ); + +/* Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. +Does not set up SPC header; use spc_init_header() for that. */ +enum { spc_file_size = 0x10200 }; /* spc_out must have this many bytes allocated */ +void spc_save_spc( SNES_SPC*, void* spc_out ); + +/* Returns non-zero if new key-on events occurred since last check. Useful for +trimming silence while saving an SPC. */ +int spc_check_kon( SNES_SPC* ); + + +/**** SPC_Filter ****/ + +typedef struct SPC_Filter SPC_Filter; + +/* Creates new filter. NULL if out of memory. */ +SPC_Filter* spc_filter_new( void ); + +/* Frees filter */ +void spc_filter_delete( SPC_Filter* ); + +/* Filters count samples of stereo sound in place. Count must be a multiple of 2. */ +void spc_filter_run( SPC_Filter*, spc_sample_t* io, int count ); + +/* Clears filter to silence */ +void spc_filter_clear( SPC_Filter* ); + +/* Sets gain (volume), where spc_filter_gain_unit is normal. Gains greater than +spc_filter_gain_unit are fine, since output is clamped to 16-bit sample range. */ +enum { spc_filter_gain_unit = 0x100 }; +void spc_filter_set_gain( SPC_Filter*, int gain ); + +/* Sets amount of bass (logarithmic scale) */ +enum { spc_filter_bass_none = 0 }; +enum { spc_filter_bass_norm = 8 }; /* normal amount */ +enum { spc_filter_bass_max = 31 }; +void spc_filter_set_bass( SPC_Filter*, int bass ); + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/waterbox/sameboy/timing.c b/waterbox/sameboy/timing.c new file mode 100644 index 0000000000..bb2501b31a --- /dev/null +++ b/waterbox/sameboy/timing.c @@ -0,0 +1,161 @@ +#include "gb.h" +#ifdef _WIN32 +#define _WIN32_WINNT 0x0500 +#include +#else +#include +#endif + +static void GB_ir_run(GB_gameboy_t *gb) +{ + if (gb->ir_queue_length == 0) return; + if (gb->cycles_since_input_ir_change >= gb->ir_queue[0].delay) { + gb->cycles_since_input_ir_change -= gb->ir_queue[0].delay; + gb->infrared_input = gb->ir_queue[0].state; + gb->ir_queue_length--; + memmove(&gb->ir_queue[0], &gb->ir_queue[1], sizeof(gb->ir_queue[0]) * (gb->ir_queue_length)); + } +} + +static void advance_tima_state_machine(GB_gameboy_t *gb) +{ + if (gb->tima_reload_state == GB_TIMA_RELOADED) { + gb->tima_reload_state = GB_TIMA_RUNNING; + } + else if (gb->tima_reload_state == GB_TIMA_RELOADING) { + gb->tima_reload_state = GB_TIMA_RELOADED; + } +} + +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles) +{ + // Affected by speed boost + gb->dma_cycles += cycles; + + advance_tima_state_machine(gb); + for (int i = 0; i < cycles; i += 4) { + GB_set_internal_div_counter(gb, gb->div_cycles + 4); + } + + if (cycles > 4) { + advance_tima_state_machine(gb); + if (cycles > 8) { + advance_tima_state_machine(gb); + } + } + + uint16_t previous_serial_cycles = gb->serial_cycles; + gb->serial_cycles += cycles; + if (gb->serial_length) { + if ((gb->serial_cycles & gb->serial_length) != (previous_serial_cycles & gb->serial_length)) { + gb->serial_length = 0; + gb->io_registers[GB_IO_SC] &= ~0x80; + /* TODO: Does SB "update" bit by bit? */ + if (gb->serial_transfer_end_callback) { + gb->io_registers[GB_IO_SB] = gb->serial_transfer_end_callback(gb); + } + else { + gb->io_registers[GB_IO_SB] = 0xFF; + } + + gb->io_registers[GB_IO_IF] |= 8; + } + } + + if (gb->cgb_double_speed) { + cycles >>=1; + } + + // Not affected by speed boost + gb->hdma_cycles += cycles; + gb->apu.apu_cycles += cycles; + gb->cycles_since_ir_change += cycles; + gb->cycles_since_input_ir_change += cycles; + gb->cycles_since_epoch += cycles >> 1; + GB_dma_run(gb); + GB_hdma_run(gb); + GB_apu_run(gb); + GB_display_run(gb, cycles); + GB_ir_run(gb); +} + +/* Standard Timers */ +static const unsigned int GB_TAC_RATIOS[] = {1024, 16, 64, 256}; + +static void increase_tima(GB_gameboy_t *gb) +{ + gb->io_registers[GB_IO_TIMA]++; + if (gb->io_registers[GB_IO_TIMA] == 0) { + gb->io_registers[GB_IO_TIMA] = gb->io_registers[GB_IO_TMA]; + gb->io_registers[GB_IO_IF] |= 4; + gb->tima_reload_state = GB_TIMA_RELOADING; + } +} + +static bool counter_overflow_check(uint32_t old, uint32_t new, uint32_t max) +{ + return (old & (max >> 1)) && !(new & (max >> 1)); +} + +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value) +{ + /* TIMA increases when a specific high-bit becomes a low-bit. */ + value &= INTERNAL_DIV_CYCLES - 1; + if ((gb->io_registers[GB_IO_TAC] & 4) && + counter_overflow_check(gb->div_cycles, value, GB_TAC_RATIOS[gb->io_registers[GB_IO_TAC] & 3])) { + increase_tima(gb); + } + gb->div_cycles = value; +} + +/* + This glitch is based on the expected results of mooneye-gb rapid_toggle test. + This glitch happens because how TIMA is increased, see GB_set_internal_div_counter. + According to GiiBiiAdvance, GBC's behavior is different, but this was not tested or implemented. +*/ +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac) +{ + /* Glitch only happens when old_tac is enabled. */ + if (!(old_tac & 4)) return; + + unsigned int old_clocks = GB_TAC_RATIOS[old_tac & 3]; + unsigned int new_clocks = GB_TAC_RATIOS[new_tac & 3]; + + /* The bit used for overflow testing must have been 1 */ + if (gb->div_cycles & (old_clocks >> 1)) { + /* And now either the timer must be disabled, or the new bit used for overflow testing be 0. */ + if (!(new_tac & 4) || gb->div_cycles & (new_clocks >> 1)) { + increase_tima(gb); + } + } +} + +void GB_rtc_run(GB_gameboy_t *gb) +{ + if ((gb->rtc_real.high & 0x40) == 0) { /* is timer running? */ + time_t current_time = gb->frontend_rtc_time; + while (gb->last_rtc_second < current_time) { + gb->last_rtc_second++; + if (++gb->rtc_real.seconds == 60) + { + gb->rtc_real.seconds = 0; + if (++gb->rtc_real.minutes == 60) + { + gb->rtc_real.minutes = 0; + if (++gb->rtc_real.hours == 24) + { + gb->rtc_real.hours = 0; + if (++gb->rtc_real.days == 0) + { + if (gb->rtc_real.high & 1) /* Bit 8 of days*/ + { + gb->rtc_real.high |= 0x80; /* Overflow bit */ + } + gb->rtc_real.high ^= 1; + } + } + } + } + } + } +} diff --git a/waterbox/sameboy/timing.h b/waterbox/sameboy/timing.h new file mode 100644 index 0000000000..bd3b2f2732 --- /dev/null +++ b/waterbox/sameboy/timing.h @@ -0,0 +1,18 @@ +#ifndef timing_h +#define timing_h +#include "gb.h" + +#ifdef GB_INTERNAL +void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles); +void GB_set_internal_div_counter(GB_gameboy_t *gb, uint32_t value); +void GB_rtc_run(GB_gameboy_t *gb); +void GB_emulate_timer_glitch(GB_gameboy_t *gb, uint8_t old_tac, uint8_t new_tac); + +enum { + GB_TIMA_RUNNING = 0, + GB_TIMA_RELOADING = 1, + GB_TIMA_RELOADED = 2 +}; +#endif + +#endif /* timing_h */ diff --git a/waterbox/sameboy/z80_cpu.c b/waterbox/sameboy/z80_cpu.c new file mode 100644 index 0000000000..9558765263 --- /dev/null +++ b/waterbox/sameboy/z80_cpu.c @@ -0,0 +1,1375 @@ +#include +#include +#include "gb.h" + + +typedef void GB_opcode_t(GB_gameboy_t *gb, uint8_t opcode); + +static void ill(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_log(gb, "Illegal Opcode. Halting.\n"); + gb->interrupt_enable = 0; + gb->halted = true; +} + +static void nop(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); +} + +static void stop(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + if (gb->io_registers[GB_IO_KEY1] & 0x1) { + /* Make sure we don't leave display_cycles not divisble by 4 in single speed mode */ + if (gb->display_cycles % 4 == 2) { + GB_advance_cycles(gb, 4); + } + + /* Todo: the switch is not instant. We should emulate this. */ + gb->cgb_double_speed ^= true; + gb->io_registers[GB_IO_KEY1] = 0; + } + else { + gb->stopped = true; + } + gb->pc++; +} + +/* Operand naming conventions for functions: + r = 8-bit register + lr = low 8-bit register + hr = high 8-bit register + rr = 16-bit register + d8 = 8-bit imm + d16 = 16-bit imm + d.. = [..] + cc = condition code (z, nz, c, nc) + */ + +static void ld_rr_d16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint16_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + value |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + gb->registers[register_id] = value; +} + +static void ld_drr_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + GB_write_memory(gb, gb->registers[register_id], gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void inc_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + gb->registers[register_id]++; +} + +static void inc_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] += 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_hr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] -= 0x100; + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F00) == 0xF00) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_hr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 0x03; + gb->registers[register_id] &= 0xFF; + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); +} + +static void rlca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x0100; + } +} + +static void rla(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit7 = (gb->registers[GB_REGISTER_AF] & 0x8000) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] & 0xFF00) << 1; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x0100; + } + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_da16_sp(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify order is correct */ + uint16_t addr; + GB_advance_cycles(gb, 4); + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_SP] & 0xFF); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr+1, gb->registers[GB_REGISTER_SP] >> 8); + GB_advance_cycles(gb, 4); +} + +static void add_hl_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t hl = gb->registers[GB_REGISTER_HL]; + uint16_t rr; + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + rr = gb->registers[register_id]; + gb->registers[GB_REGISTER_HL] = hl + rr; + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_CARRY_FLAG | GB_HALF_CARRY_FLAG); + + /* The meaning of the Half Carry flag is really hard to track -_- */ + if (((hl & 0xFFF) + (rr & 0xFFF)) & 0x1000) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ( ((unsigned long) hl) + ((unsigned long) rr) & 0x10000) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_a_drr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + register_id = (opcode >> 4) + 1; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[register_id]) << 8; + GB_advance_cycles(gb, 4); +} + +static void dec_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = (opcode >> 4) + 1; + gb->registers[register_id]--; +} + +static void inc_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) + 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + + if ((gb->registers[register_id] & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} +static void dec_lr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + uint8_t value; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + + value = (gb->registers[register_id] & 0xFF) - 1; + gb->registers[register_id] = (gb->registers[register_id] & 0xFF00) | value; + + gb->registers[GB_REGISTER_AF] &= ~(GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + + if ((gb->registers[register_id] & 0x0F) == 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[register_id] & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_lr_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = (opcode >> 4) + 1; + gb->registers[register_id] &= 0xFF00; + gb->registers[register_id] |= GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); +} + +static void rrca(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry = (gb->registers[GB_REGISTER_AF] & 0x100) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG | 0x8000; + } +} + +static void rra(GB_gameboy_t *gb, uint8_t opcode) +{ + bool bit1 = (gb->registers[GB_REGISTER_AF] & 0x0100) != 0; + bool carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] = (gb->registers[GB_REGISTER_AF] >> 1) & 0xFF00; + if (carry) { + gb->registers[GB_REGISTER_AF] |= 0x8000; + } + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify cycles are not 8 and 4 instead */ + GB_advance_cycles(gb, 4); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; + GB_advance_cycles(gb, 8); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static void jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + gb->pc += (int8_t)GB_read_memory(gb, gb->pc) + 1; + GB_advance_cycles(gb, 8); + } + else { + GB_advance_cycles(gb, 8); + gb->pc += 1; + } +} + +static void daa(GB_gameboy_t *gb, uint8_t opcode) +{ + /* This function is UGLY and UNREADABLE! But it passes Blargg's daa test! */ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= ~GB_ZERO_FLAG; + if (gb->registers[GB_REGISTER_AF] & GB_SUBSTRACT_FLAG) { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] &= ~GB_HALF_CARRY_FLAG; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0x9A00; + } + else { + gb->registers[GB_REGISTER_AF] += 0xFA00; + } + } + else if(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + gb->registers[GB_REGISTER_AF] += 0xA000; + } + } + else { + if (gb->registers[GB_REGISTER_AF] & GB_HALF_CARRY_FLAG) { + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + gb->registers[GB_REGISTER_AF] = 0; + number += 0x06; + if (number >= 0xa0) { + number -= 0xa0; + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + gb->registers[GB_REGISTER_AF] |= number << 8; + } + else { + uint16_t number = gb->registers[GB_REGISTER_AF] >> 8; + if (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) { + number += 0x100; + } + if (number > 0x99) { + number += 0x60; + } + number = (number & 0x0F) + ((number & 0x0F) > 9 ? 6 : 0) + (number & 0xFF0); + gb->registers[GB_REGISTER_AF] = number << 8; + if (number & 0xFF00) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + } + } + if ((gb->registers[GB_REGISTER_AF] & 0xFF00) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cpl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] ^= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG; +} + +static void scf(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ccf(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] ^= GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] &= ~(GB_HALF_CARRY_FLAG | GB_SUBSTRACT_FLAG); +} + +static void ld_dhli_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL]++, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_dhld_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL]--, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_dhli(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]++) << 8; + GB_advance_cycles(gb, 4); +} + +static void ld_a_dhld(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, gb->registers[GB_REGISTER_HL]--) << 8; + GB_advance_cycles(gb, 4); +} + +static void inc_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) + 1; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + + gb->registers[GB_REGISTER_AF] &= ~(GB_SUBSTRACT_FLAG | GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + if ((value & 0x0F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void dec_dhl(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]) - 1; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + + gb->registers[GB_REGISTER_AF] &= ~( GB_ZERO_FLAG | GB_HALF_CARRY_FLAG); + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if ((value & 0x0F) == 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((value & 0xFF) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void ld_dhl_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint8_t data = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], data); + GB_advance_cycles(gb, 4); +} + +uint8_t get_src_value(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + return gb->registers[GB_REGISTER_AF] >> 8; + } + uint8_t ret = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); + GB_advance_cycles(gb, 4); + return ret; + } + if (src_low) { + return gb->registers[src_register_id] & 0xFF; + } + return gb->registers[src_register_id] >> 8; +} + +static void set_src_value(GB_gameboy_t *gb, uint8_t opcode, uint8_t value) +{ + uint8_t src_register_id; + uint8_t src_low; + src_register_id = ((opcode >> 1) + 1) & 3; + src_low = opcode & 1; + + if (src_register_id == GB_REGISTER_AF) { + if (src_low) { + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= value << 8; + } + else { + GB_write_memory(gb, gb->registers[GB_REGISTER_HL], value); + GB_advance_cycles(gb, 4); + } + } + else { + if (src_low) { + gb->registers[src_register_id] &= 0xFF00; + gb->registers[src_register_id] |= value; + } + else { + gb->registers[src_register_id] &= 0xFF; + gb->registers[src_register_id] |= value << 8; + } + } +} + +/* The LD r,r instruction is extremely common and extremely simple. Decoding this opcode at runtime is a significent + performance hit, so we generate functions for every ld x,y couple (including [hl]) at compile time using macros. */ + +/* Todo: It's probably wise to do the same to all opcodes. */ + +#define LD_X_Y(x, y) \ +static void ld_##x##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ + GB_advance_cycles(gb, 4); \ + gb->x = gb->y;\ +} + +#define LD_X_DHL(x) \ +static void ld_##x##_##dhl(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +gb->x = GB_read_memory(gb, gb->registers[GB_REGISTER_HL]); \ +GB_advance_cycles(gb, 4);\ +} + +#define LD_DHL_Y(y) \ +static void ld_##dhl##_##y(GB_gameboy_t *gb, uint8_t opcode) \ +{ \ +GB_advance_cycles(gb, 4); \ +GB_write_memory(gb, gb->registers[GB_REGISTER_HL], gb->y); \ +GB_advance_cycles(gb, 4);\ +} + +LD_X_Y(b,c) LD_X_Y(b,d) LD_X_Y(b,e) LD_X_Y(b,h) LD_X_Y(b,l) LD_X_DHL(b) LD_X_Y(b,a) +LD_X_Y(c,b) LD_X_Y(c,d) LD_X_Y(c,e) LD_X_Y(c,h) LD_X_Y(c,l) LD_X_DHL(c) LD_X_Y(c,a) +LD_X_Y(d,b) LD_X_Y(d,c) LD_X_Y(d,e) LD_X_Y(d,h) LD_X_Y(d,l) LD_X_DHL(d) LD_X_Y(d,a) +LD_X_Y(e,b) LD_X_Y(e,c) LD_X_Y(e,d) LD_X_Y(e,h) LD_X_Y(e,l) LD_X_DHL(e) LD_X_Y(e,a) +LD_X_Y(h,b) LD_X_Y(h,c) LD_X_Y(h,d) LD_X_Y(h,e) LD_X_Y(h,l) LD_X_DHL(h) LD_X_Y(h,a) +LD_X_Y(l,b) LD_X_Y(l,c) LD_X_Y(l,d) LD_X_Y(l,e) LD_X_Y(l,h) LD_X_DHL(l) LD_X_Y(l,a) +LD_DHL_Y(b) LD_DHL_Y(c) LD_DHL_Y(d) LD_DHL_Y(e) LD_DHL_Y(h) LD_DHL_Y(l) LD_DHL_Y(a) +LD_X_Y(a,b) LD_X_Y(a,c) LD_X_Y(a,d) LD_X_Y(a,e) LD_X_Y(a,h) LD_X_Y(a,l) LD_X_DHL(a) + + +static void add_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t)(a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if ((uint8_t)(a + value + carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void halt(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->halted = true; + /* Despite what some online documentations say, the HALT bug also happens on a CGB, in both CGB and DMG modes. */ + if (!gb->ime && (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) != 0) { + gb->halted = false; + gb->halt_bug = true; + } +} + +static void ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + /* Todo: Verify timing */ + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 8); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] += 2; + } + else { + GB_advance_cycles(gb, 8); + } +} + +static void pop_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 4); + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[register_id] = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->registers[register_id] |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFFF0; // Make sure we don't set impossible flags on F! See Blargg's PUSH AF test. + gb->registers[GB_REGISTER_SP] += 2; +} + +static void jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr; + + } + else { + GB_advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint16_t addr = GB_read_memory(gb, gb->pc); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc + 1) << 8); + GB_advance_cycles(gb, 8); + gb->pc = addr;} + +static void call_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + if (condition_code(gb, opcode)) { + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_SP] -= 2; + uint16_t addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc++) << 8); + GB_advance_cycles(gb, 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; + } + else { + GB_advance_cycles(gb, 12); + gb->pc += 2; + } +} + +static void push_rr(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t register_id; + GB_advance_cycles(gb, 8); + register_id = ((opcode >> 4) + 1) & 3; + gb->registers[GB_REGISTER_SP] -= 2; + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->registers[register_id]) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->registers[register_id]) & 0xFF); + GB_advance_cycles(gb, 4); +} + +static void add_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a + value) << 8; + if ((uint8_t) (a + value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void adc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = (a + value + carry) << 8; + + if (gb->registers[GB_REGISTER_AF] == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) + (value & 0xF) + carry > 0x0F) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) + ((unsigned long) value) + carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sub_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a - value) << 8) | GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void sbc_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a, carry; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + gb->registers[GB_REGISTER_AF] = ((a - value - carry) << 8) | GB_SUBSTRACT_FLAG; + + if ((uint8_t) (a - value - carry) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF) + carry) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (((unsigned long) a) - ((unsigned long) value) - carry > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void and_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = ((a & value) << 8) | GB_HALF_CARRY_FLAG; + if ((a & value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void xor_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a ^ value) << 8; + if ((a ^ value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void or_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] = (a | value) << 8; + if ((a | value) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void cp_a_d8(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value, a; + GB_advance_cycles(gb, 4); + value = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + a = gb->registers[GB_REGISTER_AF] >> 8; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + gb->registers[GB_REGISTER_AF] |= GB_SUBSTRACT_FLAG; + if (a == value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + if ((a & 0xF) < (value & 0xF)) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + if (a < value) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void rst(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] -= 2; + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = opcode ^ 0xC7; +} + +static void ret(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->pc = GB_read_memory(gb, gb->registers[GB_REGISTER_SP]); + GB_advance_cycles(gb, 4); + gb->pc |= GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8; + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] += 2; +} + +static void reti(GB_gameboy_t *gb, uint8_t opcode) +{ + ret(gb, opcode); + gb->ime = true; +} + +static void call_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t call_addr = gb->pc - 1; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_SP] -= 2; + uint16_t addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= (GB_read_memory(gb, gb->pc++) << 8); + GB_advance_cycles(gb, 8); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP] + 1, (gb->pc) >> 8); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, gb->registers[GB_REGISTER_SP], (gb->pc) & 0xFF); + GB_advance_cycles(gb, 4); + gb->pc = addr; +} + +static void ld_da8_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + GB_write_memory(gb, 0xFF00 + temp, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_da8(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + uint8_t temp = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + temp) << 8; + GB_advance_cycles(gb, 4); +} + +static void ld_dc_a(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + GB_write_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF), gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_dc(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, 0xFF00 + (gb->registers[GB_REGISTER_BC] & 0xFF)) << 8; + GB_advance_cycles(gb, 4); +} + +static void add_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + uint16_t sp = gb->registers[GB_REGISTER_SP]; + GB_advance_cycles(gb, 4); + offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 12); + gb->registers[GB_REGISTER_SP] += offset; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + + /* A new instruction, a new meaning for Half Carry! */ + if ((sp & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((sp & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + gb->pc = gb->registers[GB_REGISTER_HL]; +} + +static void ld_da16_a(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + GB_advance_cycles(gb, 4); + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8; + GB_advance_cycles(gb, 4); + GB_write_memory(gb, addr, gb->registers[GB_REGISTER_AF] >> 8); + GB_advance_cycles(gb, 4); +} + +static void ld_a_da16(GB_gameboy_t *gb, uint8_t opcode) +{ + uint16_t addr; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF; + addr = GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 4); + addr |= GB_read_memory(gb, gb->pc++) << 8 ; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] |= GB_read_memory(gb, addr) << 8; + GB_advance_cycles(gb, 4); +} + +static void di(GB_gameboy_t *gb, uint8_t opcode) +{ + /* DI is NOT delayed, not even on a CGB. Mooneye's di_timing-GS test fails on a CGB + for different reasons.*/ + GB_advance_cycles(gb, 4); + gb->ime = false; +} + +static void ei(GB_gameboy_t *gb, uint8_t opcode) +{ + /* ei is actually "disable interrupts for one instruction, then enable them". */ + GB_advance_cycles(gb, 4); + gb->ime = false; + gb->ime_toggle = true; +} + +static void ld_hl_sp_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + int16_t offset; + GB_advance_cycles(gb, 4); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + offset = (int8_t) GB_read_memory(gb, gb->pc++); + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_HL] = gb->registers[GB_REGISTER_SP] + offset; + + if ((gb->registers[GB_REGISTER_SP] & 0xF) + (offset & 0xF) > 0xF) { + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + } + + if ((gb->registers[GB_REGISTER_SP] & 0xFF) + (offset & 0xFF) > 0xFF) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } +} + +static void ld_sp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 8); + gb->registers[GB_REGISTER_SP] = gb->registers[GB_REGISTER_HL]; +} + +static void rlc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1) | carry); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value << 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rrc_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x01) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit7; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit7 = (value & 0x80) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value << 1) | carry; + set_src_value(gb, opcode, value); + if (bit7) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void rr_r(GB_gameboy_t *gb, uint8_t opcode) +{ + bool carry; + uint8_t value; + bool bit1; + + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG) != 0; + bit1 = (value & 0x1) != 0; + + gb->registers[GB_REGISTER_AF] &= 0xFF00; + value = (value >> 1) | (carry << 7); + set_src_value(gb, opcode, value); + if (bit1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sla_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + bool carry; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + carry = (value & 0x80) != 0; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value << 1)); + if (carry) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if ((value & 0x7F) == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void sra_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t bit7; + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + bit7 = value & 0x80; + gb->registers[GB_REGISTER_AF] &= 0xFF00; + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + value = (value >> 1) | bit7; + set_src_value(gb, opcode, value); + if (value == 0) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void srl_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 1)); + if (value & 1) { + gb->registers[GB_REGISTER_AF] |= GB_CARRY_FLAG; + } + if (!(value >> 1)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void swap_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + gb->registers[GB_REGISTER_AF] &= 0xFF00; + set_src_value(gb, opcode, (value >> 4) | (value << 4)); + if (!value) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } +} + +static void bit_r(GB_gameboy_t *gb, uint8_t opcode) +{ + uint8_t value; + uint8_t bit; + GB_advance_cycles(gb, 4); + value = get_src_value(gb, opcode); + bit = 1 << ((opcode >> 3) & 7); + if ((opcode & 0xC0) == 0x40) { /* Bit */ + gb->registers[GB_REGISTER_AF] &= 0xFF00 | GB_CARRY_FLAG; + gb->registers[GB_REGISTER_AF] |= GB_HALF_CARRY_FLAG; + if (!(bit & value)) { + gb->registers[GB_REGISTER_AF] |= GB_ZERO_FLAG; + } + } + else if ((opcode & 0xC0) == 0x80) { /* res */ + set_src_value(gb, opcode, value & ~bit) ; + } + else if ((opcode & 0xC0) == 0xC0) { /* set */ + set_src_value(gb, opcode, value | bit) ; + } +} + +static void cb_prefix(GB_gameboy_t *gb, uint8_t opcode) +{ + GB_advance_cycles(gb, 4); + opcode = GB_read_memory(gb, gb->pc++); + switch (opcode >> 3) { + case 0: + rlc_r(gb, opcode); + break; + case 1: + rrc_r(gb, opcode); + break; + case 2: + rl_r(gb, opcode); + break; + case 3: + rr_r(gb, opcode); + break; + case 4: + sla_r(gb, opcode); + break; + case 5: + sra_r(gb, opcode); + break; + case 6: + swap_r(gb, opcode); + break; + case 7: + srl_r(gb, opcode); + break; + default: + bit_r(gb, opcode); + break; + } +} + +static GB_opcode_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + nop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rlca, /* 0X */ + ld_da16_sp, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rrca, + stop, ld_rr_d16, ld_drr_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, rla, /* 1X */ + jr_r8, add_hl_rr, ld_a_drr, dec_rr, inc_lr, dec_lr, ld_lr_d8, rra, + jr_cc_r8, ld_rr_d16, ld_dhli_a, inc_rr, inc_hr, dec_hr, ld_hr_d8, daa, /* 2X */ + jr_cc_r8, add_hl_rr, ld_a_dhli, dec_rr, inc_lr, dec_lr, ld_lr_d8, cpl, + jr_cc_r8, ld_rr_d16, ld_dhld_a, inc_rr, inc_dhl, dec_dhl, ld_dhl_d8, scf, /* 3X */ + jr_cc_r8, add_hl_rr, ld_a_dhld, dec_rr, inc_hr, dec_hr, ld_hr_d8, ccf, + nop, ld_b_c, ld_b_d, ld_b_e, ld_b_h, ld_b_l, ld_b_dhl, ld_b_a, /* 4X */ + ld_c_b, nop, ld_c_d, ld_c_e, ld_c_h, ld_c_l, ld_c_dhl, ld_c_a, + ld_d_b, ld_d_c, nop, ld_d_e, ld_d_h, ld_d_l, ld_d_dhl, ld_d_a, /* 5X */ + ld_e_b, ld_e_c, ld_e_d, nop, ld_e_h, ld_e_l, ld_e_dhl, ld_e_a, + ld_h_b, ld_h_c, ld_h_d, ld_h_e, nop, ld_h_l, ld_h_dhl, ld_h_a, /* 6X */ + ld_l_b, ld_l_c, ld_l_d, ld_l_e, ld_l_h, nop, ld_l_dhl, ld_l_a, + ld_dhl_b, ld_dhl_c, ld_dhl_d, ld_dhl_e, ld_dhl_h, ld_dhl_l, halt, ld_dhl_a, /* 7X */ + ld_a_b, ld_a_c, ld_a_d, ld_a_e, ld_a_h, ld_a_l, ld_a_dhl, nop, + add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, add_a_r, /* 8X */ + adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, adc_a_r, + sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, sub_a_r, /* 9X */ + sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, sbc_a_r, + and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, and_a_r, /* aX */ + xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, xor_a_r, + or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, or_a_r, /* bX */ + cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, cp_a_r, + ret_cc, pop_rr, jp_cc_a16, jp_a16, call_cc_a16,push_rr, add_a_d8, rst, /* cX */ + ret_cc, ret, jp_cc_a16, cb_prefix, call_cc_a16,call_a16, adc_a_d8, rst, + ret_cc, pop_rr, jp_cc_a16, ill, call_cc_a16,push_rr, sub_a_d8, rst, /* dX */ + ret_cc, reti, jp_cc_a16, ill, call_cc_a16,ill, sbc_a_d8, rst, + ld_da8_a, pop_rr, ld_dc_a, ill, ill, push_rr, and_a_d8, rst, /* eX */ + add_sp_r8, jp_hl, ld_da16_a, ill, ill, ill, xor_a_d8, rst, + ld_a_da8, pop_rr, ld_a_dc, di, ill, push_rr, or_a_d8, rst, /* fX */ + ld_hl_sp_r8,ld_sp_hl, ld_a_da16, ei, ill, ill, cp_a_d8, rst, +}; +void GB_cpu_run(GB_gameboy_t *gb) +{ + gb->vblank_just_occured = false; + bool interrupt = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + + if (interrupt) { + gb->halted = false; + } + + if (gb->hdma_on) { + GB_advance_cycles(gb, 4); + return; + } + + bool effecitve_ime = gb->ime; + if (gb->ime_toggle) { + gb->ime = !gb->ime; + gb->ime_toggle = false; + } + + if (effecitve_ime && interrupt) { + uint8_t interrupt_bit = 0; + uint8_t interrupt_queue = gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F; + while (!(interrupt_queue & 1)) { + interrupt_queue >>= 1; + interrupt_bit++; + } + gb->io_registers[GB_IO_IF] &= ~(1 << interrupt_bit); + gb->ime = false; + nop(gb, 0); + /* Run pseudo instructions rst 40-60*/ + rst(gb, 0x87 + interrupt_bit * 8); + } + else if(!gb->halted && !gb->stopped) { + uint8_t opcode = GB_read_memory(gb, gb->pc++); + if (gb->halt_bug) { + gb->pc--; + gb->halt_bug = false; + } + opcodes[opcode](gb, opcode); + } + else { + GB_advance_cycles(gb, 4); + } +} diff --git a/waterbox/sameboy/z80_cpu.h b/waterbox/sameboy/z80_cpu.h new file mode 100644 index 0000000000..1434ed7b96 --- /dev/null +++ b/waterbox/sameboy/z80_cpu.h @@ -0,0 +1,10 @@ +#ifndef z80_cpu_h +#define z80_cpu_h +#include "gb.h" + +void GB_cpu_disassemble(GB_gameboy_t *gb, uint16_t pc, uint16_t count); +#ifdef GB_INTERNAL +void GB_cpu_run(GB_gameboy_t *gb); +#endif + +#endif /* z80_cpu_h */