nuke sameboy (#2934)
This commit is contained in:
parent
09ccf0dbe8
commit
9a87a0f586
Binary file not shown.
|
@ -313,7 +313,7 @@ Sega Genesis | **Genplus-gx**
|
||||||
Sega Master System | **SMSHawk**
|
Sega Master System | **SMSHawk**
|
||||||
Sega Saturn | **Saturnus**
|
Sega Saturn | **Saturnus**
|
||||||
SNES | **BSNES**, Faust, Snes9x
|
SNES | **BSNES**, Faust, Snes9x
|
||||||
Super Game Boy | **BSNES**, **Gambatte**, **SameBoy**
|
Super Game Boy | **BSNES**, **Gambatte**
|
||||||
TI-83 | **TI83Hawk**
|
TI-83 | **TI83Hawk**
|
||||||
TurboGrafx | HyperNyma, **PCEHawk**, **TurboNyma**
|
TurboGrafx | HyperNyma, **PCEHawk**, **TurboNyma**
|
||||||
Uzebox | **Uzem**
|
Uzebox | **Uzem**
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace BizHawk.Client.Common
|
||||||
(new[] { "SNES" },
|
(new[] { "SNES" },
|
||||||
new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }),
|
new[] { CoreNames.Faust, CoreNames.Snes9X, CoreNames.Bsnes, CoreNames.Bsnes115 }),
|
||||||
(new[] { "SGB" },
|
(new[] { "SGB" },
|
||||||
new[] { CoreNames.Gambatte, CoreNames.SameBoy, CoreNames.Bsnes, CoreNames.Bsnes115}),
|
new[] { CoreNames.Gambatte, CoreNames.Bsnes, CoreNames.Bsnes115}),
|
||||||
(new[] { "GB", "GBC" },
|
(new[] { "GB", "GBC" },
|
||||||
new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }),
|
new[] { CoreNames.Gambatte, CoreNames.GbHawk, CoreNames.SubGbHawk }),
|
||||||
(new[] { "DGB" },
|
(new[] { "DGB" },
|
||||||
|
@ -322,7 +322,7 @@ namespace BizHawk.Client.Common
|
||||||
["GB"] = CoreNames.Gambatte,
|
["GB"] = CoreNames.Gambatte,
|
||||||
["GBC"] = CoreNames.Gambatte,
|
["GBC"] = CoreNames.Gambatte,
|
||||||
["DGB"] = CoreNames.DualGambatte,
|
["DGB"] = CoreNames.DualGambatte,
|
||||||
["SGB"] = CoreNames.SameBoy,
|
["SGB"] = CoreNames.Gambatte,
|
||||||
["PCE"] = CoreNames.TurboNyma,
|
["PCE"] = CoreNames.TurboNyma,
|
||||||
["PCECD"] = CoreNames.TurboNyma,
|
["PCECD"] = CoreNames.TurboNyma,
|
||||||
["SGX"] = CoreNames.TurboNyma
|
["SGX"] = CoreNames.TurboNyma
|
||||||
|
|
|
@ -1574,10 +1574,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
GBPrefs.DoGBPrefsDialog(this, Config, Game, MovieSession, gb);
|
GBPrefs.DoGBPrefsDialog(this, Config, Game, MovieSession, gb);
|
||||||
}
|
}
|
||||||
else // SameBoy
|
|
||||||
{
|
|
||||||
GenericCoreConfig.DoDialog(this, "Gameboy Settings");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GbGpuViewerMenuItem_Click(object sender, EventArgs e)
|
private void GbGpuViewerMenuItem_Click(object sender, EventArgs e)
|
||||||
|
|
|
@ -1997,7 +1997,6 @@ namespace BizHawk.Client.EmuHawk
|
||||||
break;
|
break;
|
||||||
case "GB":
|
case "GB":
|
||||||
case "GBC":
|
case "GBC":
|
||||||
case "SGB" when Emulator is Sameboy:
|
|
||||||
case "SGB" when Emulator is Gameboy:
|
case "SGB" when Emulator is Gameboy:
|
||||||
GBSubMenu.Visible = true;
|
GBSubMenu.Visible = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
using BizHawk.BizInvoke;
|
|
||||||
using BizHawk.Emulation.Cores.Waterbox;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
[BizImport(CC)]
|
|
||||||
public abstract void SetPrinterCallback(PrinterCallback callback);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,410 +0,0 @@
|
||||||
using BizHawk.Common;
|
|
||||||
using BizHawk.Emulation.Common;
|
|
||||||
using BizHawk.Emulation.Cores.Properties;
|
|
||||||
using BizHawk.Emulation.Cores.Waterbox;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy
|
|
||||||
{
|
|
||||||
[PortedCore(CoreNames.SameBoy, "LIJI32", "efc11783c7fb6da66e1dd084e41ba6a85c0bd17e", "https://sameboy.github.io/")]
|
|
||||||
public class Sameboy : WaterboxCore,
|
|
||||||
IGameboyCommon, ISaveRam,
|
|
||||||
ISettable<Sameboy.Settings, Sameboy.SyncSettings>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// the nominal length of one frame
|
|
||||||
/// </summary>
|
|
||||||
private const int TICKSPERFRAME = 35112;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// number of ticks per second (GB, CGB)
|
|
||||||
/// </summary>
|
|
||||||
private const int TICKSPERSECOND = 2097152;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// number of ticks per second (SGB)
|
|
||||||
/// </summary>
|
|
||||||
private const int TICKSPERSECOND_SGB = 2147727;
|
|
||||||
|
|
||||||
private readonly LibSameboy _core;
|
|
||||||
private readonly bool _cgb;
|
|
||||||
private readonly bool _sgb;
|
|
||||||
|
|
||||||
private readonly IntPtr[] _cachedGpuPointers = new IntPtr[4];
|
|
||||||
|
|
||||||
[CoreConstructor("SGB")]
|
|
||||||
public Sameboy(byte[] rom, CoreComm comm, Settings settings, SyncSettings syncSettings, bool deterministic)
|
|
||||||
: this(rom, comm, true, settings, syncSettings, deterministic)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
[CoreConstructor("GB")]
|
|
||||||
[CoreConstructor("GBC")]
|
|
||||||
public Sameboy(CoreComm comm, byte[] rom, Settings settings, SyncSettings syncSettings, bool deterministic)
|
|
||||||
: this(rom, comm, false, settings, syncSettings, deterministic)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public Sameboy(byte[] rom, CoreComm comm, bool sgb, Settings settings, SyncSettings syncSettings, bool deterministic)
|
|
||||||
: base(comm, new Configuration
|
|
||||||
{
|
|
||||||
DefaultWidth = sgb && (settings == null || settings.ShowSgbBorder) ? 256 : 160,
|
|
||||||
DefaultHeight = sgb && (settings == null || settings.ShowSgbBorder) ? 224 : 144,
|
|
||||||
MaxWidth = sgb ? 256 : 160,
|
|
||||||
MaxHeight = sgb ? 224 : 144,
|
|
||||||
MaxSamples = 1024,
|
|
||||||
DefaultFpsNumerator = sgb ? TICKSPERSECOND_SGB : TICKSPERSECOND,
|
|
||||||
DefaultFpsDenominator = TICKSPERFRAME,
|
|
||||||
SystemId = sgb ? "SGB" : "GB"
|
|
||||||
})
|
|
||||||
{
|
|
||||||
_corePrinterCallback = PrinterCallbackRelay;
|
|
||||||
_coreScanlineCallback = ScanlineCallbackRelay;
|
|
||||||
|
|
||||||
_core = PreInit<LibSameboy>(new WaterboxOptions
|
|
||||||
{
|
|
||||||
Filename = "sameboy.wbx",
|
|
||||||
SbrkHeapSizeKB = 192,
|
|
||||||
InvisibleHeapSizeKB = 12,
|
|
||||||
SealedHeapSizeKB = 9 * 1024,
|
|
||||||
PlainHeapSizeKB = 4,
|
|
||||||
MmapHeapSizeKB = 1024,
|
|
||||||
SkipCoreConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
|
|
||||||
SkipMemoryConsistencyCheck = comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
|
|
||||||
}, new Delegate[] { _corePrinterCallback, _coreScanlineCallback });
|
|
||||||
|
|
||||||
_cgb = (rom[0x143] & 0xc0) == 0xc0 && !sgb;
|
|
||||||
_sgb = sgb;
|
|
||||||
Console.WriteLine("Automaticly detected CGB to " + _cgb);
|
|
||||||
_syncSettings = syncSettings ?? new SyncSettings();
|
|
||||||
_settings = settings ?? new Settings();
|
|
||||||
|
|
||||||
var bios = _syncSettings.UseRealBIOS && !sgb
|
|
||||||
? comm.CoreFileProvider.GetFirmwareOrThrow(new(_cgb ? "GBC" : "GB", "World"))
|
|
||||||
: Util.DecompressGzipFile(new MemoryStream(_cgb ? Resources.SameboyCgbBoot.Value : Resources.SameboyDmgBoot.Value));
|
|
||||||
|
|
||||||
var spc = sgb
|
|
||||||
? Util.DecompressGzipFile(new MemoryStream(Resources.SgbCartPresent_SPC.Value))
|
|
||||||
: 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");
|
|
||||||
|
|
||||||
_core.GetGpuMemory(_cachedGpuPointers);
|
|
||||||
|
|
||||||
PostInit();
|
|
||||||
|
|
||||||
DeterministicEmulation = deterministic || !_syncSettings.UseRealTime;
|
|
||||||
InitializeRtc(_syncSettings.InitialTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (_sgb)
|
|
||||||
{
|
|
||||||
// The SGB SNES side code enforces U+D/L+R prohibitions
|
|
||||||
if (((uint)b & 0x30) == 0x30)
|
|
||||||
b &= unchecked((LibSameboy.Buttons)~0x30);
|
|
||||||
if (((uint)b & 0xc0) == 0xc0)
|
|
||||||
b &= unchecked((LibSameboy.Buttons)~0xc0);
|
|
||||||
}
|
|
||||||
if (i != 1)
|
|
||||||
{
|
|
||||||
b = (LibSameboy.Buttons)((uint)b << 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public new bool SaveRamModified => _core.HasSaveRam();
|
|
||||||
|
|
||||||
public new byte[] CloneSaveRam()
|
|
||||||
{
|
|
||||||
_exe.AddTransientFile(new byte[0], "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");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Settings _settings;
|
|
||||||
private SyncSettings _syncSettings;
|
|
||||||
|
|
||||||
public class Settings
|
|
||||||
{
|
|
||||||
[DisplayName("Show SGB Border")]
|
|
||||||
[DefaultValue(true)]
|
|
||||||
public bool ShowSgbBorder { get; set; }
|
|
||||||
|
|
||||||
public Settings Clone()
|
|
||||||
{
|
|
||||||
return (Settings)MemberwiseClone();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool NeedsReboot(Settings x, Settings y)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Settings()
|
|
||||||
{
|
|
||||||
SettingsUtil.SetDefaultValues(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 Settings GetSettings()
|
|
||||||
{
|
|
||||||
return _settings.Clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SyncSettings GetSyncSettings()
|
|
||||||
{
|
|
||||||
return _syncSettings.Clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PutSettingsDirtyBits PutSettings(Settings o)
|
|
||||||
{
|
|
||||||
var ret = Settings.NeedsReboot(_settings, o);
|
|
||||||
_settings = o;
|
|
||||||
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PutSettingsDirtyBits PutSyncSettings(SyncSettings o)
|
|
||||||
{
|
|
||||||
var ret = SyncSettings.NeedsReboot(_syncSettings, o);
|
|
||||||
_syncSettings = o;
|
|
||||||
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound)
|
|
||||||
{
|
|
||||||
return new LibSameboy.FrameInfo
|
|
||||||
{
|
|
||||||
Time = GetRtcTime(!DeterministicEmulation),
|
|
||||||
Keys = GetButtons(controller)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override unsafe void FrameAdvancePost()
|
|
||||||
{
|
|
||||||
if (_frontendScanlineCallback != null && _scanlineCallbackLine == -1)
|
|
||||||
_frontendScanlineCallback(_core.GetIoReg(0x40));
|
|
||||||
|
|
||||||
if (_sgb && !_settings.ShowSgbBorder)
|
|
||||||
{
|
|
||||||
fixed(int *buff = _videoBuffer)
|
|
||||||
{
|
|
||||||
int* dst = buff;
|
|
||||||
int* src = buff + (224 - 144) / 2 * 256 + (256 - 160) / 2;
|
|
||||||
for (int j = 0; j < 144; j++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 160; i++)
|
|
||||||
{
|
|
||||||
*dst++ = *src++;
|
|
||||||
}
|
|
||||||
src += 256 - 160;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BufferWidth = 160;
|
|
||||||
BufferHeight = 144;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadStateBinaryInternal(BinaryReader reader)
|
|
||||||
{
|
|
||||||
UpdateCoreScanlineCallback(false);
|
|
||||||
UpdateCorePrinterCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsCGBMode() => _cgb;
|
|
||||||
|
|
||||||
public IGPUMemoryAreas LockGPU()
|
|
||||||
{
|
|
||||||
_exe.Enter();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new GPUMemoryAreas(_exe)
|
|
||||||
{
|
|
||||||
Vram = _cachedGpuPointers[0],
|
|
||||||
Oam = _cachedGpuPointers[1],
|
|
||||||
Sppal = _cachedGpuPointers[3],
|
|
||||||
Bgpal = _cachedGpuPointers[2]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_exe.Exit();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class GPUMemoryAreas : IGPUMemoryAreas
|
|
||||||
{
|
|
||||||
private IMonitor _monitor;
|
|
||||||
public IntPtr Vram { get; init; }
|
|
||||||
|
|
||||||
public IntPtr Oam { get; init; }
|
|
||||||
|
|
||||||
public IntPtr Sppal { get; init; }
|
|
||||||
|
|
||||||
public IntPtr Bgpal { get; init; }
|
|
||||||
|
|
||||||
public GPUMemoryAreas(IMonitor monitor)
|
|
||||||
{
|
|
||||||
_monitor = monitor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_monitor?.Exit();
|
|
||||||
_monitor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ScanlineCallback _coreScanlineCallback;
|
|
||||||
private ScanlineCallback _frontendScanlineCallback;
|
|
||||||
private int _scanlineCallbackLine;
|
|
||||||
|
|
||||||
private void ScanlineCallbackRelay(byte lcdc)
|
|
||||||
{
|
|
||||||
_frontendScanlineCallback?.Invoke(lcdc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetScanlineCallback(ScanlineCallback callback, int line)
|
|
||||||
{
|
|
||||||
_frontendScanlineCallback = callback;
|
|
||||||
_scanlineCallbackLine = line;
|
|
||||||
UpdateCoreScanlineCallback(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateCoreScanlineCallback(bool now)
|
|
||||||
{
|
|
||||||
if (_frontendScanlineCallback == null)
|
|
||||||
{
|
|
||||||
_core.SetScanlineCallback(null, -1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_scanlineCallbackLine >= 0 && _scanlineCallbackLine <= 153)
|
|
||||||
{
|
|
||||||
_core.SetScanlineCallback(_coreScanlineCallback, _scanlineCallbackLine);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_core.SetScanlineCallback(null, -1);
|
|
||||||
if (_scanlineCallbackLine == -2 && now)
|
|
||||||
{
|
|
||||||
_frontendScanlineCallback(_core.GetIoReg(0x40));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly PrinterCallback _corePrinterCallback;
|
|
||||||
private PrinterCallback _frontendPrinterCallback;
|
|
||||||
|
|
||||||
private void PrinterCallbackRelay(IntPtr image, byte height, byte top_margin, byte bottom_margin, byte exposure)
|
|
||||||
{
|
|
||||||
_frontendPrinterCallback?.Invoke(image, height, top_margin, bottom_margin, exposure);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetPrinterCallback(PrinterCallback callback)
|
|
||||||
{
|
|
||||||
_frontendPrinterCallback = callback;
|
|
||||||
UpdateCorePrinterCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateCorePrinterCallback()
|
|
||||||
{
|
|
||||||
_core.SetPrinterCallback(_frontendPrinterCallback != null ? _corePrinterCallback : null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -44,7 +44,6 @@ namespace BizHawk.Emulation.Cores
|
||||||
public const string PceHawk = "PCEHawk";
|
public const string PceHawk = "PCEHawk";
|
||||||
public const string PicoDrive = "PicoDrive";
|
public const string PicoDrive = "PicoDrive";
|
||||||
public const string QuickNes = "QuickNes";
|
public const string QuickNes = "QuickNes";
|
||||||
public const string SameBoy = "SameBoy";
|
|
||||||
public const string Saturnus = "Saturnus";
|
public const string Saturnus = "Saturnus";
|
||||||
public const string SMSHawk = "SMSHawk";
|
public const string SMSHawk = "SMSHawk";
|
||||||
public const string Snes9X = "Snes9x";
|
public const string Snes9X = "Snes9x";
|
||||||
|
|
|
@ -6,7 +6,6 @@ cd libco && make -f Makefile $1 -j && cd -
|
||||||
cd gpgx && make -f Makefile $1 -j && cd -
|
cd gpgx && make -f Makefile $1 -j && cd -
|
||||||
cd libsnes && make -f Makefile $1 -j && cd -
|
cd libsnes && make -f Makefile $1 -j && cd -
|
||||||
cd picodrive && make -f Makefile $1 -j && cd -
|
cd picodrive && make -f Makefile $1 -j && cd -
|
||||||
cd sameboy && make -f Makefile $1 -j && cd -
|
|
||||||
cd snes9x && make -f Makefile $1 -j && cd -
|
cd snes9x && make -f Makefile $1 -j && cd -
|
||||||
cd uzem && make -f Makefile $1 -j && cd -
|
cd uzem && make -f Makefile $1 -j && cd -
|
||||||
cd vb && make -f Makefile $1 -j && cd -
|
cd vb && make -f Makefile $1 -j && cd -
|
||||||
|
|
|
@ -56,7 +56,6 @@ It consists of a modified musl libc, and build scripts to tie it all together.
|
||||||
cd nyma && make -f pcfx.mak install
|
cd nyma && make -f pcfx.mak install
|
||||||
cd nyma && make -f ss.mak install
|
cd nyma && make -f ss.mak install
|
||||||
cd picodrive && make install
|
cd picodrive && make install
|
||||||
cd sameboy && make install
|
|
||||||
cd snes9x && make install
|
cd snes9x && make install
|
||||||
cd uzem && make install
|
cd uzem && make install
|
||||||
cd vb && make install
|
cd vb && make install
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
// Place your settings in this file to overwrite default and user settings.
|
|
||||||
{
|
|
||||||
"editor.tabSize": 4,
|
|
||||||
"editor.insertSpaces": false,
|
|
||||||
"editor.detectIndentation": false
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
See https://sameboy.github.io/changelog/
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Lior Halphon
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,17 +0,0 @@
|
||||||
FLAGS := -Wall -Werror=int-to-pointer-cast \
|
|
||||||
-Wno-multichar \
|
|
||||||
-fomit-frame-pointer -DGB_INTERNAL -D_GNU_SOURCE
|
|
||||||
|
|
||||||
CCFLAGS := $(FLAGS) \
|
|
||||||
-std=gnu99 \
|
|
||||||
-DLSB_FIRST -Werror=pointer-to-int-cast -Werror=implicit-function-declaration
|
|
||||||
|
|
||||||
CXXFLAGS := $(FLAGS) -DSPC_NO_COPY_STATE_FUNCS -std=c++0x -fno-exceptions -fno-rtti
|
|
||||||
|
|
||||||
TARGET := sameboy.wbx
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
include ../common.mak
|
|
|
@ -1,51 +0,0 @@
|
||||||
# SameBoy
|
|
||||||
|
|
||||||
SameBoy is an open source Gameboy (DMG) and Gameboy Color (CGB) emulator, written in portable C. It has a native Cocoa frontend for OS X, and an incomplete experimental SDL frontend for other operating systems. It also includes a text-based debugger with an expression evaluator. Visit [the website](https://sameboy.github.io/).
|
|
||||||
|
|
||||||
## Features
|
|
||||||
Features common to both Cocoa and SDL versions:
|
|
||||||
* Supports Gameboy (DMG) and Gameboy Color (CGB) emulation
|
|
||||||
* Lets you choose the model you want to emulate regardless of ROM
|
|
||||||
* High quality 96KHz audio
|
|
||||||
* Battery save support
|
|
||||||
* Save states
|
|
||||||
* Includes open source DMG and CGB boot ROMs:
|
|
||||||
* Complete support for (and documentation of) *all* game-specific palettes in the CGB boot ROM, for accurate emulation of Gameboy games on a Gameboy Color
|
|
||||||
* Supports manual palette selection with key combinations, with 4 additional new palettes (A + B + direction)
|
|
||||||
* Supports palette selection in a CGB game, forcing it to run in 'paletted' DMG mode, if ROM allows doing so.
|
|
||||||
* Support for games with a non-Nintendo logo in the header
|
|
||||||
* No long animation in the DMG boot
|
|
||||||
* Advanced text-based debugger with an expression evaluator, disassembler, conditional breakpoints, conditional watchpoints, backtracing and other features
|
|
||||||
* Emulates [PCM_12 and PCM_34 registers](https://github.com/LIJI32/GBVisualizer)
|
|
||||||
* Emulates LCD timing effects, supporting the Demotronic trick, [GBVideoPlayer](https://github.com/LIJI32/GBVideoPlayer) and other tech demos
|
|
||||||
* Extermely high accuracy
|
|
||||||
* Real time clock emulation
|
|
||||||
|
|
||||||
Features currently supported only with the Cocoa version:
|
|
||||||
* Native Cocoa interface, with support for all system-wide features, such as drag-and-drop and smart titlebars
|
|
||||||
* Retina display support, allowing a wider range of scaling factors without artifacts
|
|
||||||
* Optional frame blending
|
|
||||||
* Several [scaling algorithms](https://sameboy.github.io/scaling/) (Including exclusive algorithms like OmniScale and Anti-aliased Scale2x)
|
|
||||||
* GameBoy Camera support
|
|
||||||
|
|
||||||
[Read more](https://sameboy.github.io/features/).
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
SameBoy passes many of [blargg's test ROMs](http://gbdev.gg8.se/wiki/articles/Test_ROMs#Blargg.27s_tests), as well as 77 out of 78 of [mooneye-gb's](https://github.com/Gekkio/mooneye-gb) tests (Some tests require the original boot ROMs). SameBoy should work with most games and demos, please [report](https://github.com/LIJI32/SameBoy/issues/new) any broken ROM. The latest results for SameBoy's automatic tester are available [here](https://sameboy.github.io/automation/).
|
|
||||||
|
|
||||||
## Compilation
|
|
||||||
SameBoy requires the following tools and libraries to build:
|
|
||||||
* clang
|
|
||||||
* make
|
|
||||||
* Cocoa port: OS X SDK and Xcode command line tools
|
|
||||||
* SDL port: SDL2.framework (OS X) or libsdl2 (Other platforms)
|
|
||||||
* [rgbds](https://github.com/bentley/rgbds/releases/), for boot ROM compilation
|
|
||||||
|
|
||||||
On Windows, SameBoy also requires:
|
|
||||||
* Visual Studio (For headers, etc.)
|
|
||||||
* [GnuWin](http://gnuwin32.sourceforge.net/)
|
|
||||||
* Running vcvars32 before running make. Make sure all required tools and libraries are in %PATH% and %lib%, repsectively.
|
|
||||||
|
|
||||||
To compile, simply run `make`. The targets are cocoa (Default for OS X), sdl (Default for everything else), bootroms and tester. You may also specify CONF=debug (default), CONF=release or CONF=native_release to control optimization and symbols. native_release is faster than release, but is optimized to the host's CPU and therefore is not portable. You may set BOOTROMS_DIR=... to a directory containing precompiled dmg_boot.bin and cgb_boot.bin files, otherwise the build system will compile and use SameBoy's own boot ROMs.
|
|
||||||
|
|
||||||
SameBoy was compiled and tested on macOS, Ubuntu and 32-bit Windows 7.
|
|
|
@ -1,402 +0,0 @@
|
||||||
#include <stdint.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <string.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
#ifndef apu_h
|
|
||||||
#define apu_h
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#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 */
|
|
|
@ -1,285 +0,0 @@
|
||||||
#include <stdint.h>
|
|
||||||
#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();
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void (*FrontendPrinterCallback_t)(uint32_t *image,
|
|
||||||
uint8_t height,
|
|
||||||
uint8_t top_margin,
|
|
||||||
uint8_t bottom_margin,
|
|
||||||
uint8_t exposure);
|
|
||||||
|
|
||||||
static FrontendPrinterCallback_t FrontendPrinterCallback;
|
|
||||||
|
|
||||||
static void PrinterCallback(GB_gameboy_t *gb,
|
|
||||||
uint32_t *image,
|
|
||||||
uint8_t height,
|
|
||||||
uint8_t top_margin,
|
|
||||||
uint8_t bottom_margin,
|
|
||||||
uint8_t exposure)
|
|
||||||
{
|
|
||||||
FrontendPrinterCallback(image, height, top_margin, bottom_margin, exposure);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ECL_EXPORT void SetPrinterCallback(FrontendPrinterCallback_t callback)
|
|
||||||
{
|
|
||||||
FrontendPrinterCallback = callback;
|
|
||||||
|
|
||||||
if (callback)
|
|
||||||
{
|
|
||||||
GB_connect_printer(&GB, PrinterCallback);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GB_set_serial_transfer_start_callback(&GB, NULL);
|
|
||||||
GB_set_serial_transfer_end_callback(&GB, NULL);
|
|
||||||
GB.printer.callback = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,344 +0,0 @@
|
||||||
/* blip_buf 1.1.0. http://www.slack.net/~ant/ */
|
|
||||||
|
|
||||||
#include "blip_buf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <limits.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/** \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
|
|
|
@ -1,149 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
#ifndef camera_h
|
|
||||||
#define camera_h
|
|
||||||
#include <stdint.h>
|
|
||||||
#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
|
|
|
@ -1,796 +0,0 @@
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
#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 */
|
|
|
@ -1,473 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,523 +0,0 @@
|
||||||
#ifndef GB_h
|
|
||||||
#define GB_h
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#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 */
|
|
|
@ -1,5 +0,0 @@
|
||||||
#ifndef gb_struct_def_h
|
|
||||||
#define gb_struct_def_h
|
|
||||||
struct GB_gameboy_s;
|
|
||||||
typedef struct GB_gameboy_s GB_gameboy_t;
|
|
||||||
#endif
|
|
|
@ -1,48 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include "gb.h"
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
#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 */
|
|
|
@ -1,154 +0,0 @@
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
#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 */
|
|
|
@ -1,733 +0,0 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
if (gb->input_callback)
|
|
||||||
gb->input_callback(gb);
|
|
||||||
gb->lagged = false;
|
|
||||||
if (gb->is_sgb) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
#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 */
|
|
|
@ -1,205 +0,0 @@
|
||||||
#include "gb.h"
|
|
||||||
#include "../emulibc/emulibc.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. */
|
|
||||||
|
|
||||||
// hackadoodle! we must not overflow the stack
|
|
||||||
static ECL_INVISIBLE uint32_t tmp_image[160 * 200];
|
|
||||||
|
|
||||||
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 *const image = tmp_image;
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
#ifndef printer_h
|
|
||||||
#define printer_h
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#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
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// 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));
|
|
|
@ -1,564 +0,0 @@
|
||||||
// Core SPC emulation: CPU, timers, SMP registers, memory
|
|
||||||
|
|
||||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
|
||||||
|
|
||||||
#include "SNES_SPC.h"
|
|
||||||
|
|
||||||
#include <string.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"
|
|
||||||
|
|
||||||
#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"
|
|
|
@ -1,284 +0,0 @@
|
||||||
// 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 <stdint.h>
|
|
||||||
|
|
||||||
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 <assert.h>
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,380 +0,0 @@
|
||||||
// SPC emulation support: init, sample buffering, reset, SPC loading
|
|
||||||
|
|
||||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
|
||||||
|
|
||||||
#include "SNES_SPC.h"
|
|
||||||
|
|
||||||
#include <string.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"
|
|
||||||
|
|
||||||
#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 );
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
// 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 <string.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"
|
|
||||||
|
|
||||||
#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
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,304 +0,0 @@
|
||||||
// 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 <assert.h>
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,68 +0,0 @@
|
||||||
// snes_spc 0.9.0. http://www.slack.net/~ant/
|
|
||||||
|
|
||||||
#include "SPC_Filter.h"
|
|
||||||
|
|
||||||
#include <string.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"
|
|
||||||
|
|
||||||
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 );
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
// 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
|
|
|
@ -1,186 +0,0 @@
|
||||||
// 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 <stddef.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#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<T> (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 T>
|
|
||||||
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 <new>
|
|
||||||
#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 <stdint.h> for int8_t etc.
|
|
||||||
#if defined (HAVE_STDINT_H)
|
|
||||||
#include <stdint.h>
|
|
||||||
#define BOOST
|
|
||||||
|
|
||||||
// HAVE_INTTYPES_H: If defined, use <stdint.h> for int8_t etc.
|
|
||||||
#elif defined (HAVE_INTTYPES_H)
|
|
||||||
#include <inttypes.h>
|
|
||||||
#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
|
|
|
@ -1,24 +0,0 @@
|
||||||
// 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
|
|
|
@ -1,185 +0,0 @@
|
||||||
// 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 <endian.h>
|
|
||||||
#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
|
|
|
@ -1,100 +0,0 @@
|
||||||
/* 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 <assert.h>
|
|
||||||
|
|
||||||
// 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<class T>
|
|
||||||
inline T min( T x, T y )
|
|
||||||
{
|
|
||||||
if ( x < y )
|
|
||||||
return x;
|
|
||||||
return y;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
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
|
|
|
@ -1,107 +0,0 @@
|
||||||
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.
|
|
|
@ -1,48 +0,0 @@
|
||||||
// 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
|
|
|
@ -1,83 +0,0 @@
|
||||||
/* SNES SPC-700 DSP emulator C interface (also usable from C++) */
|
|
||||||
|
|
||||||
/* snes_spc 0.9.0 */
|
|
||||||
#ifndef DSP_H
|
|
||||||
#define DSP_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#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
|
|
|
@ -1,504 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
||||||
<one line to give the library's name and a brief idea of what it does.>
|
|
||||||
Copyright (C) <year> <name of author>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1990
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
That's all there is to it!
|
|
||||||
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
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 <gblargg@gmail.com>
|
|
||||||
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 <gblargg@gmail.com>
|
|
|
@ -1,318 +0,0 @@
|
||||||
snes_spc 0.9.0: SNES SPC-700 APU Emulator
|
|
||||||
-----------------------------------------
|
|
||||||
Author : Shay Green <gblargg@gmail.com>
|
|
||||||
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 <gblargg@gmail.com>
|
|
|
@ -1,74 +0,0 @@
|
||||||
// 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 ); }
|
|
|
@ -1,149 +0,0 @@
|
||||||
/* SNES SPC-700 APU emulator C interface (also usable from C++) */
|
|
||||||
|
|
||||||
/* snes_spc 0.9.0 */
|
|
||||||
#ifndef SPC_H
|
|
||||||
#define SPC_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#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
|
|
|
@ -1,161 +0,0 @@
|
||||||
#include "gb.h"
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define _WIN32_WINNT 0x0500
|
|
||||||
#include <Windows.h>
|
|
||||||
#else
|
|
||||||
#include <sys/time.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
#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 */
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +0,0 @@
|
||||||
#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 */
|
|
Loading…
Reference in New Issue