Initial commit. 48K spectrum only.
This commit is contained in:
parent
e5ff485e8a
commit
d012472999
|
@ -96,6 +96,9 @@ namespace BizHawk.Client.ApiHawk
|
||||||
case "WSWAN":
|
case "WSWAN":
|
||||||
return CoreSystem.WonderSwan;
|
return CoreSystem.WonderSwan;
|
||||||
|
|
||||||
|
case "ZXSpectrum":
|
||||||
|
return CoreSystem.ZXSpectrum;
|
||||||
|
|
||||||
case "VB":
|
case "VB":
|
||||||
case "NGP":
|
case "NGP":
|
||||||
case "DNGP":
|
case "DNGP":
|
||||||
|
@ -205,6 +208,9 @@ namespace BizHawk.Client.ApiHawk
|
||||||
case CoreSystem.WonderSwan:
|
case CoreSystem.WonderSwan:
|
||||||
return "WSWAN";
|
return "WSWAN";
|
||||||
|
|
||||||
|
case CoreSystem.ZXSpectrum:
|
||||||
|
return "ZXSpectrum";
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value.ToString()));
|
throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value.ToString()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -427,11 +427,11 @@ namespace BizHawk.Client.ApiHawk
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return SystemInfo.DualGB;
|
return SystemInfo.DualGB;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId));
|
return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
WonderSwan,
|
WonderSwan,
|
||||||
Libretro,
|
Libretro,
|
||||||
VirtualBoy,
|
VirtualBoy,
|
||||||
NeoGeoPocket
|
NeoGeoPocket,
|
||||||
|
ZXSpectrum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,8 @@ namespace BizHawk.Client.Common
|
||||||
return SystemInfo.VirtualBoy;
|
return SystemInfo.VirtualBoy;
|
||||||
case "NGP":
|
case "NGP":
|
||||||
return SystemInfo.NeoGeoPocket;
|
return SystemInfo.NeoGeoPocket;
|
||||||
|
case "ZXSpectrum":
|
||||||
|
return SystemInfo.ZXSpectrum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ using BizHawk.Emulation.Cores.PCEngine;
|
||||||
using BizHawk.Emulation.Cores.Sega.Saturn;
|
using BizHawk.Emulation.Cores.Sega.Saturn;
|
||||||
using BizHawk.Emulation.Cores.Sony.PSP;
|
using BizHawk.Emulation.Cores.Sony.PSP;
|
||||||
using BizHawk.Emulation.Cores.Sony.PSX;
|
using BizHawk.Emulation.Cores.Sony.PSX;
|
||||||
|
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
|
||||||
using BizHawk.Emulation.DiscSystem;
|
using BizHawk.Emulation.DiscSystem;
|
||||||
|
|
||||||
using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
||||||
|
@ -657,6 +658,13 @@ namespace BizHawk.Client.Common
|
||||||
(C64.C64Settings)GetCoreSettings<C64>(),
|
(C64.C64Settings)GetCoreSettings<C64>(),
|
||||||
(C64.C64SyncSettings)GetCoreSyncSettings<C64>());
|
(C64.C64SyncSettings)GetCoreSyncSettings<C64>());
|
||||||
break;
|
break;
|
||||||
|
case "ZXSpectrum":
|
||||||
|
nextEmulator = new ZXSpectrum(
|
||||||
|
nextComm,
|
||||||
|
xmlGame.Assets.Select(a => a.Value).First(),
|
||||||
|
(ZXSpectrum.ZXSpectrumSettings)GetCoreSettings<ZXSpectrum>(),
|
||||||
|
(ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings<ZXSpectrum>());
|
||||||
|
break;
|
||||||
case "PSX":
|
case "PSX":
|
||||||
var entries = xmlGame.AssetFullPaths;
|
var entries = xmlGame.AssetFullPaths;
|
||||||
var discs = new List<Disc>();
|
var discs = new List<Disc>();
|
||||||
|
@ -990,6 +998,10 @@ namespace BizHawk.Client.Common
|
||||||
var c64 = new C64(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings<C64>(), GetCoreSyncSettings<C64>());
|
var c64 = new C64(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings<C64>(), GetCoreSyncSettings<C64>());
|
||||||
nextEmulator = c64;
|
nextEmulator = c64;
|
||||||
break;
|
break;
|
||||||
|
case "ZXSpectrum":
|
||||||
|
var zx = new ZXSpectrum(nextComm, rom.FileData, GetCoreSettings<ZXSpectrum>(), GetCoreSyncSettings<ZXSpectrum>());
|
||||||
|
nextEmulator = zx;
|
||||||
|
break;
|
||||||
case "GBA":
|
case "GBA":
|
||||||
if (Global.Config.GBA_UsemGBA)
|
if (Global.Config.GBA_UsemGBA)
|
||||||
{
|
{
|
||||||
|
|
|
@ -188,6 +188,11 @@ namespace BizHawk.Client.Common
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static SystemInfo NeoGeoPocket { get; } = new SystemInfo("Neo-Geo Pocket", CoreSystem.NeoGeoPocket, 1);
|
public static SystemInfo NeoGeoPocket { get; } = new SystemInfo("Neo-Geo Pocket", CoreSystem.NeoGeoPocket, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="SystemInfo"/> instance for ZXSpectrum
|
||||||
|
/// </summary>
|
||||||
|
public static SystemInfo ZXSpectrum { get; } = new SystemInfo("ZX Spectrum", CoreSystem.ZXSpectrum, 2);
|
||||||
|
|
||||||
#endregion Get SystemInfo
|
#endregion Get SystemInfo
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -290,7 +290,13 @@ namespace BizHawk.Client.Common
|
||||||
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
|
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
|
||||||
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 },
|
new PathEntry { System = "C64", SystemDisplayName = "Commodore 64", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 },
|
||||||
|
|
||||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Base", Path = Path.Combine(".", "PSX"), Ordinal = 0 },
|
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Base", Path = Path.Combine(".", "C64"), Ordinal = 0 },
|
||||||
|
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "ROM", Path = ".", Ordinal = 1 },
|
||||||
|
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
|
||||||
|
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Screenshots", Path = Path.Combine(".", "Screenshots"), Ordinal = 4 },
|
||||||
|
new PathEntry { System = "ZXSpectrum", SystemDisplayName = "Sinclair ZX Spectrum", Type = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 },
|
||||||
|
|
||||||
|
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Base", Path = Path.Combine(".", "PSX"), Ordinal = 0 },
|
||||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "ROM", Path = ".", Ordinal = 1 },
|
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "ROM", Path = ".", Ordinal = 1 },
|
||||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
|
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 },
|
||||||
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 },
|
new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 },
|
||||||
|
|
|
@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
".NES", ".FDS", ".UNF", ".SMS", ".GG", ".SG", ".GB", ".GBC", ".GBA", ".PCE", ".SGX", ".BIN", ".SMD", ".GEN", ".MD", ".SMC", ".SFC", ".A26", ".A78", ".LNX", ".COL", ".ROM", ".M3U", ".CUE", ".CCD", ".SGB", ".Z64", ".V64", ".N64", ".WS", ".WSC", ".XML", ".DSK", ".DO", ".PO", ".PSF", ".MINIPSF", ".NSF",
|
".NES", ".FDS", ".UNF", ".SMS", ".GG", ".SG", ".GB", ".GBC", ".GBA", ".PCE", ".SGX", ".BIN", ".SMD", ".GEN", ".MD", ".SMC", ".SFC", ".A26", ".A78", ".LNX", ".COL", ".ROM", ".M3U", ".CUE", ".CCD", ".SGB", ".Z64", ".V64", ".N64", ".WS", ".WSC", ".XML", ".DSK", ".DO", ".PO", ".PSF", ".MINIPSF", ".NSF",
|
||||||
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS"
|
".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2077,7 +2077,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
if (VersionInfo.DeveloperBuild)
|
if (VersionInfo.DeveloperBuild)
|
||||||
{
|
{
|
||||||
return FormatFilter(
|
return FormatFilter(
|
||||||
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;%ARCH%",
|
"Rom Files", "*.nes;*.fds;*.unf;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.mds;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.32x;*.col;*.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.tap;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;*.dsk;*.do;*.po;*.vb;*.ngp;*.ngc;*.psf;*.minipsf;*.nsf;*.tzx;%ARCH%",
|
||||||
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
|
"Music Files", "*.psf;*.minipsf;*.sid;*.nsf",
|
||||||
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
|
"Disc Images", "*.cue;*.ccd;*.mds;*.m3u",
|
||||||
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
|
"NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%",
|
||||||
|
@ -2105,6 +2105,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
"Apple II", "*.dsk;*.do;*.po;%ARCH%",
|
"Apple II", "*.dsk;*.do;*.po;%ARCH%",
|
||||||
"Virtual Boy", "*.vb;%ARCH%",
|
"Virtual Boy", "*.vb;%ARCH%",
|
||||||
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
|
"Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%",
|
||||||
|
"Sinclair ZX Spectrum", "*.tzx;*.tap;%ARCH%",
|
||||||
"All Files", "*.*");
|
"All Files", "*.*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
if (buckets[0].Count > 0)
|
if (buckets[0].Count > 0)
|
||||||
{
|
{
|
||||||
string tabname = Global.Emulator.SystemId == "C64" ? "Keyboard" : "Console"; // hack
|
string tabname = (Global.Emulator.SystemId == "C64" || Global.Emulator.SystemId == "ZXSpectrum") ? "Keyboard" : "Console"; // hack
|
||||||
tt.TabPages.Add(tabname);
|
tt.TabPages.Add(tabname);
|
||||||
tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size));
|
tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size));
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{ "GBC", "Game Boy Color" },
|
{ "GBC", "Game Boy Color" },
|
||||||
{ "PCFX", "PC-FX" },
|
{ "PCFX", "PC-FX" },
|
||||||
{ "32X", "32X" },
|
{ "32X", "32X" },
|
||||||
|
{ "ZXSpectrum", "ZX Spectrum" }
|
||||||
};
|
};
|
||||||
|
|
||||||
public string TargetSystem = null;
|
public string TargetSystem = null;
|
||||||
|
|
|
@ -5,6 +5,7 @@ using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using BizHawk.Common.BufferExtensions;
|
using BizHawk.Common.BufferExtensions;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Common
|
namespace BizHawk.Emulation.Common
|
||||||
{
|
{
|
||||||
|
@ -298,12 +299,23 @@ namespace BizHawk.Emulation.Common
|
||||||
case ".D64":
|
case ".D64":
|
||||||
case ".T64":
|
case ".T64":
|
||||||
case ".G64":
|
case ".G64":
|
||||||
case ".CRT":
|
case ".CRT":
|
||||||
case ".TAP":
|
|
||||||
game.System = "C64";
|
game.System = "C64";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ".Z64":
|
case ".TZX":
|
||||||
|
game.System = "ZXSpectrum";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ".TAP":
|
||||||
|
byte[] head = File.ReadAllBytes(fileName).Take(8).ToArray();
|
||||||
|
if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE"))
|
||||||
|
game.System = "C64";
|
||||||
|
else
|
||||||
|
game.System = "ZXSpectrum";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ".Z64":
|
||||||
case ".V64":
|
case ".V64":
|
||||||
case ".N64":
|
case ".N64":
|
||||||
game.System = "N64";
|
game.System = "N64";
|
||||||
|
|
|
@ -50,6 +50,9 @@ namespace BizHawk.Emulation.Common
|
||||||
FirmwareAndOption("AB16F56989B27D89BABE5F89C5A8CB3DA71A82F0", 16384, "C64", "Drive1541", "drive-1541.bin", "1541 Disk Drive Rom");
|
FirmwareAndOption("AB16F56989B27D89BABE5F89C5A8CB3DA71A82F0", 16384, "C64", "Drive1541", "drive-1541.bin", "1541 Disk Drive Rom");
|
||||||
FirmwareAndOption("D3B78C3DBAC55F5199F33F3FE0036439811F7FB3", 16384, "C64", "Drive1541II", "drive-1541ii.bin", "1541-II Disk Drive Rom");
|
FirmwareAndOption("D3B78C3DBAC55F5199F33F3FE0036439811F7FB3", 16384, "C64", "Drive1541II", "drive-1541ii.bin", "1541-II Disk Drive Rom");
|
||||||
|
|
||||||
|
// ZX Spectrum
|
||||||
|
FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM");
|
||||||
|
|
||||||
// for saturn, we think any bios region can pretty much run any iso
|
// for saturn, we think any bios region can pretty much run any iso
|
||||||
// so, we're going to lay this out carefully so that we choose things in a sensible order, but prefer the correct region
|
// so, we're going to lay this out carefully so that we choose things in a sensible order, but prefer the correct region
|
||||||
var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)");
|
var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)");
|
||||||
|
|
|
@ -256,6 +256,26 @@
|
||||||
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
|
<Compile Include="Computers\Commodore64\MOS\Vic.VideoProvider.cs" />
|
||||||
<Compile Include="Computers\Commodore64\SaveState.cs" />
|
<Compile Include="Computers\Commodore64\SaveState.cs" />
|
||||||
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
|
<Compile Include="Computers\Commodore64\User\UserPortDevice.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Buzzer.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\IKeyboard.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\MachineType.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Input.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Port.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Memory.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Sound.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Pulse.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Tape.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.Controllers.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IDebuggable.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IEmulator.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IInputPollable.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IMemoryDomains.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISettable.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.ISoundProvider.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\ZXSpectrum.IStatable.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\Atari2600.cs" />
|
<Compile Include="Consoles\Atari\2600\Atari2600.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\Atari2600.Core.cs">
|
<Compile Include="Consoles\Atari\2600\Atari2600.Core.cs">
|
||||||
<DependentUpon>Atari2600.cs</DependentUpon>
|
<DependentUpon>Atari2600.cs</DependentUpon>
|
||||||
|
@ -1328,6 +1348,8 @@
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\SpectrumBase.Screen.cs" />
|
||||||
|
<Compile Include="Computers\SinclairSpectrum\Machine\ZXSpectrum48K\ZX48.Keyboard.cs" />
|
||||||
<None Include="Consoles\Atari\docs\stella.pdf" />
|
<None Include="Consoles\Atari\docs\stella.pdf" />
|
||||||
<None Include="Consoles\Coleco\docs\colecovision tech1.pdf" />
|
<None Include="Consoles\Coleco\docs\colecovision tech1.pdf" />
|
||||||
<None Include="Consoles\Coleco\docs\colecovision tech2.pdf" />
|
<None Include="Consoles\Coleco\docs\colecovision tech2.pdf" />
|
||||||
|
@ -1338,6 +1360,7 @@
|
||||||
<None Include="Resources\dmg_boot.bin.gz" />
|
<None Include="Resources\dmg_boot.bin.gz" />
|
||||||
<None Include="Resources\sgb-cart-present.spc.gz" />
|
<None Include="Resources\sgb-cart-present.spc.gz" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">"$(SolutionDir)subwcrev.bat" "$(ProjectDir)"</PreBuildEvent>
|
<PreBuildEvent Condition=" '$(OS)' == 'Windows_NT' ">"$(SolutionDir)subwcrev.bat" "$(ProjectDir)"</PreBuildEvent>
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the piezoelectric buzzer used in the Spectrum to produce sound
|
||||||
|
/// The beeper is controlled by rapidly toggling bit 4 of port &FE
|
||||||
|
///
|
||||||
|
/// For the purposes of emulation this devices is locked to a frame
|
||||||
|
/// a list of Pulses is built up over the course of the frame and outputted at the end of the frame
|
||||||
|
/// </summary>
|
||||||
|
public class Buzzer : ISoundProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Supplied values are right for 48K spectrum
|
||||||
|
/// These will deviate for 128k and up (as there are more T-States per frame)
|
||||||
|
/// </summary>
|
||||||
|
public int SampleRate = 44100; //35000;
|
||||||
|
public int SamplesPerFrame = 882; //699;
|
||||||
|
public int TStatesPerSample = 79; //100;
|
||||||
|
|
||||||
|
public BlipBuffer BlipL { get; set; }
|
||||||
|
public BlipBuffer BlipR { get; set; }
|
||||||
|
|
||||||
|
private SpectrumBase _machine;
|
||||||
|
|
||||||
|
private long _frameStart;
|
||||||
|
private bool _tapeMode;
|
||||||
|
private int _tStatesPerFrame;
|
||||||
|
|
||||||
|
public SpeexResampler resampler { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulses collected during the last frame
|
||||||
|
/// </summary>
|
||||||
|
public List<Pulse> Pulses { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last pulse
|
||||||
|
/// </summary>
|
||||||
|
public bool LastPulse { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last T-State (cpu cycle) that the last pulse was received
|
||||||
|
/// </summary>
|
||||||
|
public int LastPulseTState { get; set; }
|
||||||
|
|
||||||
|
#region Construction & Initialisation
|
||||||
|
|
||||||
|
public Buzzer(SpectrumBase machine)
|
||||||
|
{
|
||||||
|
_machine = machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises the buzzer
|
||||||
|
/// </summary>
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
_tStatesPerFrame = _machine.UlaFrameCycleCount;
|
||||||
|
Pulses = new List<Pulse>(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the pulse value from the EAR output changes it is processed here
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fromTape"></param>
|
||||||
|
/// <param name="earPulse"></param>
|
||||||
|
public void ProcessPulseValue(bool fromTape, bool earPulse)
|
||||||
|
{
|
||||||
|
if (!fromTape && _tapeMode)
|
||||||
|
{
|
||||||
|
// tape mode is active but the pulse value came from an OUT instruction
|
||||||
|
// do not process the value
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (earPulse == LastPulse)
|
||||||
|
{
|
||||||
|
// no change detected
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the lastpulse
|
||||||
|
LastPulse = earPulse;
|
||||||
|
|
||||||
|
// get where we are in the frame
|
||||||
|
var currentULACycle = _machine.CurrentFrameCycle;
|
||||||
|
var currentBuzzerCycle = currentULACycle <= _tStatesPerFrame ? currentULACycle : _tStatesPerFrame;
|
||||||
|
var length = currentBuzzerCycle - LastPulseTState;
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
// the first T-State has changed the pulse
|
||||||
|
// do not add it
|
||||||
|
}
|
||||||
|
else if (length > 0)
|
||||||
|
{
|
||||||
|
// add the pulse
|
||||||
|
Pulse p = new Pulse
|
||||||
|
{
|
||||||
|
State = !earPulse,
|
||||||
|
Length = length
|
||||||
|
};
|
||||||
|
Pulses.Add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the last pulse tstate
|
||||||
|
LastPulseTState = currentBuzzerCycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// New frame starts
|
||||||
|
/// </summary>
|
||||||
|
public void StartFrame()
|
||||||
|
{
|
||||||
|
//DiscardSamples();
|
||||||
|
Pulses.Clear();
|
||||||
|
LastPulseTState = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frame is completed
|
||||||
|
/// </summary>
|
||||||
|
public void EndFrame()
|
||||||
|
{
|
||||||
|
// store the last pulse information
|
||||||
|
if (LastPulseTState <= _tStatesPerFrame - 1)
|
||||||
|
{
|
||||||
|
Pulse p = new Pulse
|
||||||
|
{
|
||||||
|
State = LastPulse,
|
||||||
|
Length = _tStatesPerFrame - LastPulseTState
|
||||||
|
};
|
||||||
|
Pulses.Add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the sample array
|
||||||
|
var firstSampleOffset = _frameStart % TStatesPerSample == 0 ? 0 : TStatesPerSample - (_frameStart + TStatesPerSample) % TStatesPerSample;
|
||||||
|
var samplesInFrame = (_tStatesPerFrame - firstSampleOffset - 1) / TStatesPerSample + 1;
|
||||||
|
var samples = new short[samplesInFrame];
|
||||||
|
|
||||||
|
// convert pulses to samples
|
||||||
|
var sampleIndex = 0;
|
||||||
|
var currentEnd = _frameStart;
|
||||||
|
|
||||||
|
foreach (var pulse in Pulses)
|
||||||
|
{
|
||||||
|
var firstSample = currentEnd % TStatesPerSample == 0
|
||||||
|
? currentEnd : currentEnd + TStatesPerSample - currentEnd % TStatesPerSample;
|
||||||
|
|
||||||
|
for (var i = firstSample; i < currentEnd + pulse.Length; i += TStatesPerSample)
|
||||||
|
{
|
||||||
|
samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 2) : (short)0;
|
||||||
|
|
||||||
|
//resampler.EnqueueSample(samples[sampleIndex - 1], samples[sampleIndex - 1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
currentEnd += pulse.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill the _sampleBuffer for ISoundProvider
|
||||||
|
soundBufferContains = (int)samplesInFrame;
|
||||||
|
|
||||||
|
if (soundBuffer.Length != soundBufferContains)
|
||||||
|
soundBuffer = new short[soundBufferContains];
|
||||||
|
|
||||||
|
samples.CopyTo(soundBuffer, 0);
|
||||||
|
|
||||||
|
_frameStart += _tStatesPerFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the spectrum is set to receive tape input, the EAR output on the ULA is disabled
|
||||||
|
/// (so no buzzer sound is emitted)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tapeMode"></param>
|
||||||
|
public void SetTapeMode(bool tapeMode)
|
||||||
|
{
|
||||||
|
_tapeMode = tapeMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region ISoundProvider
|
||||||
|
|
||||||
|
private short[] soundBuffer = new short[882];
|
||||||
|
private int soundBufferContains = 0;
|
||||||
|
|
||||||
|
public bool CanProvideAsync => false;
|
||||||
|
|
||||||
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||||
|
|
||||||
|
public void SetSyncMode(SyncSoundMode mode)
|
||||||
|
{
|
||||||
|
if (mode != SyncSoundMode.Sync)
|
||||||
|
throw new InvalidOperationException("Only Sync mode is supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesAsync(short[] samples)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Async is not available");
|
||||||
|
short[] stereoBuffer = new short[soundBuffer.Length * 2];
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 0; i < soundBufferContains; i++)
|
||||||
|
{
|
||||||
|
stereoBuffer[index++] = soundBuffer[i];
|
||||||
|
stereoBuffer[index++] = soundBuffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
samples = stereoBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DiscardSamples()
|
||||||
|
{
|
||||||
|
soundBufferContains = 0;
|
||||||
|
soundBuffer = new short[SamplesPerFrame];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||||
|
{
|
||||||
|
// convert to stereo
|
||||||
|
short[] stereoBuffer = new short[soundBufferContains * 2];
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 0; i < soundBufferContains; i++)
|
||||||
|
{
|
||||||
|
stereoBuffer[index++] = soundBuffer[i];
|
||||||
|
stereoBuffer[index++] = soundBuffer[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
samples = stereoBuffer;
|
||||||
|
nsamp = soundBufferContains;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a spectrum keyboard
|
||||||
|
/// </summary>
|
||||||
|
public interface IKeyboard
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The calling spectrumbase class
|
||||||
|
/// </summary>
|
||||||
|
SpectrumBase _machine { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The keyboard matrix for a particular spectrum model
|
||||||
|
/// </summary>
|
||||||
|
string[] KeyboardMatrix { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For 16/48k models
|
||||||
|
/// </summary>
|
||||||
|
bool Issue2 { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current keyboard line status
|
||||||
|
/// </summary>
|
||||||
|
//byte[] LineStatus { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the spectrum key status
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="isPressed"></param>
|
||||||
|
void SetKeyStatus(string key, bool isPressed);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the status of a spectrum key
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
bool GetKeyStatus(string key);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the query byte
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lines"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
byte GetLineStatus(byte lines);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a keyboard byte
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
byte ReadKeyboardByte(ushort addr);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a key in the keyboard matrix and returns the relevent byte value
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
byte GetByteFromKeyMatrix(string key);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public enum MachineType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sinclair Spectrum 48K model
|
||||||
|
/// </summary>
|
||||||
|
ZXSpectrum48
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles all ZX-level input
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase
|
||||||
|
{
|
||||||
|
private readonly bool[] _keyboardPressed = new bool[64];
|
||||||
|
int _pollIndex;
|
||||||
|
private bool _restorePressed;
|
||||||
|
|
||||||
|
|
||||||
|
public void PollInput()
|
||||||
|
{
|
||||||
|
Spectrum.InputCallbacks.Call();
|
||||||
|
|
||||||
|
// scan keyboard
|
||||||
|
_pollIndex = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < KeyboardDevice.KeyboardMatrix.Length; i++)
|
||||||
|
{
|
||||||
|
string key = KeyboardDevice.KeyboardMatrix[i];
|
||||||
|
bool prevState = KeyboardDevice.GetKeyStatus(key);
|
||||||
|
bool currState = Spectrum._controller.IsPressed(key);
|
||||||
|
|
||||||
|
//if (currState != prevState)
|
||||||
|
KeyboardDevice.SetKeyStatus(key, currState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The abstract class that all emulated models will inherit from
|
||||||
|
/// * Memory *
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Byte array of total system memory (ROM + RAM + paging)
|
||||||
|
/// </summary>
|
||||||
|
public byte[] RAM { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte of data from a specified memory address
|
||||||
|
/// (with memory contention if appropriate)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual byte ReadMemory(ushort addr)
|
||||||
|
{
|
||||||
|
var data = RAM[addr];
|
||||||
|
if ((addr & 0xC000) == 0x4000)
|
||||||
|
{
|
||||||
|
// addr is in RAM not ROM - apply memory contention if neccessary
|
||||||
|
var delay = GetContentionValue(CurrentFrameCycle);
|
||||||
|
CPU.TotalExecutedCycles += delay;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte of data from a specified memory address
|
||||||
|
/// (with no memory contention)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual byte PeekMemory(ushort addr)
|
||||||
|
{
|
||||||
|
var data = RAM[addr];
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte of data to a specified memory address
|
||||||
|
/// (with memory contention if appropriate)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public virtual void WriteMemory(ushort addr, byte value)
|
||||||
|
{
|
||||||
|
if (addr < 0x4000)
|
||||||
|
{
|
||||||
|
// Do nothing - we cannot write to ROM
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (addr < 0xC000)
|
||||||
|
{
|
||||||
|
if (!CPU.IFF1)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
// possible contended RAM
|
||||||
|
var delay = GetContentionValue(CurrentFrameCycle);
|
||||||
|
CPU.TotalExecutedCycles += delay;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// uncontended RAM - do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
// Check whether memory is ROM or RAM
|
||||||
|
switch (addr & 0xC000)
|
||||||
|
{
|
||||||
|
case 0x0000:
|
||||||
|
// Do nothing - we cannot write to ROM
|
||||||
|
return;
|
||||||
|
case 0x4000:
|
||||||
|
// Address is RAM - apply contention if neccessary
|
||||||
|
var delay = GetContentionValue(_frameCycles);
|
||||||
|
CPU.TotalExecutedCycles += delay;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
RAM[addr] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte of data to a specified memory address
|
||||||
|
/// (without contention)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public virtual void PokeMemory(ushort addr, byte value)
|
||||||
|
{
|
||||||
|
if (addr < 0x4000)
|
||||||
|
{
|
||||||
|
// Do nothing - we cannot write to ROM
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAM[addr] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills memory from buffer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer"></param>
|
||||||
|
/// <param name="startAddress"></param>
|
||||||
|
public virtual void FillMemory(byte[] buffer, ushort startAddress)
|
||||||
|
{
|
||||||
|
buffer?.CopyTo(RAM, startAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ULA reads the memory at the specified address
|
||||||
|
/// (No memory contention)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="addr"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual byte FetchScreenMemory(ushort addr)
|
||||||
|
{
|
||||||
|
var value = RAM[(addr & 0x3FFF) + 0x4000];
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the memory contention value for the specified T-State (cycle)
|
||||||
|
/// The ZX Spectrum memory access is contended when the ULA is accessing the lower 16k of RAM
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Cycle"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual byte GetContentionValue(int cycle)
|
||||||
|
{
|
||||||
|
var val = _renderingCycleTable[cycle % UlaFrameCycleCount].ContentionDelay;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The abstract class that all emulated models will inherit from
|
||||||
|
/// * Port Access *
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The last OUT data that was sent to the ULA
|
||||||
|
/// </summary>
|
||||||
|
protected byte LastULAOutByte;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a byte of data from a specified port address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual byte ReadPort(ushort port)
|
||||||
|
{
|
||||||
|
CPU.TotalExecutedCycles += 4;
|
||||||
|
|
||||||
|
byte result = 0xFF;
|
||||||
|
|
||||||
|
// get the high byte from Regs[6]
|
||||||
|
ushort high = CPU.Regs[6];
|
||||||
|
|
||||||
|
// combine the low byte (passed in as port) and the high byte (maybe not needed)
|
||||||
|
ushort word = Convert.ToUInt16(((byte)port << 8 | (byte)high));
|
||||||
|
|
||||||
|
// Check whether the low bit is reset
|
||||||
|
// Technically the ULA should respond to every even I/O address
|
||||||
|
bool lowBitReset = (port & 0x0001) == 0;
|
||||||
|
|
||||||
|
// Kempston Joystick
|
||||||
|
//not implemented yet
|
||||||
|
|
||||||
|
if (lowBitReset)
|
||||||
|
{
|
||||||
|
// Even I/O address so get input
|
||||||
|
// The high byte indicates which half-row of keys is being polled
|
||||||
|
/*
|
||||||
|
IN: Reads keys (bit 0 to bit 4 inclusive)
|
||||||
|
0xfefe SHIFT, Z, X, C, V 0xeffe 0, 9, 8, 7, 6
|
||||||
|
0xfdfe A, S, D, F, G 0xdffe P, O, I, U, Y
|
||||||
|
0xfbfe Q, W, E, R, T 0xbffe ENTER, L, K, J, H
|
||||||
|
0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B
|
||||||
|
*/
|
||||||
|
|
||||||
|
// read keyboard input
|
||||||
|
if (high != 0)
|
||||||
|
result = KeyboardDevice.GetLineStatus((byte)high);
|
||||||
|
|
||||||
|
var ear = TapeDevice.GetEarBit(CurrentFrameCycle);
|
||||||
|
if (!ear)
|
||||||
|
{
|
||||||
|
result = (byte)(result & Convert.ToInt32("10111111", 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
bool tapeIsPlaying = false;
|
||||||
|
int tapeBit = 0;
|
||||||
|
|
||||||
|
if (tapeIsPlaying)
|
||||||
|
{
|
||||||
|
if (tapeBit == 0)
|
||||||
|
{
|
||||||
|
// reset is EAR ON
|
||||||
|
result = (byte)(result & ~(TAPE_BIT));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set is EAR OFF
|
||||||
|
result |= TAPE_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0)
|
||||||
|
{
|
||||||
|
result = (byte)(result & ~(TAPE_BIT));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result |= TAPE_BIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
// read EAR pulse from tape device
|
||||||
|
//todo
|
||||||
|
|
||||||
|
bool earBit = false;
|
||||||
|
|
||||||
|
if (earBit)
|
||||||
|
tapeBit = Convert.ToInt32("11111111", 2);
|
||||||
|
else
|
||||||
|
tapeBit = Convert.ToInt32("10111111", 2);
|
||||||
|
|
||||||
|
//var earBit = _tapeDevice.GetEarBit(_cpu.Tacts);
|
||||||
|
|
||||||
|
if (!earBit)
|
||||||
|
result = (byte)(result & tapeBit);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// devices other than the ULA will respond here
|
||||||
|
// (e.g. the AY sound chip in a 128k spectrum
|
||||||
|
|
||||||
|
// AY register activate
|
||||||
|
// Kemptson Mouse
|
||||||
|
|
||||||
|
|
||||||
|
// if unused port the floating memory bus should be returned (still todo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a byte of data to a specified port address
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
public virtual void WritePort(ushort port, byte value)
|
||||||
|
{
|
||||||
|
CPU.TotalExecutedCycles += 4;
|
||||||
|
|
||||||
|
// Check whether the low bit is reset
|
||||||
|
// Technically the ULA should respond to every even I/O address
|
||||||
|
bool lowBitReset = (port & 0x0001) == 0;
|
||||||
|
|
||||||
|
// Only even addresses address the ULA
|
||||||
|
if (lowBitReset)
|
||||||
|
{
|
||||||
|
// store the last OUT byte
|
||||||
|
LastULAOutByte = value;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Bit 7 6 5 4 3 2 1 0
|
||||||
|
+-------------------------------+
|
||||||
|
| | | | E | M | Border |
|
||||||
|
+-------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Border - LSB 3 bits hold the border colour
|
||||||
|
BorderColour = value & BORDER_BIT;
|
||||||
|
|
||||||
|
// Buzzer
|
||||||
|
var beepVal = (int)value & (EAR_BIT);
|
||||||
|
|
||||||
|
if (((int)value & MIC_BIT) == 0)
|
||||||
|
beepVal = (int)value & (MIC_BIT);
|
||||||
|
|
||||||
|
//var micval = (int)value & (MIC_BIT);
|
||||||
|
|
||||||
|
|
||||||
|
// if tape is not playing
|
||||||
|
BuzzerDevice.ProcessPulseValue(false, (beepVal) != 0);
|
||||||
|
|
||||||
|
// tape
|
||||||
|
//_tapeDevice.ProcessMicBit((data & 0x08) != 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,910 @@
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The abstract class that all emulated models will inherit from
|
||||||
|
/// * Screen *
|
||||||
|
/// - A goodly portion of the screen rendering code has been taken from:
|
||||||
|
/// - https://github.com/Dotneteer/spectnetide
|
||||||
|
/// - (MIT Licensed)
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase : IVideoProvider
|
||||||
|
{
|
||||||
|
#region State
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main screen buffer
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] _frameBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pixel and attribute info stored while rendering the screen
|
||||||
|
/// </summary>
|
||||||
|
protected byte _pixelByte1;
|
||||||
|
protected byte _pixelByte2;
|
||||||
|
protected byte _attrByte1;
|
||||||
|
protected byte _attrByte2;
|
||||||
|
protected int _xPos;
|
||||||
|
protected int _yPos;
|
||||||
|
protected int[] _flashOffColors;
|
||||||
|
protected int[] _flashOnColors;
|
||||||
|
protected ScreenRenderingCycle[] _renderingCycleTable;
|
||||||
|
protected bool _flashPhase;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Statics
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The standard ULA palette
|
||||||
|
/// </summary>
|
||||||
|
private static readonly int[] ULAPalette =
|
||||||
|
{
|
||||||
|
Colors.ARGB(0x00, 0x00, 0x00), // Black
|
||||||
|
Colors.ARGB(0x00, 0x00, 0xD7), // Blue
|
||||||
|
Colors.ARGB(0xD7, 0x00, 0x00), // Red
|
||||||
|
Colors.ARGB(0xD7, 0x00, 0xD7), // Magenta
|
||||||
|
Colors.ARGB(0x00, 0xD7, 0x00), // Green
|
||||||
|
Colors.ARGB(0x00, 0xD7, 0xD7), // Cyan
|
||||||
|
Colors.ARGB(0xD7, 0xD7, 0x00), // Yellow
|
||||||
|
Colors.ARGB(0xD7, 0xD7, 0xD7), // White
|
||||||
|
Colors.ARGB(0x00, 0x00, 0x00), // Bright Black
|
||||||
|
Colors.ARGB(0x00, 0x00, 0xFF), // Bright Blue
|
||||||
|
Colors.ARGB(0xFF, 0x00, 0x00), // Bright Red
|
||||||
|
Colors.ARGB(0xFF, 0x00, 0xFF), // Bright Magenta
|
||||||
|
Colors.ARGB(0x00, 0xFF, 0x00), // Bright Green
|
||||||
|
Colors.ARGB(0x00, 0xFF, 0xFF), // Bright Cyan
|
||||||
|
Colors.ARGB(0xFF, 0xFF, 0x00), // Bright Yellow
|
||||||
|
Colors.ARGB(0xFF, 0xFF, 0xFF), // Bright White
|
||||||
|
};
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ScreenConfig
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of displayed pixels in a display row
|
||||||
|
/// </summary>
|
||||||
|
protected int DisplayWidth = 256;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of display lines
|
||||||
|
/// </summary>
|
||||||
|
protected int DisplayLines = 192;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of frames after the flash is toggled
|
||||||
|
/// </summary>
|
||||||
|
protected int FlashToggleFrames = 25;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of lines used for vertical sync
|
||||||
|
/// </summary>
|
||||||
|
protected int VerticalSyncLines = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of top border lines that are not visible
|
||||||
|
/// when rendering the screen
|
||||||
|
/// </summary>
|
||||||
|
protected int NonVisibleBorderTopLines = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of border lines before the display
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderTopLines = 48;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of border lines after the display
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderBottomLines = 48;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of bottom border lines that are not visible
|
||||||
|
/// when rendering the screen
|
||||||
|
/// </summary>
|
||||||
|
protected int NonVisibleBorderBottomLines = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of lines in the screen
|
||||||
|
/// </summary>
|
||||||
|
protected int ScreenLines;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The first screen line that contains the top left display pixel
|
||||||
|
/// </summary>
|
||||||
|
protected int FirstDisplayLine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last screen line that contains the bottom right display pixel
|
||||||
|
/// </summary>
|
||||||
|
protected int LastDisplayLine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of border pixels to the left of the display
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderLeftPixels = 48;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of border pixels to the right of the display
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderRightPixels = 48;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total width of the screen in pixels
|
||||||
|
/// </summary>
|
||||||
|
protected int ScreenWidth;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Horizontal blanking time (HSync+blanking).
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int HorizontalBlankingTime = 40;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time of displaying left part of the border.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderLeftTime = 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time of displaying a pixel row.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int DisplayLineTime = 128;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time of displaying right part of the border.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int BorderRightTime = 24;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time used to render the nonvisible right part of the border.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int NonVisibleBorderRightTime = 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time of displaying a full screen line.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int ScreenLineTime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time the data of a particular pixel should be prefetched
|
||||||
|
/// before displaying it.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int PixelDataPrefetchTime = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time the data of a particular pixel attribute should be prefetched
|
||||||
|
/// before displaying it.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int AttributeDataPrefetchTime = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tact within the line that should display the first pixel.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int FirstPixelCycleInLine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tact in which the top left pixel should be displayed.
|
||||||
|
/// Given in Z80 clock cycles.
|
||||||
|
/// </summary>
|
||||||
|
protected int FirstDisplayPixelCycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The tact in which the top left screen pixel (border) should be displayed
|
||||||
|
/// </summary>
|
||||||
|
protected int FirstScreenPixelCycle;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the number of Z80 clock cycles used for the full rendering
|
||||||
|
/// of the screen.
|
||||||
|
/// </summary>
|
||||||
|
public int UlaFrameCycleCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last rendered ULA cycle
|
||||||
|
/// </summary>
|
||||||
|
public int LastRenderedULACycle;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This structure defines information related to a particular T-State
|
||||||
|
/// (cycle) of ULA screen rendering
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
public struct ScreenRenderingCycle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tha rendering phase to be applied for the particular tact
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0)]
|
||||||
|
public ScreenRenderingPhase Phase;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display memory contention delay
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(1)]
|
||||||
|
public byte ContentionDelay;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display memory address used in the particular tact
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(2)]
|
||||||
|
public ushort PixelByteToFetchAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display memory address used in the particular tact
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(4)]
|
||||||
|
public ushort AttributeToFetchAddress;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pixel X coordinate
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(6)]
|
||||||
|
public ushort XPos;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pixel Y coordinate
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(8)]
|
||||||
|
public ushort YPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This enumeration defines the particular phases of ULA rendering
|
||||||
|
/// </summary>
|
||||||
|
public enum ScreenRenderingPhase : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA does not do any rendering
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA simple sets the border color to display the current pixel.
|
||||||
|
/// </summary>
|
||||||
|
Border,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA sets the border color to display the current pixel. It
|
||||||
|
/// prepares to display the fist pixel in the row with prefetching the
|
||||||
|
/// corresponding byte from the display memory.
|
||||||
|
/// </summary>
|
||||||
|
BorderAndFetchPixelByte,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA sets the border color to display the current pixel. It has
|
||||||
|
/// already fetched the 8 pixel bits to display. It carries on
|
||||||
|
/// preparing to display the fist pixel in the row with prefetching the
|
||||||
|
/// corresponding attribute byte from the display memory.
|
||||||
|
/// </summary>
|
||||||
|
BorderAndFetchPixelAttribute,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte1 sequentially during a
|
||||||
|
/// single Z80 clock cycle.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte1 sequentially during a
|
||||||
|
/// single Z80 clock cycle. It prepares to display the pixels of the next
|
||||||
|
/// byte in the row with prefetching the corresponding byte from the
|
||||||
|
/// display memory.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte1AndFetchByte2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte1 sequentially during a
|
||||||
|
/// single Z80 clock cycle. It prepares to display the pixels of the next
|
||||||
|
/// byte in the row with prefetching the corresponding attribute from the
|
||||||
|
/// display memory.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte1AndFetchAttribute2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte2 sequentially during a
|
||||||
|
/// single Z80 clock cycle.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte2 sequentially during a
|
||||||
|
/// single Z80 clock cycle. It prepares to display the pixels of the next
|
||||||
|
/// byte in the row with prefetching the corresponding byte from the
|
||||||
|
/// display memory.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte2AndFetchByte1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA displays the next two pixels of Byte2 sequentially during a
|
||||||
|
/// single Z80 clock cycle. It prepares to display the pixels of the next
|
||||||
|
/// byte in the row with prefetching the corresponding attribute from the
|
||||||
|
/// display memory.
|
||||||
|
/// </summary>
|
||||||
|
DisplayByte2AndFetchAttribute1
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Border
|
||||||
|
|
||||||
|
private int _borderColour;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the ULA border color
|
||||||
|
/// </summary>
|
||||||
|
public int BorderColour
|
||||||
|
{
|
||||||
|
get { return _borderColour; }
|
||||||
|
set { _borderColour = value & 0x07; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void ResetBorder()
|
||||||
|
{
|
||||||
|
BorderColour = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Screen Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ULA renders the screen between two specified T-States (cycles)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fromCycle"></param>
|
||||||
|
/// <param name="toCycle"></param>
|
||||||
|
public void RenderScreen(int fromCycle, int toCycle)
|
||||||
|
{
|
||||||
|
// Adjust cycle boundaries
|
||||||
|
fromCycle = fromCycle % UlaFrameCycleCount;
|
||||||
|
toCycle = toCycle % UlaFrameCycleCount;
|
||||||
|
|
||||||
|
// Do rendering action for cycles based on the rendering phase
|
||||||
|
for (int curr = fromCycle; curr <= toCycle; curr++)
|
||||||
|
{
|
||||||
|
var ulaCycle = _renderingCycleTable[curr];
|
||||||
|
_xPos = ulaCycle.XPos;
|
||||||
|
_yPos = ulaCycle.YPos;
|
||||||
|
|
||||||
|
switch (ulaCycle.Phase)
|
||||||
|
{
|
||||||
|
case ScreenRenderingPhase.None:
|
||||||
|
// --- Invisible screen area, nothing to do
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.Border:
|
||||||
|
// --- Fetch the border color from ULA and set the corresponding border pixels
|
||||||
|
SetPixels(BorderColour, BorderColour);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.BorderAndFetchPixelByte:
|
||||||
|
// --- Fetch the border color from ULA and set the corresponding border pixels
|
||||||
|
SetPixels(BorderColour, BorderColour);
|
||||||
|
// --- Obtain the future pixel byte
|
||||||
|
_pixelByte1 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.BorderAndFetchPixelAttribute:
|
||||||
|
// --- Fetch the border color from ULA and set the corresponding border pixels
|
||||||
|
SetPixels(BorderColour, BorderColour);
|
||||||
|
// --- Obtain the future attribute byte
|
||||||
|
_attrByte1 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte1:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte1 & 0x80, _attrByte1),
|
||||||
|
GetColor(_pixelByte1 & 0x40, _attrByte1));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte1 <<= 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte1AndFetchByte2:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte1 & 0x80, _attrByte1),
|
||||||
|
GetColor(_pixelByte1 & 0x40, _attrByte1));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte1 <<= 2;
|
||||||
|
// --- Obtain the next pixel byte
|
||||||
|
_pixelByte2 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte1AndFetchAttribute2:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte1 & 0x80, _attrByte1),
|
||||||
|
GetColor(_pixelByte1 & 0x40, _attrByte1));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte1 <<= 2;
|
||||||
|
// --- Obtain the next attribute
|
||||||
|
_attrByte2 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte2:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte2 & 0x80, _attrByte2),
|
||||||
|
GetColor(_pixelByte2 & 0x40, _attrByte2));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte2 <<= 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte2AndFetchByte1:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte2 & 0x80, _attrByte2),
|
||||||
|
GetColor(_pixelByte2 & 0x40, _attrByte2));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte2 <<= 2;
|
||||||
|
// --- Obtain the next pixel byte
|
||||||
|
_pixelByte1 = FetchScreenMemory(ulaCycle.PixelByteToFetchAddress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScreenRenderingPhase.DisplayByte2AndFetchAttribute1:
|
||||||
|
// --- Display bit 7 and 6 according to the corresponding color
|
||||||
|
SetPixels(
|
||||||
|
GetColor(_pixelByte2 & 0x80, _attrByte2),
|
||||||
|
GetColor(_pixelByte2 & 0x40, _attrByte2));
|
||||||
|
// --- Shift in the subsequent bits
|
||||||
|
_pixelByte2 <<= 2;
|
||||||
|
// --- Obtain the next attribute
|
||||||
|
_attrByte1 = FetchScreenMemory(ulaCycle.AttributeToFetchAddress);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the specified cycle is in the visible area of the screen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line index</param>
|
||||||
|
/// <param name="cycleInLine">Tacts index within the line</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True, if the tact is visible on the screen; otherwise, false
|
||||||
|
/// </returns>
|
||||||
|
public virtual bool IsCycleVisible(int line, int cycleInLine)
|
||||||
|
{
|
||||||
|
var firstVisibleLine = VerticalSyncLines + NonVisibleBorderTopLines;
|
||||||
|
var lastVisibleLine = firstVisibleLine + BorderTopLines + DisplayLines + BorderBottomLines;
|
||||||
|
return
|
||||||
|
line >= firstVisibleLine
|
||||||
|
&& line < lastVisibleLine
|
||||||
|
&& cycleInLine >= HorizontalBlankingTime
|
||||||
|
&& cycleInLine < ScreenLineTime - NonVisibleBorderRightTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether the cycle is in the display area of the screen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line index</param>
|
||||||
|
/// <param name="cycleInLine">Tacts index within the line</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True, if the tact is within the display area of the screen; otherwise, false.
|
||||||
|
/// </returns>
|
||||||
|
public virtual bool IsCycleInDisplayArea(int line, int cycleInLine)
|
||||||
|
{
|
||||||
|
return line >= FirstDisplayLine
|
||||||
|
&& line <= LastDisplayLine
|
||||||
|
&& cycleInLine >= FirstPixelCycleInLine
|
||||||
|
&& cycleInLine < FirstPixelCycleInLine + DisplayLineTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the two adjacent screen pixels belonging to the specified cycle to the given
|
||||||
|
/// color
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="colorIndex1">Color index of the first pixel</param>
|
||||||
|
/// <param name="colorIndex2">Color index of the second pixel</param>
|
||||||
|
protected virtual void SetPixels(int colorIndex1, int colorIndex2)
|
||||||
|
{
|
||||||
|
var pos = _yPos * ScreenWidth + _xPos;
|
||||||
|
_frameBuffer[pos++] = (byte)colorIndex1;
|
||||||
|
_frameBuffer[pos] = (byte)colorIndex2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the color index for the specified pixel value according
|
||||||
|
/// to the given color attribute
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pixelValue">0 for paper pixel, non-zero for ink pixel</param>
|
||||||
|
/// <param name="attr">Color attribute</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual int GetColor(int pixelValue, byte attr)
|
||||||
|
{
|
||||||
|
var offset = (pixelValue == 0 ? 0 : 0x100) + attr;
|
||||||
|
return _flashPhase
|
||||||
|
? _flashOnColors[offset]
|
||||||
|
: _flashOffColors[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the ULA cycle to start screen rendering from the beginning
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void ResetULACycle()
|
||||||
|
{
|
||||||
|
LastRenderedULACycle = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the ULA cycle table
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void InitULACycleTable()
|
||||||
|
{
|
||||||
|
_renderingCycleTable = new ScreenRenderingCycle[UlaFrameCycleCount];
|
||||||
|
|
||||||
|
// loop through every cycle
|
||||||
|
for (var cycle = 0; cycle < UlaFrameCycleCount; cycle++)
|
||||||
|
{
|
||||||
|
var line = cycle / ScreenLineTime;
|
||||||
|
var cycleInLine = cycle % ScreenLineTime;
|
||||||
|
|
||||||
|
var cycleItem = new ScreenRenderingCycle
|
||||||
|
{
|
||||||
|
Phase = ScreenRenderingPhase.None,
|
||||||
|
ContentionDelay = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsCycleVisible(line, cycleInLine))
|
||||||
|
{
|
||||||
|
// calculate pixel positions
|
||||||
|
cycleItem.XPos = (ushort)((cycleInLine - HorizontalBlankingTime) * 2);
|
||||||
|
cycleItem.YPos = (ushort)(line - VerticalSyncLines - NonVisibleBorderTopLines);
|
||||||
|
|
||||||
|
if (!IsCycleInDisplayArea(line, cycleInLine))
|
||||||
|
{
|
||||||
|
// we are in the border
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.Border;
|
||||||
|
// set the border colour
|
||||||
|
if (line >= FirstDisplayLine &&
|
||||||
|
line <= LastDisplayLine)
|
||||||
|
{
|
||||||
|
if (cycleInLine == FirstPixelCycleInLine - PixelDataPrefetchTime)
|
||||||
|
{
|
||||||
|
// left or right border beside the display area
|
||||||
|
// fetch the first pixel data byte of the current line (2 cycles away)
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.BorderAndFetchPixelByte;
|
||||||
|
cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2);
|
||||||
|
cycleItem.ContentionDelay = 6;
|
||||||
|
}
|
||||||
|
else if (cycleInLine == FirstPixelCycleInLine - AttributeDataPrefetchTime)
|
||||||
|
{
|
||||||
|
// fetch the first attribute data byte of the current line (1 cycle away)
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.BorderAndFetchPixelAttribute;
|
||||||
|
cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1);
|
||||||
|
cycleItem.ContentionDelay = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var pixelCycle = cycleInLine - FirstPixelCycleInLine;
|
||||||
|
// the ULA will perform a different action based on the current cycle (T-State)
|
||||||
|
switch (pixelCycle & 7)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
// Display the current cycle pixels
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte1;
|
||||||
|
cycleItem.ContentionDelay = 4;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// Display the current cycle pixels
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte1;
|
||||||
|
cycleItem.ContentionDelay = 3;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// While displaying the current cycle pixels, we need to prefetch the
|
||||||
|
// pixel data byte 2 cycles away
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte1AndFetchByte2;
|
||||||
|
cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2);
|
||||||
|
cycleItem.ContentionDelay = 2;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// While displaying the current cycle pixels, we need to prefetch the
|
||||||
|
// attribute data byte 1 cycle away
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte1AndFetchAttribute2;
|
||||||
|
cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1);
|
||||||
|
cycleItem.ContentionDelay = 1;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
// Display the current cycle pixels
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte2;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
if (cycleInLine < FirstPixelCycleInLine + DisplayLineTime - 2)
|
||||||
|
{
|
||||||
|
// There are still more bytes to display in this line.
|
||||||
|
// While displaying the current cycle pixels, we need to prefetch the
|
||||||
|
// pixel data byte 2 cycles away
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte2AndFetchByte1;
|
||||||
|
cycleItem.PixelByteToFetchAddress = CalculatePixelByteAddress(line, cycleInLine + 2);
|
||||||
|
cycleItem.ContentionDelay = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Last byte in this line.
|
||||||
|
// Display the current cycle pixels
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
if (cycleInLine < FirstPixelCycleInLine + DisplayLineTime - 1)
|
||||||
|
{
|
||||||
|
// There are still more bytes to display in this line.
|
||||||
|
// While displaying the current cycle pixels, we need to prefetch the
|
||||||
|
// attribute data byte 1 cycle away
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte2AndFetchAttribute1;
|
||||||
|
cycleItem.AttributeToFetchAddress = CalculateAttributeAddress(line, cycleInLine + 1);
|
||||||
|
cycleItem.ContentionDelay = 5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Last byte in this line.
|
||||||
|
// Display the current cycle pixels
|
||||||
|
cycleItem.Phase = ScreenRenderingPhase.DisplayByte2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the calulated cycle item
|
||||||
|
_renderingCycleTable[cycle] = cycleItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the pixel address for the specified line and tact within
|
||||||
|
/// the line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line index</param>
|
||||||
|
/// <param name="tactInLine">Tacts index within the line</param>
|
||||||
|
/// <returns>ZX spectrum screen memory address</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Memory address bits:
|
||||||
|
/// C0-C2: pixel count within a byte -- not used in address calculation
|
||||||
|
/// C3-C7: pixel byte within a line
|
||||||
|
/// V0-V7: pixel line address
|
||||||
|
///
|
||||||
|
/// Direct Pixel Address (da)
|
||||||
|
/// =================================================================
|
||||||
|
/// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 |
|
||||||
|
/// =================================================================
|
||||||
|
/// | 0 | 0 | 0 |V7 |V6 |V5 |V4 |V3 |V2 |V1 |V0 |C7 |C6 |C5 |C4 |C3 |
|
||||||
|
/// =================================================================
|
||||||
|
/// | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 0xF81F
|
||||||
|
/// =================================================================
|
||||||
|
/// | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0x0700
|
||||||
|
/// =================================================================
|
||||||
|
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0x00E0
|
||||||
|
/// =================================================================
|
||||||
|
///
|
||||||
|
/// Spectrum Pixel Address
|
||||||
|
/// =================================================================
|
||||||
|
/// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 |
|
||||||
|
/// =================================================================
|
||||||
|
/// | 0 | 0 | 0 |V7 |V6 |V2 |V1 |V0 |V5 |V4 |V3 |C7 |C6 |C5 |C4 |C3 |
|
||||||
|
/// =================================================================
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual ushort CalculatePixelByteAddress(int line, int cycleInLine)
|
||||||
|
{
|
||||||
|
var row = line - FirstDisplayLine;
|
||||||
|
var column = 2 * (cycleInLine - (HorizontalBlankingTime + BorderLeftTime));
|
||||||
|
var da = 0x4000 | (column >> 3) | (row << 5);
|
||||||
|
return (ushort)((da & 0xF81F) // --- Reset V5, V4, V3, V2, V1
|
||||||
|
| ((da & 0x0700) >> 3) // --- Keep V5, V4, V3 only
|
||||||
|
| ((da & 0x00E0) << 3)); // --- Exchange the V2, V1, V0 bit
|
||||||
|
// --- group with V5, V4, V3
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the pixel attribute address for the specified line and
|
||||||
|
/// tact within the line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line index</param>
|
||||||
|
/// <param name="tactInLine">Tacts index within the line</param>
|
||||||
|
/// <returns>ZX spectrum screen memory address</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Memory address bits:
|
||||||
|
/// C0-C2: pixel count within a byte -- not used in address calculation
|
||||||
|
/// C3-C7: pixel byte within a line
|
||||||
|
/// V0-V7: pixel line address
|
||||||
|
///
|
||||||
|
/// Spectrum Attribute Address
|
||||||
|
/// =================================================================
|
||||||
|
/// |A15|A14|A13|A12|A11|A10|A9 |A8 |A7 |A6 |A5 |A4 |A3 |A2 |A1 |A0 |
|
||||||
|
/// =================================================================
|
||||||
|
/// | 0 | 1 | 0 | 1 | 1 | 0 |V7 |V6 |V5 |V4 |V3 |C7 |C6 |C5 |C4 |C3 |
|
||||||
|
/// =================================================================
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual ushort CalculateAttributeAddress(int line, int cycleInLine)
|
||||||
|
{
|
||||||
|
var row = line - FirstDisplayLine;
|
||||||
|
var column = 2 * (cycleInLine - (HorizontalBlankingTime + BorderLeftTime));
|
||||||
|
var da = (column >> 3) | ((row >> 3) << 5);
|
||||||
|
return (ushort)(0x5800 + da);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Initialisation
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises the screen configuration calculations
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void InitScreenConfig()
|
||||||
|
{
|
||||||
|
ScreenLines = BorderTopLines + DisplayLines + BorderBottomLines;
|
||||||
|
FirstDisplayLine = VerticalSyncLines + NonVisibleBorderTopLines + BorderTopLines;
|
||||||
|
LastDisplayLine = FirstDisplayLine + DisplayLines - 1;
|
||||||
|
ScreenWidth = BorderLeftPixels + DisplayWidth + BorderRightPixels;
|
||||||
|
FirstPixelCycleInLine = HorizontalBlankingTime + BorderLeftTime;
|
||||||
|
ScreenLineTime = FirstPixelCycleInLine + DisplayLineTime + BorderRightTime + NonVisibleBorderRightTime;
|
||||||
|
UlaFrameCycleCount = (FirstDisplayLine + DisplayLines + BorderBottomLines + NonVisibleBorderTopLines) * ScreenLineTime;
|
||||||
|
FirstScreenPixelCycle = (VerticalSyncLines + NonVisibleBorderTopLines) * ScreenLineTime + HorizontalBlankingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inits the screen
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void InitScreen()
|
||||||
|
{
|
||||||
|
//BorderDevice.Reset();
|
||||||
|
_flashPhase = false;
|
||||||
|
|
||||||
|
_frameBuffer = new byte[ScreenWidth * ScreenLines];
|
||||||
|
|
||||||
|
InitULACycleTable();
|
||||||
|
|
||||||
|
// --- Calculate color conversion table
|
||||||
|
_flashOffColors = new int[0x200];
|
||||||
|
_flashOnColors = new int[0x200];
|
||||||
|
|
||||||
|
for (var attr = 0; attr < 0x100; attr++)
|
||||||
|
{
|
||||||
|
var ink = (attr & 0x07) | ((attr & 0x40) >> 3);
|
||||||
|
var paper = ((attr & 0x38) >> 3) | ((attr & 0x40) >> 3);
|
||||||
|
_flashOffColors[attr] = paper;
|
||||||
|
_flashOffColors[0x100 + attr] = ink;
|
||||||
|
_flashOnColors[attr] = (attr & 0x80) != 0 ? ink : paper;
|
||||||
|
_flashOnColors[0x100 + attr] = (attr & 0x80) != 0 ? paper : ink;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region VBLANK Interrupt
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The longest instruction cycle count
|
||||||
|
/// </summary>
|
||||||
|
protected const int LONGEST_OP_CYCLES = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ULA cycle to raise the interrupt at
|
||||||
|
/// </summary>
|
||||||
|
protected int InterruptCycle = 32;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signs that an interrupt has been raised in this frame.
|
||||||
|
/// </summary>
|
||||||
|
protected bool InterruptRaised;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signs that the interrupt signal has been revoked
|
||||||
|
/// </summary>
|
||||||
|
protected bool InterruptRevoked;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the interrupt - this should happen every frame in order to raise
|
||||||
|
/// the VBLANK interrupt in the proceding frame
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ResetInterrupt()
|
||||||
|
{
|
||||||
|
InterruptRaised = false;
|
||||||
|
InterruptRevoked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates an interrupt in the current phase if needed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentCycle"></param>
|
||||||
|
protected virtual void CheckForInterrupt(int currentCycle)
|
||||||
|
{
|
||||||
|
if (InterruptRevoked)
|
||||||
|
{
|
||||||
|
// interrupt has already been handled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCycle < InterruptCycle)
|
||||||
|
{
|
||||||
|
// interrupt does not need to be raised yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentCycle > InterruptCycle + LONGEST_OP_CYCLES)
|
||||||
|
{
|
||||||
|
// interrupt should have already been raised and the cpu may or
|
||||||
|
// may not have caught it. The time has passed so revoke the signal
|
||||||
|
InterruptRevoked = true;
|
||||||
|
//CPU.IFF1 = true;
|
||||||
|
CPU.FlagI = false;
|
||||||
|
//CPU.NonMaskableInterruptPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InterruptRaised)
|
||||||
|
{
|
||||||
|
// INT is raised but not yet revoked
|
||||||
|
// CPU has NOT handled it yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if CPU is masking the interrupt do not raise it
|
||||||
|
//if (!CPU.IFF1)
|
||||||
|
//return;
|
||||||
|
|
||||||
|
// Raise the interrupt
|
||||||
|
InterruptRaised = true;
|
||||||
|
//CPU.IFF1 = false;
|
||||||
|
//CPU.IFF2 = false;
|
||||||
|
CPU.FlagI = true;
|
||||||
|
FrameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IVideoProvider
|
||||||
|
|
||||||
|
public int VirtualWidth => ScreenWidth;
|
||||||
|
public int VirtualHeight => ScreenLines;
|
||||||
|
public int BufferWidth => ScreenWidth;
|
||||||
|
public int BufferHeight => ScreenLines;
|
||||||
|
public int BackgroundColor => ULAPalette[BorderColour];
|
||||||
|
|
||||||
|
public int VsyncNumerator
|
||||||
|
{
|
||||||
|
get { return 3500000; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int VsyncDenominator
|
||||||
|
{
|
||||||
|
get { return UlaFrameCycleCount; }
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
public int VsyncNumerator => NullVideo.DefaultVsyncNum;
|
||||||
|
public int VsyncDenominator => NullVideo.DefaultVsyncDen;
|
||||||
|
*/
|
||||||
|
public int[] GetVideoBuffer()
|
||||||
|
{
|
||||||
|
// convert the generated _framebuffer into ARGB colours via the ULAPalette
|
||||||
|
int[] trans = new int[_frameBuffer.Length];
|
||||||
|
for (int i = 0; i < _frameBuffer.Length; i++)
|
||||||
|
trans[i] = ULAPalette[_frameBuffer[i]];
|
||||||
|
return trans; //_frameBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The abstract class that all emulated models will inherit from
|
||||||
|
/// * Sound *
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The abstract class that all emulated models will inherit from
|
||||||
|
/// * Main properties / fields / contruction*
|
||||||
|
/// </summary>
|
||||||
|
public abstract partial class SpectrumBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The calling ZXSpectrum class (piped in via constructor)
|
||||||
|
/// </summary>
|
||||||
|
protected ZXSpectrum Spectrum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reference to the instantiated Z80 cpu (piped in via constructor)
|
||||||
|
/// </summary>
|
||||||
|
protected Z80A CPU { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectrum buzzer/beeper
|
||||||
|
/// </summary>
|
||||||
|
public Buzzer BuzzerDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectrum keyboard
|
||||||
|
/// </summary>
|
||||||
|
public virtual IKeyboard KeyboardDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The spectrum datacorder device
|
||||||
|
/// </summary>
|
||||||
|
public virtual Tape TapeDevice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signs whether the frame has ended
|
||||||
|
/// </summary>
|
||||||
|
public bool FrameCompleted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overflow from the previous frame (in Z80 cycles)
|
||||||
|
/// </summary>
|
||||||
|
public int OverFlow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total number of frames rendered
|
||||||
|
/// </summary>
|
||||||
|
public int FrameCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current cycle (T-State) that we are at in the frame
|
||||||
|
/// </summary>
|
||||||
|
public int _frameCycles;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stores where we are in the frame after each CPU cycle
|
||||||
|
/// </summary>
|
||||||
|
public int LastFrameStartCPUTick;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current frame cycle according to the CPU tick count
|
||||||
|
/// </summary>
|
||||||
|
public virtual int CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mask constants
|
||||||
|
/// </summary>
|
||||||
|
protected const int BORDER_BIT = 0x07;
|
||||||
|
protected const int EAR_BIT = 0x10;
|
||||||
|
protected const int MIC_BIT = 0x08;
|
||||||
|
protected const int TAPE_BIT = 0x40;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a single frame
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ExecuteFrame()
|
||||||
|
{
|
||||||
|
FrameCompleted = false;
|
||||||
|
BuzzerDevice.StartFrame();
|
||||||
|
|
||||||
|
PollInput();
|
||||||
|
|
||||||
|
while (CurrentFrameCycle <= UlaFrameCycleCount)
|
||||||
|
{
|
||||||
|
// check for interrupt
|
||||||
|
CheckForInterrupt(CurrentFrameCycle);
|
||||||
|
|
||||||
|
// run a single CPU instruction
|
||||||
|
CPU.ExecuteOne();
|
||||||
|
|
||||||
|
// run a rendering cycle according to the current CPU cycle count
|
||||||
|
var lastCycle = CurrentFrameCycle;
|
||||||
|
RenderScreen(LastRenderedULACycle + 1, lastCycle);
|
||||||
|
LastRenderedULACycle = lastCycle;
|
||||||
|
|
||||||
|
// test
|
||||||
|
if (CPU.IFF1)
|
||||||
|
{
|
||||||
|
//Random rnd = new Random();
|
||||||
|
//ushort rU = (ushort)rnd.Next(0x4000, 0x8000);
|
||||||
|
//PokeMemory(rU, (byte)rnd.Next(7));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have reached the end of a frame
|
||||||
|
LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow;
|
||||||
|
LastRenderedULACycle = OverFlow;
|
||||||
|
|
||||||
|
BuzzerDevice.EndFrame();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FrameCount++;
|
||||||
|
|
||||||
|
// setup for next frame
|
||||||
|
OverFlow = CurrentFrameCycle % UlaFrameCycleCount;
|
||||||
|
ResetInterrupt();
|
||||||
|
FrameCompleted = true;
|
||||||
|
|
||||||
|
if (FrameCount % FlashToggleFrames == 0)
|
||||||
|
{
|
||||||
|
_flashPhase = !_flashPhase;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderScreen(0, OverFlow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes one cycle of the emulated machine
|
||||||
|
/// </summary>
|
||||||
|
public virtual void ExecuteCycle()
|
||||||
|
{
|
||||||
|
// check for interrupt
|
||||||
|
CheckForInterrupt(CurrentFrameCycle);
|
||||||
|
|
||||||
|
// run a single CPU instruction
|
||||||
|
CPU.ExecuteOne();
|
||||||
|
|
||||||
|
// run a rendering cycle according to the current CPU cycle count
|
||||||
|
var lastCycle = CurrentFrameCycle;
|
||||||
|
RenderScreen(LastRenderedULACycle + 1, lastCycle);
|
||||||
|
|
||||||
|
// has the frame completed?
|
||||||
|
FrameCompleted = CurrentFrameCycle >= UlaFrameCycleCount;
|
||||||
|
|
||||||
|
if (CurrentFrameCycle > 50000)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hard reset of the emulated machine
|
||||||
|
/// </summary>
|
||||||
|
public virtual void HardReset()
|
||||||
|
{
|
||||||
|
ResetBorder();
|
||||||
|
ResetInterrupt();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Soft reset of the emulated machine
|
||||||
|
/// </summary>
|
||||||
|
public virtual void SoftReset()
|
||||||
|
{
|
||||||
|
ResetBorder();
|
||||||
|
ResetInterrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The 48k keyboard device
|
||||||
|
/// </summary>
|
||||||
|
public class Keyboard48 : IKeyboard
|
||||||
|
{
|
||||||
|
public SpectrumBase _machine { get; set; }
|
||||||
|
public string[] KeyboardMatrix { get; set; }
|
||||||
|
private readonly byte[] LineStatus;
|
||||||
|
public bool Issue2 { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public Keyboard48(SpectrumBase machine)
|
||||||
|
{
|
||||||
|
_machine = machine;
|
||||||
|
|
||||||
|
KeyboardMatrix = new string[]
|
||||||
|
{
|
||||||
|
// 0xfefe - 0 - 4
|
||||||
|
"Key Caps Shift", "Key Z", "Key X", "Key C", "Key V",
|
||||||
|
// 0xfdfe - 5 - 9
|
||||||
|
"Key A", "Key S", "Key D", "Key F", "Key G",
|
||||||
|
// 0xfbfe - 10 - 14
|
||||||
|
"Key Q", "Key W", "Key E", "Key R", "Key T",
|
||||||
|
// 0xf7fe - 15 - 19
|
||||||
|
"Key 1", "Key 2", "Key 3", "Key 4", "Key 5",
|
||||||
|
// 0xeffe - 20 - 24
|
||||||
|
"Key 0", "Key 9", "Key 8", "Key 7", "Key 6",
|
||||||
|
// 0xdffe - 25 - 29
|
||||||
|
"Key P", "Key O", "Key I", "Key U", "Key Y",
|
||||||
|
// 0xbffe - 30 - 34
|
||||||
|
"Key Return", "Key L", "Key K", "Key J", "Key H",
|
||||||
|
// 0x7ffe - 35 - 39
|
||||||
|
"Key Space", "Key Sym Shift", "Key M", "Key N", "Key B"
|
||||||
|
};
|
||||||
|
|
||||||
|
LineStatus = new byte[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetKeyStatus(string key, bool isPressed)
|
||||||
|
{
|
||||||
|
byte keyByte = GetByteFromKeyMatrix(key);
|
||||||
|
var lineIndex = keyByte / 5;
|
||||||
|
var lineMask = 1 << keyByte % 5;
|
||||||
|
|
||||||
|
LineStatus[lineIndex] = isPressed ? (byte)(LineStatus[lineIndex] | lineMask)
|
||||||
|
: (byte)(LineStatus[lineIndex] & ~lineMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetKeyStatus(string key)
|
||||||
|
{
|
||||||
|
byte keyByte = GetByteFromKeyMatrix(key);
|
||||||
|
var lineIndex = keyByte / 5;
|
||||||
|
var lineMask = 1 << keyByte % 5;
|
||||||
|
return (LineStatus[lineIndex] & lineMask) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte GetLineStatus(byte lines)
|
||||||
|
{
|
||||||
|
byte status = 0;
|
||||||
|
lines = (byte)~lines;
|
||||||
|
var lineIndex = 0;
|
||||||
|
while (lines > 0)
|
||||||
|
{
|
||||||
|
if ((lines & 0x01) != 0)
|
||||||
|
status |= LineStatus[lineIndex];
|
||||||
|
lineIndex++;
|
||||||
|
lines >>= 1;
|
||||||
|
}
|
||||||
|
var result = (byte)~status;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte ReadKeyboardByte(ushort addr)
|
||||||
|
{
|
||||||
|
return GetLineStatus((byte)(addr >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte GetByteFromKeyMatrix(string key)
|
||||||
|
{
|
||||||
|
int index = Array.IndexOf(KeyboardMatrix, key);
|
||||||
|
return (byte)index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public class ZX48 : SpectrumBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Main constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spectrum"></param>
|
||||||
|
/// <param name="cpu"></param>
|
||||||
|
public ZX48(ZXSpectrum spectrum, Z80A cpu)
|
||||||
|
{
|
||||||
|
Spectrum = spectrum;
|
||||||
|
CPU = cpu;
|
||||||
|
|
||||||
|
RAM = new byte[0x4000 + 0xC000];
|
||||||
|
|
||||||
|
InitScreenConfig();
|
||||||
|
InitScreen();
|
||||||
|
|
||||||
|
ResetULACycle();
|
||||||
|
|
||||||
|
BuzzerDevice = new Buzzer(this);
|
||||||
|
BuzzerDevice.Init();
|
||||||
|
|
||||||
|
KeyboardDevice = new Keyboard48(this);
|
||||||
|
|
||||||
|
TapeDevice = new Tape();
|
||||||
|
TapeDevice.Init(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The MIC and EAR pins in the spectrum deal in on/off pulses of varying lengths
|
||||||
|
/// This struct therefore represents 1 of these pulses
|
||||||
|
/// </summary>
|
||||||
|
public struct Pulse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True: High State
|
||||||
|
/// False: Low State
|
||||||
|
/// </summary>
|
||||||
|
public bool State { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulse length in Z80 T-States (cycles)
|
||||||
|
/// </summary>
|
||||||
|
public int Length { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the tape device (or DATACORDER as AMSTRAD liked to call it)
|
||||||
|
/// </summary>
|
||||||
|
public class Tape
|
||||||
|
{
|
||||||
|
protected bool _micBitState;
|
||||||
|
|
||||||
|
public SpectrumBase _machine { get; set; }
|
||||||
|
public Buzzer _buzzer { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void Init(SpectrumBase machine)
|
||||||
|
{
|
||||||
|
_machine = machine;
|
||||||
|
_buzzer = machine.BuzzerDevice;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Reset()
|
||||||
|
{
|
||||||
|
_micBitState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the EAR bit read from tape
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cpuCycles"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual bool GetEarBit(int cpuCycles)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the mic bit change
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="micBit"></param>
|
||||||
|
public virtual void ProcessMicBit(bool micBit)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The standard 48K Spectrum keyboard
|
||||||
|
/// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ControllerDefinition ZXSpectrumControllerDefinition48 = new ControllerDefinition
|
||||||
|
{
|
||||||
|
Name = "ZXSpectrum Controller 48K",
|
||||||
|
BoolButtons =
|
||||||
|
{
|
||||||
|
// Joystick interface (not yet emulated) - Could be Kempston/Cursor/Protek
|
||||||
|
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button",
|
||||||
|
// Keyboard - row 1
|
||||||
|
"Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0",
|
||||||
|
// Keyboard - row 2
|
||||||
|
"Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P",
|
||||||
|
// Keyboard - row 3
|
||||||
|
"Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return",
|
||||||
|
// Keyboard - row 4
|
||||||
|
"Key Caps Shift", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Sym Shift", "Key Space",
|
||||||
|
// Tape functions
|
||||||
|
"Play Tape", "Stop Tape", "RTZ Tape", "Record Tape"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The newer spectrum keyboard (models 48k+, 128k)
|
||||||
|
/// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ControllerDefinition ZXSpectrumControllerDefinition128 = new ControllerDefinition
|
||||||
|
{
|
||||||
|
Name = "ZXSpectrum Controller 48KPlus",
|
||||||
|
BoolButtons =
|
||||||
|
{
|
||||||
|
// Joystick interface (not yet emulated) - Could be Kempston/Cursor/Protek
|
||||||
|
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button",
|
||||||
|
// Keyboard - row 1
|
||||||
|
"Key True Video", "Key Inv Video", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Break",
|
||||||
|
// Keyboard - row 2
|
||||||
|
"Key Delete", "Key Graph", "Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P",
|
||||||
|
// Keyboard - row 3
|
||||||
|
"Key Extend Mode", "Key Edit", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return",
|
||||||
|
// Keyboard - row 4
|
||||||
|
"Key Caps Shift", "Key Caps Lock", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Period",
|
||||||
|
// Keyboard - row 5
|
||||||
|
"Key Symbol Shift", "Key Semi-Colon", "Key Inverted-Comma", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", "Key Symbol Shift",
|
||||||
|
// Tape functions
|
||||||
|
"Play Tape", "Stop Tape", "RTZ Tape", "Record Tape"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amstrad models - same as the previous keyboard layout but with built in ZX Interface 2 joystick ports
|
||||||
|
/// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ControllerDefinition ZXSpectrumControllerDefinitionPlus = new ControllerDefinition
|
||||||
|
{
|
||||||
|
Name = "ZXSpectrum Controller 48KPlus",
|
||||||
|
BoolButtons =
|
||||||
|
{
|
||||||
|
// Joystick interface (not yet emulated)
|
||||||
|
"P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button",
|
||||||
|
// Keyboard - row 1
|
||||||
|
"Key True Video", "Key Inv Video", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5", "Key 6", "Key 7", "Key 8", "Key 9", "Key 0", "Key Break",
|
||||||
|
// Keyboard - row 2
|
||||||
|
"Key Delete", "Key Graph", "Key Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P",
|
||||||
|
// Keyboard - row 3
|
||||||
|
"Key Extend Mode", "Key Edit", "Key A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return",
|
||||||
|
// Keyboard - row 4
|
||||||
|
"Key Caps Shift", "Key Caps Lock", "Key Z", "Key X", "Key C", "Key V", "Key B", "Key N", "Key M", "Key Period",
|
||||||
|
// Keyboard - row 5
|
||||||
|
"Key Symbol Shift", "Key Semi-Colon", "Key Inverted-Comma", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", "Key Symbol Shift",
|
||||||
|
// Tape functions
|
||||||
|
"Play Tape", "Stop Tape", "RTZ Tape", "Record Tape"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using BizHawk.Common.NumberExtensions;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum : IDebuggable
|
||||||
|
{
|
||||||
|
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, RegisterValue>
|
||||||
|
{
|
||||||
|
["A"] = _cpu.Regs[_cpu.A],
|
||||||
|
["AF"] = _cpu.Regs[_cpu.F] + (_cpu.Regs[_cpu.A] << 8),
|
||||||
|
["B"] = _cpu.Regs[_cpu.B],
|
||||||
|
["BC"] = _cpu.Regs[_cpu.C] + (_cpu.Regs[_cpu.B] << 8),
|
||||||
|
["C"] = _cpu.Regs[_cpu.C],
|
||||||
|
["D"] = _cpu.Regs[_cpu.D],
|
||||||
|
["DE"] = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8),
|
||||||
|
["E"] = _cpu.Regs[_cpu.E],
|
||||||
|
["F"] = _cpu.Regs[_cpu.F],
|
||||||
|
["H"] = _cpu.Regs[_cpu.H],
|
||||||
|
["HL"] = _cpu.Regs[_cpu.L] + (_cpu.Regs[_cpu.H] << 8),
|
||||||
|
["I"] = _cpu.Regs[_cpu.I],
|
||||||
|
["IX"] = _cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8),
|
||||||
|
["IY"] = _cpu.Regs[_cpu.Iyl] + (_cpu.Regs[_cpu.Iyh] << 8),
|
||||||
|
["L"] = _cpu.Regs[_cpu.L],
|
||||||
|
["PC"] = _cpu.Regs[_cpu.PCl] + (_cpu.Regs[_cpu.PCh] << 8),
|
||||||
|
["R"] = _cpu.Regs[_cpu.R],
|
||||||
|
["Shadow AF"] = _cpu.Regs[_cpu.F_s] + (_cpu.Regs[_cpu.A_s] << 8),
|
||||||
|
["Shadow BC"] = _cpu.Regs[_cpu.C_s] + (_cpu.Regs[_cpu.B_s] << 8),
|
||||||
|
["Shadow DE"] = _cpu.Regs[_cpu.E_s] + (_cpu.Regs[_cpu.D_s] << 8),
|
||||||
|
["Shadow HL"] = _cpu.Regs[_cpu.L_s] + (_cpu.Regs[_cpu.H_s] << 8),
|
||||||
|
["SP"] = _cpu.Regs[_cpu.Iyl] + (_cpu.Regs[_cpu.Iyh] << 8),
|
||||||
|
["Flag C"] = _cpu.FlagC,
|
||||||
|
["Flag N"] = _cpu.FlagN,
|
||||||
|
["Flag P/V"] = _cpu.FlagP,
|
||||||
|
["Flag 3rd"] = _cpu.Flag3,
|
||||||
|
["Flag H"] = _cpu.FlagH,
|
||||||
|
["Flag 5th"] = _cpu.Flag5,
|
||||||
|
["Flag Z"] = _cpu.FlagZ,
|
||||||
|
["Flag S"] = _cpu.FlagS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCpuRegister(string register, int value)
|
||||||
|
{
|
||||||
|
switch (register)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
case "A":
|
||||||
|
_cpu.Regs[_cpu.A] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "AF":
|
||||||
|
_cpu.Regs[_cpu.F] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.A] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "B":
|
||||||
|
_cpu.Regs[_cpu.B] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "BC":
|
||||||
|
_cpu.Regs[_cpu.C] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.B] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
_cpu.Regs[_cpu.C] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
_cpu.Regs[_cpu.D] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "DE":
|
||||||
|
_cpu.Regs[_cpu.E] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.D] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "E":
|
||||||
|
_cpu.Regs[_cpu.E] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "F":
|
||||||
|
_cpu.Regs[_cpu.F] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "H":
|
||||||
|
_cpu.Regs[_cpu.H] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "HL":
|
||||||
|
_cpu.Regs[_cpu.L] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.H] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "I":
|
||||||
|
_cpu.Regs[_cpu.I] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "IX":
|
||||||
|
_cpu.Regs[_cpu.Ixl] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.Ixh] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "IY":
|
||||||
|
_cpu.Regs[_cpu.Iyl] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.Iyh] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "L":
|
||||||
|
_cpu.Regs[_cpu.L] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "PC":
|
||||||
|
_cpu.Regs[_cpu.PCl] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.PCh] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "R":
|
||||||
|
_cpu.Regs[_cpu.R] = (ushort)value;
|
||||||
|
break;
|
||||||
|
case "Shadow AF":
|
||||||
|
_cpu.Regs[_cpu.F_s] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.A_s] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "Shadow BC":
|
||||||
|
_cpu.Regs[_cpu.C_s] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.B_s] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "Shadow DE":
|
||||||
|
_cpu.Regs[_cpu.E_s] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.D_s] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "Shadow HL":
|
||||||
|
_cpu.Regs[_cpu.L_s] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.H_s] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
case "SP":
|
||||||
|
_cpu.Regs[_cpu.SPl] = (ushort)(value & 0xFF);
|
||||||
|
_cpu.Regs[_cpu.SPh] = (ushort)(value & 0xFF00);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IMemoryCallbackSystem MemoryCallbacks { get; }
|
||||||
|
|
||||||
|
public bool CanStep(StepType type) => false;
|
||||||
|
|
||||||
|
[FeatureNotImplemented]
|
||||||
|
public void Step(StepType type)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TotalExecutedCycles => _cpu.TotalExecutedCycles;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Common.NumberExtensions;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum : IEmulator
|
||||||
|
{
|
||||||
|
public IEmulatorServiceProvider ServiceProvider { get; }
|
||||||
|
|
||||||
|
public ControllerDefinition ControllerDefinition { get; set; }
|
||||||
|
|
||||||
|
public void FrameAdvance(IController controller, bool render, bool renderSound)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
|
||||||
|
if (_tracer.Enabled)
|
||||||
|
{
|
||||||
|
_cpu.TraceCallback = s => _tracer.Put(s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cpu.TraceCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_machine.ExecuteFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Frame => _machine.FrameCount;
|
||||||
|
|
||||||
|
public string SystemId => "ZXSpectrum";
|
||||||
|
|
||||||
|
public bool DeterministicEmulation => true;
|
||||||
|
|
||||||
|
public void ResetCounters()
|
||||||
|
{
|
||||||
|
_machine.FrameCount = 0;
|
||||||
|
_lagCount = 0;
|
||||||
|
_isLag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoreComm CoreComm { get; }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_machine != null)
|
||||||
|
{
|
||||||
|
_machine = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum : IInputPollable
|
||||||
|
{
|
||||||
|
public int LagCount
|
||||||
|
{
|
||||||
|
get { return _lagCount; }
|
||||||
|
set { _lagCount = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLagFrame
|
||||||
|
{
|
||||||
|
get { return _isLag; }
|
||||||
|
set { _isLag = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IInputCallbackSystem InputCallbacks { get; }
|
||||||
|
|
||||||
|
private int _lagCount = 0;
|
||||||
|
private bool _isLag = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum //: IMemoryDomains
|
||||||
|
{
|
||||||
|
private MemoryDomainList memoryDomains;
|
||||||
|
private readonly Dictionary<string, MemoryDomainByteArray> _byteArrayDomains = new Dictionary<string, MemoryDomainByteArray>();
|
||||||
|
private bool _memoryDomainsInit = false;
|
||||||
|
|
||||||
|
private void SetupMemoryDomains()
|
||||||
|
{
|
||||||
|
var domains = new List<MemoryDomain>
|
||||||
|
{
|
||||||
|
new MemoryDomainDelegate("System Bus", 0x10000, MemoryDomain.Endian.Little,
|
||||||
|
(addr) =>
|
||||||
|
{
|
||||||
|
if (addr < 0 || addr >= 65536)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
return _cpu.ReadMemory((ushort)addr);
|
||||||
|
},
|
||||||
|
(addr, value) =>
|
||||||
|
{
|
||||||
|
if (addr < 0 || addr >= 65536)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
_cpu.WriteMemory((ushort)addr, value);
|
||||||
|
}, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
SyncAllByteArrayDomains();
|
||||||
|
|
||||||
|
memoryDomains = new MemoryDomainList(_byteArrayDomains.Values.Concat(domains).ToList());
|
||||||
|
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(memoryDomains);
|
||||||
|
|
||||||
|
_memoryDomainsInit = true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncAllByteArrayDomains()
|
||||||
|
{
|
||||||
|
SyncByteArrayDomain("Main RAM", _machine.RAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncByteArrayDomain(string name, byte[] data)
|
||||||
|
{
|
||||||
|
if (_memoryDomainsInit)
|
||||||
|
{
|
||||||
|
var m = _byteArrayDomains[name];
|
||||||
|
m.Data = data;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var m = new MemoryDomainByteArray(name, MemoryDomain.Endian.Little, data, true, 1);
|
||||||
|
_byteArrayDomains.Add(name, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum : ISettable<ZXSpectrum.ZXSpectrumSettings, ZXSpectrum.ZXSpectrumSyncSettings>
|
||||||
|
{
|
||||||
|
internal ZXSpectrumSettings Settings = new ZXSpectrumSettings();
|
||||||
|
internal ZXSpectrumSyncSettings SyncSettings = new ZXSpectrumSyncSettings();
|
||||||
|
|
||||||
|
public ZXSpectrumSettings GetSettings()
|
||||||
|
{
|
||||||
|
return Settings.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZXSpectrumSyncSettings GetSyncSettings()
|
||||||
|
{
|
||||||
|
return SyncSettings.Clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PutSettings(ZXSpectrumSettings o)
|
||||||
|
{
|
||||||
|
Settings = o;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PutSyncSettings(ZXSpectrumSyncSettings o)
|
||||||
|
{
|
||||||
|
SyncSettings = o;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class ZXSpectrumSettings
|
||||||
|
{
|
||||||
|
[DisplayName("Spectrum model")]
|
||||||
|
[Description("The model of spectrum to be emulated")]
|
||||||
|
[DefaultValue(MachineType.ZXSpectrum48)]
|
||||||
|
public MachineType MachineType { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Border type")]
|
||||||
|
[Description("Select how to show the border area")]
|
||||||
|
[DefaultValue(BorderType.Full)]
|
||||||
|
public BorderType BorderType { get; set; }
|
||||||
|
|
||||||
|
[DisplayName("Tape Load Speed")]
|
||||||
|
[Description("Select how fast the spectrum loads the game from tape")]
|
||||||
|
[DefaultValue(TapeLoadSpeed.Accurate)]
|
||||||
|
public TapeLoadSpeed TapeLoadSpeed { get; set; }
|
||||||
|
|
||||||
|
public ZXSpectrumSettings Clone()
|
||||||
|
{
|
||||||
|
return (ZXSpectrumSettings)MemberwiseClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZXSpectrumSettings()
|
||||||
|
{
|
||||||
|
BizHawk.Common.SettingsUtil.SetDefaultValues(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ZXSpectrumSyncSettings
|
||||||
|
{
|
||||||
|
public ZXSpectrumSyncSettings Clone()
|
||||||
|
{
|
||||||
|
return (ZXSpectrumSyncSettings)MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the Spectrum border
|
||||||
|
/// </summary>
|
||||||
|
public enum BorderType
|
||||||
|
{
|
||||||
|
Full,
|
||||||
|
Medium,
|
||||||
|
Small
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The speed at which the tape is loaded
|
||||||
|
/// </summary>
|
||||||
|
public enum TapeLoadSpeed
|
||||||
|
{
|
||||||
|
Accurate,
|
||||||
|
Fast,
|
||||||
|
Fastest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum //: ISoundProvider
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
public partial class ZXSpectrum : IStatable
|
||||||
|
{
|
||||||
|
public bool BinarySaveStatesPreferred
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveStateText(TextWriter writer)
|
||||||
|
{
|
||||||
|
SyncState(new Serializer(writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadStateText(TextReader reader)
|
||||||
|
{
|
||||||
|
SyncState(new Serializer(reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveStateBinary(BinaryWriter bw)
|
||||||
|
{
|
||||||
|
SyncState(new Serializer(bw));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadStateBinary(BinaryReader br)
|
||||||
|
{
|
||||||
|
SyncState(new Serializer(br));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] SaveStateBinary()
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
BinaryWriter bw = new BinaryWriter(ms);
|
||||||
|
SaveStateBinary(bw);
|
||||||
|
bw.Flush();
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
byte[] core = null;
|
||||||
|
if (ser.IsWriter)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
ms.Close();
|
||||||
|
core = ms.ToArray();
|
||||||
|
}
|
||||||
|
_cpu.SyncState(ser);
|
||||||
|
|
||||||
|
ser.BeginSection("ZXSpectrum");
|
||||||
|
byte[] ram = new byte[_machine.RAM.Length];
|
||||||
|
_machine.RAM.CopyTo(ram, 0);
|
||||||
|
ser.Sync("RAM", ref ram, false);
|
||||||
|
//_vdp.SyncState(ser);
|
||||||
|
//PSG.SyncState(ser);
|
||||||
|
//ser.Sync("RAM", ref _ram, false);
|
||||||
|
ser.Sync("Frame", ref _machine.FrameCount);
|
||||||
|
ser.Sync("LagCount", ref _lagCount);
|
||||||
|
ser.Sync("IsLag", ref _isLag);
|
||||||
|
ser.EndSection();
|
||||||
|
|
||||||
|
if (ser.IsReader)
|
||||||
|
{
|
||||||
|
//SyncAllByteArrayDomains();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] _stateBuffer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
using BizHawk.Emulation.Common;
|
||||||
|
using BizHawk.Emulation.Cores.Components;
|
||||||
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
|
||||||
|
{
|
||||||
|
[Core(
|
||||||
|
"ZXHawk",
|
||||||
|
"Asnivor",
|
||||||
|
isPorted: false,
|
||||||
|
isReleased: false)]
|
||||||
|
[ServiceNotApplicable(typeof(IDriveLight))]
|
||||||
|
public partial class ZXSpectrum : IDebuggable, IInputPollable, IStatable, IRegionable
|
||||||
|
{
|
||||||
|
[CoreConstructor("ZXSpectrum")]
|
||||||
|
public ZXSpectrum(CoreComm comm, byte[] file, object settings, object syncSettings)
|
||||||
|
{
|
||||||
|
PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings());
|
||||||
|
PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings());
|
||||||
|
|
||||||
|
var ser = new BasicServiceProvider(this);
|
||||||
|
ServiceProvider = ser;
|
||||||
|
InputCallbacks = new InputCallbackSystem();
|
||||||
|
|
||||||
|
CoreComm = comm;
|
||||||
|
|
||||||
|
_cpu = new Z80A();
|
||||||
|
|
||||||
|
_tracer = new TraceBuffer { Header = _cpu.TraceHeader };
|
||||||
|
|
||||||
|
switch (Settings.MachineType)
|
||||||
|
{
|
||||||
|
case MachineType.ZXSpectrum48:
|
||||||
|
ControllerDefinition = ZXSpectrumControllerDefinition48;
|
||||||
|
Init(MachineType.ZXSpectrum48, Settings.BorderType, Settings.TapeLoadSpeed);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException("Machine not yet emulated");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_cpu.MemoryCallbacks = MemoryCallbacks;
|
||||||
|
|
||||||
|
HardReset = _machine.HardReset;
|
||||||
|
SoftReset = _machine.SoftReset;
|
||||||
|
|
||||||
|
_cpu.FetchMemory = _machine.ReadMemory;
|
||||||
|
_cpu.ReadMemory = _machine.ReadMemory;
|
||||||
|
_cpu.WriteMemory = _machine.WriteMemory;
|
||||||
|
_cpu.ReadHardware = _machine.ReadPort;
|
||||||
|
_cpu.WriteHardware = _machine.WritePort;
|
||||||
|
|
||||||
|
ser.Register<ITraceable>(_tracer);
|
||||||
|
ser.Register<IDisassemblable>(_cpu);
|
||||||
|
ser.Register<IVideoProvider>(_machine);
|
||||||
|
ser.Register<ISoundProvider>(_machine.BuzzerDevice);
|
||||||
|
|
||||||
|
HardReset();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<string> romDis = new List<string>();
|
||||||
|
List<DISA> disas = new List<DISA>();
|
||||||
|
for (int i = 0x00; i < 0x4000; i++)
|
||||||
|
{
|
||||||
|
DISA d = new DISA();
|
||||||
|
ushort size;
|
||||||
|
d.Dis = _cpu.Disassemble((ushort)i, _machine.ReadMemory, out size);
|
||||||
|
d.Size = size;
|
||||||
|
disas.Add(d);
|
||||||
|
romDis.Add(d.Dis);
|
||||||
|
//i = i + size - 1;
|
||||||
|
//romDis.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DISA
|
||||||
|
{
|
||||||
|
public ushort Size { get; set; }
|
||||||
|
public string Dis { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
//private int _cyclesPerFrame;
|
||||||
|
|
||||||
|
public Action HardReset;
|
||||||
|
public Action SoftReset;
|
||||||
|
|
||||||
|
private readonly Z80A _cpu;
|
||||||
|
private byte[] _systemRom;
|
||||||
|
private readonly TraceBuffer _tracer;
|
||||||
|
public IController _controller;
|
||||||
|
private SpectrumBase _machine;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private byte[] GetFirmware(int length, params string[] names)
|
||||||
|
{
|
||||||
|
var result = names.Select(n => CoreComm.CoreFileProvider.GetFirmware("ZXSpectrum", n, false)).FirstOrDefault(b => b != null && b.Length == length);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
throw new MissingFirmwareException($"At least one of these firmwares is required: {string.Join(", ", names)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed)
|
||||||
|
{
|
||||||
|
// setup the emulated model based on the MachineType
|
||||||
|
switch (machineType)
|
||||||
|
{
|
||||||
|
case MachineType.ZXSpectrum48:
|
||||||
|
_machine = new ZX48(this, _cpu);
|
||||||
|
_systemRom = GetFirmware(0x4000, "48ROM");
|
||||||
|
_machine.FillMemory(_systemRom, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IRegionable
|
||||||
|
|
||||||
|
public DisplayType Region => DisplayType.PAL;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue