From d012472999889132abc03ba9ae9d890a6174c11d Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 23 Nov 2017 17:26:15 +0000 Subject: [PATCH 001/105] Initial commit. 48K spectrum only. --- ...izHawkSystemIdToCoreSystemEnumConverter.cs | 6 + BizHawk.Client.ApiHawk/Classes/ClientApi.cs | 4 +- BizHawk.Client.Common/Api/CoreSystem.cs | 3 +- BizHawk.Client.Common/Global.cs | 2 + BizHawk.Client.Common/RomLoader.cs | 12 + BizHawk.Client.Common/SystemInfo.cs | 5 + BizHawk.Client.Common/config/PathEntry.cs | 8 +- BizHawk.Client.EmuHawk/FileLoader.cs | 2 +- BizHawk.Client.EmuHawk/MainForm.cs | 3 +- .../config/ControllerConfig.cs | 2 +- .../config/FirmwaresConfig.cs | 1 + BizHawk.Emulation.Common/Database/Database.cs | 18 +- .../Database/FirmwareDatabase.cs | 3 + .../BizHawk.Emulation.Cores.csproj | 23 + .../Computers/SinclairSpectrum/Buzzer.cs | 245 +++++ .../Computers/SinclairSpectrum/IKeyboard.cs | 65 ++ .../SinclairSpectrum/Machine/MachineType.cs | 16 + .../Machine/SpectrumBase.Input.cs | 32 + .../Machine/SpectrumBase.Memory.cs | 147 +++ .../Machine/SpectrumBase.Port.cs | 173 ++++ .../Machine/SpectrumBase.Screen.cs | 910 ++++++++++++++++++ .../Machine/SpectrumBase.Sound.cs | 17 + .../SinclairSpectrum/Machine/SpectrumBase.cs | 174 ++++ .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 92 ++ .../Machine/ZXSpectrum48K/ZX48.cs | 38 + .../Computers/SinclairSpectrum/Pulse.cs | 26 + .../Computers/SinclairSpectrum/Tape.cs | 52 + .../ZXSpectrum.Controllers.cs | 85 ++ .../ZXSpectrum.IDebuggable.cs | 147 +++ .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 51 + .../ZXSpectrum.IInputPollable.cs | 26 + .../ZXSpectrum.IMemoryDomains.cs | 68 ++ .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 95 ++ .../ZXSpectrum.ISoundProvider.cs | 10 + .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 75 ++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 135 +++ 36 files changed, 2761 insertions(+), 10 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs diff --git a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs index 31b65c5df5..fcce1118cf 100644 --- a/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs +++ b/BizHawk.Client.ApiHawk/Classes/BizHawkSystemIdToCoreSystemEnumConverter.cs @@ -96,6 +96,9 @@ namespace BizHawk.Client.ApiHawk case "WSWAN": return CoreSystem.WonderSwan; + case "ZXSpectrum": + return CoreSystem.ZXSpectrum; + case "VB": case "NGP": case "DNGP": @@ -205,6 +208,9 @@ namespace BizHawk.Client.ApiHawk case CoreSystem.WonderSwan: return "WSWAN"; + case CoreSystem.ZXSpectrum: + return "ZXSpectrum"; + default: throw new IndexOutOfRangeException(string.Format("{0} is missing in convert list", value.ToString())); } diff --git a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs index 12ee67277c..767d755105 100644 --- a/BizHawk.Client.ApiHawk/Classes/ClientApi.cs +++ b/BizHawk.Client.ApiHawk/Classes/ClientApi.cs @@ -427,11 +427,11 @@ namespace BizHawk.Client.ApiHawk } else { - return SystemInfo.DualGB; + return SystemInfo.DualGB; } default: - return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId)); + return SystemInfo.FindByCoreSystem(SystemIdConverter.Convert(Global.Emulator.SystemId)); } } } diff --git a/BizHawk.Client.Common/Api/CoreSystem.cs b/BizHawk.Client.Common/Api/CoreSystem.cs index 132a832b98..9bdc7d8232 100644 --- a/BizHawk.Client.Common/Api/CoreSystem.cs +++ b/BizHawk.Client.Common/Api/CoreSystem.cs @@ -29,6 +29,7 @@ WonderSwan, Libretro, VirtualBoy, - NeoGeoPocket + NeoGeoPocket, + ZXSpectrum } } diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index b592f1696b..edd287ef49 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -149,6 +149,8 @@ namespace BizHawk.Client.Common return SystemInfo.VirtualBoy; case "NGP": return SystemInfo.NeoGeoPocket; + case "ZXSpectrum": + return SystemInfo.ZXSpectrum; } } } diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 99e355081e..cd1ce95f22 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -19,6 +19,7 @@ using BizHawk.Emulation.Cores.PCEngine; using BizHawk.Emulation.Cores.Sega.Saturn; using BizHawk.Emulation.Cores.Sony.PSP; using BizHawk.Emulation.Cores.Sony.PSX; +using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; using BizHawk.Emulation.DiscSystem; using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx; @@ -657,6 +658,13 @@ namespace BizHawk.Client.Common (C64.C64Settings)GetCoreSettings(), (C64.C64SyncSettings)GetCoreSyncSettings()); break; + case "ZXSpectrum": + nextEmulator = new ZXSpectrum( + nextComm, + xmlGame.Assets.Select(a => a.Value).First(), + (ZXSpectrum.ZXSpectrumSettings)GetCoreSettings(), + (ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings()); + break; case "PSX": var entries = xmlGame.AssetFullPaths; var discs = new List(); @@ -990,6 +998,10 @@ namespace BizHawk.Client.Common var c64 = new C64(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); nextEmulator = c64; break; + case "ZXSpectrum": + var zx = new ZXSpectrum(nextComm, rom.FileData, GetCoreSettings(), GetCoreSyncSettings()); + nextEmulator = zx; + break; case "GBA": if (Global.Config.GBA_UsemGBA) { diff --git a/BizHawk.Client.Common/SystemInfo.cs b/BizHawk.Client.Common/SystemInfo.cs index 82414adc50..2d65f1729c 100644 --- a/BizHawk.Client.Common/SystemInfo.cs +++ b/BizHawk.Client.Common/SystemInfo.cs @@ -188,6 +188,11 @@ namespace BizHawk.Client.Common /// public static SystemInfo NeoGeoPocket { get; } = new SystemInfo("Neo-Geo Pocket", CoreSystem.NeoGeoPocket, 1); + /// + /// Gets the instance for ZXSpectrum + /// + public static SystemInfo ZXSpectrum { get; } = new SystemInfo("ZX Spectrum", CoreSystem.ZXSpectrum, 2); + #endregion Get SystemInfo /// diff --git a/BizHawk.Client.Common/config/PathEntry.cs b/BizHawk.Client.Common/config/PathEntry.cs index 0be34e26c7..75f5fd0bc6 100644 --- a/BizHawk.Client.Common/config/PathEntry.cs +++ b/BizHawk.Client.Common/config/PathEntry.cs @@ -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 = "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 = "Savestates", Path = Path.Combine(".", "State"), Ordinal = 2 }, new PathEntry { System = "PSX", SystemDisplayName = "Playstation", Type = "Save RAM", Path = Path.Combine(".", "SaveRAM"), Ordinal = 3 }, diff --git a/BizHawk.Client.EmuHawk/FileLoader.cs b/BizHawk.Client.EmuHawk/FileLoader.cs index 0009646fb4..db3c00d76d 100644 --- a/BizHawk.Client.EmuHawk/FileLoader.cs +++ b/BizHawk.Client.EmuHawk/FileLoader.cs @@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk 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", - ".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS" + ".EXE", ".PRG", ".D64", "*G64", ".CRT", ".TAP", ".32X", ".MDS", ".TZX" }; } diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 170dbb2322..750c70fcf8 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -2077,7 +2077,7 @@ namespace BizHawk.Client.EmuHawk if (VersionInfo.DeveloperBuild) { 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", "Disc Images", "*.cue;*.ccd;*.mds;*.m3u", "NES", "*.nes;*.fds;*.unf;*.nsf;%ARCH%", @@ -2105,6 +2105,7 @@ namespace BizHawk.Client.EmuHawk "Apple II", "*.dsk;*.do;*.po;%ARCH%", "Virtual Boy", "*.vb;%ARCH%", "Neo Geo Pocket", "*.ngp;*.ngc;%ARCH%", + "Sinclair ZX Spectrum", "*.tzx;*.tap;%ARCH%", "All Files", "*.*"); } diff --git a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index ab488b1724..9800e5ec13 100644 --- a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -176,7 +176,7 @@ namespace BizHawk.Client.EmuHawk 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[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size)); } diff --git a/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs b/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs index 5b2e5eca92..7b016a8cab 100644 --- a/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs +++ b/BizHawk.Client.EmuHawk/config/FirmwaresConfig.cs @@ -52,6 +52,7 @@ namespace BizHawk.Client.EmuHawk { "GBC", "Game Boy Color" }, { "PCFX", "PC-FX" }, { "32X", "32X" }, + { "ZXSpectrum", "ZX Spectrum" } }; public string TargetSystem = null; diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index df1a2562c7..d2d2bfccee 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using BizHawk.Common.BufferExtensions; +using System.Linq; namespace BizHawk.Emulation.Common { @@ -298,12 +299,23 @@ namespace BizHawk.Emulation.Common case ".D64": case ".T64": case ".G64": - case ".CRT": - case ".TAP": + case ".CRT": game.System = "C64"; 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 ".N64": game.System = "N64"; diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index dde7623f46..7d8eb5fe63 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -50,6 +50,9 @@ namespace BizHawk.Emulation.Common 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"); + // 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 // 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)"); diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 05baf2d9d0..63380c7fa3 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,6 +256,26 @@ + + + + + + + + + + + + + + + + + + + + Atari2600.cs @@ -1328,6 +1348,8 @@ + + @@ -1338,6 +1360,7 @@ + "$(SolutionDir)subwcrev.bat" "$(ProjectDir)" diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs new file mode 100644 index 0000000000..6a34619dcd --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs @@ -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 +{ + /// + /// 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 + /// + public class Buzzer : ISoundProvider + { + /// + /// Supplied values are right for 48K spectrum + /// These will deviate for 128k and up (as there are more T-States per frame) + /// + 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; } + + /// + /// Pulses collected during the last frame + /// + public List Pulses { get; private set; } + + /// + /// The last pulse + /// + public bool LastPulse { get; private set; } + + /// + /// The last T-State (cpu cycle) that the last pulse was received + /// + public int LastPulseTState { get; set; } + + #region Construction & Initialisation + + public Buzzer(SpectrumBase machine) + { + _machine = machine; + } + + /// + /// Initialises the buzzer + /// + public void Init() + { + _tStatesPerFrame = _machine.UlaFrameCycleCount; + Pulses = new List(1000); + } + + #endregion + + /// + /// When the pulse value from the EAR output changes it is processed here + /// + /// + /// + 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; + } + + /// + /// New frame starts + /// + public void StartFrame() + { + //DiscardSamples(); + Pulses.Clear(); + LastPulseTState = 0; + } + + /// + /// Frame is completed + /// + 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; + } + + /// + /// When the spectrum is set to receive tape input, the EAR output on the ULA is disabled + /// (so no buzzer sound is emitted) + /// + /// + 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 + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs new file mode 100644 index 0000000000..f053d044aa --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs @@ -0,0 +1,65 @@ + + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a spectrum keyboard + /// + public interface IKeyboard + { + /// + /// The calling spectrumbase class + /// + SpectrumBase _machine { get; } + + /// + /// The keyboard matrix for a particular spectrum model + /// + string[] KeyboardMatrix { get; set; } + + /// + /// For 16/48k models + /// + bool Issue2 { get; set; } + + /// + /// The current keyboard line status + /// + //byte[] LineStatus { get; set; } + + /// + /// Sets the spectrum key status + /// + /// + /// + void SetKeyStatus(string key, bool isPressed); + + /// + /// Gets the status of a spectrum key + /// + /// + /// + bool GetKeyStatus(string key); + + /// + /// Returns the query byte + /// + /// + /// + byte GetLineStatus(byte lines); + + /// + /// Reads a keyboard byte + /// + /// + /// + byte ReadKeyboardByte(ushort addr); + + /// + /// Looks up a key in the keyboard matrix and returns the relevent byte value + /// + /// + /// + byte GetByteFromKeyMatrix(string key); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs new file mode 100644 index 0000000000..829f1a0e5b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -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 + { + /// + /// Sinclair Spectrum 48K model + /// + ZXSpectrum48 + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs new file mode 100644 index 0000000000..a9eedbc44d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -0,0 +1,32 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Handles all ZX-level input + /// + 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); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs new file mode 100644 index 0000000000..d37ab3622b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -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 +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Memory * + /// + public abstract partial class SpectrumBase + { + /// + /// Byte array of total system memory (ROM + RAM + paging) + /// + public byte[] RAM { get; set; } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + 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; + } + + /// + /// Reads a byte of data from a specified memory address + /// (with no memory contention) + /// + /// + /// + public virtual byte PeekMemory(ushort addr) + { + var data = RAM[addr]; + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (without contention) + /// + /// + /// + public virtual void PokeMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + + RAM[addr] = value; + } + + /// + /// Fills memory from buffer + /// + /// + /// + public virtual void FillMemory(byte[] buffer, ushort startAddress) + { + buffer?.CopyTo(RAM, startAddress); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// + /// + /// + public virtual byte FetchScreenMemory(ushort addr) + { + var value = RAM[(addr & 0x3FFF) + 0x4000]; + return value; + } + + /// + /// 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 + /// + /// + /// + public virtual byte GetContentionValue(int cycle) + { + var val = _renderingCycleTable[cycle % UlaFrameCycleCount].ContentionDelay; + return val; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs new file mode 100644 index 0000000000..29f7c2b6eb --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -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 +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Port Access * + /// + public abstract partial class SpectrumBase + { + /// + /// The last OUT data that was sent to the ULA + /// + protected byte LastULAOutByte; + + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + 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; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + 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); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs new file mode 100644 index 0000000000..4023bf112c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -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 +{ + /// + /// 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) + /// + public abstract partial class SpectrumBase : IVideoProvider + { + #region State + + /// + /// The main screen buffer + /// + protected byte[] _frameBuffer; + + /// + /// Pixel and attribute info stored while rendering the screen + /// + 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 + + /// + /// The standard ULA palette + /// + 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 + + /// + /// The number of displayed pixels in a display row + /// + protected int DisplayWidth = 256; + + /// + /// Number of display lines + /// + protected int DisplayLines = 192; + + /// + /// The number of frames after the flash is toggled + /// + protected int FlashToggleFrames = 25; + + /// + /// Number of lines used for vertical sync + /// + protected int VerticalSyncLines = 8; + + /// + /// The number of top border lines that are not visible + /// when rendering the screen + /// + protected int NonVisibleBorderTopLines = 8; + + /// + /// The number of border lines before the display + /// + protected int BorderTopLines = 48; + + /// + /// The number of border lines after the display + /// + protected int BorderBottomLines = 48; + + /// + /// The number of bottom border lines that are not visible + /// when rendering the screen + /// + protected int NonVisibleBorderBottomLines = 8; + + /// + /// The total number of lines in the screen + /// + protected int ScreenLines; + + /// + /// The first screen line that contains the top left display pixel + /// + protected int FirstDisplayLine; + + /// + /// The last screen line that contains the bottom right display pixel + /// + protected int LastDisplayLine; + + /// + /// The number of border pixels to the left of the display + /// + protected int BorderLeftPixels = 48; + + /// + /// The number of border pixels to the right of the display + /// + protected int BorderRightPixels = 48; + + /// + /// The total width of the screen in pixels + /// + protected int ScreenWidth; + + /// + /// Horizontal blanking time (HSync+blanking). + /// Given in Z80 clock cycles. + /// + protected int HorizontalBlankingTime = 40; + + /// + /// The time of displaying left part of the border. + /// Given in Z80 clock cycles. + /// + protected int BorderLeftTime = 24; + + /// + /// The time of displaying a pixel row. + /// Given in Z80 clock cycles. + /// + protected int DisplayLineTime = 128; + + /// + /// The time of displaying right part of the border. + /// Given in Z80 clock cycles. + /// + protected int BorderRightTime = 24; + + /// + /// The time used to render the nonvisible right part of the border. + /// Given in Z80 clock cycles. + /// + protected int NonVisibleBorderRightTime = 8; + + /// + /// The time of displaying a full screen line. + /// Given in Z80 clock cycles. + /// + protected int ScreenLineTime; + + /// + /// The time the data of a particular pixel should be prefetched + /// before displaying it. + /// Given in Z80 clock cycles. + /// + protected int PixelDataPrefetchTime = 2; + + /// + /// The time the data of a particular pixel attribute should be prefetched + /// before displaying it. + /// Given in Z80 clock cycles. + /// + protected int AttributeDataPrefetchTime = 1; + + /// + /// The tact within the line that should display the first pixel. + /// Given in Z80 clock cycles. + /// + protected int FirstPixelCycleInLine; + + /// + /// The tact in which the top left pixel should be displayed. + /// Given in Z80 clock cycles. + /// + protected int FirstDisplayPixelCycle; + + /// + /// The tact in which the top left screen pixel (border) should be displayed + /// + protected int FirstScreenPixelCycle; + + /// + /// Defines the number of Z80 clock cycles used for the full rendering + /// of the screen. + /// + public int UlaFrameCycleCount; + + /// + /// The last rendered ULA cycle + /// + public int LastRenderedULACycle; + + + /// + /// This structure defines information related to a particular T-State + /// (cycle) of ULA screen rendering + /// + [StructLayout(LayoutKind.Explicit)] + public struct ScreenRenderingCycle + { + /// + /// Tha rendering phase to be applied for the particular tact + /// + [FieldOffset(0)] + public ScreenRenderingPhase Phase; + + /// + /// Display memory contention delay + /// + [FieldOffset(1)] + public byte ContentionDelay; + + /// + /// Display memory address used in the particular tact + /// + [FieldOffset(2)] + public ushort PixelByteToFetchAddress; + + /// + /// Display memory address used in the particular tact + /// + [FieldOffset(4)] + public ushort AttributeToFetchAddress; + + /// + /// Pixel X coordinate + /// + [FieldOffset(6)] + public ushort XPos; + + /// + /// Pixel Y coordinate + /// + [FieldOffset(8)] + public ushort YPos; + } + + /// + /// This enumeration defines the particular phases of ULA rendering + /// + public enum ScreenRenderingPhase : byte + { + /// + /// The ULA does not do any rendering + /// + None, + + /// + /// The ULA simple sets the border color to display the current pixel. + /// + Border, + + /// + /// 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. + /// + BorderAndFetchPixelByte, + + /// + /// 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. + /// + BorderAndFetchPixelAttribute, + + /// + /// The ULA displays the next two pixels of Byte1 sequentially during a + /// single Z80 clock cycle. + /// + DisplayByte1, + + /// + /// 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. + /// + DisplayByte1AndFetchByte2, + + /// + /// 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. + /// + DisplayByte1AndFetchAttribute2, + + /// + /// The ULA displays the next two pixels of Byte2 sequentially during a + /// single Z80 clock cycle. + /// + DisplayByte2, + + /// + /// 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. + /// + DisplayByte2AndFetchByte1, + + /// + /// 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. + /// + DisplayByte2AndFetchAttribute1 + } + + #endregion + + #region Border + + private int _borderColour; + + /// + /// Gets or sets the ULA border color + /// + public int BorderColour + { + get { return _borderColour; } + set { _borderColour = value & 0x07; } + } + + protected virtual void ResetBorder() + { + BorderColour = 0; + } + + #endregion + + #region Screen Methods + + /// + /// ULA renders the screen between two specified T-States (cycles) + /// + /// + /// + 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; + } + } + } + + /// + /// Tests whether the specified cycle is in the visible area of the screen. + /// + /// Line index + /// Tacts index within the line + /// + /// True, if the tact is visible on the screen; otherwise, false + /// + 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; + } + + /// + /// Tests whether the cycle is in the display area of the screen. + /// + /// Line index + /// Tacts index within the line + /// + /// True, if the tact is within the display area of the screen; otherwise, false. + /// + public virtual bool IsCycleInDisplayArea(int line, int cycleInLine) + { + return line >= FirstDisplayLine + && line <= LastDisplayLine + && cycleInLine >= FirstPixelCycleInLine + && cycleInLine < FirstPixelCycleInLine + DisplayLineTime; + } + + /// + /// Sets the two adjacent screen pixels belonging to the specified cycle to the given + /// color + /// + /// Color index of the first pixel + /// Color index of the second pixel + protected virtual void SetPixels(int colorIndex1, int colorIndex2) + { + var pos = _yPos * ScreenWidth + _xPos; + _frameBuffer[pos++] = (byte)colorIndex1; + _frameBuffer[pos] = (byte)colorIndex2; + } + + /// + /// Gets the color index for the specified pixel value according + /// to the given color attribute + /// + /// 0 for paper pixel, non-zero for ink pixel + /// Color attribute + /// + protected virtual int GetColor(int pixelValue, byte attr) + { + var offset = (pixelValue == 0 ? 0 : 0x100) + attr; + return _flashPhase + ? _flashOnColors[offset] + : _flashOffColors[offset]; + } + + /// + /// Resets the ULA cycle to start screen rendering from the beginning + /// + protected virtual void ResetULACycle() + { + LastRenderedULACycle = -1; + } + + /// + /// Initializes the ULA cycle table + /// + 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; + } + } + + /// + /// Calculates the pixel address for the specified line and tact within + /// the line + /// + /// Line index + /// Tacts index within the line + /// ZX spectrum screen memory address + /// + /// 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 | + /// ================================================================= + /// + 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 + } + + /// + /// Calculates the pixel attribute address for the specified line and + /// tact within the line + /// + /// Line index + /// Tacts index within the line + /// ZX spectrum screen memory address + /// + /// 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 | + /// ================================================================= + /// + 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 + + /// + /// Initialises the screen configuration calculations + /// + 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; + } + + /// + /// Inits the screen + /// + 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 + + /// + /// The longest instruction cycle count + /// + protected const int LONGEST_OP_CYCLES = 23; + + /// + /// The ULA cycle to raise the interrupt at + /// + protected int InterruptCycle = 32; + + /// + /// Signs that an interrupt has been raised in this frame. + /// + protected bool InterruptRaised; + + /// + /// Signs that the interrupt signal has been revoked + /// + protected bool InterruptRevoked; + + /// + /// Resets the interrupt - this should happen every frame in order to raise + /// the VBLANK interrupt in the proceding frame + /// + public virtual void ResetInterrupt() + { + InterruptRaised = false; + InterruptRevoked = false; + } + + /// + /// Generates an interrupt in the current phase if needed + /// + /// + 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 + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs new file mode 100644 index 0000000000..994e32df80 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs @@ -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 +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Sound * + /// + public abstract partial class SpectrumBase + { + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs new file mode 100644 index 0000000000..759f9402d1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -0,0 +1,174 @@ +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components.Z80A; +using System; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Main properties / fields / contruction* + /// + public abstract partial class SpectrumBase + { + /// + /// The calling ZXSpectrum class (piped in via constructor) + /// + protected ZXSpectrum Spectrum { get; set; } + + /// + /// Reference to the instantiated Z80 cpu (piped in via constructor) + /// + protected Z80A CPU { get; set; } + + /// + /// The spectrum buzzer/beeper + /// + public Buzzer BuzzerDevice { get; set; } + + /// + /// The spectrum keyboard + /// + public virtual IKeyboard KeyboardDevice { get; set; } + + /// + /// The spectrum datacorder device + /// + public virtual Tape TapeDevice { get; set; } + + /// + /// Signs whether the frame has ended + /// + public bool FrameCompleted; + + /// + /// Overflow from the previous frame (in Z80 cycles) + /// + public int OverFlow; + + /// + /// The total number of frames rendered + /// + public int FrameCount; + + /// + /// The current cycle (T-State) that we are at in the frame + /// + public int _frameCycles; + + /// + /// Stores where we are in the frame after each CPU cycle + /// + public int LastFrameStartCPUTick; + + /// + /// Gets the current frame cycle according to the CPU tick count + /// + public virtual int CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick; + + /// + /// Mask constants + /// + protected const int BORDER_BIT = 0x07; + protected const int EAR_BIT = 0x10; + protected const int MIC_BIT = 0x08; + protected const int TAPE_BIT = 0x40; + + /// + /// Executes a single frame + /// + 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); + } + + /// + /// Executes one cycle of the emulated machine + /// + 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) + { + + } + } + + /// + /// Hard reset of the emulated machine + /// + public virtual void HardReset() + { + ResetBorder(); + ResetInterrupt(); + + } + + /// + /// Soft reset of the emulated machine + /// + public virtual void SoftReset() + { + ResetBorder(); + ResetInterrupt(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs new file mode 100644 index 0000000000..ada3db6803 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -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 +{ + /// + /// The 48k keyboard device + /// + 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; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs new file mode 100644 index 0000000000..1b598f0474 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -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 + { + /// + /// Main constructor + /// + /// + /// + 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); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs new file mode 100644 index 0000000000..af14d5cea7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs @@ -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 +{ + /// + /// The MIC and EAR pins in the spectrum deal in on/off pulses of varying lengths + /// This struct therefore represents 1 of these pulses + /// + public struct Pulse + { + /// + /// True: High State + /// False: Low State + /// + public bool State { get; set; } + + /// + /// Pulse length in Z80 T-States (cycles) + /// + public int Length { get; set; } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs new file mode 100644 index 0000000000..222a3bb257 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs @@ -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 +{ + /// + /// Represents the tape device (or DATACORDER as AMSTRAD liked to call it) + /// + 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; + } + + + /// + /// the EAR bit read from tape + /// + /// + /// + public virtual bool GetEarBit(int cpuCycles) + { + return false; + } + + /// + /// Processes the mic bit change + /// + /// + public virtual void ProcessMicBit(bool micBit) + { + + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs new file mode 100644 index 0000000000..6d06bc54c3 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -0,0 +1,85 @@ + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZXSpectrum + { + + /// + /// The standard 48K Spectrum keyboard + /// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg + /// + 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" + } + }; + + /// + /// The newer spectrum keyboard (models 48k+, 128k) + /// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg + /// + 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" + } + }; + + /// + /// 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 + /// + 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" + } + }; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs new file mode 100644 index 0000000000..b473bac3f8 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs @@ -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 GetCpuFlagsAndRegisters() + { + return new Dictionary + { + ["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; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs new file mode 100644 index 0000000000..8b10763146 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -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; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs new file mode 100644 index 0000000000..70a3b15712 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs @@ -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; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs new file mode 100644 index 0000000000..956c5eb206 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -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 _byteArrayDomains = new Dictionary(); + private bool _memoryDomainsInit = false; + + private void SetupMemoryDomains() + { + var domains = new List + { + 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(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); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs new file mode 100644 index 0000000000..32db755068 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -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 + { + 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(); + } + } + + /// + /// The size of the Spectrum border + /// + public enum BorderType + { + Full, + Medium, + Small + } + + /// + /// The speed at which the tape is loaded + /// + public enum TapeLoadSpeed + { + Accurate, + Fast, + Fastest + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs new file mode 100644 index 0000000000..81dad0d813 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs @@ -0,0 +1,10 @@ +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZXSpectrum //: ISoundProvider + { + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs new file mode 100644 index 0000000000..e41c43b832 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -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; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs new file mode 100644 index 0000000000..750c8475d6 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -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(_tracer); + ser.Register(_cpu); + ser.Register(_machine); + ser.Register(_machine.BuzzerDevice); + + HardReset(); + + + + List romDis = new List(); + List disas = new List(); + 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 + + + } +} From 30483f30036f0a4aebd948a4d0e19836df2131cf Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 24 Nov 2017 18:43:04 +0000 Subject: [PATCH 002/105] Started tape impl. --- .../BizHawk.Emulation.Cores.csproj | 31 +- .../SinclairSpectrum/{ => Hardware}/Buzzer.cs | 0 .../Hardware/DefaultTapeProvider.cs | 135 ++ .../{ => Hardware/Interfaces}/IKeyboard.cs | 0 .../Interfaces/ISaveToTapeProvider.cs | 35 + .../Interfaces/ISupportsTapeBlockPlayback.cs | 39 + .../ISupportsTapeBlockSetPlayback.cs | 21 + .../Interfaces/ITapeContentProvider.cs | 25 + .../Hardware/Interfaces/ITapeData.cs | 24 + .../Interfaces/ITapeDataSerialization.cs | 27 + .../Hardware/Interfaces/ITapeProvider.cs | 47 + .../SinclairSpectrum/Hardware/Tape.cs | 691 ++++++++ .../Hardware/TapeBlockSetPlayer.cs | 102 ++ .../Hardware/TapeDataBlockPlayer.cs | 218 +++ .../Hardware/TapeFilePlayer.cs | 113 ++ .../Machine/SpectrumBase.Memory.cs | 120 +- .../Machine/SpectrumBase.Port.cs | 10 +- .../Machine/SpectrumBase.Screen.cs | 21 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 16 +- .../Machine/ZXSpectrum48K/ZX48.cs | 123 +- .../Media/Tape/TAP/TapDataBlock.cs | 90 ++ .../Media/Tape/TAP/TapPlayer.cs | 85 + .../Media/Tape/TAP/TapReader.cs | 52 + .../Media/Tape/TZX/BlockAbstraction.cs | 172 ++ .../Media/Tape/TZX/DataBlocks.cs | 1411 +++++++++++++++++ .../SinclairSpectrum/Media/Tape/TZX/Info.cs | 250 +++ .../SinclairSpectrum/Media/Tape/TZX/Types.cs | 282 ++++ .../Media/Tape/TZX/TzxException.cs | 28 + .../Media/Tape/TZX/TzxHeader.cs | 68 + .../Media/Tape/TZX/TzxPlayer.cs | 83 + .../Media/Tape/TZX/TzxReader.cs | 125 ++ .../Computers/SinclairSpectrum/RomData.cs | 47 + .../Computers/SinclairSpectrum/Tape.cs | 52 - .../ZXSpectrum.IMemoryDomains.cs | 2 +- .../ZXSpectrum.ISoundProvider.cs | 10 - .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 6 +- .../SinclairSpectrum/ZXSpectrum.Util.cs | 16 + .../Computers/SinclairSpectrum/ZXSpectrum.cs | 17 +- 38 files changed, 4451 insertions(+), 143 deletions(-) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/{ => Hardware}/Buzzer.cs (100%) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/{ => Hardware/Interfaces}/IKeyboard.cs (100%) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 63380c7fa3..2e488bd9e9 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,8 +256,19 @@ - - + + + + + + + + + + + + + @@ -265,8 +276,20 @@ + + + + + + + + + + + - + + @@ -274,8 +297,8 @@ - + Atari2600.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Buzzer.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs new file mode 100644 index 0000000000..f9bbf30846 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public class DefaultTapeProvider : ITapeProvider + { + public const string RESOURCE_FOLDER = "TzxResources"; + public const string DEFAULT_SAVE_FILE_DIR = @"C:\Temp\ZxSpectrumSavedFiles"; + public const string DEFAULT_NAME = "SavedFile"; + public const string DEFAULT_EXT = ".tzx"; + private string _suggestedName; + private string _fullFileName; + private int _dataBlockCount; + + private byte[] _file; + + /// + /// The directory files should be saved to + /// + public string SaveFileFolder { get; } + + + + public DefaultTapeProvider(byte[] file, string saveFolder = null) + { + SaveFileFolder = string.IsNullOrWhiteSpace(saveFolder) + ? DEFAULT_SAVE_FILE_DIR + : saveFolder; + + _file = file; + } + + /// + /// The component provider should be able to reset itself + /// + /// + /* + public override void Reset() + { + _dataBlockCount = 0; + _suggestedName = null; + _fullFileName = null; + } + */ + + /// + /// Tha tape set to load the content from + /// + public string TapeSetName { get; set; } + + /// + /// Gets a binary reader that provider TZX content + /// + /// BinaryReader instance to obtain the content from + public BinaryReader GetTapeContent() + { + Stream stream = new MemoryStream(_file); + var reader = new BinaryReader(stream); + return reader; + } + + /// + /// Creates a tape file with the specified name + /// + /// + public void CreateTapeFile() + { + //Reset(); + } + + /// + /// This method sets the name of the file according to the + /// Spectrum SAVE HEADER information + /// + /// + public void SetName(string name) + { + _suggestedName = name; + } + + /// + /// Appends the TZX block to the tape file + /// + /// + public void SaveTapeBlock(ITapeDataSerialization block) + { + if (_dataBlockCount == 0) + { + if (!Directory.Exists(SaveFileFolder)) + { + Directory.CreateDirectory(SaveFileFolder); + } + var baseFileName = $"{_suggestedName ?? DEFAULT_NAME}_{DateTime.Now:yyyyMMdd_HHmmss}{DEFAULT_EXT}"; + _fullFileName = Path.Combine(SaveFileFolder, baseFileName); + using (var writer = new BinaryWriter(File.Create(_fullFileName))) + { + var header = new TzxHeader(); + header.WriteTo(writer); + } + } + _dataBlockCount++; + + var stream = File.Open(_fullFileName, FileMode.Append); + using (var writer = new BinaryWriter(stream)) + { + block.WriteTo(writer); + } + } + + /// + /// The tape provider can finalize the tape when all + /// TZX blocks are written. + /// + public void FinalizeTapeFile() + { + } + + /// + /// Obtains the specified resource stream ot the given assembly + /// + /// Assembly to get the resource stream from + /// Resource name + private static Stream GetFileResource(Assembly asm, string resourceName) + { + var resourceFullName = $"{asm.GetName().Name}.{RESOURCE_FOLDER}.{resourceName}"; + return asm.GetManifestResourceStream(resourceFullName); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/IKeyboard.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs new file mode 100644 index 0000000000..05a4de9e06 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs @@ -0,0 +1,35 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This interface describes the behavior of an object that + /// provides tape content + /// + public interface ISaveToTapeProvider + { + /// + /// Creates a tape file with the specified name + /// + /// + void CreateTapeFile(); + + /// + /// This method sets the name of the file according to the + /// Spectrum SAVE HEADER information + /// + /// + void SetName(string name); + + /// + /// Appends the tape block to the tape file + /// + /// + void SaveTapeBlock(ITapeDataSerialization block); + + /// + /// The tape provider can finalize the tape when all + /// tape blocks are written. + /// + void FinalizeTapeFile(); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs new file mode 100644 index 0000000000..969944fa92 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This interface represents that the implementing class supports + /// emulating tape playback of a single tape block + /// + public interface ISupportsTapeBlockPlayback + { + /// + /// The current playing phase + /// + PlayPhase PlayPhase { get; } + + /// + /// The tact count of the CPU when playing starts + /// + long StartCycle { get; } + + /// + /// Initializes the player + /// + void InitPlay(long startCycle); + + /// + /// Gets the EAR bit value for the specified tact + /// + /// Tacts to retrieve the EAR bit + /// + /// The EAR bit value to play back + /// + bool GetEarBit(long currentCycle); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs new file mode 100644 index 0000000000..1312761e51 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This interface represents that the implementing class supports + /// emulating tape playback of a set of subsequent tape blocks + /// + public interface ISupportsTapeBlockSetPlayback : ISupportsTapeBlockPlayback + { + /// + /// Moves the player to the next playable block + /// + /// Tacts time to start the next block + void NextBlock(long currentCycle); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs new file mode 100644 index 0000000000..62f8c6f814 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs @@ -0,0 +1,25 @@ + +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This interface describes the behavior of an object that + /// provides tape content + /// + public interface ITapeContentProvider + { + /// + /// Tha tape set to load the content from + /// + string TapeSetName { get; set; } + + /// + /// Gets a binary reader that provides tape content + /// + /// BinaryReader instance to obtain the content from + BinaryReader GetTapeContent(); + + void Reset(); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs new file mode 100644 index 0000000000..5879c57536 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represetns the data in the tape + /// + public interface ITapeData + { + /// + /// Block Data + /// + byte[] Data { get; } + + /// + /// Pause after this block (given in milliseconds) + /// + ushort PauseAfter { get; } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs new file mode 100644 index 0000000000..9f48e9097e --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Defines the serialization operations of a TZX record + /// + public interface ITapeDataSerialization + { + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + void ReadFrom(BinaryReader reader); + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + void WriteTo(BinaryWriter writer); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs new file mode 100644 index 0000000000..d82bc3ee64 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs @@ -0,0 +1,47 @@ +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This interface describes the behavior of an object that + /// provides TZX tape content + /// + public interface ITapeProvider + { + /// + /// Tha tape set to load the content from + /// + string TapeSetName { get; set; } + + /// + /// Gets a binary reader that provider TZX content + /// + /// BinaryReader instance to obtain the content from + BinaryReader GetTapeContent(); + + /// + /// Creates a tape file with the specified name + /// + /// + void CreateTapeFile(); + + /// + /// This method sets the name of the file according to the + /// Spectrum SAVE HEADER information + /// + /// + void SetName(string name); + + /// + /// Appends the TZX block to the tape file + /// + /// + void SaveTapeBlock(ITapeDataSerialization block); + + /// + /// The tape provider can finalize the tape when all + /// TZX blocks are written. + /// + void FinalizeTapeFile(); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs new file mode 100644 index 0000000000..f493c7bda9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs @@ -0,0 +1,691 @@ +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 +{ + /* + * Much of the TAPE implementation has been taken from: https://github.com/Dotneteer/spectnetide + * + * MIT License + + Copyright (c) 2017 Istvan Novak + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + */ + + /// + /// Represents the tape device (or DATACORDER as AMSTRAD liked to call it) + /// + public class Tape + { + private SpectrumBase _machine { get; set; } + private Z80A _cpu { get; set; } + private Buzzer _buzzer { get; set; } + + private TapeOperationMode _currentMode; + private TapeFilePlayer _tapePlayer; + private bool _micBitState; + private long _lastMicBitActivityCycle; + private SavePhase _savePhase; + private int _pilotPulseCount; + private int _bitOffset; + private byte _dataByte; + private int _dataLength; + private byte[] _dataBuffer; + private int _dataBlockCount; + private MicPulseType _prevDataPulse; + + /// + /// Number of tacts after save mod can be exited automatically + /// + public const int SAVE_STOP_SILENCE = 17500000; + + /// + /// The address of the ERROR routine in the Spectrum ROM + /// + public const ushort ERROR_ROM_ADDRESS = 0x0008; + + /// + /// The maximum distance between two scans of the EAR bit + /// + public const int MAX_TACT_JUMP = 10000; + + /// + /// The width tolerance of save pulses + /// + public const int SAVE_PULSE_TOLERANCE = 24; + + /// + /// Minimum number of pilot pulses before SYNC1 + /// + public const int MIN_PILOT_PULSE_COUNT = 3000; + + /// + /// Lenght of the data buffer to allocate for the SAVE operation + /// + public const int DATA_BUFFER_LENGTH = 0x10000; + + /// + /// Gets the TZX tape content provider + /// + public ITapeContentProvider ContentProvider { get; } + + /// + /// Gets the tape Save provider + /// + public ISaveToTapeProvider SaveToTapeProvider { get; } + + /// + /// The TapeFilePlayer that can playback tape content + /// + public TapeFilePlayer TapeFilePlayer => _tapePlayer; + + /// + /// The current operation mode of the tape + /// + public TapeOperationMode CurrentMode => _currentMode; + + + public virtual void Init(SpectrumBase machine) + { + _machine = machine; + _cpu = _machine.CPU; + _buzzer = machine.BuzzerDevice; + Reset(); + } + + public Tape(ITapeContentProvider contentProvider, ISaveToTapeProvider saveToTapeProvider) + { + ContentProvider = contentProvider; + SaveToTapeProvider = saveToTapeProvider; + } + + public virtual void Reset() + { + ContentProvider?.Reset(); + _tapePlayer = null; + _currentMode = TapeOperationMode.Passive; + _savePhase = SavePhase.None; + _micBitState = true; + } + + public void CPUFrameCompleted() + { + SetTapeMode(); + if (CurrentMode == TapeOperationMode.Load + //&& HostVm.ExecuteCycleOptions.FastTapeMode + && TapeFilePlayer != null + && TapeFilePlayer.PlayPhase != PlayPhase.Completed + && _machine.Spectrum.Get16BitPC() == _machine.RomData.LoadBytesRoutineAddress) + { + /* + if (FastLoadFromTzx()) + { + FastLoadCompleted?.Invoke(this, EventArgs.Empty); + } + */ + } + } + + /// + /// Sets the current tape mode according to the current PC register + /// and the MIC bit state + /// + public void SetTapeMode() + { + switch (_currentMode) + { + case TapeOperationMode.Passive: + if (_machine.Spectrum.Get16BitPC() == _machine.RomData.LoadBytesRoutineAddress) + { + EnterLoadMode(); + } + else if (_machine.Spectrum.Get16BitPC() == _machine.RomData.SaveBytesRoutineAddress) + { + EnterSaveMode(); + } + + var res = _machine.Spectrum.Get16BitPC(); + + return; + case TapeOperationMode.Save: + if (_machine.Spectrum.Get16BitPC() == ERROR_ROM_ADDRESS + || (int)(_cpu.TotalExecutedCycles - _lastMicBitActivityCycle) > SAVE_STOP_SILENCE) + { + LeaveSaveMode(); + } + return; + case TapeOperationMode.Load: + if ((_tapePlayer?.Eof ?? false) || _machine.Spectrum.Get16BitPC() == ERROR_ROM_ADDRESS) + { + LeaveLoadMode(); + } + return; + } + } + + /// + /// Puts the device in save mode. From now on, every MIC pulse is recorded + /// + private void EnterSaveMode() + { + _currentMode = TapeOperationMode.Save; + _savePhase = SavePhase.None; + _micBitState = true; + _lastMicBitActivityCycle = _cpu.TotalExecutedCycles; + _pilotPulseCount = 0; + _prevDataPulse = MicPulseType.None; + _dataBlockCount = 0; + SaveToTapeProvider?.CreateTapeFile(); + } + + /// + /// Leaves the save mode. Stops recording MIC pulses + /// + private void LeaveSaveMode() + { + _currentMode = TapeOperationMode.Passive; + SaveToTapeProvider?.FinalizeTapeFile(); + } + + /// + /// Puts the device in load mode. From now on, EAR pulses are played by a device + /// + private void EnterLoadMode() + { + _currentMode = TapeOperationMode.Load; + + var contentReader = ContentProvider?.GetTapeContent(); + if (contentReader == null) return; + + // --- Play the content + _tapePlayer = new TapeFilePlayer(contentReader); + _tapePlayer.ReadContent(); + _tapePlayer.InitPlay(_cpu.TotalExecutedCycles); + _buzzer.SetTapeMode(true); + } + + /// + /// Leaves the load mode. Stops the device that playes EAR pulses + /// + private void LeaveLoadMode() + { + _currentMode = TapeOperationMode.Passive; + _tapePlayer = null; + ContentProvider?.Reset(); + _buzzer.SetTapeMode(false); + } + + + /// + /// the EAR bit read from tape + /// + /// + /// + public virtual bool GetEarBit(int cpuCycles) + { + if (_currentMode != TapeOperationMode.Load) + { + return true; + } + + var earBit = _tapePlayer?.GetEarBit(cpuCycles) ?? true; + _buzzer.ProcessPulseValue(true, earBit); + return earBit; + } + + /// + /// Processes the mic bit change + /// + /// + public virtual void ProcessMicBit(bool micBit) + { + if (_currentMode != TapeOperationMode.Save + || _micBitState == micBit) + { + return; + } + + var length = _cpu.TotalExecutedCycles - _lastMicBitActivityCycle; + + // --- Classify the pulse by its width + var pulse = MicPulseType.None; + if (length >= TapeDataBlockPlayer.BIT_0_PL - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.BIT_0_PL + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.Bit0; + } + else if (length >= TapeDataBlockPlayer.BIT_1_PL - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.BIT_1_PL + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.Bit1; + } + if (length >= TapeDataBlockPlayer.PILOT_PL - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.PILOT_PL + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.Pilot; + } + else if (length >= TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.SYNC_1_PL + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.Sync1; + } + else if (length >= TapeDataBlockPlayer.SYNC_2_PL - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.SYNC_2_PL + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.Sync2; + } + else if (length >= TapeDataBlockPlayer.TERM_SYNC - SAVE_PULSE_TOLERANCE + && length <= TapeDataBlockPlayer.TERM_SYNC + SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.TermSync; + } + else if (length < TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.TooShort; + } + else if (length > TapeDataBlockPlayer.PILOT_PL + 2 * SAVE_PULSE_TOLERANCE) + { + pulse = MicPulseType.TooLong; + } + + _micBitState = micBit; + _lastMicBitActivityCycle = _cpu.TotalExecutedCycles; + + // --- Lets process the pulse according to the current SAVE phase and pulse width + var nextPhase = SavePhase.Error; + switch (_savePhase) + { + case SavePhase.None: + if (pulse == MicPulseType.TooShort || pulse == MicPulseType.TooLong) + { + nextPhase = SavePhase.None; + } + else if (pulse == MicPulseType.Pilot) + { + _pilotPulseCount = 1; + nextPhase = SavePhase.Pilot; + } + break; + case SavePhase.Pilot: + if (pulse == MicPulseType.Pilot) + { + _pilotPulseCount++; + nextPhase = SavePhase.Pilot; + } + else if (pulse == MicPulseType.Sync1 && _pilotPulseCount >= MIN_PILOT_PULSE_COUNT) + { + nextPhase = SavePhase.Sync1; + } + break; + case SavePhase.Sync1: + if (pulse == MicPulseType.Sync2) + { + nextPhase = SavePhase.Sync2; + } + break; + case SavePhase.Sync2: + if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) + { + // --- Next pulse starts data, prepare for receiving it + _prevDataPulse = pulse; + nextPhase = SavePhase.Data; + _bitOffset = 0; + _dataByte = 0; + _dataLength = 0; + _dataBuffer = new byte[DATA_BUFFER_LENGTH]; + } + break; + case SavePhase.Data: + if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) + { + if (_prevDataPulse == MicPulseType.None) + { + // --- We are waiting for the second half of the bit pulse + _prevDataPulse = pulse; + nextPhase = SavePhase.Data; + } + else if (_prevDataPulse == pulse) + { + // --- We received a full valid bit pulse + nextPhase = SavePhase.Data; + _prevDataPulse = MicPulseType.None; + + // --- Add this bit to the received data + _bitOffset++; + _dataByte = (byte)(_dataByte * 2 + (pulse == MicPulseType.Bit0 ? 0 : 1)); + if (_bitOffset == 8) + { + // --- We received a full byte + _dataBuffer[_dataLength++] = _dataByte; + _dataByte = 0; + _bitOffset = 0; + } + } + } + else if (pulse == MicPulseType.TermSync) + { + // --- We received the terminating pulse, the datablock has been completed + nextPhase = SavePhase.None; + _dataBlockCount++; + + // --- Create and save the data block + var dataBlock = new TzxStandardSpeedDataBlock + { + Data = _dataBuffer, + DataLength = (ushort)_dataLength + }; + + // --- If this is the first data block, extract the name from the header + if (_dataBlockCount == 1 && _dataLength == 0x13) + { + // --- It's a header! + var sb = new StringBuilder(16); + for (var i = 2; i <= 11; i++) + { + sb.Append((char)_dataBuffer[i]); + } + var name = sb.ToString().TrimEnd(); + SaveToTapeProvider?.SetName(name); + } + SaveToTapeProvider?.SaveTapeBlock(dataBlock); + } + break; + } + _savePhase = nextPhase; + } + } + + /// + /// This enum represents the operation mode of the tape + /// + public enum TapeOperationMode : byte + { + /// + /// The tape device is passive + /// + Passive = 0, + + /// + /// The tape device is saving information (MIC pulses) + /// + Save, + + /// + /// The tape device generates EAR pulses from a player + /// + Load + } + + /// + /// This class represents a spectrum tape header + /// + public class SpectrumTapeHeader + { + private const int HEADER_LEN = 19; + private const int TYPE_OFFS = 1; + private const int NAME_OFFS = 2; + private const int NAME_LEN = 10; + private const int DATA_LEN_OFFS = 12; + private const int PAR1_OFFS = 14; + private const int PAR2_OFFS = 16; + private const int CHK_OFFS = 18; + + /// + /// The bytes of the header + /// + public byte[] HeaderBytes { get; } + + /// + /// Initializes a new instance of the class. + /// + public SpectrumTapeHeader() + { + HeaderBytes = new byte[HEADER_LEN]; + for (var i = 0; i < HEADER_LEN; i++) HeaderBytes[i] = 0x00; + CalcChecksum(); + } + + /// + /// Initializes a new instance with the specified header data. + /// + /// Header data + public SpectrumTapeHeader(byte[] header) + { + if (header == null) throw new ArgumentNullException(nameof(header)); + if (header.Length != HEADER_LEN) + { + throw new ArgumentException($"Header must be exactly {HEADER_LEN} bytes long"); + } + HeaderBytes = new byte[HEADER_LEN]; + header.CopyTo(HeaderBytes, 0); + CalcChecksum(); + } + + /// + /// Gets or sets the type of the header + /// + public byte Type + { + get { return HeaderBytes[TYPE_OFFS]; } + set + { + HeaderBytes[TYPE_OFFS] = (byte)(value & 0x03); + CalcChecksum(); + } + } + + /// + /// Gets or sets the program name + /// + public string Name + { + get + { + var name = new StringBuilder(NAME_LEN + 4); + for (var i = NAME_OFFS; i < NAME_OFFS + NAME_LEN; i++) + { + name.Append((char)HeaderBytes[i]); + } + return name.ToString().TrimEnd(); + } + set + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (value.Length > NAME_LEN) value = value.Substring(0, NAME_LEN); + else if (value.Length < NAME_LEN) value = value.PadRight(NAME_LEN, ' '); + for (var i = NAME_OFFS; i < NAME_OFFS + NAME_LEN; i++) + { + HeaderBytes[i] = (byte)value[i - NAME_OFFS]; + } + CalcChecksum(); + } + } + + /// + /// Gets or sets the Data Length + /// + public ushort DataLength + { + get { return GetWord(DATA_LEN_OFFS); } + set { SetWord(DATA_LEN_OFFS, value); } + } + + /// + /// Gets or sets Parameter1 + /// + public ushort Parameter1 + { + get { return GetWord(PAR1_OFFS); } + set { SetWord(PAR1_OFFS, value); } + } + + /// + /// Gets or sets Parameter2 + /// + public ushort Parameter2 + { + get { return GetWord(PAR2_OFFS); } + set { SetWord(PAR2_OFFS, value); } + } + + /// + /// Gets the value of checksum + /// + public byte Checksum => HeaderBytes[CHK_OFFS]; + + /// + /// Calculate the checksum + /// + private void CalcChecksum() + { + var chk = 0x00; + for (var i = 0; i < HEADER_LEN - 1; i++) chk ^= HeaderBytes[i]; + HeaderBytes[CHK_OFFS] = (byte)chk; + } + + /// + /// Gets the word value from the specified offset + /// + private ushort GetWord(int offset) => + (ushort)(HeaderBytes[offset] + 256 * HeaderBytes[offset + 1]); + + /// + /// Sets the word value at the specified offset + /// + private void SetWord(int offset, ushort value) + { + HeaderBytes[offset] = (byte)(value & 0xff); + HeaderBytes[offset + 1] = (byte)(value >> 8); + CalcChecksum(); + } + } + + /// + /// This enum defines the MIC pulse types according to their widths + /// + public enum MicPulseType : byte + { + /// + /// No pulse information + /// + None = 0, + + /// + /// Too short to be a valid pulse + /// + TooShort, + + /// + /// Too long to be a valid pulse + /// + TooLong, + + /// + /// PILOT pulse (Length: 2168 cycles) + /// + Pilot, + + /// + /// SYNC1 pulse (Length: 667 cycles) + /// + Sync1, + + /// + /// SYNC2 pulse (Length: 735 cycles) + /// + Sync2, + + /// + /// BIT0 pulse (Length: 855 cycles) + /// + Bit0, + + /// + /// BIT1 pulse (Length: 1710 cycles) + /// + Bit1, + + /// + /// TERM_SYNC pulse (Length: 947 cycles) + /// + TermSync + } + + /// + /// Represents the playing phase of the current block + /// + public enum PlayPhase + { + /// + /// The player is passive + /// + None = 0, + + /// + /// Pilot signals + /// + Pilot, + + /// + /// Sync signals at the end of the pilot + /// + Sync, + + /// + /// Bits in the data block + /// + Data, + + /// + /// Short terminating sync signal before pause + /// + TermSync, + + /// + /// Pause after the data block + /// + Pause, + + /// + /// The entire block has been played back + /// + Completed + } + + /// + /// This enumeration defines the phases of the SAVE operation + /// + public enum SavePhase : byte + { + /// No SAVE operation is in progress + None = 0, + + /// Emitting PILOT impulses + Pilot, + + /// Emitting SYNC1 impulse + Sync1, + + /// Emitting SYNC2 impulse + Sync2, + + /// Emitting BIT0/BIT1 impulses + Data, + + /// Unexpected pulse detected + Error + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs new file mode 100644 index 0000000000..372a752bf1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class is responsible to "play" a tape file. + /// + public class TapeBlockSetPlayer : ISupportsTapeBlockSetPlayback + { + /// + /// All data blocks that can be played back + /// + public List DataBlocks { get; } + + /// + /// Signs that the player completed playing back the file + /// + public bool Eof { get; private set; } + + /// + /// Gets the currently playing block's index + /// + public int CurrentBlockIndex { get; private set; } + + /// + /// The current playable block + /// + public ISupportsTapeBlockPlayback CurrentBlock => DataBlocks[CurrentBlockIndex]; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase { get; private set; } + + /// + /// The cycle count of the CPU when playing starts + /// + public long StartCycle { get; private set; } + + public TapeBlockSetPlayer(List dataBlocks) + { + DataBlocks = dataBlocks; + Eof = dataBlocks.Count == 0; + } + + /// + /// Initializes the player + /// + public void InitPlay(long startTact) + { + CurrentBlockIndex = -1; + NextBlock(startTact); + PlayPhase = PlayPhase.None; + StartCycle = startTact; + } + + /// + /// Gets the EAR bit value for the specified cycle + /// + /// Cycles to retrieve the EAR bit + /// + /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block + /// + public bool GetEarBit(long currentCycle) + { + // --- Check for EOF + if (CurrentBlockIndex == DataBlocks.Count - 1 + && (CurrentBlock.PlayPhase == PlayPhase.Pause || CurrentBlock.PlayPhase == PlayPhase.Completed)) + { + Eof = true; + } + if (CurrentBlockIndex >= DataBlocks.Count || CurrentBlock == null) + { + // --- After all playable block played back, there's nothing more to do + PlayPhase = PlayPhase.Completed; + return true; + } + var earbit = CurrentBlock.GetEarBit(currentCycle); + if (CurrentBlock.PlayPhase == PlayPhase.Completed) + { + NextBlock(currentCycle); + } + return earbit; + } + + /// + /// Moves the current block index to the next playable block + /// + /// Cycles time to start the next block + public void NextBlock(long currentCycle) + { + if (CurrentBlockIndex >= DataBlocks.Count - 1) + { + PlayPhase = PlayPhase.Completed; + Eof = true; + return; + } + CurrentBlockIndex++; + CurrentBlock.InitPlay(currentCycle); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs new file mode 100644 index 0000000000..547d7698b9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs @@ -0,0 +1,218 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents the standard speed data block in a tape file + /// + public class TapeDataBlockPlayer : ISupportsTapeBlockPlayback, ITapeData + { + /// + /// Pause after this block (default: 1000ms) + /// + public ushort PauseAfter { get; } + + /// + /// Block Data + /// + public byte[] Data { get; } + + /// + /// Initializes a new instance + /// + public TapeDataBlockPlayer(byte[] data, ushort pauseAfter) + { + PauseAfter = pauseAfter; + Data = data; + } + + /// + /// Pilot pulse length + /// + public const int PILOT_PL = 2168; + + /// + /// Pilot pulses in the ROM header block + /// + public const int HEADER_PILOT_COUNT = 8063; + + /// + /// Pilot pulses in the ROM data block + /// + public const int DATA_PILOT_COUNT = 3223; + + /// + /// Sync 1 pulse length + /// + public const int SYNC_1_PL = 667; + + /// + /// Sync 2 pulse lenth + /// + public const int SYNC_2_PL = 735; + + /// + /// Bit 0 pulse length + /// + public const int BIT_0_PL = 855; + + /// + /// Bit 1 pulse length + /// + public const int BIT_1_PL = 1710; + + /// + /// End sync pulse length + /// + public const int TERM_SYNC = 947; + + /// + /// 1 millisecond pause + /// + public const int PAUSE_MS = 3500; + + private int _pilotEnds; + private int _sync1Ends; + private int _sync2Ends; + private int _bitStarts; + private int _bitPulseLength; + private bool _currentBit; + private long _termSyncEnds; + private long _pauseEnds; + + /// + /// The index of the currently playing byte + /// + public int ByteIndex { get; private set; } + + /// + /// The mask of the currently playing bit in the current byte + /// + public byte BitMask { get; private set; } + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase { get; private set; } + + /// + /// The cycle count of the CPU when playing starts + /// + public long StartCycle { get; private set; } + + /// + /// Last cycle queried + /// + public long LastCycle { get; private set; } + + /// + /// Initializes the player + /// + public void InitPlay(long startTact) + { + PlayPhase = PlayPhase.Pilot; + StartCycle = LastCycle = startTact; + _pilotEnds = ((Data[0] & 0x80) == 0 ? HEADER_PILOT_COUNT : DATA_PILOT_COUNT) * PILOT_PL; + _sync1Ends = _pilotEnds + SYNC_1_PL; + _sync2Ends = _sync1Ends + SYNC_2_PL; + ByteIndex = 0; + BitMask = 0x80; + } + + /// + /// Gets the EAR bit value for the specified cycle + /// + /// Tacts to retrieve the EAR bit + /// + /// The EAR bit value to play back + /// + public bool GetEarBit(long currentCycle) + { + var pos = (int)(currentCycle - StartCycle); + LastCycle = currentCycle; + + if (PlayPhase == PlayPhase.Pilot || PlayPhase == PlayPhase.Sync) + { + // --- Generate the appropriate pilot or sync EAR bit + if (pos <= _pilotEnds) + { + // --- Alternating pilot pulses + return (pos / PILOT_PL) % 2 == 0; + } + if (pos <= _sync1Ends) + { + // --- 1st sync pulse + PlayPhase = PlayPhase.Sync; + return false; + } + if (pos <= _sync2Ends) + { + // --- 2nd sync pulse + PlayPhase = PlayPhase.Sync; + return true; + } + PlayPhase = PlayPhase.Data; + _bitStarts = _sync2Ends; + _currentBit = (Data[ByteIndex] & BitMask) != 0; + _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; + } + if (PlayPhase == PlayPhase.Data) + { + // --- Data block playback + // --- Generate current bit pulse + var bitPos = pos - _bitStarts; + if (bitPos < _bitPulseLength) + { + // --- First pulse of the bit + return false; + } + if (bitPos < 2 * _bitPulseLength) + { + // --- Second pulse of the bit + return true; + } + + // --- Move to the next bit, or byte + if ((BitMask >>= 1) == 0) + { + BitMask = 0x80; + ByteIndex++; + } + + // --- Prepare the next bit + if (ByteIndex < Data.Length) + { + _bitStarts += 2 * _bitPulseLength; + _currentBit = (Data[ByteIndex] & BitMask) != 0; + _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; + // --- We're in the first pulse of the next bit + return false; + } + + // --- We've played back all data bytes, send terminating pulse + PlayPhase = PlayPhase.TermSync; + _termSyncEnds = currentCycle + TERM_SYNC; + return false; + } + + if (PlayPhase == PlayPhase.TermSync) + { + if (currentCycle < _termSyncEnds) + { + return false; + } + + // --- We've played back all data, not, it's pause time + PlayPhase = PlayPhase.Pause; + _pauseEnds = currentCycle + PAUSE_MS * PauseAfter; + return true; + } + + // --- We need to produce pause signs + if (currentCycle > _pauseEnds) + { + PlayPhase = PlayPhase.Completed; + } + return true; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs new file mode 100644 index 0000000000..933899a6f5 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public class TapeFilePlayer : ISupportsTapeBlockPlayback + { + private readonly BinaryReader _reader; + private TapeBlockSetPlayer _player; + + /// + /// Data blocks to play back + /// + public List DataBlocks { get; private set; } + + /// + /// Signs that the player completed playing back the file + /// + public bool Eof => _player.Eof; + + /// + /// Initializes the player from the specified reader + /// + /// BinaryReader instance to get tape file data from + public TapeFilePlayer(BinaryReader reader) + { + _reader = reader; + } + + /// + /// Reads in the content of the tape file so that it can be played + /// + /// True, if read was successful; otherwise, false + public bool ReadContent() + { + // --- First try TzxReader + var tzxReader = new TzxReader(_reader); + var readerFound = false; + try + { + readerFound = tzxReader.ReadContent(); + } + catch (Exception) + { + // --- This exception is intentionally ingnored + } + + if (readerFound) + { + // --- This is a .TZX format + DataBlocks = tzxReader.DataBlocks.Where(b => b is ISupportsTapeBlockPlayback) + .Cast() + .ToList(); + _player = new TapeBlockSetPlayer(DataBlocks); + return true; + } + + // --- Let's assume .TAP tap format + _reader.BaseStream.Seek(0, SeekOrigin.Begin); + var tapReader = new TapReader(_reader); + readerFound = tapReader.ReadContent(); + DataBlocks = tapReader.DataBlocks.Cast() + .ToList(); + _player = new TapeBlockSetPlayer(DataBlocks); + return readerFound; + } + + /// + /// Gets the currently playing block's index + /// + public int CurrentBlockIndex => _player.CurrentBlockIndex; + + /// + /// The current playable block + /// + public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase => _player.PlayPhase; + + /// + /// The tact count of the CPU when playing starts + /// + public long StartCycle => _player.StartCycle; + + /// + /// Initializes the player + /// + public void InitPlay(long startCycle) + { + _player.InitPlay(startCycle); + } + + /// + /// Gets the EAR bit value for the specified cycle + /// + /// Tacts to retrieve the EAR bit + /// + /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block + /// + public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); + + /// + /// Moves the current block index to the next playable block + /// + /// Tacts time to start the next block + public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index d37ab3622b..a466c965d7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -13,9 +13,52 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public abstract partial class SpectrumBase { /// - /// Byte array of total system memory (ROM + RAM + paging) + /// ROM Banks + /// + public byte[] ROM0 = new byte[0x4000]; + public byte[] ROM1 = new byte[0x4000]; + public byte[] ROM2 = new byte[0x4000]; + public byte[] ROM3 = new byte[0x4000]; + + /// + /// RAM Banks /// - public byte[] RAM { get; set; } + public byte[] RAM0 = new byte[0x4000]; // Bank 0 + public byte[] RAM1 = new byte[0x4000]; // Bank 1 + public byte[] RAM2 = new byte[0x4000]; // Bank 2 + public byte[] RAM3 = new byte[0x4000]; // Bank 3 + public byte[] RAM4 = new byte[0x4000]; // Bank 4 + public byte[] RAM5 = new byte[0x4000]; // Bank 5 + public byte[] RAM6 = new byte[0x4000]; // Bank 6 + public byte[] RAM7 = new byte[0x4000]; // Bank 7 + + /// + /// Represents the addressable memory space of the spectrum + /// All banks for the emulated system should be added during initialisation + /// + public Dictionary Memory = new Dictionary(); + + /// + /// Simulates reading from the bus + /// Paging should be handled here + /// + /// + /// + public virtual byte ReadBus(ushort addr) + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// Simulates writing to the bus + /// Paging should be handled here + /// + /// + /// + public virtual void WriteBus(ushort addr, byte value) + { + throw new NotImplementedException("Must be overriden"); + } /// /// Reads a byte of data from a specified memory address @@ -25,16 +68,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// 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; + throw new NotImplementedException("Must be overriden"); } - + /* /// /// Reads a byte of data from a specified memory address /// (with no memory contention) @@ -43,9 +79,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual byte PeekMemory(ushort addr) { - var data = RAM[addr]; + var data = ReadBus(addr); return data; } + */ /// /// Writes a byte of data to a specified memory address @@ -55,44 +92,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// 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; + throw new NotImplementedException("Must be overriden"); } + /* /// /// Writes a byte of data to a specified memory address /// (without contention) @@ -106,9 +109,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Do nothing - we cannot write to ROM return; } - - RAM[addr] = value; + + WriteBus(addr, value); } + */ /// /// Fills memory from buffer @@ -117,7 +121,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual void FillMemory(byte[] buffer, ushort startAddress) { - buffer?.CopyTo(RAM, startAddress); + //buffer?.CopyTo(RAM, startAddress); + } + + /// + /// Sets up the ROM + /// + /// + /// + public virtual void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); } /// @@ -128,7 +144,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual byte FetchScreenMemory(ushort addr) { - var value = RAM[(addr & 0x3FFF) + 0x4000]; + var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; return value; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index 29f7c2b6eb..6cefe2f954 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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)); + ushort word = Convert.ToUInt16((port << 8 | high)); // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address @@ -152,7 +152,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Border - LSB 3 bits hold the border colour BorderColour = value & BORDER_BIT; - + /* // Buzzer var beepVal = (int)value & (EAR_BIT); @@ -160,13 +160,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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); + + BuzzerDevice.ProcessPulseValue(false, (value & 0x10) != 0); + TapeDevice.ProcessMicBit((value & 0x08) != 0); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 4023bf112c..bdada81b35 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -9,12 +9,27 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { + /* + * Much of the SCREEN implementation has been taken from: https://github.com/Dotneteer/spectnetide + * + * MIT License + + Copyright (c) 2017 Istvan Novak + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + */ + /// /// The 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) /// public abstract partial class SpectrumBase : IVideoProvider { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 759f9402d1..53fe905d01 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -13,12 +13,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The calling ZXSpectrum class (piped in via constructor) /// - protected ZXSpectrum Spectrum { get; set; } + public ZXSpectrum Spectrum { get; set; } /// /// Reference to the instantiated Z80 cpu (piped in via constructor) /// - protected Z80A CPU { get; set; } + public Z80A CPU { get; set; } + + /// + /// ROM and extended info + /// + public RomData RomData { get; set; } /// /// The spectrum buzzer/beeper @@ -35,6 +40,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual Tape TapeDevice { get; set; } + /// + /// The tape provider + /// + public virtual ITapeProvider TapeProvider { get; set; } + /// /// Signs whether the frame has ended /// @@ -111,7 +121,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.EndFrame(); - + TapeDevice.CPUFrameCompleted(); FrameCount++; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 1b598f0474..32bc6bf1b3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -9,17 +9,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public class ZX48 : SpectrumBase { + #region Construction + /// /// Main constructor /// /// /// - public ZX48(ZXSpectrum spectrum, Z80A cpu) + public ZX48(ZXSpectrum spectrum, Z80A cpu, byte[] file) { Spectrum = spectrum; CPU = cpu; - RAM = new byte[0x4000 + 0xC000]; + // init addressable memory from ROM and RAM banks + Memory.Add(0, ROM0); + Memory.Add(1, RAM0); + Memory.Add(2, RAM1); + Memory.Add(3, RAM2); + + //RAM = new byte[0x4000 + 0xC000]; InitScreenConfig(); InitScreen(); @@ -31,8 +39,117 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); - TapeDevice = new Tape(); + TapeProvider = new DefaultTapeProvider(file); + + TapeDevice = new Tape(null, null); TapeDevice.Init(this); } + + #endregion + + #region MemoryMapping + + /* 48K Spectrum has NO memory paging + * + * 0xffff +--------+ + | Bank 2 | + | | + | | + | | + 0xc000 +--------+ + | Bank 1 | + | | + | | + | | + 0x8000 +--------+ + | Bank 0 | + | | + | | + | screen | + 0x4000 +--------+ + | ROM 0 | + | | + | | + | | + 0x0000 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + return bank[index]; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + bank[index] = value; + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + + #endregion + + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs new file mode 100644 index 0000000000..3c4858b533 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs @@ -0,0 +1,90 @@ + +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class describes a TAP Block + /// + public sealed class TapDataBlock : + ITapeData, + ITapeDataSerialization, + ISupportsTapeBlockPlayback + { + private TapeDataBlockPlayer _player; + + /// + /// Block Data + /// + public byte[] Data { get; private set; } + + /// + /// Pause after this block (given in milliseconds) + /// + public ushort PauseAfter => 1000; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public void ReadFrom(BinaryReader reader) + { + var length = reader.ReadUInt16(); + Data = reader.ReadBytes(length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public void WriteTo(BinaryWriter writer) + { + writer.Write((ushort)Data.Length); + writer.Write(Data); + } + + /// + /// The index of the currently playing byte + /// + /// This proprty is made public for test purposes + public int ByteIndex => _player.ByteIndex; + + /// + /// The mask of the currently playing bit in the current byte + /// + public byte BitMask => _player.BitMask; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase => _player.PlayPhase; + + /// + /// The tact count of the CPU when playing starts + /// + public long StartCycle => _player.StartCycle; + + /// + /// Last tact queried + /// + public long LastCycle => _player.LastCycle; + + /// + /// Initializes the player + /// + public void InitPlay(long startTact) + { + _player = new TapeDataBlockPlayer(Data, PauseAfter); + _player.InitPlay(startTact); + } + + /// + /// Gets the EAR bit value for the specified tact + /// + /// Tacts to retrieve the EAR bit + /// + /// The EAR bit value to play back + /// + public bool GetEarBit(long currentTact) => _player.GetEarBit(currentTact); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs new file mode 100644 index 0000000000..faa7d23c02 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs @@ -0,0 +1,85 @@ + +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class is responsible to "play" a TAP file. + /// + public class TapPlayer : TapReader, ISupportsTapeBlockPlayback + { + private TapeBlockSetPlayer _player; + + /// + /// Signs that the player completed playing back the file + /// + public bool Eof => _player.Eof; + + /// + /// Initializes the player from the specified reader + /// + /// BinaryReader instance to get TZX file data from + public TapPlayer(BinaryReader reader) : base(reader) + { + } + + /// + /// Reads in the content of the TZX file so that it can be played + /// + /// True, if read was successful; otherwise, false + public override bool ReadContent() + { + var success = base.ReadContent(); + + var blocks = DataBlocks.Cast() + .ToList(); + _player = new TapeBlockSetPlayer(blocks); + return success; + } + + /// + /// Gets the currently playing block's index + /// + public int CurrentBlockIndex => _player.CurrentBlockIndex; + + /// + /// The current playable block + /// + public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase => _player.PlayPhase; + + /// + /// The tact count of the CPU when playing starts + /// + public long StartCycle => _player.StartCycle; + + /// + /// Initializes the player + /// + public void InitPlay(long startCycle) + { + _player.InitPlay(startCycle); + } + + /// + /// Gets the EAR bit value for the specified tact + /// + /// Tacts to retrieve the EAR bit + /// + /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block + /// + public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); + + /// + /// Moves the current block index to the next playable block + /// + /// Tacts time to start the next block + public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs new file mode 100644 index 0000000000..b915daf0fc --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs @@ -0,0 +1,52 @@ + +using System.Collections.Generic; +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class reads a TAP file + /// + public class TapReader + { + private readonly BinaryReader _reader; + + /// + /// Data blocks of this TZX file + /// + public IList DataBlocks { get; } + + /// + /// Initializes the player from the specified reader + /// + /// + public TapReader(BinaryReader reader) + { + _reader = reader; + DataBlocks = new List(); + } + + /// + /// Reads in the content of the TZX file so that it can be played + /// + /// True, if read was successful; otherwise, false + public virtual bool ReadContent() + { + try + { + while (_reader.BaseStream.Position != _reader.BaseStream.Length) + { + var tapBlock = new TapDataBlock(); + tapBlock.ReadFrom(_reader); + DataBlocks.Add(tapBlock); + } + return true; + } + catch + { + // --- This exception is intentionally ignored + return false; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs new file mode 100644 index 0000000000..a5145928e9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs @@ -0,0 +1,172 @@ + +using System; +using System.IO; +using System.Text; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class describes a TZX Block + /// + public abstract class TzxDataBlockBase : ITapeDataSerialization + { + /// + /// The ID of the block + /// + public abstract int BlockId { get; } + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public abstract void ReadFrom(BinaryReader reader); + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public abstract void WriteTo(BinaryWriter writer); + + /// + /// Override this method to check the content of the block + /// + public virtual bool IsValid => true; + + /// + /// Reads the specified number of words from the reader. + /// + /// Reader to obtain the input from + /// Number of words to get + /// Word array read from the input + public static ushort[] ReadWords(BinaryReader reader, int count) + { + var result = new ushort[count]; + var bytes = reader.ReadBytes(2 * count); + for (var i = 0; i < count; i++) + { + result[i] = (ushort)(bytes[i * 2] + bytes[i * 2 + 1] << 8); + } + return result; + } + + /// + /// Writes the specified array of words to the writer + /// + /// Output + /// Word array + public static void WriteWords(BinaryWriter writer, ushort[] words) + { + foreach (var word in words) + { + writer.Write(word); + } + } + + /// + /// Converts the provided bytes to an ASCII string + /// + /// Bytes to convert + /// First byte offset + /// Number of bytes + /// ASCII string representation + public static string ToAsciiString(byte[] bytes, int offset = 0, int count = -1) + { + if (count < 0) count = bytes.Length - offset; + var sb = new StringBuilder(); + for (var i = offset; i < count; i++) + { + sb.Append(Convert.ToChar(bytes[i])); + } + return sb.ToString(); + } + } + + /// + /// Base class for all TZX block type with data length of 3 bytes + /// + public abstract class Tzx3ByteDataBlockBase : TzxDataBlockBase + { + /// + /// Used bits in the last byte (other bits should be 0) + /// + /// + /// (e.g. if this is 6, then the bits used(x) in the last byte are: + /// xxxxxx00, where MSb is the leftmost bit, LSb is the rightmost bit) + /// + public byte LastByteUsedBits { get; set; } + + /// + /// Lenght of block data + /// + public byte[] DataLength { get; set; } + + /// + /// Block Data + /// + public byte[] Data { get; set; } + + /// + /// Override this method to check the content of the block + /// + public override bool IsValid => GetLength() == Data.Length; + + /// + /// Calculates data length + /// + protected int GetLength() + { + return DataLength[0] + DataLength[1] << 8 + DataLength[2] << 16; + } + } + + /// + /// This class represents a TZX data block with empty body + /// + public abstract class TzxBodylessDataBlockBase : TzxDataBlockBase + { + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + } + } + + /// + /// This class represents a deprecated block + /// + public abstract class TzxDeprecatedDataBlockBase : TzxDataBlockBase + { + /// + /// Reads through the block infromation, and does not store it + /// + /// Stream to read the block from + public abstract void ReadThrough(BinaryReader reader); + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + throw new InvalidOperationException("Deprecated TZX data blocks cannot be written."); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs new file mode 100644 index 0000000000..18ce828a09 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs @@ -0,0 +1,1411 @@ + +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxArchiveInfoDataBlock : Tzx3ByteDataBlockBase + { + /// + /// Length of the whole block (without these two bytes) + /// + public ushort Length { get; set; } + + /// + /// Number of text strings + /// + public byte StringCount { get; set; } + + /// + /// List of text strings + /// + public TzxText[] TextStrings { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x32; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Length = reader.ReadUInt16(); + StringCount = reader.ReadByte(); + TextStrings = new TzxText[StringCount]; + for (var i = 0; i < StringCount; i++) + { + var text = new TzxText(); + text.ReadFrom(reader); + TextStrings[i] = text; + } + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Length); + writer.Write(StringCount); + foreach (var text in TextStrings) + { + text.WriteTo(writer); + } + } + } + + /// + /// This block was created to support the Commodore 64 standard + /// ROM and similar tape blocks. + /// + public class TzxC64RomTypeDataBlock : TzxDeprecatedDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x16; + + /// + /// Reads through the block infromation, and does not store it + /// + /// Stream to read the block from + public override void ReadThrough(BinaryReader reader) + { + var length = reader.ReadInt32(); + reader.ReadBytes(length - 4); + } + } + + /// + /// This block is made to support another type of encoding that is + /// commonly used by the C64. + /// + public class TzxC64TurboTapeDataBlock : TzxDeprecatedDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x17; + + /// + /// Reads through the block infromation, and does not store it + /// + /// Stream to read the block from + public override void ReadThrough(BinaryReader reader) + { + var length = reader.ReadInt32(); + reader.ReadBytes(length - 4); + } + } + + /// + /// This block is an analogue of the CALL Subroutine statement. + /// + /// + /// It basically executes a sequence of blocks that are somewhere + /// else and then goes back to the next block. Because more than + /// one call can be normally used you can include a list of sequences + /// to be called. The 'nesting' of call blocks is also not allowed + /// for the simplicity reasons. You can, of course, use the CALL + /// blocks in the LOOP sequences and vice versa. + /// + public class TzxCallSequenceDataBlock : TzxDataBlockBase + { + /// + /// Number of group name + /// + public byte NumberOfCalls { get; set; } + + /// + /// Group name bytes + /// + public ushort[] BlockOffsets { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x26; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + NumberOfCalls = reader.ReadByte(); + BlockOffsets = ReadWords(reader, NumberOfCalls); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(NumberOfCalls); + WriteWords(writer, BlockOffsets); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxCswRecordingDataBlock : TzxDataBlockBase + { + /// + /// Block length (without these four bytes) + /// + public uint BlockLength { get; set; } + + /// + /// Pause after this block + /// + public ushort PauseAfter { get; set; } + + /// + /// Sampling rate + /// + public byte[] SamplingRate { get; set; } + + /// + /// Compression type + /// + /// + /// 0x01=RLE, 0x02=Z-RLE + /// + public byte CompressionType { get; set; } + + /// + /// Number of stored pulses (after decompression, for validation purposes) + /// + public uint PulseCount { get; set; } + + /// + /// CSW data, encoded according to the CSW file format specification + /// + public byte[] Data { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x18; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + BlockLength = reader.ReadUInt32(); + PauseAfter = reader.ReadUInt16(); + SamplingRate = reader.ReadBytes(3); + CompressionType = reader.ReadByte(); + PulseCount = reader.ReadUInt32(); + var length = (int)BlockLength - 4 /* PauseAfter*/ - 3 /* SamplingRate */ + - 1 /* CompressionType */ - 4 /* PulseCount */; + Data = reader.ReadBytes(length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(BlockLength); + writer.Write(PauseAfter); + writer.Write(SamplingRate); + writer.Write(CompressionType); + writer.Write(PulseCount); + writer.Write(Data); + } + + /// + /// Override this method to check the content of the block + /// + public override bool IsValid => BlockLength == 4 + 3 + 1 + 4 + Data.Length; + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxCustomInfoDataBlock : Tzx3ByteDataBlockBase + { + /// + /// Identification string (in ASCII) + /// + public byte[] Id { get; set; } + + /// + /// String representation of the ID + /// + public string IdText => ToAsciiString(Id); + + /// + /// Length of the custom info + /// + public uint Length { get; set; } + + /// + /// Custom information + /// + public byte[] CustomInfo { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x35; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Id = reader.ReadBytes(10); + Length = reader.ReadUInt32(); + CustomInfo = reader.ReadBytes((int)Length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Id); + writer.Write(Length); + writer.Write(CustomInfo); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxDirectRecordingDataBlock : Tzx3ByteDataBlockBase + { + /// + /// Number of T-states per sample (bit of data) + /// + public ushort TactsPerSample { get; set; } + + /// + /// Pause after this block + /// + public ushort PauseAfter { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x15; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + TactsPerSample = reader.ReadUInt16(); + PauseAfter = reader.ReadUInt16(); + LastByteUsedBits = reader.ReadByte(); + DataLength = reader.ReadBytes(3); + Data = reader.ReadBytes(GetLength()); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(TactsPerSample); + writer.Write(PauseAfter); + writer.Write(LastByteUsedBits); + writer.Write(DataLength); + writer.Write(Data); + } + } + + /// + /// This is a special block that would normally be generated only by emulators. + /// + public class TzxEmulationInfoDataBlock : TzxDeprecatedDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x34; + + /// + /// Reads through the block infromation, and does not store it + /// + /// Stream to read the block from + public override void ReadThrough(BinaryReader reader) + { + reader.ReadBytes(8); + } + } + + /// + /// Represents a generalized data block in a TZX file + /// + public class TzxGeneralizedDataBlock : TzxDataBlockBase + { + /// + /// Block length (without these four bytes) + /// + public uint BlockLength { get; set; } + + /// + /// Pause after this block + /// + public ushort PauseAfter { get; set; } + + /// + /// Total number of symbols in pilot/sync block (can be 0) + /// + public uint Totp { get; set; } + + /// + /// Maximum number of pulses per pilot/sync symbol + /// + public byte Npp { get; set; } + + /// + /// Number of pilot/sync symbols in the alphabet table (0=256) + /// + public byte Asp { get; set; } + + /// + /// Total number of symbols in data stream (can be 0) + /// + public uint Totd { get; set; } + + /// + /// Maximum number of pulses per data symbol + /// + public byte Npd { get; set; } + + /// + /// Number of data symbols in the alphabet table (0=256) + /// + public byte Asd { get; set; } + + /// + /// Pilot and sync symbols definition table + /// + /// + /// This field is present only if Totp > 0 + /// + public TzxSymDef[] PilotSymDef { get; set; } + + /// + /// Pilot and sync data stream + /// + /// + /// This field is present only if Totd > 0 + /// + public TzxPrle[] PilotStream { get; set; } + + /// + /// Data symbols definition table + /// + /// + /// This field is present only if Totp > 0 + /// + public TzxSymDef[] DataSymDef { get; set; } + + /// + /// Data stream + /// + /// + /// This field is present only if Totd > 0 + /// + public TzxPrle[] DataStream { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x19; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + BlockLength = reader.ReadUInt32(); + PauseAfter = reader.ReadUInt16(); + Totp = reader.ReadUInt32(); + Npp = reader.ReadByte(); + Asp = reader.ReadByte(); + Totd = reader.ReadUInt32(); + Npd = reader.ReadByte(); + Asd = reader.ReadByte(); + + PilotSymDef = new TzxSymDef[Asp]; + for (var i = 0; i < Asp; i++) + { + var symDef = new TzxSymDef(Npp); + symDef.ReadFrom(reader); + PilotSymDef[i] = symDef; + } + + PilotStream = new TzxPrle[Totp]; + for (var i = 0; i < Totp; i++) + { + PilotStream[i].Symbol = reader.ReadByte(); + PilotStream[i].Repetitions = reader.ReadUInt16(); + } + + DataSymDef = new TzxSymDef[Asd]; + for (var i = 0; i < Asd; i++) + { + var symDef = new TzxSymDef(Npd); + symDef.ReadFrom(reader); + DataSymDef[i] = symDef; + } + + DataStream = new TzxPrle[Totd]; + for (var i = 0; i < Totd; i++) + { + DataStream[i].Symbol = reader.ReadByte(); + DataStream[i].Repetitions = reader.ReadUInt16(); + } + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(BlockLength); + writer.Write(PauseAfter); + writer.Write(Totp); + writer.Write(Npp); + writer.Write(Asp); + writer.Write(Totd); + writer.Write(Npd); + writer.Write(Asd); + for (var i = 0; i < Asp; i++) + { + PilotSymDef[i].WriteTo(writer); + } + for (var i = 0; i < Totp; i++) + { + writer.Write(PilotStream[i].Symbol); + writer.Write(PilotStream[i].Repetitions); + } + + for (var i = 0; i < Asd; i++) + { + DataSymDef[i].WriteTo(writer); + } + + for (var i = 0; i < Totd; i++) + { + writer.Write(DataStream[i].Symbol); + writer.Write(DataStream[i].Repetitions); + } + } + } + + /// + /// This block is generated when you merge two ZX Tape files together. + /// + /// + /// It is here so that you can easily copy the files together and use + /// them. Of course, this means that resulting file would be 10 bytes + /// longer than if this block was not used. All you have to do if + /// you encounter this block ID is to skip next 9 bytes. + /// + public class TzxGlueDataBlock : TzxDataBlockBase + { + /// + /// Value: { "XTape!", 0x1A, MajorVersion, MinorVersion } + /// + /// + /// Just skip these 9 bytes and you will end up on the next ID. + /// + public byte[] Glue { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x5A; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Glue = reader.ReadBytes(9); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Glue); + } + } + + /// + /// This indicates the end of a group. This block has no body. + /// + public class TzxGroupEndDataBlock : TzxBodylessDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x22; + } + + /// + /// This block marks the start of a group of blocks which are + /// to be treated as one single (composite) block. + /// + public class TzxGroupStartDataBlock : TzxDataBlockBase + { + /// + /// Number of group name + /// + public byte Length { get; set; } + + /// + /// Group name bytes + /// + public byte[] Chars { get; set; } + + /// + /// Gets the group name + /// + public string GroupName => ToAsciiString(Chars); + + /// + /// The ID of the block + /// + public override int BlockId => 0x21; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Length = reader.ReadByte(); + Chars = reader.ReadBytes(Length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Length); + writer.Write(Chars); + } + } + + /// + /// + /// + public class TzxHardwareInfoDataBlock : TzxDataBlockBase + { + /// + /// Number of machines and hardware types for which info is supplied + /// + public byte HwCount { get; set; } + + /// + /// List of machines and hardware + /// + public TzxHwInfo[] HwInfo { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x33; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + HwCount = reader.ReadByte(); + HwInfo = new TzxHwInfo[HwCount]; + for (var i = 0; i < HwCount; i++) + { + var hw = new TzxHwInfo(); + hw.ReadFrom(reader); + HwInfo[i] = hw; + } + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(HwCount); + foreach (var hw in HwInfo) + { + hw.WriteTo(writer); + } + } + } + + /// + /// This block will enable you to jump from one block to another within the file. + /// + /// + /// Jump 0 = 'Loop Forever' - this should never happen + /// Jump 1 = 'Go to the next block' - it is like NOP in assembler + /// Jump 2 = 'Skip one block' + /// Jump -1 = 'Go to the previous block' + /// + public class TzxJumpDataBlock : TzxDataBlockBase + { + /// + /// Relative jump value + /// + /// + /// + public short Jump { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x23; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Jump = reader.ReadInt16(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Jump); + } + } + + /// + /// It means that the utility should jump back to the start + /// of the loop if it hasn't been run for the specified number + /// of times. + /// + public class TzxLoopEndDataBlock : TzxBodylessDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x25; + } + + /// + /// If you have a sequence of identical blocks, or of identical + /// groups of blocks, you can use this block to tell how many + /// times they should be repeated. + /// + public class TzxLoopStartDataBlock : TzxDataBlockBase + { + /// + /// Number of repetitions (greater than 1) + /// + public ushort Loops { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x24; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Loops = reader.ReadUInt16(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Loops); + } + } + + /// + /// This will enable the emulators to display a message for a given time. + /// + /// + /// This should not stop the tape and it should not make silence. If the + /// time is 0 then the emulator should wait for the user to press a key. + /// + public class TzxMessageDataBlock : TzxDataBlockBase + { + /// + /// Time (in seconds) for which the message should be displayed + /// + public byte Time { get; set; } + + /// + /// Length of the description + /// + public byte MessageLength { get; set; } + + /// + /// The description bytes + /// + public byte[] Message; + + /// + /// The string form of description + /// + public string MessageText => ToAsciiString(Message); + + /// + /// The ID of the block + /// + public override int BlockId => 0x31; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Time = reader.ReadByte(); + MessageLength = reader.ReadByte(); + Message = reader.ReadBytes(MessageLength); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Time); + writer.Write(MessageLength); + writer.Write(Message); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxPulseSequenceDataBlock : TzxDataBlockBase + { + /// + /// Pause after this block + /// + public byte PulseCount { get; set; } + + /// + /// Lenght of block data + /// + public ushort[] PulseLengths { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x13; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + PulseCount = reader.ReadByte(); + PulseLengths = ReadWords(reader, PulseCount); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(PulseCount); + WriteWords(writer, PulseLengths); + } + + /// + /// Override this method to check the content of the block + /// + public override bool IsValid => PulseCount == PulseLengths.Length; + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxPureDataBlock : Tzx3ByteDataBlockBase + { + /// + /// Length of the zero bit + /// + public ushort ZeroBitPulseLength { get; set; } + + /// + /// Length of the one bit + /// + public ushort OneBitPulseLength { get; set; } + + /// + /// Pause after this block + /// + public ushort PauseAfter { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x14; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + ZeroBitPulseLength = reader.ReadUInt16(); + OneBitPulseLength = reader.ReadUInt16(); + LastByteUsedBits = reader.ReadByte(); + PauseAfter = reader.ReadUInt16(); + DataLength = reader.ReadBytes(3); + Data = reader.ReadBytes(GetLength()); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(ZeroBitPulseLength); + writer.Write(OneBitPulseLength); + writer.Write(LastByteUsedBits); + writer.Write(PauseAfter); + writer.Write(DataLength); + writer.Write(Data); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxPureToneDataBlock : TzxDataBlockBase + { + /// + /// Pause after this block + /// + public ushort PulseLength { get; private set; } + + /// + /// Lenght of block data + /// + public ushort PulseCount { get; private set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x12; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + PulseLength = reader.ReadUInt16(); + PulseCount = reader.ReadUInt16(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(PulseLength); + writer.Write(PulseCount); + } + } + + /// + /// This block indicates the end of the Called Sequence. + /// The next block played will be the block after the last + /// CALL block + /// + public class TzxReturnFromSequenceDataBlock : TzxBodylessDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x27; + } + + /// + /// Pause (silence) or 'Stop the Tape' block + /// + public class TzxSelectDataBlock : TzxDataBlockBase + { + /// + /// Length of the whole block (without these two bytes) + /// + public ushort Length { get; set; } + + /// + /// Number of selections + /// + public byte SelectionCount { get; set; } + + /// + /// List of selections + /// + public TzxSelect[] Selections { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x28; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Length = reader.ReadUInt16(); + SelectionCount = reader.ReadByte(); + Selections = new TzxSelect[SelectionCount]; + foreach (var selection in Selections) + { + selection.ReadFrom(reader); + } + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Length); + writer.Write(SelectionCount); + foreach (var selection in Selections) + { + selection.WriteTo(writer); + } + } + } + + /// + /// This block sets the current signal level to the specified value (high or low). + /// + public class TzxSetSignalLevelDataBlock : TzxDataBlockBase + { + /// + /// Length of the block without these four bytes + /// + public uint Lenght { get; } = 1; + + /// + /// Signal level (0=low, 1=high) + /// + public byte SignalLevel { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x2B; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + reader.ReadUInt32(); + SignalLevel = reader.ReadByte(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Lenght); + writer.Write(SignalLevel); + } + } + + /// + /// Pause (silence) or 'Stop the Tape' block + /// + public class TzxSilenceDataBlock : TzxDataBlockBase + { + /// + /// Duration of silence + /// + /// + /// This will make a silence (low amplitude level (0)) for a given time + /// in milliseconds. If the value is 0 then the emulator or utility should + /// (in effect) STOP THE TAPE, i.e. should not continue loading until + /// the user or emulator requests it. + /// + public ushort Duration { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x20; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Duration = reader.ReadUInt16(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Duration); + } + } + + /// + /// This block was created to support the Commodore 64 standard + /// ROM and similar tape blocks. + /// + public class TzxSnapshotBlock : TzxDeprecatedDataBlockBase + { + /// + /// The ID of the block + /// + public override int BlockId => 0x40; + + /// + /// Reads through the block infromation, and does not store it + /// + /// Stream to read the block from + public override void ReadThrough(BinaryReader reader) + { + var length = reader.ReadInt32(); + length = length & 0x00FFFFFF; + reader.ReadBytes(length); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxStandardSpeedDataBlock : TzxDataBlockBase, ISupportsTapeBlockPlayback, ITapeData + { + private TapeDataBlockPlayer _player; + + /// + /// Pause after this block (default: 1000ms) + /// + public ushort PauseAfter { get; set; } = 1000; + + /// + /// Lenght of block data + /// + public ushort DataLength { get; set; } + + /// + /// Block Data + /// + public byte[] Data { get; set; } + + /// + /// The ID of the block + /// + public override int BlockId => 0x10; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + PauseAfter = reader.ReadUInt16(); + DataLength = reader.ReadUInt16(); + Data = reader.ReadBytes(DataLength); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write((byte)BlockId); + writer.Write(PauseAfter); + writer.Write(DataLength); + writer.Write(Data, 0, DataLength); + } + + /// + /// The index of the currently playing byte + /// + /// This proprty is made public for test purposes + public int ByteIndex => _player.ByteIndex; + + /// + /// The mask of the currently playing bit in the current byte + /// + public byte BitMask => _player.BitMask; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase => _player.PlayPhase; + + /// + /// The tact count of the CPU when playing starts + /// + public long StartCycle=> _player.StartCycle; + + /// + /// Last tact queried + /// + public long LastTact => _player.LastCycle; + + /// + /// Initializes the player + /// + public void InitPlay(long startCycle) + { + _player = new TapeDataBlockPlayer(Data, PauseAfter); + _player.InitPlay(startCycle); + } + + /// + /// Gets the EAR bit value for the specified tact + /// + /// Tacts to retrieve the EAR bit + /// + /// The EAR bit value to play back + /// + public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); + } + + /// + /// When this block is encountered, the tape will stop ONLY if + /// the machine is an 48K Spectrum. + /// + /// + /// This block is to be used for multiloading games that load one + /// level at a time in 48K mode, but load the entire tape at once + /// if in 128K mode. This block has no body of its own, but follows + /// the extension rule. + /// + public class TzxStopTheTape48DataBlock : TzxDataBlockBase + { + /// + /// Length of the block without these four bytes (0) + /// + public uint Lenght { get; } = 0; + + /// + /// The ID of the block + /// + public override int BlockId => 0x2A; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + reader.ReadUInt32(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Lenght); + } + } + + /// + /// This is meant to identify parts of the tape, so you know where level 1 starts, + /// where to rewind to when the game ends, etc. + /// + /// + /// This description is not guaranteed to be shown while the tape is playing, + /// but can be read while browsing the tape or changing the tape pointer. + /// + public class TzxTextDescriptionDataBlock : TzxDataBlockBase + { + /// + /// Length of the description + /// + public byte DescriptionLength { get; set; } + + /// + /// The description bytes + /// + public byte[] Description; + + /// + /// The string form of description + /// + public string DescriptionText => ToAsciiString(Description); + + /// + /// The ID of the block + /// + public override int BlockId => 0x30; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + DescriptionLength = reader.ReadByte(); + Description = reader.ReadBytes(DescriptionLength); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(DescriptionLength); + writer.Write(Description); + } + } + + /// + /// Represents the standard speed data block in a TZX file + /// + public class TzxTurboSpeedDataBlock : Tzx3ByteDataBlockBase + { + /// + /// Length of pilot pulse + /// + public ushort PilotPulseLength { get; set; } + + /// + /// Length of the first sync pulse + /// + public ushort Sync1PulseLength { get; set; } + + /// + /// Length of the second sync pulse + /// + public ushort Sync2PulseLength { get; set; } + + /// + /// Length of the zero bit + /// + public ushort ZeroBitPulseLength { get; set; } + + /// + /// Length of the one bit + /// + public ushort OneBitPulseLength { get; set; } + + /// + /// Length of the pilot tone + /// + public ushort PilotToneLength { get; set; } + + /// + /// Pause after this block + /// + public ushort PauseAfter { get; set; } + + public TzxTurboSpeedDataBlock() + { + PilotPulseLength = 2168; + Sync1PulseLength = 667; + Sync2PulseLength = 735; + ZeroBitPulseLength = 855; + OneBitPulseLength = 1710; + PilotToneLength = 8063; + LastByteUsedBits = 8; + } + + /// + /// The ID of the block + /// + public override int BlockId => 0x11; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + PilotPulseLength = reader.ReadUInt16(); + Sync1PulseLength = reader.ReadUInt16(); + Sync2PulseLength = reader.ReadUInt16(); + ZeroBitPulseLength = reader.ReadUInt16(); + OneBitPulseLength = reader.ReadUInt16(); + PilotToneLength = reader.ReadUInt16(); + LastByteUsedBits = reader.ReadByte(); + PauseAfter = reader.ReadUInt16(); + DataLength = reader.ReadBytes(3); + Data = reader.ReadBytes(GetLength()); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(PilotPulseLength); + writer.Write(Sync1PulseLength); + writer.Write(Sync2PulseLength); + writer.Write(ZeroBitPulseLength); + writer.Write(OneBitPulseLength); + writer.Write(PilotToneLength); + writer.Write(LastByteUsedBits); + writer.Write(PauseAfter); + writer.Write(DataLength); + writer.Write(Data); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs new file mode 100644 index 0000000000..0a83aa3bfc --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs @@ -0,0 +1,250 @@ + +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This blocks contains information about the hardware that the programs on this tape use. + /// + public class TzxHwInfo : ITapeDataSerialization + { + /// + /// Hardware type + /// + public byte HwType { get; set; } + + /// + /// Hardwer Id + /// + public byte HwId { get; set; } + + /// + /// Information about the tape + /// + /// + /// 00 - The tape RUNS on this machine or with this hardware, + /// but may or may not use the hardware or special features of the machine. + /// 01 - The tape USES the hardware or special features of the machine, + /// such as extra memory or a sound chip. + /// 02 - The tape RUNS but it DOESN'T use the hardware + /// or special features of the machine. + /// 03 - The tape DOESN'T RUN on this machine or with this hardware. + /// + public byte TapeInfo; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public void ReadFrom(BinaryReader reader) + { + HwType = reader.ReadByte(); + HwId = reader.ReadByte(); + TapeInfo = reader.ReadByte(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public void WriteTo(BinaryWriter writer) + { + writer.Write(HwType); + writer.Write(HwId); + writer.Write(TapeInfo); + } + } + + /// + /// Symbol repetitions + /// + public struct TzxPrle + { + /// + /// Symbol represented + /// + public byte Symbol; + + /// + /// Number of repetitions + /// + public ushort Repetitions; + } + + /// + /// This block represents an extremely wide range of data encoding techniques. + /// + /// + /// The basic idea is that each loading component (pilot tone, sync pulses, data) + /// is associated to a specific sequence of pulses, where each sequence (wave) can + /// contain a different number of pulses from the others. In this way we can have + /// a situation where bit 0 is represented with 4 pulses and bit 1 with 8 pulses. + /// + public class TzxSymDef : ITapeDataSerialization + { + /// + /// Bit 0 - Bit 1: Starting symbol polarity + /// + /// + /// 00: opposite to the current level (make an edge, as usual) - default + /// 01: same as the current level(no edge - prolongs the previous pulse) + /// 10: force low level + /// 11: force high level + /// + public byte SymbolFlags; + + /// + /// The array of pulse lengths + /// + public ushort[] PulseLengths; + + public TzxSymDef(byte maxPulses) + { + PulseLengths = new ushort[maxPulses]; + } + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public void ReadFrom(BinaryReader reader) + { + SymbolFlags = reader.ReadByte(); + PulseLengths = TzxDataBlockBase.ReadWords(reader, PulseLengths.Length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public void WriteTo(BinaryWriter writer) + { + writer.Write(SymbolFlags); + TzxDataBlockBase.WriteWords(writer, PulseLengths); + } + } + + /// + /// This is meant to identify parts of the tape, so you know where level 1 starts, + /// where to rewind to when the game ends, etc. + /// + /// + /// This description is not guaranteed to be shown while the tape is playing, + /// but can be read while browsing the tape or changing the tape pointer. + /// + public class TzxText : ITapeDataSerialization + { + /// + /// Text identification byte. + /// + /// + /// 00 - Full title + /// 01 - Software house/publisher + /// 02 - Author(s) + /// 03 - Year of publication + /// 04 - Language + /// 05 - Game/utility type + /// 06 - Price + /// 07 - Protection scheme/loader + /// 08 - Origin + /// FF - Comment(s) + /// + public byte Type { get; set; } + + /// + /// Length of the description + /// + public byte Length { get; set; } + + /// + /// The description bytes + /// + public byte[] TextBytes; + + /// + /// The string form of description + /// + public string Text => TzxDataBlockBase.ToAsciiString(TextBytes); + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public void ReadFrom(BinaryReader reader) + { + Type = reader.ReadByte(); + Length = reader.ReadByte(); + TextBytes = reader.ReadBytes(Length); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public void WriteTo(BinaryWriter writer) + { + writer.Write(Type); + writer.Write(Length); + writer.Write(TextBytes); + } + } + + /// + /// This block represents select structure + /// + public class TzxSelect : ITapeDataSerialization + { + /// + /// Bit 0 - Bit 1: Starting symbol polarity + /// + /// + /// 00: opposite to the current level (make an edge, as usual) - default + /// 01: same as the current level(no edge - prolongs the previous pulse) + /// 10: force low level + /// 11: force high level + /// + public ushort BlockOffset; + + /// + /// Length of the description + /// + public byte DescriptionLength { get; set; } + + /// + /// The description bytes + /// + public byte[] Description; + + /// + /// The string form of description + /// + public string DescriptionText => TzxDataBlockBase.ToAsciiString(Description); + + public TzxSelect(byte length) + { + DescriptionLength = length; + } + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public void ReadFrom(BinaryReader reader) + { + BlockOffset = reader.ReadUInt16(); + DescriptionLength = reader.ReadByte(); + Description = reader.ReadBytes(DescriptionLength); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public void WriteTo(BinaryWriter writer) + { + writer.Write(BlockOffset); + writer.Write(DescriptionLength); + writer.Write(Description); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs new file mode 100644 index 0000000000..8742a35679 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs @@ -0,0 +1,282 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Identified AD or DA converter types + /// + public enum TzxAdOrDaConverterType : byte + { + HarleySystemsAdc8P2 = 0x00, + BlackboardElectronics = 0x01 + } + + /// + /// Identified computer types + /// + public enum TzxComputerType : byte + { + ZxSpectrum16 = 0x00, + ZxSpectrum48OrPlus = 0x01, + ZxSpectrum48Issue1 = 0x02, + ZxSpectrum128 = 0x03, + ZxSpectrum128P2 = 0x04, + ZxSpectrum128P2AOr3 = 0x05, + Tc2048 = 0x06, + Ts2068 = 0x07, + Pentagon128 = 0x08, + SamCoupe = 0x09, + DidaktikM = 0x0A, + DidaktikGama = 0x0B, + Zx80 = 0x0C, + Zx81 = 0x0D, + ZxSpectrum128Spanish = 0x0E, + ZxSpectrumArabic = 0x0F, + Tk90X = 0x10, + Tk95 = 0x11, + Byte = 0x12, + Elwro800D3 = 0x13, + ZsScorpion256 = 0x14, + AmstradCpc464 = 0x15, + AmstradCpc664 = 0x16, + AmstradCpc6128 = 0x17, + AmstradCpc464P = 0x18, + AmstradCpc6128P = 0x19, + JupiterAce = 0x1A, + Enterprise = 0x1B, + Commodore64 = 0x1C, + Commodore128 = 0x1D, + InvesSpectrumP = 0x1E, + Profi = 0x1F, + GrandRomMax = 0x20, + Kay1024 = 0x21, + IceFelixHc91 = 0x22, + IceFelixHc2000 = 0x23, + AmaterskeRadioMistrum = 0x24, + Quorum128 = 0x25, + MicroArtAtm = 0x26, + MicroArtAtmTurbo2 = 0x27, + Chrome = 0x28, + ZxBadaloc = 0x29, + Ts1500 = 0x2A, + Lambda = 0x2B, + Tk65 = 0x2C, + Zx97 = 0x2D + } + + /// + /// Identified digitizer types + /// + public enum TzxDigitizerType : byte + { + RdDigitalTracer = 0x00, + DkTronicsLightPen = 0x01, + MicrographPad = 0x02, + RomnticRobotVideoface = 0x03 + } + + /// + /// Identified EPROM programmer types + /// + public enum TzxEpromProgrammerType : byte + { + OrmeElectronics = 0x00 + } + + /// + /// Identified external storage types + /// + public enum TzxExternalStorageType : byte + { + ZxMicroDrive = 0x00, + OpusDiscovery = 0x01, + MgtDisciple = 0x02, + MgtPlusD = 0x03, + RobotronicsWafaDrive = 0x04, + TrDosBetaDisk = 0x05, + ByteDrive = 0x06, + Watsford = 0x07, + Fiz = 0x08, + Radofin = 0x09, + DidaktikDiskDrive = 0x0A, + BsDos = 0x0B, + ZxSpectrumP3DiskDrive = 0x0C, + JloDiskInterface = 0x0D, + TimexFdd3000 = 0x0E, + ZebraDiskDrive = 0x0F, + RamexMillenia = 0x10, + Larken = 0x11, + KempstonDiskInterface = 0x12, + Sandy = 0x13, + ZxSpectrumP3EHardDisk = 0x14, + ZxAtaSp = 0x15, + DivIde = 0x16, + ZxCf = 0x17 + } + + /// + /// Identified graphics types + /// + public enum TzxGraphicsType : byte + { + WrxHiRes = 0x00, + G007 = 0x01, + Memotech = 0x02, + LambdaColour = 0x03 + } + + /// + /// Represents the hardware types that can be defined + /// + public enum TzxHwType : byte + { + Computer = 0x00, + ExternalStorage = 0x01, + RomOrRamTypeAddOn = 0x02, + SoundDevice = 0x03, + JoyStick = 0x04, + Mouse = 0x05, + OtherController = 0x06, + SerialPort = 0x07, + ParallelPort = 0x08, + Printer = 0x09, + Modem = 0x0A, + Digitizer = 0x0B, + NetworkAdapter = 0x0C, + Keyboard = 0x0D, + AdOrDaConverter = 0x0E, + EpromProgrammer = 0x0F, + Graphics = 0x10 + } + + /// + /// Identified joystick types + /// + public enum TzxJoystickType + { + Kempston = 0x00, + ProtekCursor = 0x01, + Sinclair2Left = 0x02, + Sinclair1Right = 0x03, + Fuller = 0x04 + } + + /// + /// Identified keyboard and keypad types + /// + public enum TzxKeyboardType : byte + { + KeypadForZxSpectrum128K = 0x00 + } + + /// + /// Identified modem types + /// + public enum TzxModemTypes : byte + { + PrismVtx5000 = 0x00, + Westridge2050 = 0x01 + } + + /// + /// Identified mouse types + /// + public enum TzxMouseType : byte + { + AmxMouse = 0x00, + KempstonMouse = 0x01 + } + + /// + /// Identified network adapter types + /// + public enum TzxNetworkAdapterType : byte + { + ZxInterface1 = 0x00 + } + + /// + /// Identified other controller types + /// + public enum TzxOtherControllerType : byte + { + Trisckstick = 0x00, + ZxLightGun = 0x01, + ZebraGraphicTablet = 0x02, + DefnederLightGun = 0x03 + } + + /// + /// Identified parallel port types + /// + public enum TzxParallelPortType : byte + { + KempstonS = 0x00, + KempstonE = 0x01, + ZxSpectrum3P = 0x02, + Tasman = 0x03, + DkTronics = 0x04, + Hilderbay = 0x05, + InesPrinterface = 0x06, + ZxLprintInterface3 = 0x07, + MultiPrint = 0x08, + OpusDiscovery = 0x09, + Standard8255 = 0x0A + } + + /// + /// Identified printer types + /// + public enum TzxPrinterType : byte + { + ZxPrinter = 0x00, + GenericPrinter = 0x01, + EpsonCompatible = 0x02 + } + + /// + /// Identifier ROM or RAM add-on types + /// + public enum TzxRomRamAddOnType : byte + { + SamRam = 0x00, + MultifaceOne = 0x01, + Multiface128K = 0x02, + MultifaceP3 = 0x03, + MultiPrint = 0x04, + Mb02 = 0x05, + SoftRom = 0x06, + Ram1K = 0x07, + Ram16K = 0x08, + Ram48K = 0x09, + Mem8To16KUsed = 0x0A + } + + /// + /// Identified serial port types + /// + public enum TzxSerialPortType : byte + { + ZxInterface1 = 0x00, + ZxSpectrum128 = 0x01 + } + + /// + /// Identified sound device types + /// + public enum TzxSoundDeviceType : byte + { + ClassicAy = 0x00, + FullerBox = 0x01, + CurrahMicroSpeech = 0x02, + SpectDrum = 0x03, + MelodikAyAcbStereo = 0x04, + AyAbcStereo = 0x05, + RamMusinMachine = 0x06, + Covox = 0x07, + GeneralSound = 0x08, + IntecEdiB8001 = 0x09, + ZonXAy = 0x0A, + QuickSilvaAy = 0x0B, + JupiterAce = 0x0C + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs new file mode 100644 index 0000000000..8ebe4921e5 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs @@ -0,0 +1,28 @@ +using System; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class represents a TZX-related exception + /// + public class TzxException : Exception + { + /// + /// Initializes the exception with the specified message + /// + /// Exception message + public TzxException(string message) : base(message) + { + } + + /// + /// Initializes the exception with the specified message + /// and inner exception + /// + /// Exception message + /// Inner exception + public TzxException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs new file mode 100644 index 0000000000..e6901b4d7b --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents the header of the TZX file + /// + public class TzxHeader : TzxDataBlockBase + { + public static IReadOnlyList TzxSignature = + new ReadOnlyCollection(new byte[] { 0x5A, 0x58, 0x54, 0x61, 0x70, 0x65, 0x21 }); + public byte[] Signature { get; private set; } + public byte Eot { get; private set; } + public byte MajorVersion { get; private set; } + public byte MinorVersion { get; private set; } + + public TzxHeader(byte majorVersion = 1, byte minorVersion = 20) + { + Signature = TzxSignature.ToArray(); + Eot = 0x1A; + MajorVersion = majorVersion; + MinorVersion = minorVersion; + } + + /// + /// The ID of the block + /// + public override int BlockId => 0x00; + + /// + /// Reads the content of the block from the specified binary stream. + /// + /// Stream to read the block from + public override void ReadFrom(BinaryReader reader) + { + Signature = reader.ReadBytes(7); + Eot = reader.ReadByte(); + MajorVersion = reader.ReadByte(); + MinorVersion = reader.ReadByte(); + } + + /// + /// Writes the content of the block to the specified binary stream. + /// + /// Stream to write the block to + public override void WriteTo(BinaryWriter writer) + { + writer.Write(Signature); + writer.Write(Eot); + writer.Write(MajorVersion); + writer.Write(MinorVersion); + } + + #region Overrides of TzxDataBlockBase + + /// + /// Override this method to check the content of the block + /// + public override bool IsValid => Signature.SequenceEqual(TzxSignature) + && Eot == 0x1A + && MajorVersion == 1; + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs new file mode 100644 index 0000000000..55797b80df --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs @@ -0,0 +1,83 @@ +using System.IO; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class is responsible to "play" a TZX file. + /// + public class TzxPlayer : TzxReader, ISupportsTapeBlockPlayback + { + private TapeBlockSetPlayer _player; + + /// + /// Signs that the player completed playing back the file + /// + public bool Eof => _player.Eof; + + /// + /// Initializes the player from the specified reader + /// + /// BinaryReader instance to get TZX file data from + public TzxPlayer(BinaryReader reader) : base(reader) + { + } + + /// + /// Reads in the content of the TZX file so that it can be played + /// + /// True, if read was successful; otherwise, false + public override bool ReadContent() + { + var success = base.ReadContent(); + var blocks = DataBlocks.Where(b => b is ISupportsTapeBlockPlayback) + .Cast() + .ToList(); + _player = new TapeBlockSetPlayer(blocks); + return success; + } + + /// + /// Gets the currently playing block's index + /// + public int CurrentBlockIndex => _player.CurrentBlockIndex; + + /// + /// The current playable block + /// + public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; + + /// + /// The current playing phase + /// + public PlayPhase PlayPhase => _player.PlayPhase; + + /// + /// The tact count of the CPU when playing starts + /// + public long StartCycle => _player.StartCycle; + + /// + /// Initializes the player + /// + public void InitPlay(long startTact) + { + _player.InitPlay(startTact); + } + + /// + /// Gets the EAR bit value for the specified tact + /// + /// Tacts to retrieve the EAR bit + /// + /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block + /// + public bool GetEarBit(long currentTact) => _player.GetEarBit(currentTact); + + /// + /// Moves the current block index to the next playable block + /// + /// Tacts time to start the next block + public void NextBlock(long currentTact) => _player.NextBlock(currentTact); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs new file mode 100644 index 0000000000..ad7ea786ab --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// This class reads a TZX file + /// + public class TzxReader + { + private readonly BinaryReader _reader; + + public static Dictionary DataBlockTypes = new Dictionary + { + {0x10, typeof(TzxStandardSpeedDataBlock)}, + {0x11, typeof(TzxTurboSpeedDataBlock)}, + {0x12, typeof(TzxPureToneDataBlock)}, + {0x13, typeof(TzxPulseSequenceDataBlock)}, + {0x14, typeof(TzxPureDataBlock)}, + {0x15, typeof(TzxDirectRecordingDataBlock)}, + {0x16, typeof(TzxC64RomTypeDataBlock)}, + {0x17, typeof(TzxC64TurboTapeDataBlock)}, + {0x18, typeof(TzxCswRecordingDataBlock)}, + {0x19, typeof(TzxGeneralizedDataBlock)}, + {0x20, typeof(TzxSilenceDataBlock)}, + {0x21, typeof(TzxGroupStartDataBlock)}, + {0x22, typeof(TzxGroupEndDataBlock)}, + {0x23, typeof(TzxJumpDataBlock)}, + {0x24, typeof(TzxLoopStartDataBlock)}, + {0x25, typeof(TzxLoopEndDataBlock)}, + {0x26, typeof(TzxCallSequenceDataBlock)}, + {0x27, typeof(TzxReturnFromSequenceDataBlock)}, + {0x28, typeof(TzxSelectDataBlock)}, + {0x2A, typeof(TzxStopTheTape48DataBlock)}, + {0x2B, typeof(TzxSetSignalLevelDataBlock)}, + {0x30, typeof(TzxTextDescriptionDataBlock)}, + {0x31, typeof(TzxMessageDataBlock)}, + {0x32, typeof(TzxArchiveInfoDataBlock)}, + {0x33, typeof(TzxHardwareInfoDataBlock)}, + {0x34, typeof(TzxEmulationInfoDataBlock)}, + {0x35, typeof(TzxCustomInfoDataBlock)}, + {0x40, typeof(TzxSnapshotBlock)}, + {0x5A, typeof(TzxGlueDataBlock)}, + }; + + /// + /// Data blocks of this TZX file + /// + public IList DataBlocks { get; } + + /// + /// Major version number of the file + /// + public byte MajorVersion { get; private set; } + + /// + /// Minor version number of the file + /// + public byte MinorVersion { get; private set; } + + /// + /// Initializes the player from the specified reader + /// + /// + public TzxReader(BinaryReader reader) + { + _reader = reader; + DataBlocks = new List(); + } + + /// + /// Reads in the content of the TZX file so that it can be played + /// + /// True, if read was successful; otherwise, false + public virtual bool ReadContent() + { + var header = new TzxHeader(); + try + { + header.ReadFrom(_reader); + if (!header.IsValid) + { + throw new TzxException("Invalid TZX header"); + } + MajorVersion = header.MajorVersion; + MinorVersion = header.MinorVersion; + + while (_reader.BaseStream.Position != _reader.BaseStream.Length) + { + var blockType = _reader.ReadByte(); + Type type; + if (!DataBlockTypes.TryGetValue(blockType, out type)) + { + throw new TzxException($"Unkonwn TZX block type: {blockType}"); + } + + try + { + var block = Activator.CreateInstance(type) as TzxDataBlockBase; + if (block.GetType() == typeof(TzxDeprecatedDataBlockBase)) + { + ((TzxDeprecatedDataBlockBase)block as TzxDeprecatedDataBlockBase).ReadThrough(_reader); + } + else + { + block?.ReadFrom(_reader); + } + DataBlocks.Add(block); + } + catch (Exception ex) + { + throw new TzxException($"Cannot read TZX data block {type}.", ex); + } + } + return true; + } + catch + { + // --- This exception is intentionally ignored + return false; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs new file mode 100644 index 0000000000..cbdfcf42e7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + + public class RomData + { + /// + /// ROM Contents + /// + public byte[] RomBytes { get; set; } + + /// + /// Useful ROM addresses that are needed during tape operations + /// + public ushort SaveBytesRoutineAddress { get; set; } + public ushort SaveBytesResumeAddress { get; set; } + public ushort LoadBytesRoutineAddress { get; set; } + public ushort LoadBytesResumeAddress { get; set; } + public ushort LoadBytesInvalidHeaderAddress { get; set; } + + public static RomData InitROM(MachineType machineType, byte[] rom) + { + RomData RD = new RomData(); + RD.RomBytes = new byte[rom.Length]; + RD.RomBytes = rom; + + switch (machineType) + { + case MachineType.ZXSpectrum48: + RD.SaveBytesRoutineAddress = 0x04C2; + RD.SaveBytesResumeAddress = 0x0000; + RD.LoadBytesRoutineAddress = 0x056C; + RD.LoadBytesResumeAddress = 0x05E2; + RD.LoadBytesInvalidHeaderAddress = 0x05B6; + break; + } + + return RD; + } + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs deleted file mode 100644 index 222a3bb257..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Tape.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Represents the tape device (or DATACORDER as AMSTRAD liked to call it) - /// - 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; - } - - - /// - /// the EAR bit read from tape - /// - /// - /// - public virtual bool GetEarBit(int cpuCycles) - { - return false; - } - - /// - /// Processes the mic bit change - /// - /// - public virtual void ProcessMicBit(bool micBit) - { - - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs index 956c5eb206..e223f4aeeb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -48,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private void SyncAllByteArrayDomains() { - SyncByteArrayDomain("Main RAM", _machine.RAM); + //SyncByteArrayDomain("Main RAM", _machine.RAM); } private void SyncByteArrayDomain(string name, byte[] data) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs deleted file mode 100644 index 81dad0d813..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using BizHawk.Emulation.Common; -using BizHawk.Emulation.Cores.Components; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public partial class ZXSpectrum //: ISoundProvider - { - - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index e41c43b832..8de32cfa15 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -53,9 +53,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.SyncState(ser); ser.BeginSection("ZXSpectrum"); - byte[] ram = new byte[_machine.RAM.Length]; - _machine.RAM.CopyTo(ram, 0); - ser.Sync("RAM", ref ram, false); + //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); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs new file mode 100644 index 0000000000..52bce8d20f --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs @@ -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 partial class ZXSpectrum + { + public ushort Get16BitPC() + { + return Convert.ToUInt16(_cpu.PCh << 8 | _cpu.PCl); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 750c8475d6..a77c7a501c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -35,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition48; - Init(MachineType.ZXSpectrum48, Settings.BorderType, Settings.TapeLoadSpeed); + Init(MachineType.ZXSpectrum48, Settings.BorderType, Settings.TapeLoadSpeed, _file); break; default: throw new InvalidOperationException("Machine not yet emulated"); @@ -90,14 +90,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public Action SoftReset; private readonly Z80A _cpu; - private byte[] _systemRom; + //private byte[] _systemRom; private readonly TraceBuffer _tracer; public IController _controller; private SpectrumBase _machine; - - - + private byte[] _file; private byte[] GetFirmware(int length, params string[] names) { @@ -111,15 +109,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } - private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed) + private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, byte[] file) { // 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); + _machine = new ZX48(this, _cpu, file); + var _systemRom = GetFirmware(0x4000, "48ROM"); + var romData = RomData.InitROM(machineType, _systemRom); + _machine.InitROM(romData); break; } } From 7287afc5da2fb12b8b1cf3db4909a92a434c4a04 Mon Sep 17 00:00:00 2001 From: Asnvior Date: Tue, 28 Nov 2017 19:28:22 +0000 Subject: [PATCH 003/105] More SynState work --- BizHawk.Client.Common/config/PathEntry.cs | 2 +- .../SinclairSpectrum/Hardware/Buzzer.cs | 94 +++++-- .../Hardware/DefaultTapeProvider.cs | 8 +- .../Hardware/Interfaces/IKeyboard.cs | 4 + .../Hardware/Interfaces/ITapeProvider.cs | 6 + .../SinclairSpectrum/Hardware/Tape.cs | 182 +++++++++++-- .../Hardware/TapeBlockSetPlayer.cs | 2 +- .../Hardware/TapeFilePlayer.cs | 4 + .../Machine/SpectrumBase.Input.cs | 31 ++- .../Machine/SpectrumBase.Memory.cs | 11 +- .../Machine/SpectrumBase.Port.cs | 71 +----- .../SinclairSpectrum/Machine/SpectrumBase.cs | 114 ++++++--- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 45 +++- .../Machine/ZXSpectrum48K/ZX48.cs | 35 ++- .../Computers/SinclairSpectrum/RomData.cs | 62 ++++- .../ZXSpectrum.IMemoryDomains.cs | 25 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 10 +- .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 14 +- .../SinclairSpectrum/ZXSpectrum.Util.cs | 240 +++++++++++++++++- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- 20 files changed, 753 insertions(+), 209 deletions(-) diff --git a/BizHawk.Client.Common/config/PathEntry.cs b/BizHawk.Client.Common/config/PathEntry.cs index 75f5fd0bc6..22fcad0a76 100644 --- a/BizHawk.Client.Common/config/PathEntry.cs +++ b/BizHawk.Client.Common/config/PathEntry.cs @@ -290,7 +290,7 @@ 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 = "Cheats", Path = Path.Combine(".", "Cheats"), Ordinal = 5 }, - 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 = "Base", Path = Path.Combine(".", "ZXSpectrum"), 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 }, diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs index 6a34619dcd..ace668868f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs @@ -1,8 +1,10 @@  +using BizHawk.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components; using System; using System.Collections.Generic; +using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -19,20 +21,49 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Supplied values are right for 48K spectrum /// These will deviate for 128k and up (as there are more T-States per frame) /// - public int SampleRate = 44100; //35000; - public int SamplesPerFrame = 882; //699; - public int TStatesPerSample = 79; //100; + //public int SampleRate = 44100; //35000; + //public int SamplesPerFrame = 882; //699; + //public int TStatesPerSample = 79; //100; + + /// + /// Sample Rate + /// This usually has to be 44100 for ISoundProvider + /// + public int SampleRate + { + get { return _sampleRate; } + set { _sampleRate = value; } + } + + /// + /// Number of samples in one frame + /// + public int SamplesPerFrame + { + get { return _samplesPerFrame; } + set { _samplesPerFrame = value; } + } - public BlipBuffer BlipL { get; set; } - public BlipBuffer BlipR { get; set; } + /// + /// Number of TStates in each sample + /// + public int TStatesPerSample + { + get { return _tStatesPerSample; } + set { _tStatesPerSample = value; } + } private SpectrumBase _machine; + /// + /// State fields + /// private long _frameStart; private bool _tapeMode; private int _tStatesPerFrame; - - public SpeexResampler resampler { get; set; } + private int _sampleRate; + private int _samplesPerFrame; + private int _tStatesPerSample; /// /// Pulses collected during the last frame @@ -42,7 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The last pulse /// - public bool LastPulse { get; private set; } + public bool LastPulse { get; set; } /// /// The last T-State (cpu cycle) that the last pulse was received @@ -59,9 +90,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Initialises the buzzer /// - public void Init() + public void Init(int sampleRate, int tStatesPerFrame) { - _tStatesPerFrame = _machine.UlaFrameCycleCount; + _sampleRate = sampleRate; + _tStatesPerFrame = tStatesPerFrame; + + // get divisors + var divs = from a in Enumerable.Range(2, _tStatesPerFrame / 2) + where _tStatesPerFrame % a == 0 + select a; + + // get the highest int value under 120 (this will be TStatesPerSample) + _tStatesPerSample = divs.Where(a => a < 100).Last(); + + // get _samplesPerFrame + _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + Pulses = new List(1000); } @@ -206,16 +250,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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; + throw new NotSupportedException("Async is not available"); } public void DiscardSamples() @@ -241,5 +276,24 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + + public void SyncState(Serializer ser) + { + ser.BeginSection("Buzzer"); + ser.Sync("_frameStart", ref _frameStart); + ser.Sync("_tapeMode", ref _tapeMode); + ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); + ser.Sync("_sampleRate", ref _sampleRate); + ser.Sync("_samplesPerFrame", ref _samplesPerFrame); + ser.Sync("_tStatesPerSample", ref _tStatesPerSample); + + ser.Sync("soundBuffer", ref soundBuffer, false); + ser.Sync("soundBufferContains", ref soundBufferContains); + ser.EndSection(); + } + + } + + } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs index f9bbf30846..2ee8ef6f63 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs @@ -25,7 +25,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public string SaveFileFolder { get; } - + public DefaultTapeProvider(byte[] file, string saveFolder = null) { @@ -40,14 +40,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The component provider should be able to reset itself /// /// - /* - public override void Reset() + + public void Reset() { _dataBlockCount = 0; _suggestedName = null; _fullFileName = null; } - */ + /// /// Tha tape set to load the content from diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs index f053d044aa..fe4f188ca6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs @@ -1,5 +1,7 @@  +using BizHawk.Common; + namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// @@ -61,5 +63,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// byte GetByteFromKeyMatrix(string key); + + void SyncState(Serializer ser); } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs index d82bc3ee64..91a4457a31 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs @@ -43,5 +43,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// TZX blocks are written. /// void FinalizeTapeFile(); + + /// + /// Provider can reset itself + /// + void Reset(); + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs index f493c7bda9..203fef91b2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs @@ -1,4 +1,5 @@ -using BizHawk.Emulation.Cores.Components.Z80A; +using BizHawk.Common; +using BizHawk.Emulation.Cores.Components.Z80A; using System; using System.Collections.Generic; using System.Linq; @@ -78,14 +79,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public const int DATA_BUFFER_LENGTH = 0x10000; /// - /// Gets the TZX tape content provider + /// Gets the tape content provider /// - public ITapeContentProvider ContentProvider { get; } - - /// - /// Gets the tape Save provider - /// - public ISaveToTapeProvider SaveToTapeProvider { get; } + public ITapeProvider TapeProvider { get; } /// /// The TapeFilePlayer that can playback tape content @@ -98,6 +94,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public TapeOperationMode CurrentMode => _currentMode; + private bool _fastLoad = false; + + public virtual void Init(SpectrumBase machine) { _machine = machine; @@ -106,15 +105,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Reset(); } - public Tape(ITapeContentProvider contentProvider, ISaveToTapeProvider saveToTapeProvider) + public Tape(ITapeProvider tapeProvider) { - ContentProvider = contentProvider; - SaveToTapeProvider = saveToTapeProvider; + TapeProvider = tapeProvider; } public virtual void Reset() { - ContentProvider?.Reset(); + TapeProvider?.Reset(); _tapePlayer = null; _currentMode = TapeOperationMode.Passive; _savePhase = SavePhase.None; @@ -125,17 +123,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { SetTapeMode(); if (CurrentMode == TapeOperationMode.Load - //&& HostVm.ExecuteCycleOptions.FastTapeMode + && _fastLoad && TapeFilePlayer != null && TapeFilePlayer.PlayPhase != PlayPhase.Completed - && _machine.Spectrum.Get16BitPC() == _machine.RomData.LoadBytesRoutineAddress) + && _cpu.RegPC == 1529) //_machine.RomData.LoadBytesRoutineAddress) { - /* + if (FastLoadFromTzx()) { - FastLoadCompleted?.Invoke(this, EventArgs.Empty); + //FastLoadCompleted?.Invoke(this, EventArgs.Empty); } - */ + } } @@ -148,27 +146,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (_currentMode) { case TapeOperationMode.Passive: - if (_machine.Spectrum.Get16BitPC() == _machine.RomData.LoadBytesRoutineAddress) + if (_cpu.RegPC == 1523) // _machine.RomData.LoadBytesRoutineAddress) //1529 { EnterLoadMode(); } - else if (_machine.Spectrum.Get16BitPC() == _machine.RomData.SaveBytesRoutineAddress) + else if (_cpu.RegPC == 2416) // _machine.RomData.SaveBytesRoutineAddress) { EnterSaveMode(); } - var res = _machine.Spectrum.Get16BitPC(); + var res = _cpu.RegPC; + var res2 = _machine.Spectrum.RegPC; return; case TapeOperationMode.Save: - if (_machine.Spectrum.Get16BitPC() == ERROR_ROM_ADDRESS + if (_cpu.RegPC == ERROR_ROM_ADDRESS || (int)(_cpu.TotalExecutedCycles - _lastMicBitActivityCycle) > SAVE_STOP_SILENCE) { LeaveSaveMode(); } return; case TapeOperationMode.Load: - if ((_tapePlayer?.Eof ?? false) || _machine.Spectrum.Get16BitPC() == ERROR_ROM_ADDRESS) + if ((_tapePlayer?.Eof ?? false) || _cpu.RegPC == ERROR_ROM_ADDRESS) { LeaveLoadMode(); } @@ -188,7 +187,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _pilotPulseCount = 0; _prevDataPulse = MicPulseType.None; _dataBlockCount = 0; - SaveToTapeProvider?.CreateTapeFile(); + TapeProvider?.CreateTapeFile(); } /// @@ -197,7 +196,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private void LeaveSaveMode() { _currentMode = TapeOperationMode.Passive; - SaveToTapeProvider?.FinalizeTapeFile(); + TapeProvider?.FinalizeTapeFile(); } /// @@ -207,7 +206,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _currentMode = TapeOperationMode.Load; - var contentReader = ContentProvider?.GetTapeContent(); + var contentReader = TapeProvider?.GetTapeContent(); if (contentReader == null) return; // --- Play the content @@ -224,10 +223,116 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _currentMode = TapeOperationMode.Passive; _tapePlayer = null; - ContentProvider?.Reset(); + TapeProvider?.Reset(); _buzzer.SetTapeMode(false); } + /// + /// Loads the next TZX player block instantly without emulation + /// EAR bit processing + /// + /// True, if fast load is operative + private bool FastLoadFromTzx() + { + var c = _machine.Spectrum; + + var blockType = TapeFilePlayer.CurrentBlock.GetType(); + bool canFlash = TapeFilePlayer.CurrentBlock is ITapeData; + + // --- Check, if we can load the current block in a fast way + if (!(TapeFilePlayer.CurrentBlock is ITapeData) + || TapeFilePlayer.PlayPhase == PlayPhase.Completed) + { + // --- We cannot play this block + return false; + } + + var currentData = TapeFilePlayer.CurrentBlock as ITapeData; + + var regs = _cpu.Regs; + + //regs.AF = regs._AF_; + //c.Set16BitAF(c.Get16BitAF_()); + _cpu.A = _cpu.A_s; + _cpu.F = _cpu.F_s; + + // --- Check if the operation is LOAD or VERIFY + var isVerify = (c.RegAF & 0xFF01) == 0xFF00; + + // --- At this point IX contains the address to load the data, + // --- DE shows the #of bytes to load. A contains 0x00 for header, + // --- 0xFF for data block + var data = currentData.Data; + if (data[0] != regs[_cpu.A]) + { + // --- This block has a different type we're expecting + regs[_cpu.A] = (byte)(regs[_cpu.A] ^ regs[_cpu.L]); + regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.Z; + regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; + c.RegPC = _machine.RomData.LoadBytesInvalidHeaderAddress; + + // --- Get the next block + TapeFilePlayer.NextBlock(_cpu.TotalExecutedCycles); + return true; + } + + // --- It is time to load the block + var curIndex = 1; + //var memory = _machine.me MemoryDevice; + regs[_cpu.H] = regs[_cpu.A]; + while (c.RegDE > 0) + { + var de16 = c.RegDE; + var ix16 = c.RegIX; + if (curIndex > data.Length - 1) + { + // --- No more data to read + //break; + } + + regs[_cpu.L] = data[curIndex]; + if (isVerify && regs[_cpu.L] != _machine.ReadBus(c.RegIX)) + { + // --- Verify failed + regs[_cpu.A] = (byte)(_machine.ReadBus(c.RegIX) ^ regs[_cpu.L]); + regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.Z; + regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; + c.RegPC = _machine.RomData.LoadBytesInvalidHeaderAddress; + return true; + } + + // --- Store the loaded data byte + _machine.WriteBus(c.RegIX, (byte)regs[_cpu.L]); + regs[_cpu.H] ^= regs[_cpu.L]; + curIndex++; + //regs.IX++; + //c.Set16BitIX((ushort)((int)c.Get16BitIX() + 1)); + c.RegIX++; + //regs.DE--; + //c.Set16BitDE((ushort)((int)c.Get16BitDE() - 1)); + //_cpu.Regs[_cpu.E]--; + c.RegDE--; + var te = c.RegDE; + } + + // --- Check the parity byte at the end of the data stream + if (curIndex > data.Length - 1 || regs[_cpu.H] != data[curIndex]) + { + // --- Carry is reset to sign an error + regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; + } + else + { + // --- Carry is set to sign success + regs[_cpu.F] |= (byte)ZXSpectrum.FlagsSetMask.C; + } + c.RegPC = _machine.RomData.LoadBytesResumeAddress; + + // --- Get the next block + TapeFilePlayer.NextBlock(_cpu.TotalExecutedCycles); + return true; + } + /// /// the EAR bit read from tape @@ -398,14 +503,35 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum sb.Append((char)_dataBuffer[i]); } var name = sb.ToString().TrimEnd(); - SaveToTapeProvider?.SetName(name); + TapeProvider?.SetName(name); } - SaveToTapeProvider?.SaveTapeBlock(dataBlock); + TapeProvider?.SaveTapeBlock(dataBlock); } break; } _savePhase = nextPhase; } + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapeDevice"); + ser.Sync("_micBitState", ref _micBitState); + ser.Sync("_lastMicBitActivityCycle", ref _lastMicBitActivityCycle); + ser.Sync("_pilotPulseCount", ref _pilotPulseCount); + ser.Sync("_bitOffset", ref _bitOffset); + ser.Sync("_dataByte", ref _dataByte); + ser.Sync("_dataLength", ref _dataLength); + ser.Sync("_dataBlockCount", ref _dataBlockCount); + ser.Sync("_dataBuffer", ref _dataBuffer, false); + ser.SyncEnum("_currentMode", ref _currentMode); + ser.SyncEnum("_savePhase", ref _savePhase); + ser.SyncEnum("_prevDataPulse", ref _prevDataPulse); + /* + private TapeFilePlayer _tapePlayer; + */ + + ser.EndSection(); + } } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs index 372a752bf1..c08b488acf 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs @@ -36,7 +36,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The cycle count of the CPU when playing starts /// public long StartCycle { get; private set; } - + public TapeBlockSetPlayer(List dataBlocks) { DataBlocks = dataBlocks; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs index 933899a6f5..25c75df0f8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs @@ -5,6 +5,10 @@ using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { + /// + /// This class recognizes .TZX and .TAP files, and playes back + /// the content accordingly. + /// public class TapeFilePlayer : ISupportsTapeBlockPlayback { private readonly BinaryReader _reader; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index a9eedbc44d..01ac5ab51a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -6,27 +6,42 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public abstract partial class SpectrumBase { - private readonly bool[] _keyboardPressed = new bool[64]; - int _pollIndex; - private bool _restorePressed; + string Play = "Play Tape"; + string Stop = "Stop Tape"; + string RTZ = "RTZ Tape"; + string Record = "Record Tape"; - 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) + if (currState != prevState) KeyboardDevice.SetKeyStatus(key, currState); } + + // Tape control + if (Spectrum._controller.IsPressed(Play)) + { + + } + if (Spectrum._controller.IsPressed(Stop)) + { + + } + if (Spectrum._controller.IsPressed(RTZ)) + { + + } + if (Spectrum._controller.IsPressed(Record)) + { + + } } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index a466c965d7..d993b15f32 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -144,10 +144,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual byte FetchScreenMemory(ushort addr) { - var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; + //var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; + var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000)); return value; } + /// + /// Helper function to refresh memory array (probably not the best way to do things) + /// + public virtual void ReInitMemory() + { + throw new NotImplementedException("Must be overriden"); + } + /// /// 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 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index 6cefe2f954..a8b715a214 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -56,58 +56,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // read keyboard input if (high != 0) result = KeyboardDevice.GetLineStatus((byte)high); - - var ear = TapeDevice.GetEarBit(CurrentFrameCycle); + + var ear = TapeDevice.GetEarBit(CPU.TotalExecutedCycles); 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 { @@ -152,25 +106,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // 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); + // Buzzer + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); - // if tape is not playing - BuzzerDevice.ProcessPulseValue(false, (beepVal) != 0); - */ - - // tape - //_tapeDevice.ProcessMicBit((data & 0x08) != 0); - - BuzzerDevice.ProcessPulseValue(false, (value & 0x10) != 0); - TapeDevice.ProcessMicBit((value & 0x08) != 0); + // Tape + TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 53fe905d01..6183843006 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -1,4 +1,5 @@ -using BizHawk.Emulation.Common; +using BizHawk.Common; +using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.Z80A; using System; @@ -105,14 +106,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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 @@ -137,30 +131,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RenderScreen(0, OverFlow); } - - /// - /// Executes one cycle of the emulated machine - /// - 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) - { - - } - } /// /// Hard reset of the emulated machine @@ -168,8 +138,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public virtual void HardReset() { ResetBorder(); - ResetInterrupt(); - + ResetInterrupt(); } /// @@ -180,5 +149,80 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ResetBorder(); ResetInterrupt(); } + + public void SyncState(Serializer ser) + { + ser.BeginSection("ZXMachine"); + ser.Sync("FrameCompleted", ref FrameCompleted); + ser.Sync("OverFlow", ref OverFlow); + ser.Sync("FrameCount", ref FrameCount); + ser.Sync("_frameCycles", ref _frameCycles); + ser.Sync("LastFrameStartCPUTick", ref LastFrameStartCPUTick); + ser.Sync("LastULAOutByte", ref LastULAOutByte); + ser.Sync("_flashPhase", ref _flashPhase); + ser.Sync("_frameBuffer", ref _frameBuffer, false); + ser.Sync("_flashOffColors", ref _flashOffColors, false); + ser.Sync("_flashOnColors", ref _flashOnColors, false); + ser.Sync("InterruptCycle", ref InterruptCycle); + ser.Sync("InterruptRaised", ref InterruptRaised); + ser.Sync("InterruptRevoked", ref InterruptRevoked); + ser.Sync("UlaFrameCycleCount", ref UlaFrameCycleCount); + ser.Sync("FirstScreenPixelCycle", ref FirstScreenPixelCycle); + ser.Sync("FirstDisplayPixelCycle", ref FirstDisplayPixelCycle); + ser.Sync("FirstPixelCycleInLine", ref FirstPixelCycleInLine); + ser.Sync("AttributeDataPrefetchTime", ref AttributeDataPrefetchTime); + ser.Sync("PixelDataPrefetchTime", ref PixelDataPrefetchTime); + ser.Sync("ScreenLineTime", ref ScreenLineTime); + ser.Sync("NonVisibleBorderRightTime", ref NonVisibleBorderRightTime); + ser.Sync("BorderRightTime", ref BorderRightTime); + ser.Sync("DisplayLineTime", ref DisplayLineTime); + ser.Sync("BorderLeftTime", ref BorderLeftTime); + ser.Sync("HorizontalBlankingTime", ref HorizontalBlankingTime); + ser.Sync("ScreenWidth", ref ScreenWidth); + ser.Sync("BorderRightPixels", ref BorderRightPixels); + ser.Sync("BorderLeftPixels", ref BorderLeftPixels); + ser.Sync("FirstDisplayLine", ref FirstDisplayLine); + ser.Sync("ScreenLines", ref ScreenLines); + ser.Sync("NonVisibleBorderBottomLines", ref NonVisibleBorderBottomLines); + ser.Sync("BorderBottomLines", ref BorderBottomLines); + ser.Sync("BorderTopLines", ref BorderTopLines); + ser.Sync("NonVisibleBorderTopLines", ref NonVisibleBorderTopLines); + ser.Sync("VerticalSyncLines", ref VerticalSyncLines); + ser.Sync("FlashToggleFrames", ref FlashToggleFrames); + ser.Sync("DisplayLines", ref DisplayLines); + ser.Sync("DisplayWidth", ref DisplayWidth); + ser.Sync("_pixelByte1", ref _pixelByte1); + ser.Sync("_pixelByte2", ref _pixelByte2); + ser.Sync("_attrByte1", ref _attrByte1); + ser.Sync("_attrByte2", ref _attrByte2); + ser.Sync("_xPos", ref _xPos); + ser.Sync("_yPos", ref _yPos); + ser.Sync("DisplayWidth", ref DisplayWidth); + ser.Sync("DisplayWidth", ref DisplayWidth); + ser.Sync("DisplayWidth", ref DisplayWidth); + ser.Sync("DisplayWidth", ref DisplayWidth); + ser.Sync("_borderColour", ref _borderColour); + ser.Sync("ROM0", ref ROM0, false); + ser.Sync("ROM1", ref ROM1, false); + ser.Sync("ROM2", ref ROM2, false); + ser.Sync("ROM3", ref ROM3, false); + ser.Sync("RAM0", ref RAM0, false); + ser.Sync("RAM1", ref RAM1, false); + ser.Sync("RAM2", ref RAM2, false); + ser.Sync("RAM3", ref RAM3, false); + ser.Sync("RAM4", ref RAM4, false); + ser.Sync("RAM5", ref RAM5, false); + ser.Sync("RAM6", ref RAM6, false); + ser.Sync("RAM7", ref RAM7, false); + + RomData.SyncState(ser); + KeyboardDevice.SyncState(ser); + BuzzerDevice.SyncState(ser); + TapeDevice.SyncState(ser); + + ser.EndSection(); + + ReInitMemory(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index ada3db6803..501dd6186b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,10 +13,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class Keyboard48 : IKeyboard { public SpectrumBase _machine { get; set; } - public string[] KeyboardMatrix { get; set; } - private readonly byte[] LineStatus; + private byte[] LineStatus; public bool Issue2 { get; set; } + private string[] _keyboardMatrix; + public string[] KeyboardMatrix + { + get { return _keyboardMatrix; } + set { _keyboardMatrix = value; } + } public Keyboard48(SpectrumBase machine) { @@ -51,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var lineMask = 1 << keyByte % 5; LineStatus[lineIndex] = isPressed ? (byte)(LineStatus[lineIndex] | lineMask) - : (byte)(LineStatus[lineIndex] & ~lineMask); + : (byte)(LineStatus[lineIndex] & ~lineMask); } public bool GetKeyStatus(string key) @@ -64,18 +70,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte GetLineStatus(byte lines) { - byte status = 0; - lines = (byte)~lines; - var lineIndex = 0; - while (lines > 0) + lock(this) { - if ((lines & 0x01) != 0) - status |= LineStatus[lineIndex]; - lineIndex++; - lines >>= 1; + 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; } - var result = (byte)~status; - return result; } public byte ReadKeyboardByte(ushort addr) @@ -88,5 +98,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum int index = Array.IndexOf(KeyboardMatrix, key); return (byte)index; } + + public void SyncState(Serializer ser) + { + ser.BeginSection("Keyboard"); + ser.Sync("LineStatus", ref LineStatus, false); + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 32bc6bf1b3..ffbc38c13c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -22,10 +22,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CPU = cpu; // init addressable memory from ROM and RAM banks + /* Memory.Add(0, ROM0); Memory.Add(1, RAM0); Memory.Add(2, RAM1); Memory.Add(3, RAM2); + */ + ReInitMemory(); //RAM = new byte[0x4000 + 0xC000]; @@ -35,13 +38,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ResetULACycle(); BuzzerDevice = new Buzzer(this); - BuzzerDevice.Init(); + BuzzerDevice.Init(44100, UlaFrameCycleCount); KeyboardDevice = new Keyboard48(this); TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new Tape(null, null); + TapeDevice = new Tape(TapeProvider); TapeDevice.Init(this); } @@ -147,6 +150,34 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = RAM0; + else + Memory.Add(1, RAM0); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM1; + else + Memory.Add(2, RAM1); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM2; + else + Memory.Add(3, RAM2); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM3; + else + Memory.Add(4, RAM3); + } + #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs index cbdfcf42e7..97dffe206b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,16 +13,48 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// ROM Contents /// - public byte[] RomBytes { get; set; } - + public byte[] RomBytes + { + get { return _romBytes; } + set { _romBytes = value; } + } + /// /// Useful ROM addresses that are needed during tape operations /// - public ushort SaveBytesRoutineAddress { get; set; } - public ushort SaveBytesResumeAddress { get; set; } - public ushort LoadBytesRoutineAddress { get; set; } - public ushort LoadBytesResumeAddress { get; set; } - public ushort LoadBytesInvalidHeaderAddress { get; set; } + public ushort SaveBytesRoutineAddress + { + get { return _saveBytesRoutineAddress; } + set { _saveBytesRoutineAddress = value; } + } + public ushort LoadBytesRoutineAddress + { + get { return _loadBytesRoutineAddress; } + set { _loadBytesRoutineAddress = value; } + } + public ushort SaveBytesResumeAddress + { + get { return _saveBytesResumeAddress; } + set { _saveBytesResumeAddress = value; } + } + public ushort LoadBytesResumeAddress + { + get { return _loadBytesResumeAddress; } + set { _loadBytesResumeAddress = value; } + } + public ushort LoadBytesInvalidHeaderAddress + { + get { return _loadBytesInvalidHeaderAddress; } + set { _loadBytesInvalidHeaderAddress = value; } + } + + private byte[] _romBytes; + private ushort _saveBytesRoutineAddress; + private ushort _loadBytesRoutineAddress; + private ushort _saveBytesResumeAddress; + private ushort _loadBytesResumeAddress; + private ushort _loadBytesInvalidHeaderAddress; + public static RomData InitROM(MachineType machineType, byte[] rom) { @@ -34,7 +67,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case MachineType.ZXSpectrum48: RD.SaveBytesRoutineAddress = 0x04C2; RD.SaveBytesResumeAddress = 0x0000; - RD.LoadBytesRoutineAddress = 0x056C; + RD.LoadBytesRoutineAddress = 0x0808; //0x0556; //0x056C; RD.LoadBytesResumeAddress = 0x05E2; RD.LoadBytesInvalidHeaderAddress = 0x05B6; break; @@ -43,5 +76,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return RD; } + public void SyncState(Serializer ser) + { + ser.BeginSection("RomData"); + ser.Sync("RomBytes", ref _romBytes, false); + ser.Sync("_saveBytesRoutineAddress", ref _saveBytesRoutineAddress); + ser.Sync("_loadBytesRoutineAddress", ref _loadBytesRoutineAddress); + ser.Sync("_saveBytesResumeAddress", ref _saveBytesResumeAddress); + ser.Sync("_loadBytesResumeAddress", ref _loadBytesResumeAddress); + ser.Sync("_loadBytesInvalidHeaderAddress", ref _loadBytesInvalidHeaderAddress); + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs index e223f4aeeb..85083f3fcc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { throw new ArgumentOutOfRangeException(); } - return _cpu.ReadMemory((ushort)addr); + return _machine.ReadBus((ushort)addr); }, (addr, value) => { @@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum throw new ArgumentOutOfRangeException(); } - _cpu.WriteMemory((ushort)addr, value); + _machine.WriteBus((ushort)addr, value); }, 1) }; @@ -47,13 +47,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } private void SyncAllByteArrayDomains() - { - //SyncByteArrayDomain("Main RAM", _machine.RAM); + { + + SyncByteArrayDomain("ROM0", _machine.ROM0); + SyncByteArrayDomain("ROM1", _machine.ROM1); + SyncByteArrayDomain("ROM2", _machine.ROM2); + SyncByteArrayDomain("ROM3", _machine.ROM3); + SyncByteArrayDomain("RAM0", _machine.RAM0); + SyncByteArrayDomain("RAM1", _machine.RAM1); + SyncByteArrayDomain("RAM2", _machine.RAM2); + SyncByteArrayDomain("RAM3", _machine.RAM3); + SyncByteArrayDomain("RAM4", _machine.RAM4); + SyncByteArrayDomain("RAM5", _machine.RAM5); + SyncByteArrayDomain("RAM6", _machine.RAM6); + SyncByteArrayDomain("RAM7", _machine.RAM7); + } private void SyncByteArrayDomain(string name, byte[] data) { - if (_memoryDomainsInit) + + if (_memoryDomainsInit || _byteArrayDomains.ContainsKey(name)) { var m = _byteArrayDomains[name]; m.Data = data; @@ -63,6 +77,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var m = new MemoryDomainByteArray(name, MemoryDomain.Endian.Little, data, true, 1); _byteArrayDomains.Add(name, m); } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 32db755068..33dddae230 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -48,10 +48,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [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() { @@ -66,6 +63,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class ZXSpectrumSyncSettings { + [DisplayName("Tape Load Speed")] + [Description("Select how fast the spectrum loads the game from tape")] + [DefaultValue(TapeLoadSpeed.Accurate)] + public TapeLoadSpeed TapeLoadSpeed { get; set; } + public ZXSpectrumSyncSettings Clone() { return (ZXSpectrumSyncSettings)MemberwiseClone(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index 8de32cfa15..a9c407ca14 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -53,23 +53,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _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); + _cpu.SyncState(ser); + _machine.SyncState(ser); ser.Sync("Frame", ref _machine.FrameCount); ser.Sync("LagCount", ref _lagCount); ser.Sync("IsLag", ref _isLag); + //ser.Sync("_memoryDomainsInit", ref _memoryDomainsInit); + ser.EndSection(); if (ser.IsReader) { - //SyncAllByteArrayDomains(); + SyncAllByteArrayDomains(); } } - - private byte[] _stateBuffer; } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs index 52bce8d20f..893edbae8a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs @@ -8,9 +8,245 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZXSpectrum { - public ushort Get16BitPC() + /* + * CPU Helper Methods + */ + + public ushort RegPC { - return Convert.ToUInt16(_cpu.PCh << 8 | _cpu.PCl); + get { return (ushort)((_cpu.Regs[0] << 8 | _cpu.Regs[1])); } + set + { + _cpu.Regs[1] = (ushort)(value & 0xFF); + _cpu.Regs[0] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegIX + { + get { return (ushort)((_cpu.Regs[15] << 8 | _cpu.Regs[16] )); } + set + { + _cpu.Regs[16] = (ushort)(value & 0xFF); + _cpu.Regs[15] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegDE + { + get { return (ushort)((_cpu.Regs[8] << 8 | _cpu.Regs[9] )); } + set + { + _cpu.Regs[9] = (ushort)(value & 0xFF); + _cpu.Regs[8] = (ushort)((value >> 8) & 0xFF); + } + } + + public ushort RegAF + { + get { return (ushort)((_cpu.Regs[4] << 8 | _cpu.Regs[5])); } + set + { + _cpu.Regs[5] = (ushort)(value & 0xFF); + _cpu.Regs[4] = (ushort)((value >> 8) & 0xFF); + } + } + + + /// + /// Gets the IX word value + /// + /// + public ushort Get16BitIX() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.Ixh] | _cpu.Regs[_cpu.Ixl] << 8); + } + + /// + /// Set the IX word value + /// + /// + /// + public void Set16BitIX(ushort IX) + { + _cpu.Regs[_cpu.Ixh] = (ushort)(IX & 0xFF); + _cpu.Regs[_cpu.Ixl] = (ushort)((IX >> 8) & 0xff); + } + + /// + /// Gets the AF word value + /// + /// + public ushort Get16BitAF() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.A] | _cpu.Regs[_cpu.F] << 8); + } + + /// + /// Set the AF word value + /// + /// + /// + public void Set16BitAF(ushort AF) + { + _cpu.Regs[_cpu.A] = (ushort)(AF & 0xFF); + _cpu.Regs[_cpu.F] = (ushort)((AF >> 8) & 0xff); + } + + /// + /// Gets the AF shadow word value + /// + /// + public ushort Get16BitAF_() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.A_s] | _cpu.Regs[_cpu.F_s] << 8); + } + + /// + /// Set the AF shadow word value + /// + /// + /// + public void Set16BitAF_(ushort AF_) + { + _cpu.Regs[_cpu.A_s] = (ushort)(AF_ & 0xFF); + _cpu.Regs[_cpu.F_s] = (ushort)((AF_ >> 8) & 0xff); + } + + /// + /// Gets the DE word value + /// + /// + public ushort Get16BitDE() + { + return Convert.ToUInt16(_cpu.Regs[_cpu.E] | _cpu.Regs[_cpu.D] << 8); + } + + /// + /// Set the DE word value + /// + /// + /// + public void Set16BitDE(ushort DE) + { + _cpu.Regs[_cpu.D] = (ushort)(DE & 0xFF); + _cpu.Regs[_cpu.E] = (ushort)((DE >> 8) & 0xff); + } + + + /// + /// Z80 Status Indicator Flag Reset masks + /// + /// + [Flags] + public enum FlagsResetMask : byte + { + /// Sign Flag + S = 0x7F, + + /// Zero Flag + Z = 0xBF, + + /// This flag is not used. + R5 = 0xDF, + + /// Half Carry Flag + H = 0xEF, + + /// This flag is not used. + R3 = 0xF7, + + /// Parity/Overflow Flag + PV = 0xFB, + + /// Add/Subtract Flag + N = 0xFD, + + /// Carry Flag + C = 0xFE, + } + + /// + /// Z80 Status Indicator Flag Set masks + /// + /// + [Flags] + public enum FlagsSetMask : byte + { + /// Sign Flag + /// + /// The Sign Flag (S) stores the state of the most-significant bit of + /// the Accumulator (bit 7). When the Z80 CPU performs arithmetic + /// operations on signed numbers, the binary twos complement notation + /// is used to represent and process numeric information. + /// + S = 0x80, + + /// + /// Zero Flag + /// + /// + /// The Zero Flag is set (1) or cleared (0) if the result generated by + /// the execution of certain instructions is 0. For 8-bit arithmetic and + /// logical operations, the Z flag is set to a 1 if the resulting byte in + /// the Accumulator is 0. If the byte is not 0, the Z flag is reset to 0. + /// + Z = 0x40, + + /// This flag is not used. + R5 = 0x20, + + /// Half Carry Flag + /// + /// The Half Carry Flag (H) is set (1) or cleared (0) depending on the + /// Carry and Borrow status between bits 3 and 4 of an 8-bit arithmetic + /// operation. This flag is used by the Decimal Adjust Accumulator (DAA) + /// instruction to correct the result of a packed BCD add or subtract operation. + /// + H = 0x10, + + /// This flag is not used. + R3 = 0x08, + + /// Parity/Overflow Flag + /// + /// The Parity/Overflow (P/V) Flag is set to a specific state depending on + /// the operation being performed. For arithmetic operations, this flag + /// indicates an overflow condition when the result in the Accumulator is + /// greater than the maximum possible number (+127) or is less than the + /// minimum possible number (–128). This overflow condition is determined by + /// examining the sign bits of the operands. + /// + PV = 0x04, + + /// Add/Subtract Flag + /// + /// The Add/Subtract Flag (N) is used by the Decimal Adjust Accumulator + /// instruction (DAA) to distinguish between the ADD and SUB instructions. + /// For ADD instructions, N is cleared to 0. For SUB instructions, N is set to 1. + /// + N = 0x02, + + /// Carry Flag + /// + /// The Carry Flag (C) is set or cleared depending on the operation being performed. + /// + C = 0x01, + + /// + /// Combination of S, Z, and PV + /// + SZPV = S | Z | PV, + + /// + /// Combination of N, and H + /// + NH = N | H, + + /// + /// Combination of R3, and R5 + /// + R3R5 = R3 | R5 } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index a77c7a501c..cb94079bad 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -35,7 +35,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition48; - Init(MachineType.ZXSpectrum48, Settings.BorderType, Settings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum48, Settings.BorderType, SyncSettings.TapeLoadSpeed, file); break; default: throw new InvalidOperationException("Machine not yet emulated"); From dba8e1f0496dce38cee526455c92c97601238fe9 Mon Sep 17 00:00:00 2001 From: Asnvior Date: Tue, 28 Nov 2017 20:09:00 +0000 Subject: [PATCH 004/105] Added readme --- .../BizHawk.Emulation.Cores.csproj | 1 + .../Computers/SinclairSpectrum/readme.md | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 2e488bd9e9..1026644554 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1373,6 +1373,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md new file mode 100644 index 0000000000..eacc65afa1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -0,0 +1,29 @@ +## ZXHawk + +At this moment this is still *very* experimental and needs a lot more work. + +### Implemented and sorta working +* IEmulator +* ZX Spectrum 48k model +* ULA video output (implementing IVideoProvider) +* ULA Mode 1 VBLANK interrupt generation +* Beeper/Buzzer output (implementing ISoundProvider) +* Keyboard input (implementing IInputPollable) +* Tape device that will load spectrum games in realtime (*.tzx and *.tap) +* IStatable (although this is not currently working/implemented properly during tape load operations) + +### Some progress +* ISettable - There are some Settings and SyncSettings instantiated, although they are not really used and I haven't yet figured out how to wire these up to the front-end yet + +### Not working +* Interrupt Mode 2 (Z80A) - usually invokes a soft reboot when the game raises one (can be seen in 'Chaos - Battle of the Wizards' after initial game setup when the game board tries to load) +* IMemoryDomains - I started looking at this but didn't really know what I was doing yet +* IDebuggable +* Default keyboard keymappings (you have to configure yourself in the core controller settings) +* Joystick support (I still need to implement a Kemptston joystick and interface) +* Manual tape device control (at the moment the tape device detects when the spectrum goes into 'loadbytes' mode and auto-plays the tape. This is not ideal and manual control should be implemented so the user can start/stop manually, return to zero etc..) +* Only standard spectrum tape blocks currently work. Any fancy SpeedLock encoded (and similar) blocks do not + +### Known bugs +* The 'return' keyboard key is acting the same as Space/Break when doing a BASIC RUN or LOAD "" command. The upshot of this is that upon boot, when you go to load the attached spectrum cassette (you have to type: "J", then "SYMSHIFT + P", then "SYMSHIFT + P", then RETURN) it more often than not interrupts the load routine. You then have to try again but hitting the RETURN key at the end of the sequence for as small a time as possible. Rinse and repeat until the load process starts. Clearly NOT ideal. +* Audible 'popping' from the emulated buzzer after a load state operation From d09e73b592a5a44b546f1b301e00f2f529fbb29c Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Wed, 29 Nov 2017 16:28:08 -0500 Subject: [PATCH 005/105] z80: implement data bus -needed for ZXspectrum mode 2 interrupts -use with FetchDB function --- .../CPUs/Z80A/Interrupts.cs | 18 +++++++++--------- .../CPUs/Z80A/Operations.cs | 11 +++++++++++ BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs | 2 +- BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs | 12 ++++++++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs index 8dc1cd24ce..88e0a264c2 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs @@ -89,22 +89,22 @@ namespace BizHawk.Emulation.Cores.Components.Z80A cur_instr = new ushort[] {IDLE, IDLE, + FTCH_DB, + TR, Z, DB, + TR, W, I, + IDLE, DEC16, SPl, SPh, WR, SPl, SPh, PCh, IDLE, DEC16, SPl, SPh, - WR, SPl, SPh, PCl, - IDLE, - ASGN, PCl, 0, - TR, PCh, I, - IDLE, + WR, SPl, SPh, PCl, IDLE, - RD, Z, PCl, PCh, - INC16, PCl, PCh, + RD, PCl, Z, W, + INC16, Z, W, + IDLE, + RD, PCh, Z, W, IDLE, - RD, W, PCl, PCh, IDLE, - TR16, PCl, PCh, Z, W, OP }; } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs index cfaf517ec7..81f9e1b93a 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs @@ -8,31 +8,37 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public void Read_Func(ushort dest, ushort src_l, ushort src_h) { Regs[dest] = ReadMemory((ushort)(Regs[src_l] | (Regs[src_h]) << 8)); + Regs[DB] = Regs[dest]; } public void I_Read_Func(ushort dest, ushort src_l, ushort src_h, ushort inc) { Regs[dest] = ReadMemory((ushort)((Regs[src_l] | (Regs[src_h] << 8)) + inc)); + Regs[DB] = Regs[dest]; } public void Write_Func(ushort dest_l, ushort dest_h, ushort src) { + Regs[DB] = Regs[src]; WriteMemory((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), (byte)Regs[src]); } public void I_Write_Func(ushort dest_l, ushort dest_h, ushort inc, ushort src) { + Regs[DB] = Regs[src]; WriteMemory((ushort)((Regs[dest_l] | (Regs[dest_h] << 8)) + inc), (byte)Regs[src]); } public void OUT_Func(ushort dest, ushort src) { + Regs[DB] = Regs[src]; WriteHardware(Regs[dest], (byte)(Regs[src])); } public void IN_Func(ushort dest, ushort src) { Regs[dest] = ReadHardware(Regs[src]); + Regs[DB] = Regs[dest]; } public void TR_Func(ushort dest, ushort src) @@ -738,5 +744,10 @@ namespace BizHawk.Emulation.Cores.Components.Z80A Flag3 = (Regs[A] & 0x08) != 0; } } + + public void FTCH_DB_Func() + { + Regs[DB] = FetchDB(); + } } } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs index 628243c19e..14e447bc65 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Registers.cs @@ -6,7 +6,6 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public partial class Z80A { // registers - // note these are not constants. When shadows are used, they will be changed accordingly public ushort PCl = 0; public ushort PCh = 1; public ushort SPl = 2; @@ -40,6 +39,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public ushort E_s = 29; public ushort H_s = 30; public ushort L_s = 31; + public ushort DB = 32; public ushort[] Regs = new ushort[36]; diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs index 46abee9809..623a2aa126 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs @@ -74,6 +74,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public const ushort SET_FL_IR = 59; public const ushort I_BIT = 60; public const ushort HL_BIT = 61; + public const ushort FTCH_DB = 62; public byte temp_R; @@ -106,6 +107,11 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public Func ReadHardware; public Action WriteHardware; + // Data BUs + // Interrupting Devices are responsible for putting a value onto the data bus + // for as long as the interrupt is valid + public Func FetchDB; + //this only calls when the first byte of an instruction is fetched. public Action OnExecFetch; @@ -339,6 +345,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A case HALT: halted = true; + // NOTE: Check how halt state effects the DB + Regs[DB] = 0xFF; + if (EI_pending > 0) { EI_pending--; @@ -595,6 +604,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A case SET_FL_IR: SET_FL_IR_Func(cur_instr[instr_pntr++]); break; + case FTCH_DB: + FTCH_DB_Func(); + break; } totalExecutedCycles++; } From 7428e8e6737a452fcb0e52a1485d109bb2641f0d Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Wed, 29 Nov 2017 16:30:54 -0500 Subject: [PATCH 006/105] ZX Spectrum: Draft DB access --- .../Machine/SpectrumBase.Memory.cs | 354 +++++++++--------- 1 file changed, 182 insertions(+), 172 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index d993b15f32..5b371bbc7f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -1,172 +1,182 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// The abstract class that all emulated models will inherit from - /// * Memory * - /// - public abstract partial class SpectrumBase - { - /// - /// ROM Banks - /// - public byte[] ROM0 = new byte[0x4000]; - public byte[] ROM1 = new byte[0x4000]; - public byte[] ROM2 = new byte[0x4000]; - public byte[] ROM3 = new byte[0x4000]; - - /// - /// RAM Banks - /// - public byte[] RAM0 = new byte[0x4000]; // Bank 0 - public byte[] RAM1 = new byte[0x4000]; // Bank 1 - public byte[] RAM2 = new byte[0x4000]; // Bank 2 - public byte[] RAM3 = new byte[0x4000]; // Bank 3 - public byte[] RAM4 = new byte[0x4000]; // Bank 4 - public byte[] RAM5 = new byte[0x4000]; // Bank 5 - public byte[] RAM6 = new byte[0x4000]; // Bank 6 - public byte[] RAM7 = new byte[0x4000]; // Bank 7 - - /// - /// Represents the addressable memory space of the spectrum - /// All banks for the emulated system should be added during initialisation - /// - public Dictionary Memory = new Dictionary(); - - /// - /// Simulates reading from the bus - /// Paging should be handled here - /// - /// - /// - public virtual byte ReadBus(ushort addr) - { - throw new NotImplementedException("Must be overriden"); - } - - /// - /// Simulates writing to the bus - /// Paging should be handled here - /// - /// - /// - public virtual void WriteBus(ushort addr, byte value) - { - throw new NotImplementedException("Must be overriden"); - } - - /// - /// Reads a byte of data from a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public virtual byte ReadMemory(ushort addr) - { - throw new NotImplementedException("Must be overriden"); - } - /* - /// - /// Reads a byte of data from a specified memory address - /// (with no memory contention) - /// - /// - /// - public virtual byte PeekMemory(ushort addr) - { - var data = ReadBus(addr); - return data; - } - */ - - /// - /// Writes a byte of data to a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public virtual void WriteMemory(ushort addr, byte value) - { - throw new NotImplementedException("Must be overriden"); - } - - /* - /// - /// Writes a byte of data to a specified memory address - /// (without contention) - /// - /// - /// - public virtual void PokeMemory(ushort addr, byte value) - { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - - WriteBus(addr, value); - } - */ - - /// - /// Fills memory from buffer - /// - /// - /// - public virtual void FillMemory(byte[] buffer, ushort startAddress) - { - //buffer?.CopyTo(RAM, startAddress); - } - - /// - /// Sets up the ROM - /// - /// - /// - public virtual void InitROM(RomData romData) - { - RomData = romData; - // for 16/48k machines only ROM0 is used (no paging) - RomData.RomBytes?.CopyTo(ROM0, 0); - } - - /// - /// ULA reads the memory at the specified address - /// (No memory contention) - /// - /// - /// - public virtual byte FetchScreenMemory(ushort addr) - { - //var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; - var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000)); - return value; - } - - /// - /// Helper function to refresh memory array (probably not the best way to do things) - /// - public virtual void ReInitMemory() - { - throw new NotImplementedException("Must be overriden"); - } - - /// - /// 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 - /// - /// - /// - public virtual byte GetContentionValue(int cycle) - { - var val = _renderingCycleTable[cycle % UlaFrameCycleCount].ContentionDelay; - return val; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// The abstract class that all emulated models will inherit from + /// * Memory * + /// + public abstract partial class SpectrumBase + { + /// + /// ROM Banks + /// + public byte[] ROM0 = new byte[0x4000]; + public byte[] ROM1 = new byte[0x4000]; + public byte[] ROM2 = new byte[0x4000]; + public byte[] ROM3 = new byte[0x4000]; + + /// + /// RAM Banks + /// + public byte[] RAM0 = new byte[0x4000]; // Bank 0 + public byte[] RAM1 = new byte[0x4000]; // Bank 1 + public byte[] RAM2 = new byte[0x4000]; // Bank 2 + public byte[] RAM3 = new byte[0x4000]; // Bank 3 + public byte[] RAM4 = new byte[0x4000]; // Bank 4 + public byte[] RAM5 = new byte[0x4000]; // Bank 5 + public byte[] RAM6 = new byte[0x4000]; // Bank 6 + public byte[] RAM7 = new byte[0x4000]; // Bank 7 + + /// + /// Represents the addressable memory space of the spectrum + /// All banks for the emulated system should be added during initialisation + /// + public Dictionary Memory = new Dictionary(); + + /// + /// Simulates reading from the bus + /// Paging should be handled here + /// + /// + /// + public virtual byte ReadBus(ushort addr) + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// Pushes a value onto the data bus that should be valid as long as the interrupt is true + /// + /// + /// + public virtual byte PushBus() + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// Simulates writing to the bus + /// Paging should be handled here + /// + /// + /// + public virtual void WriteBus(ushort addr, byte value) + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public virtual byte ReadMemory(ushort addr) + { + throw new NotImplementedException("Must be overriden"); + } + /* + /// + /// Reads a byte of data from a specified memory address + /// (with no memory contention) + /// + /// + /// + public virtual byte PeekMemory(ushort addr) + { + var data = ReadBus(addr); + return data; + } + */ + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public virtual void WriteMemory(ushort addr, byte value) + { + throw new NotImplementedException("Must be overriden"); + } + + /* + /// + /// Writes a byte of data to a specified memory address + /// (without contention) + /// + /// + /// + public virtual void PokeMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + + WriteBus(addr, value); + } + */ + + /// + /// Fills memory from buffer + /// + /// + /// + public virtual void FillMemory(byte[] buffer, ushort startAddress) + { + //buffer?.CopyTo(RAM, startAddress); + } + + /// + /// Sets up the ROM + /// + /// + /// + public virtual void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// + /// + /// + public virtual byte FetchScreenMemory(ushort addr) + { + //var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; + var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000)); + return value; + } + + /// + /// Helper function to refresh memory array (probably not the best way to do things) + /// + public virtual void ReInitMemory() + { + throw new NotImplementedException("Must be overriden"); + } + + /// + /// 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 + /// + /// + /// + public virtual byte GetContentionValue(int cycle) + { + var val = _renderingCycleTable[cycle % UlaFrameCycleCount].ContentionDelay; + return val; + } + } +} From 07b9e1243c5738cbe1cfcd9f3385c5d09707ed1f Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Wed, 29 Nov 2017 16:31:53 -0500 Subject: [PATCH 007/105] ZX Spectrum draft DB access --- .../Machine/ZXSpectrum48K/ZX48.cs | 382 +++++++++--------- 1 file changed, 196 insertions(+), 186 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index ffbc38c13c..57e5e14c1a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -1,186 +1,196 @@ -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 - { - #region Construction - - /// - /// Main constructor - /// - /// - /// - public ZX48(ZXSpectrum spectrum, Z80A cpu, byte[] file) - { - Spectrum = spectrum; - CPU = cpu; - - // init addressable memory from ROM and RAM banks - /* - Memory.Add(0, ROM0); - Memory.Add(1, RAM0); - Memory.Add(2, RAM1); - Memory.Add(3, RAM2); - */ - ReInitMemory(); - - //RAM = new byte[0x4000 + 0xC000]; - - InitScreenConfig(); - InitScreen(); - - ResetULACycle(); - - BuzzerDevice = new Buzzer(this); - BuzzerDevice.Init(44100, UlaFrameCycleCount); - - KeyboardDevice = new Keyboard48(this); - - TapeProvider = new DefaultTapeProvider(file); - - TapeDevice = new Tape(TapeProvider); - TapeDevice.Init(this); - } - - #endregion - - #region MemoryMapping - - /* 48K Spectrum has NO memory paging - * - * 0xffff +--------+ - | Bank 2 | - | | - | | - | | - 0xc000 +--------+ - | Bank 1 | - | | - | | - | | - 0x8000 +--------+ - | Bank 0 | - | | - | | - | screen | - 0x4000 +--------+ - | ROM 0 | - | | - | | - | | - 0x0000 +--------+ - */ - - /// - /// Simulates reading from the bus (no contention) - /// Paging should be handled here - /// - /// - /// - public override byte ReadBus(ushort addr) - { - int divisor = addr / 0x4000; - // paging logic goes here - - var bank = Memory[divisor]; - var index = addr % 0x4000; - return bank[index]; - } - - /// - /// Simulates writing to the bus (no contention) - /// Paging should be handled here - /// - /// - /// - public override void WriteBus(ushort addr, byte value) - { - int divisor = addr / 0x4000; - // paging logic goes here - - var bank = Memory[divisor]; - var index = addr % 0x4000; - bank[index] = value; - } - - /// - /// Reads a byte of data from a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public override byte ReadMemory(ushort addr) - { - var data = ReadBus(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; - } - - /// - /// Writes a byte of data to a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public override void WriteMemory(ushort addr, byte value) - { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - else if (addr < 0xC000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } - - WriteBus(addr, value); - } - - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = RAM0; - else - Memory.Add(1, RAM0); - - if (Memory.ContainsKey(2)) - Memory[2] = RAM1; - else - Memory.Add(2, RAM1); - - if (Memory.ContainsKey(3)) - Memory[3] = RAM2; - else - Memory.Add(3, RAM2); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM3; - else - Memory.Add(4, RAM3); - } - - - #endregion - - - } -} +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 + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX48(ZXSpectrum spectrum, Z80A cpu, byte[] file) + { + Spectrum = spectrum; + CPU = cpu; + + // init addressable memory from ROM and RAM banks + /* + Memory.Add(0, ROM0); + Memory.Add(1, RAM0); + Memory.Add(2, RAM1); + Memory.Add(3, RAM2); + */ + ReInitMemory(); + + //RAM = new byte[0x4000 + 0xC000]; + + InitScreenConfig(); + InitScreen(); + + ResetULACycle(); + + BuzzerDevice = new Buzzer(this); + BuzzerDevice.Init(44100, UlaFrameCycleCount); + + KeyboardDevice = new Keyboard48(this); + + TapeProvider = new DefaultTapeProvider(file); + + TapeDevice = new Tape(TapeProvider); + TapeDevice.Init(this); + } + + #endregion + + #region MemoryMapping + + /* 48K Spectrum has NO memory paging + * + * 0xffff +--------+ + | Bank 2 | + | | + | | + | | + 0xc000 +--------+ + | Bank 1 | + | | + | | + | | + 0x8000 +--------+ + | Bank 0 | + | | + | | + | screen | + 0x4000 +--------+ + | ROM 0 | + | | + | | + | | + 0x0000 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + return bank[index]; + } + + /// + /// Pushes a value onto the data bus that should be valid as long as the interrupt is true + /// + /// + /// + public override byte PushBus() + { + return 0xFF; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + bank[index] = value; + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = RAM0; + else + Memory.Add(1, RAM0); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM1; + else + Memory.Add(2, RAM1); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM2; + else + Memory.Add(3, RAM2); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM3; + else + Memory.Add(4, RAM3); + } + + + #endregion + + + } +} From 30061a35362df160a97290290e302c01283d64c4 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Wed, 29 Nov 2017 16:32:34 -0500 Subject: [PATCH 008/105] ZX Spectrum draft DB Access --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 271 +++++++++--------- 1 file changed, 137 insertions(+), 134 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index cb94079bad..98617170a5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -1,134 +1,137 @@ -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, SyncSettings.TapeLoadSpeed, file); - 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(_tracer); - ser.Register(_cpu); - ser.Register(_machine); - ser.Register(_machine.BuzzerDevice); - - HardReset(); - - - - List romDis = new List(); - List disas = new List(); - 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[] _file; - - 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, byte[] file) - { - // setup the emulated model based on the MachineType - switch (machineType) - { - case MachineType.ZXSpectrum48: - _machine = new ZX48(this, _cpu, file); - var _systemRom = GetFirmware(0x4000, "48ROM"); - var romData = RomData.InitROM(machineType, _systemRom); - _machine.InitROM(romData); - break; - } - } - - #region IRegionable - - public DisplayType Region => DisplayType.PAL; - - #endregion - - - } -} +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, SyncSettings.TapeLoadSpeed, file); + 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; + _cpu.FetchDB = _machine.PushBus; + + ser.Register(_tracer); + ser.Register(_cpu); + ser.Register(_machine); + ser.Register(_machine.BuzzerDevice); + + HardReset(); + + + + List romDis = new List(); + List disas = new List(); + 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); + } + + SetupMemoryDomains(); + } + + 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[] _file; + + 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, byte[] file) + { + // setup the emulated model based on the MachineType + switch (machineType) + { + case MachineType.ZXSpectrum48: + _machine = new ZX48(this, _cpu, file); + var _systemRom = GetFirmware(0x4000, "48ROM"); + var romData = RomData.InitROM(machineType, _systemRom); + _machine.InitROM(romData); + break; + } + } + + #region IRegionable + + public DisplayType Region => DisplayType.PAL; + + #endregion + + + } +} From b38760caeb22fb859c5d17869148946aac7ca248 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 30 Nov 2017 09:41:30 +0000 Subject: [PATCH 009/105] Internal facility to use DiagROM --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 98617170a5..806b02ccc3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -3,6 +3,7 @@ using BizHawk.Emulation.Cores.Components; using BizHawk.Emulation.Cores.Components.Z80A; using System; using System.Collections.Generic; +using System.IO; using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum @@ -29,13 +30,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu = new Z80A(); - _tracer = new TraceBuffer { Header = _cpu.TraceHeader }; - + _tracer = new TraceBuffer { Header = _cpu.TraceHeader }; + + _file = file; + switch (Settings.MachineType) { case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition48; - Init(MachineType.ZXSpectrum48, Settings.BorderType, SyncSettings.TapeLoadSpeed, file); + Init(MachineType.ZXSpectrum48, Settings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; default: throw new InvalidOperationException("Machine not yet emulated"); @@ -62,46 +65,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum HardReset(); - - - List romDis = new List(); - List disas = new List(); - 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); - } - SetupMemoryDomains(); } - - 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[] _file; + + public bool DiagRom = false; + private byte[] GetFirmware(int length, params string[] names) { + if (DiagRom & File.Exists(Directory.GetCurrentDirectory() + @"\DiagROM.v28")) + { + var rom = File.ReadAllBytes(Directory.GetCurrentDirectory() + @"\DiagROM.v28"); + return rom; + } + var result = names.Select(n => CoreComm.CoreFileProvider.GetFirmware("ZXSpectrum", n, false)).FirstOrDefault(b => b != null && b.Length == length); if (result == null) { From 64bb08cbb763509393fb4c9c7c0b190f60cd96c6 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 30 Nov 2017 12:08:36 +0000 Subject: [PATCH 010/105] un-refactored input code and added some +keyboard combinations --- .../Hardware/Interfaces/IKeyboard.cs | 18 +- .../Machine/SpectrumBase.Input.cs | 28 ++- .../Machine/SpectrumBase.Port.cs | 82 ++++++- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 212 +++++++++++++++++- .../ZXSpectrum.Controllers.cs | 69 +----- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- 6 files changed, 326 insertions(+), 85 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs index fe4f188ca6..0240fd52f5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs @@ -20,14 +20,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string[] KeyboardMatrix { get; set; } /// - /// For 16/48k models + /// Other keyboard keys that are not in the matrix + /// (usually keys derived from key combos) /// - bool Issue2 { get; set; } + string[] NonMatrixKeys { get; set; } /// - /// The current keyboard line status + /// Represents the spectrum key state /// - //byte[] LineStatus { get; set; } + int[] KeyLine { get; set; } + + /// + /// There are some slight differences in how PortIN and PortOUT functions + /// between Issue2 and Issue3 keyboards (16k/48k spectrum only) + /// It is possible that some very old games require Issue2 emulation + /// + bool IsIssue2Keyboard { get; set; } /// /// Sets the spectrum key status @@ -64,6 +72,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// byte GetByteFromKeyMatrix(string key); + + void SyncState(Serializer ser); } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 01ac5ab51a..d9fb158b9f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -15,14 +15,29 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { Spectrum.InputCallbacks.Call(); - 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); + lock (this) + { + // parse single keyboard matrix keys + 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) + //if (currState != prevState) KeyboardDevice.SetKeyStatus(key, currState); + } + + // non matrix keys + foreach (string k in KeyboardDevice.NonMatrixKeys) + { + if (!k.StartsWith("Key")) + continue; + + bool currState = Spectrum._controller.IsPressed(k); + + KeyboardDevice.SetKeyStatus(k, currState); + } } // Tape control @@ -45,3 +60,4 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index a8b715a214..a37b24d22d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -26,18 +26,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { CPU.TotalExecutedCycles += 4; - byte result = 0xFF; + int 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((port << 8 | high)); + //ushort word = Convert.ToUInt16((port << 8 | high)); + int word = (high << 8) + port; // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x0001) == 0; - + bool lowBitReset = (port & 0x0001) == 0; + // Kempston Joystick //not implemented yet @@ -53,15 +54,80 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B */ + if ((word & 0x8000) == 0) + result &= KeyboardDevice.KeyLine[7]; + + if ((word & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((word & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((word & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((word & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((word & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((word & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((word & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + if (TapeDevice.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (KeyboardDevice.IsIssue2Keyboard) + { + if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + else + { + if ((LastULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + /* // read keyboard input if (high != 0) - result = KeyboardDevice.GetLineStatus((byte)high); + result &= KeyboardDevice.GetLineStatus((byte)high); var ear = TapeDevice.GetEarBit(CPU.TotalExecutedCycles); if (!ear) { result = (byte)(result & Convert.ToInt32("10111111", 2)); } + */ } else { @@ -75,7 +141,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // if unused port the floating memory bus should be returned (still todo) } - return result; + return (byte)(result & 0xff); } /// @@ -103,10 +169,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum | | | | E | M | Border | +-------------------------------+ */ - + // Border - LSB 3 bits hold the border colour BorderColour = value & BORDER_BIT; - + // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index 501dd6186b..c20faa7fa4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -13,9 +13,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class Keyboard48 : IKeyboard { public SpectrumBase _machine { get; set; } - private byte[] LineStatus; - public bool Issue2 { get; set; } + private byte[] LineStatus; private string[] _keyboardMatrix; + private int[] _keyLine; + private bool _isIssue2Keyboard; + private string[] _nonMatrixKeys; + + public bool IsIssue2Keyboard + { + get { return _isIssue2Keyboard; } + set { _isIssue2Keyboard = value; } + } + + public int[] KeyLine + { + get { return _keyLine; } + set { _keyLine = value; } + } public string[] KeyboardMatrix { @@ -23,6 +37,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum set { _keyboardMatrix = value; } } + public string[] NonMatrixKeys + { + get { return _nonMatrixKeys; } + set { _nonMatrixKeys = value; } + } + public Keyboard48(SpectrumBase machine) { _machine = machine; @@ -44,23 +64,185 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // 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" + "Key Space", "Key Symbol Shift", "Key M", "Key N", "Key B" }; + var nonMatrix = new List(); + foreach (var key in ZXSpectrum.ZXSpectrumControllerDefinition.BoolButtons) + { + if (!KeyboardMatrix.Any(s => s == key)) + nonMatrix.Add(key); + } + NonMatrixKeys = nonMatrix.ToArray(); + LineStatus = new byte[8]; + _keyLine = new int[] { 255, 255, 255, 255, 255, 255, 255, 255 }; + IsIssue2Keyboard = false; } - public void SetKeyStatus(string key, bool isPressed) + 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); + */ + + int k = GetByteFromKeyMatrix(key); + + if (isPressed) + { + switch (k) + { + // 0xfefe - 0 - 4 + case 0: _keyLine[0] = (_keyLine[0] & ~(0x01)); break; + case 1: _keyLine[0] = (_keyLine[0] & ~(0x02)); break; + case 2: _keyLine[0] = (_keyLine[0] & ~(0x04)); break; + case 3: _keyLine[0] = (_keyLine[0] & ~(0x08)); break; + case 4: _keyLine[0] = (_keyLine[0] & ~(0x10)); break; + // 0xfdfe - 5 - 9 + case 5: _keyLine[1] = (_keyLine[1] & ~(0x01)); break; + case 6: _keyLine[1] = (_keyLine[1] & ~(0x02)); break; + case 7: _keyLine[1] = (_keyLine[1] & ~(0x04)); break; + case 8: _keyLine[1] = (_keyLine[1] & ~(0x08)); break; + case 9: _keyLine[1] = (_keyLine[1] & ~(0x10)); break; + // 0xfbfe - 10 - 14 + case 10: _keyLine[2] = (_keyLine[2] & ~(0x01)); break; + case 11: _keyLine[2] = (_keyLine[2] & ~(0x02)); break; + case 12: _keyLine[2] = (_keyLine[2] & ~(0x04)); break; + case 13: _keyLine[2] = (_keyLine[2] & ~(0x08)); break; + case 14: _keyLine[2] = (_keyLine[2] & ~(0x10)); break; + // 0xf7fe - 15 - 19 + case 15: _keyLine[3] = (_keyLine[3] & ~(0x01)); break; + case 16: _keyLine[3] = (_keyLine[3] & ~(0x02)); break; + case 17: _keyLine[3] = (_keyLine[3] & ~(0x04)); break; + case 18: _keyLine[3] = (_keyLine[3] & ~(0x08)); break; + case 19: _keyLine[3] = (_keyLine[3] & ~(0x10)); break; + // 0xeffe - 20 - 24 + case 20: _keyLine[4] = (_keyLine[4] & ~(0x01)); break; + case 21: _keyLine[4] = (_keyLine[4] & ~(0x02)); break; + case 22: _keyLine[4] = (_keyLine[4] & ~(0x04)); break; + case 23: _keyLine[4] = (_keyLine[4] & ~(0x08)); break; + case 24: _keyLine[4] = (_keyLine[4] & ~(0x10)); break; + // 0xdffe - 25 - 29 + case 25: _keyLine[5] = (_keyLine[5] & ~(0x01)); break; + case 26: _keyLine[5] = (_keyLine[5] & ~(0x02)); break; + case 27: _keyLine[5] = (_keyLine[5] & ~(0x04)); break; + case 28: _keyLine[5] = (_keyLine[5] & ~(0x08)); break; + case 29: _keyLine[5] = (_keyLine[5] & ~(0x10)); break; + // 0xbffe - 30 - 34 + case 30: _keyLine[6] = (_keyLine[6] & ~(0x01)); break; + case 31: _keyLine[6] = (_keyLine[6] & ~(0x02)); break; + case 32: _keyLine[6] = (_keyLine[6] & ~(0x04)); break; + case 33: _keyLine[6] = (_keyLine[6] & ~(0x08)); break; + case 34: _keyLine[6] = (_keyLine[6] & ~(0x10)); break; + // 0x7ffe - 35 - 39 + case 35: _keyLine[7] = (_keyLine[7] & ~(0x01)); break; + case 36: _keyLine[7] = (_keyLine[7] & ~(0x02)); break; + case 37: _keyLine[7] = (_keyLine[7] & ~(0x04)); break; + case 38: _keyLine[7] = (_keyLine[7] & ~(0x08)); break; + case 39: _keyLine[7] = (_keyLine[7] & ~(0x10)); break; + } + } + else + { + switch (k) + { + // 0xfefe - 0 - 4 + case 0: _keyLine[0] = (_keyLine[0] | (0x01)); break; + case 1: _keyLine[0] = (_keyLine[0] | (0x02)); break; + case 2: _keyLine[0] = (_keyLine[0] | (0x04)); break; + case 3: _keyLine[0] = (_keyLine[0] | (0x08)); break; + case 4: _keyLine[0] = (_keyLine[0] | (0x10)); break; + // 0xfdfe - 5 - 9 + case 5: _keyLine[1] = (_keyLine[1] | (0x01)); break; + case 6: _keyLine[1] = (_keyLine[1] | (0x02)); break; + case 7: _keyLine[1] = (_keyLine[1] | (0x04)); break; + case 8: _keyLine[1] = (_keyLine[1] | (0x08)); break; + case 9: _keyLine[1] = (_keyLine[1] | (0x10)); break; + // 0xfbfe - 10 - 14 + case 10: _keyLine[2] = (_keyLine[2] | (0x01)); break; + case 11: _keyLine[2] = (_keyLine[2] | (0x02)); break; + case 12: _keyLine[2] = (_keyLine[2] | (0x04)); break; + case 13: _keyLine[2] = (_keyLine[2] | (0x08)); break; + case 14: _keyLine[2] = (_keyLine[2] | (0x10)); break; + // 0xf7fe - 15 - 19 + case 15: _keyLine[3] = (_keyLine[3] | (0x01)); break; + case 16: _keyLine[3] = (_keyLine[3] | (0x02)); break; + case 17: _keyLine[3] = (_keyLine[3] | (0x04)); break; + case 18: _keyLine[3] = (_keyLine[3] | (0x08)); break; + case 19: _keyLine[3] = (_keyLine[3] | (0x10)); break; + // 0xeffe - 20 - 24 + case 20: _keyLine[4] = (_keyLine[4] | (0x01)); break; + case 21: _keyLine[4] = (_keyLine[4] | (0x02)); break; + case 22: _keyLine[4] = (_keyLine[4] | (0x04)); break; + case 23: _keyLine[4] = (_keyLine[4] | (0x08)); break; + case 24: _keyLine[4] = (_keyLine[4] | (0x10)); break; + // 0xdffe - 25 - 29 + case 25: _keyLine[5] = (_keyLine[5] | (0x01)); break; + case 26: _keyLine[5] = (_keyLine[5] | (0x02)); break; + case 27: _keyLine[5] = (_keyLine[5] | (0x04)); break; + case 28: _keyLine[5] = (_keyLine[5] | (0x08)); break; + case 29: _keyLine[5] = (_keyLine[5] | (0x10)); break; + // 0xbffe - 30 - 34 + case 30: _keyLine[6] = (_keyLine[6] | (0x01)); break; + case 31: _keyLine[6] = (_keyLine[6] | (0x02)); break; + case 32: _keyLine[6] = (_keyLine[6] | (0x04)); break; + case 33: _keyLine[6] = (_keyLine[6] | (0x08)); break; + case 34: _keyLine[6] = (_keyLine[6] | (0x10)); break; + // 0x7ffe - 35 - 39 + case 35: _keyLine[7] = (_keyLine[7] | (0x01)); break; + case 36: _keyLine[7] = (_keyLine[7] | (0x02)); break; + case 37: _keyLine[7] = (_keyLine[7] | (0x04)); break; + case 38: _keyLine[7] = (_keyLine[7] | (0x08)); break; + case 39: _keyLine[7] = (_keyLine[7] | (0x10)); break; + } + } + + // Combination keys that are not in the keyboard matrix + // but are available on the Spectrum+, 128k +2 & +3 + // (GetByteFromKeyMatrix() should return 255) + // Processed after the matrix keys - only presses handled (unpressed get done above) + if (k == 255) + { + if (isPressed) + { + switch (key) + { + // Delete key (simulates Caps Shift + 0) + case "Key Delete": + _keyLine[0] = _keyLine[0] & ~(0x1); + _keyLine[4] = _keyLine[4] & ~(0x1); + break; + // Cursor left (simulates Caps Shift + 5) + case "Key Left Cursor": + _keyLine[0] = _keyLine[0] & ~(0x1); + _keyLine[3] = _keyLine[3] & ~(0x10); + break; + // Cursor right (simulates Caps Shift + 8) + case "Key Right Cursor": + _keyLine[0] = _keyLine[0] & ~(0x1); + _keyLine[4] = _keyLine[4] & ~(0x04); + break; + // Cursor up (simulates Caps Shift + 7) + case "Key Up Cursor": + _keyLine[0] = _keyLine[0] & ~(0x1); + _keyLine[4] = _keyLine[4] & ~(0x08); + break; + // Cursor down (simulates Caps Shift + 6) + case "Key Down Cursor": + _keyLine[0] = _keyLine[0] & ~(0x1); + _keyLine[4] = _keyLine[4] & ~(0x10); + break; + } + } + } } - public bool GetKeyStatus(string key) + public bool GetKeyStatus(string key) { byte keyByte = GetByteFromKeyMatrix(key); var lineIndex = keyByte / 5; @@ -68,8 +250,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return (LineStatus[lineIndex] & lineMask) != 0; } - public byte GetLineStatus(byte lines) + public byte GetLineStatus(byte lines) { + /* lock(this) { byte status = 0; @@ -86,9 +269,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return result; } + */ + + switch (lines) + { + case 0xfe: return (byte)KeyLine[0]; + case 0xfd: return (byte)KeyLine[1]; + case 0xfb: return (byte)KeyLine[2]; + case 0xf7: return (byte)KeyLine[3]; + case 0xef: return (byte)KeyLine[4]; + case 0xdf: return (byte)KeyLine[5]; + case 0xbf: return (byte)KeyLine[6]; + case 0x7f: return (byte)KeyLine[7]; + default: return 0; + } } - public byte ReadKeyboardByte(ushort addr) + public byte ReadKeyboardByte(ushort addr) { return GetLineStatus((byte)(addr >> 8)); } @@ -103,6 +300,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { ser.BeginSection("Keyboard"); ser.Sync("LineStatus", ref LineStatus, false); + ser.Sync("_keyLine", ref _keyLine, false); ser.EndSection(); } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index 6d06bc54c3..d0f40166ec 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -4,79 +4,30 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZXSpectrum - { + { /// - /// The standard 48K Spectrum keyboard + /// Controller mapping includes all keyboard keys from the following models: /// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg + /// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg /// - private static readonly ControllerDefinition ZXSpectrumControllerDefinition48 = new ControllerDefinition + public static readonly ControllerDefinition ZXSpectrumControllerDefinition = new ControllerDefinition { - Name = "ZXSpectrum Controller 48K", + Name = "ZXSpectrum Controller", 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", + /*"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 Q", "Key W", "Key E", "Key R", "Key T", "Key Y", "Key U", "Key I", "Key O", "Key P", + "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 A", "Key S", "Key D", "Key F", "Key G", "Key H", "Key J", "Key K", "Key L", "Key Return", + /*"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 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" - } - }; - - /// - /// The newer spectrum keyboard (models 48k+, 128k) - /// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg - /// - 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", + "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" - } - }; - - /// - /// 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 - /// - 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", + "Key Symbol Shift", /*"Key Semi-Colon", "Key Quote",*/ "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", /*"Key Comma",*/ // Tape functions "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape" } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 806b02ccc3..1a56bdfd4d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (Settings.MachineType) { case MachineType.ZXSpectrum48: - ControllerDefinition = ZXSpectrumControllerDefinition48; + ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum48, Settings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; default: From 0ac60123202f1f969e0890b7c7a40b26c62ebf7d Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 1 Dec 2017 11:36:57 +0000 Subject: [PATCH 011/105] small refactor --- .../Hardware/Interfaces/IKeyboard.cs | 5 ++ .../Machine/SpectrumBase.Input.cs | 8 ++- .../Machine/SpectrumBase.Port.cs | 51 +++++++------- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 69 ++++++++++++------- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- .../Computers/SinclairSpectrum/readme.md | 2 +- 6 files changed, 83 insertions(+), 54 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs index 0240fd52f5..83b8782226 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs @@ -30,6 +30,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// int[] KeyLine { get; set; } + /// + /// Resets the line status + /// + void ResetLineStatus(); + /// /// There are some slight differences in how PortIN and PortOUT functions /// between Issue2 and Issue3 keyboards (16k/48k spectrum only) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index d9fb158b9f..c1818cb61a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -20,12 +20,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // parse single keyboard matrix keys for (var i = 0; i < KeyboardDevice.KeyboardMatrix.Length; i++) { + + string key = KeyboardDevice.KeyboardMatrix[i]; - //bool prevState = KeyboardDevice.GetKeyStatus(key); + bool prevState = KeyboardDevice.GetKeyStatus(key); bool currState = Spectrum._controller.IsPressed(key); - //if (currState != prevState) - KeyboardDevice.SetKeyStatus(key, currState); + if (currState != prevState) + KeyboardDevice.SetKeyStatus(key, currState); } // non matrix keys diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index a37b24d22d..600a1c259e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -33,17 +33,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // combine the low byte (passed in as port) and the high byte (maybe not needed) //ushort word = Convert.ToUInt16((port << 8 | high)); + int word = (high << 8) + port; + //port += (ushort)(CPU.Regs[CPU.A] << 8); + // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x0001) == 0; + bool lowBitReset = (word & 0x0001) == 0; // Kempston Joystick //not implemented yet - if (lowBitReset) - { + if (port == 254) + { // Even I/O address so get input // The high byte indicates which half-row of keys is being polled /* @@ -54,33 +57,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B */ - if ((word & 0x8000) == 0) + if (high == 0xfe) + result &= KeyboardDevice.KeyLine[0]; + if (high == 0xfd) + result &= KeyboardDevice.KeyLine[1]; + if (high == 0xfb) + result &= KeyboardDevice.KeyLine[2]; + if (high == 0xf7) + result &= KeyboardDevice.KeyLine[3]; + if (high == 0xef) + result &= KeyboardDevice.KeyLine[4]; + if (high == 0xdf) + result &= KeyboardDevice.KeyLine[5]; + if (high == 0xbf) + result &= KeyboardDevice.KeyLine[6]; + if (high == 0x7f) result &= KeyboardDevice.KeyLine[7]; - if ((word & 0x4000) == 0) - result &= KeyboardDevice.KeyLine[6]; - - if ((word & 0x2000) == 0) - result &= KeyboardDevice.KeyLine[5]; - - if ((word & 0x1000) == 0) - result &= KeyboardDevice.KeyLine[4]; - - if ((word & 0x800) == 0) - result &= KeyboardDevice.KeyLine[3]; - - if ((word & 0x400) == 0) - result &= KeyboardDevice.KeyLine[2]; - - if ((word & 0x200) == 0) - result &= KeyboardDevice.KeyLine[1]; - - if ((word & 0x100) == 0) - result &= KeyboardDevice.KeyLine[0]; result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 + if (TapeDevice.CurrentMode == TapeOperationMode.Load) { if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) @@ -128,6 +126,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = (byte)(result & Convert.ToInt32("10111111", 2)); } */ + } else { @@ -140,8 +139,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // if unused port the floating memory bus should be returned (still todo) } - - return (byte)(result & 0xff); + + return (byte)result; } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index c20faa7fa4..096408a101 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -82,6 +82,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void SetKeyStatus(string key, bool isPressed) { + int k = GetByteFromKeyMatrix(key); + /* byte keyByte = GetByteFromKeyMatrix(key); var lineIndex = keyByte / 5; @@ -90,57 +92,65 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum LineStatus[lineIndex] = isPressed ? (byte)(LineStatus[lineIndex] | lineMask) : (byte)(LineStatus[lineIndex] & ~lineMask); */ + if (k != 255) + { + var lineIndex = k / 5; + var lineMask = 1 << k % 5; - int k = GetByteFromKeyMatrix(key); - + _keyLine[lineIndex] = isPressed + ? (byte)(_keyLine[lineIndex] & ~lineMask) + : (byte)(_keyLine[lineIndex] | lineMask); + } + + /* if (isPressed) { switch (k) { // 0xfefe - 0 - 4 - case 0: _keyLine[0] = (_keyLine[0] & ~(0x01)); break; + case 0: _keyLine[0] = (_keyLine[0] & ~(0x1)); break; case 1: _keyLine[0] = (_keyLine[0] & ~(0x02)); break; case 2: _keyLine[0] = (_keyLine[0] & ~(0x04)); break; case 3: _keyLine[0] = (_keyLine[0] & ~(0x08)); break; case 4: _keyLine[0] = (_keyLine[0] & ~(0x10)); break; // 0xfdfe - 5 - 9 - case 5: _keyLine[1] = (_keyLine[1] & ~(0x01)); break; + case 5: _keyLine[1] = (_keyLine[1] & ~(0x1)); break; case 6: _keyLine[1] = (_keyLine[1] & ~(0x02)); break; case 7: _keyLine[1] = (_keyLine[1] & ~(0x04)); break; case 8: _keyLine[1] = (_keyLine[1] & ~(0x08)); break; case 9: _keyLine[1] = (_keyLine[1] & ~(0x10)); break; // 0xfbfe - 10 - 14 - case 10: _keyLine[2] = (_keyLine[2] & ~(0x01)); break; + case 10: _keyLine[2] = (_keyLine[2] & ~(0x1)); break; case 11: _keyLine[2] = (_keyLine[2] & ~(0x02)); break; case 12: _keyLine[2] = (_keyLine[2] & ~(0x04)); break; case 13: _keyLine[2] = (_keyLine[2] & ~(0x08)); break; case 14: _keyLine[2] = (_keyLine[2] & ~(0x10)); break; // 0xf7fe - 15 - 19 - case 15: _keyLine[3] = (_keyLine[3] & ~(0x01)); break; + case 15: _keyLine[3] = (_keyLine[3] & ~(0x1)); break; case 16: _keyLine[3] = (_keyLine[3] & ~(0x02)); break; case 17: _keyLine[3] = (_keyLine[3] & ~(0x04)); break; case 18: _keyLine[3] = (_keyLine[3] & ~(0x08)); break; case 19: _keyLine[3] = (_keyLine[3] & ~(0x10)); break; // 0xeffe - 20 - 24 - case 20: _keyLine[4] = (_keyLine[4] & ~(0x01)); break; + case 20: _keyLine[4] = (_keyLine[4] & ~(0x1)); break; case 21: _keyLine[4] = (_keyLine[4] & ~(0x02)); break; case 22: _keyLine[4] = (_keyLine[4] & ~(0x04)); break; case 23: _keyLine[4] = (_keyLine[4] & ~(0x08)); break; case 24: _keyLine[4] = (_keyLine[4] & ~(0x10)); break; // 0xdffe - 25 - 29 - case 25: _keyLine[5] = (_keyLine[5] & ~(0x01)); break; + case 25: _keyLine[5] = (_keyLine[5] & ~(0x1)); break; case 26: _keyLine[5] = (_keyLine[5] & ~(0x02)); break; case 27: _keyLine[5] = (_keyLine[5] & ~(0x04)); break; case 28: _keyLine[5] = (_keyLine[5] & ~(0x08)); break; case 29: _keyLine[5] = (_keyLine[5] & ~(0x10)); break; // 0xbffe - 30 - 34 - case 30: _keyLine[6] = (_keyLine[6] & ~(0x01)); break; + case 30: _keyLine[6] = (_keyLine[6] & ~(0x1)); break; case 31: _keyLine[6] = (_keyLine[6] & ~(0x02)); break; case 32: _keyLine[6] = (_keyLine[6] & ~(0x04)); break; case 33: _keyLine[6] = (_keyLine[6] & ~(0x08)); break; case 34: _keyLine[6] = (_keyLine[6] & ~(0x10)); break; // 0x7ffe - 35 - 39 - case 35: _keyLine[7] = (_keyLine[7] & ~(0x01)); break; + case 35: _keyLine[7] = (_keyLine[7] & ~(0x1)); break; case 36: _keyLine[7] = (_keyLine[7] & ~(0x02)); break; case 37: _keyLine[7] = (_keyLine[7] & ~(0x04)); break; case 38: _keyLine[7] = (_keyLine[7] & ~(0x08)); break; @@ -152,49 +162,49 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (k) { // 0xfefe - 0 - 4 - case 0: _keyLine[0] = (_keyLine[0] | (0x01)); break; + case 0: _keyLine[0] = (_keyLine[0] | (0x1)); break; case 1: _keyLine[0] = (_keyLine[0] | (0x02)); break; case 2: _keyLine[0] = (_keyLine[0] | (0x04)); break; case 3: _keyLine[0] = (_keyLine[0] | (0x08)); break; case 4: _keyLine[0] = (_keyLine[0] | (0x10)); break; // 0xfdfe - 5 - 9 - case 5: _keyLine[1] = (_keyLine[1] | (0x01)); break; + case 5: _keyLine[1] = (_keyLine[1] | (0x1)); break; case 6: _keyLine[1] = (_keyLine[1] | (0x02)); break; case 7: _keyLine[1] = (_keyLine[1] | (0x04)); break; case 8: _keyLine[1] = (_keyLine[1] | (0x08)); break; case 9: _keyLine[1] = (_keyLine[1] | (0x10)); break; // 0xfbfe - 10 - 14 - case 10: _keyLine[2] = (_keyLine[2] | (0x01)); break; + case 10: _keyLine[2] = (_keyLine[2] | (0x1)); break; case 11: _keyLine[2] = (_keyLine[2] | (0x02)); break; case 12: _keyLine[2] = (_keyLine[2] | (0x04)); break; case 13: _keyLine[2] = (_keyLine[2] | (0x08)); break; case 14: _keyLine[2] = (_keyLine[2] | (0x10)); break; // 0xf7fe - 15 - 19 - case 15: _keyLine[3] = (_keyLine[3] | (0x01)); break; + case 15: _keyLine[3] = (_keyLine[3] | (0x1)); break; case 16: _keyLine[3] = (_keyLine[3] | (0x02)); break; case 17: _keyLine[3] = (_keyLine[3] | (0x04)); break; case 18: _keyLine[3] = (_keyLine[3] | (0x08)); break; case 19: _keyLine[3] = (_keyLine[3] | (0x10)); break; // 0xeffe - 20 - 24 - case 20: _keyLine[4] = (_keyLine[4] | (0x01)); break; + case 20: _keyLine[4] = (_keyLine[4] | (0x1)); break; case 21: _keyLine[4] = (_keyLine[4] | (0x02)); break; case 22: _keyLine[4] = (_keyLine[4] | (0x04)); break; case 23: _keyLine[4] = (_keyLine[4] | (0x08)); break; case 24: _keyLine[4] = (_keyLine[4] | (0x10)); break; // 0xdffe - 25 - 29 - case 25: _keyLine[5] = (_keyLine[5] | (0x01)); break; + case 25: _keyLine[5] = (_keyLine[5] | (0x1)); break; case 26: _keyLine[5] = (_keyLine[5] | (0x02)); break; case 27: _keyLine[5] = (_keyLine[5] | (0x04)); break; case 28: _keyLine[5] = (_keyLine[5] | (0x08)); break; case 29: _keyLine[5] = (_keyLine[5] | (0x10)); break; // 0xbffe - 30 - 34 - case 30: _keyLine[6] = (_keyLine[6] | (0x01)); break; + case 30: _keyLine[6] = (_keyLine[6] | (0x1)); break; case 31: _keyLine[6] = (_keyLine[6] | (0x02)); break; case 32: _keyLine[6] = (_keyLine[6] | (0x04)); break; case 33: _keyLine[6] = (_keyLine[6] | (0x08)); break; case 34: _keyLine[6] = (_keyLine[6] | (0x10)); break; // 0x7ffe - 35 - 39 - case 35: _keyLine[7] = (_keyLine[7] | (0x01)); break; + case 35: _keyLine[7] = (_keyLine[7] | (0x1)); break; case 36: _keyLine[7] = (_keyLine[7] | (0x02)); break; case 37: _keyLine[7] = (_keyLine[7] | (0x04)); break; case 38: _keyLine[7] = (_keyLine[7] | (0x08)); break; @@ -202,6 +212,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + */ + // Combination keys that are not in the keyboard matrix // but are available on the Spectrum+, 128k +2 & +3 // (GetByteFromKeyMatrix() should return 255) @@ -247,12 +259,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum byte keyByte = GetByteFromKeyMatrix(key); var lineIndex = keyByte / 5; var lineMask = 1 << keyByte % 5; - return (LineStatus[lineIndex] & lineMask) != 0; + + return (_keyLine[lineIndex] & lineMask) == 0; + } + + public void ResetLineStatus() + { + lock (this) + { + for (int i = 0; i < KeyLine.Length; i++) + KeyLine[i] = 255; + } } public byte GetLineStatus(byte lines) { - /* + lock(this) { byte status = 0; @@ -261,7 +283,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum while (lines > 0) { if ((lines & 0x01) != 0) - status |= LineStatus[lineIndex]; + status |= (byte)_keyLine[lineIndex]; lineIndex++; lines >>= 1; } @@ -269,8 +291,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return result; } - */ - + + /* switch (lines) { case 0xfe: return (byte)KeyLine[0]; @@ -283,6 +305,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case 0x7f: return (byte)KeyLine[7]; default: return 0; } + */ } public byte ReadKeyboardByte(ushort addr) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 1a56bdfd4d..9add8bdeec 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -56,7 +56,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.WriteMemory = _machine.WriteMemory; _cpu.ReadHardware = _machine.ReadPort; _cpu.WriteHardware = _machine.WritePort; - _cpu.FetchDB = _machine.PushBus; + _cpu.FetchDB = _machine.PushBus; ser.Register(_tracer); ser.Register(_cpu); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index eacc65afa1..3b2fbec9d2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -7,6 +7,7 @@ At this moment this is still *very* experimental and needs a lot more work. * ZX Spectrum 48k model * ULA video output (implementing IVideoProvider) * ULA Mode 1 VBLANK interrupt generation +* IM2 Interrupts and DataBus implementation (thanks Aloysha) * Beeper/Buzzer output (implementing ISoundProvider) * Keyboard input (implementing IInputPollable) * Tape device that will load spectrum games in realtime (*.tzx and *.tap) @@ -16,7 +17,6 @@ At this moment this is still *very* experimental and needs a lot more work. * ISettable - There are some Settings and SyncSettings instantiated, although they are not really used and I haven't yet figured out how to wire these up to the front-end yet ### Not working -* Interrupt Mode 2 (Z80A) - usually invokes a soft reboot when the game raises one (can be seen in 'Chaos - Battle of the Wizards' after initial game setup when the game board tries to load) * IMemoryDomains - I started looking at this but didn't really know what I was doing yet * IDebuggable * Default keyboard keymappings (you have to configure yourself in the core controller settings) From a9f8828063a9b794b57d42fddfc014badec3e8b4 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 1 Dec 2017 08:20:18 -0500 Subject: [PATCH 012/105] z80: fix port access behaviour --- .../CPUs/Z80A/Operations.cs | 8 ++++---- .../CPUs/Z80A/Tables_Direct.cs | 20 +++++++++---------- BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs index 81f9e1b93a..adf7452c29 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Operations.cs @@ -29,15 +29,15 @@ namespace BizHawk.Emulation.Cores.Components.Z80A WriteMemory((ushort)((Regs[dest_l] | (Regs[dest_h] << 8)) + inc), (byte)Regs[src]); } - public void OUT_Func(ushort dest, ushort src) + public void OUT_Func(ushort dest_l, ushort dest_h, ushort src) { Regs[DB] = Regs[src]; - WriteHardware(Regs[dest], (byte)(Regs[src])); + WriteHardware((ushort)(Regs[dest_l] | (Regs[dest_h] << 8)), (byte)(Regs[src])); } - public void IN_Func(ushort dest, ushort src) + public void IN_Func(ushort dest, ushort src_l, ushort src_h) { - Regs[dest] = ReadHardware(Regs[src]); + Regs[dest] = ReadHardware((ushort)(Regs[src_l] | (Regs[src_h]) << 8)); Regs[DB] = Regs[dest]; } diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs index 71b39d6018..e708b02bac 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Direct.cs @@ -452,13 +452,13 @@ namespace BizHawk.Emulation.Cores.Components.Z80A { cur_instr = new ushort[] {IDLE, - RD, ALU, PCl, PCh, + RD, Z, PCl, PCh, IDLE, INC16, PCl, PCh, TR, W, A, - OUT, ALU, A, - TR, Z, ALU, - INC16, Z, ALU, + OUT, Z, W, A, + IDLE, + INC16, Z, W, IDLE, IDLE, OP}; @@ -469,9 +469,9 @@ namespace BizHawk.Emulation.Cores.Components.Z80A cur_instr = new ushort[] {IDLE, IDLE, - OUT, dest, src, - IDLE, TR16, Z, W, C, B, + OUT, Z, W, src, + IDLE, INC16, Z, W, IDLE, OP}; @@ -481,12 +481,12 @@ namespace BizHawk.Emulation.Cores.Components.Z80A { cur_instr = new ushort[] {IDLE, - RD, ALU, PCl, PCh, + RD, Z, PCl, PCh, IDLE, INC16, PCl, PCh, TR, W, A, - IN, A, ALU, - TR, Z, ALU, + IN, A, Z, W, + IDLE, INC16, Z, W, IDLE, IDLE, @@ -498,7 +498,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A cur_instr = new ushort[] {IDLE, IDLE, - IN, dest, src, + IN, dest, src, B, IDLE, TR16, Z, W, C, B, INC16, Z, W, diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs index 623a2aa126..182056f026 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs @@ -578,10 +578,10 @@ namespace BizHawk.Emulation.Cores.Components.Z80A iff1 = iff2; break; case OUT: - OUT_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); + OUT_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; case IN: - IN_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++]); + IN_Func(cur_instr[instr_pntr++], cur_instr[instr_pntr++], cur_instr[instr_pntr++]); break; case NEG: NEG_8_Func(cur_instr[instr_pntr++]); From f1fee7dc6ea5b1ee1f48264e419e828e7a60cb85 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 1 Dec 2017 08:51:18 -0500 Subject: [PATCH 013/105] TI832: fix port accesses --- BizHawk.Emulation.Cores/Calculator/TI83.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.cs b/BizHawk.Emulation.Cores/Calculator/TI83.cs index cfe82f74eb..403f0fa7b8 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.cs @@ -137,6 +137,8 @@ namespace BizHawk.Emulation.Cores.Calculators private void WriteHardware(ushort addr, byte value) { + addr &= 0xFF; + switch (addr) { case 0: // PORT_LINK @@ -232,6 +234,8 @@ namespace BizHawk.Emulation.Cores.Calculators private byte ReadHardware(ushort addr) { + addr &= 0xFF; + switch (addr) { case 0: // PORT_LINK From 42db9479393615f162e733e59573ac92c76d30a2 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 1 Dec 2017 14:34:45 +0000 Subject: [PATCH 014/105] updated keyscanning code --- .../Machine/SpectrumBase.Port.cs | 58 ++++++++++++------- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 5 +- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index 600a1c259e..d42f7301f9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -24,29 +24,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual byte ReadPort(ushort port) { - CPU.TotalExecutedCycles += 4; - int 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((port << 8 | high)); - - int word = (high << 8) + port; - - //port += (ushort)(CPU.Regs[CPU.A] << 8); - // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address - bool lowBitReset = (word & 0x0001) == 0; + bool lowBitReset = (port & 0x0001) == 0; + + ContendPort((ushort)port); + // Kempston Joystick - //not implemented yet + if (port == 0x1f) + { - if (port == 254) - { + } + else if (lowBitReset) + { // Even I/O address so get input // The high byte indicates which half-row of keys is being polled /* @@ -57,6 +50,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 0xf7fe 1, 2, 3, 4, 5 0x7ffe SPACE, SYM SHFT, M, N, B */ + result &= KeyboardDevice.GetLineStatus((byte)(port >> 8)); + /* if (high == 0xfe) result &= KeyboardDevice.KeyLine[0]; if (high == 0xfd) @@ -73,7 +68,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result &= KeyboardDevice.KeyLine[6]; if (high == 0x7f) result &= KeyboardDevice.KeyLine[7]; - +*/ result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 @@ -150,11 +145,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// 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; + bool lowBitReset = (port & 0x01) == 0; + + ContendPort(port); // Only even addresses address the ULA if (lowBitReset) @@ -179,5 +174,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } } + + /// + /// Apply I/O contention if necessary + /// + /// + public virtual void ContendPort(ushort port) + { + var lowBit = (port & 0x0001) != 0; + var ulaHigh = (port & 0xc000) == 0x4000; + var cfc = CurrentFrameCycle; + if (cfc < 1) + cfc = 1; + + if (ulaHigh) + { + CPU.TotalExecutedCycles += GetContentionValue(cfc - 1); + } + else + { + if (!lowBit) + CPU.TotalExecutedCycles += GetContentionValue(cfc); + } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index 096408a101..afa886675a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -273,8 +273,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } public byte GetLineStatus(byte lines) - { - + { lock(this) { byte status = 0; @@ -287,7 +286,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum lineIndex++; lines >>= 1; } - var result = (byte)~status; + var result = (byte)status; return result; } From fb8fd2ae900922f600c58aff3d817c8c259f6b24 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 1 Dec 2017 15:34:47 +0000 Subject: [PATCH 015/105] Fixed input detection --- .../Machine/SpectrumBase.Port.cs | 56 ++++++++----------- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 12 +--- 2 files changed, 26 insertions(+), 42 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index d42f7301f9..b5d0b8bdee 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -30,8 +30,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ContendPort((ushort)port); - + ContendPort((ushort)port); // Kempston Joystick if (port == 0x1f) @@ -48,27 +47,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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 - */ + */ - result &= KeyboardDevice.GetLineStatus((byte)(port >> 8)); - /* - if (high == 0xfe) - result &= KeyboardDevice.KeyLine[0]; - if (high == 0xfd) - result &= KeyboardDevice.KeyLine[1]; - if (high == 0xfb) - result &= KeyboardDevice.KeyLine[2]; - if (high == 0xf7) - result &= KeyboardDevice.KeyLine[3]; - if (high == 0xef) - result &= KeyboardDevice.KeyLine[4]; - if (high == 0xdf) - result &= KeyboardDevice.KeyLine[5]; - if (high == 0xbf) - result &= KeyboardDevice.KeyLine[6]; - if (high == 0x7f) + if ((port & 0x8000) == 0) result &= KeyboardDevice.KeyLine[7]; -*/ + + if ((port & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((port & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((port & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((port & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((port & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((port & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((port & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 @@ -110,17 +113,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } - /* - // read keyboard input - if (high != 0) - result &= KeyboardDevice.GetLineStatus((byte)high); - - var ear = TapeDevice.GetEarBit(CPU.TotalExecutedCycles); - if (!ear) - { - result = (byte)(result & Convert.ToInt32("10111111", 2)); - } - */ } else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index afa886675a..e04eedc217 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -77,21 +77,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum LineStatus = new byte[8]; _keyLine = new int[] { 255, 255, 255, 255, 255, 255, 255, 255 }; - IsIssue2Keyboard = false; + IsIssue2Keyboard = true; } public void SetKeyStatus(string key, bool isPressed) { int k = GetByteFromKeyMatrix(key); - - /* - byte keyByte = GetByteFromKeyMatrix(key); - var lineIndex = keyByte / 5; - var lineMask = 1 << keyByte % 5; - - LineStatus[lineIndex] = isPressed ? (byte)(LineStatus[lineIndex] | lineMask) - : (byte)(LineStatus[lineIndex] & ~lineMask); - */ + if (k != 255) { var lineIndex = k / 5; From 048c65cd7eddfc4d7bc72824a4cff05d1727cca8 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 1 Dec 2017 17:33:56 +0000 Subject: [PATCH 016/105] Implemented Kempston Joystick (hardcoded J1) --- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/KempstonJoystick.cs | 75 +++++++++++++++++++ .../Machine/SpectrumBase.Input.cs | 10 +++ .../Machine/SpectrumBase.Port.cs | 4 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 5 ++ .../Machine/ZXSpectrum48K/ZX48.cs | 1 + 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 251a064158..13af57d15a 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -266,6 +266,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs new file mode 100644 index 0000000000..5da278df76 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public class KempstonJoystick + { + private int _joyLine; + private SpectrumBase _machine; + + public readonly string[] _bitPos = new string[] + { + "P1 Right", + "P1 Left", + "P1 Down", + "P1 Up", + "P1 Button" + }; + + /* + Active bits high + 0 0 0 F U D L R + */ + public int JoyLine + { + get { return _joyLine; } + set { _joyLine = value; } + } + + public KempstonJoystick(SpectrumBase machine) + { + _machine = machine; + _joyLine = 0; + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + if (isPressed) + _joyLine |= (1 << pos); + else + _joyLine &= ~(1 << pos); + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + return (_joyLine & (1 << pos)) != 0; + } + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(_bitPos, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index c1818cb61a..9f0c736069 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -42,6 +42,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + // J1 + foreach (string j in KempstonDevice._bitPos) + { + bool prevState = KempstonDevice.GetJoyInput(j); + bool currState = Spectrum._controller.IsPressed(j); + + if (currState != prevState) + KempstonDevice.SetJoyInput(j, currState); + } + // Tape control if (Spectrum._controller.IsPressed(Play)) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index b5d0b8bdee..be31fead3f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -33,9 +33,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ContendPort((ushort)port); // Kempston Joystick - if (port == 0x1f) + if ((port & 0xe0) == 0 || (port & 0x20) == 0) { - + return (byte)KempstonDevice.JoyLine; } else if (lowBitReset) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 6183843006..7cd878d1cb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -46,6 +46,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual ITapeProvider TapeProvider { get; set; } + /// + /// Kempston joystick + /// + public virtual KempstonJoystick KempstonDevice { get; set; } + /// /// Signs whether the frame has ended /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 57e5e14c1a..3deae820b1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -41,6 +41,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.Init(44100, UlaFrameCycleCount); KeyboardDevice = new Keyboard48(this); + KempstonDevice = new KempstonJoystick(this); TapeProvider = new DefaultTapeProvider(file); From 2b880d863b60bdc22d205a0bafdfaedab367a992 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 1 Dec 2017 17:40:45 +0000 Subject: [PATCH 017/105] updated readme --- BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index 3b2fbec9d2..bbced33297 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -10,20 +10,19 @@ At this moment this is still *very* experimental and needs a lot more work. * IM2 Interrupts and DataBus implementation (thanks Aloysha) * Beeper/Buzzer output (implementing ISoundProvider) * Keyboard input (implementing IInputPollable) +* Kempston joystick (mapped to J1 currently) * Tape device that will load spectrum games in realtime (*.tzx and *.tap) * IStatable (although this is not currently working/implemented properly during tape load operations) +* IMemoryDomains (I think) ### Some progress * ISettable - There are some Settings and SyncSettings instantiated, although they are not really used and I haven't yet figured out how to wire these up to the front-end yet ### Not working -* IMemoryDomains - I started looking at this but didn't really know what I was doing yet * IDebuggable * Default keyboard keymappings (you have to configure yourself in the core controller settings) -* Joystick support (I still need to implement a Kemptston joystick and interface) * Manual tape device control (at the moment the tape device detects when the spectrum goes into 'loadbytes' mode and auto-plays the tape. This is not ideal and manual control should be implemented so the user can start/stop manually, return to zero etc..) * Only standard spectrum tape blocks currently work. Any fancy SpeedLock encoded (and similar) blocks do not ### Known bugs -* The 'return' keyboard key is acting the same as Space/Break when doing a BASIC RUN or LOAD "" command. The upshot of this is that upon boot, when you go to load the attached spectrum cassette (you have to type: "J", then "SYMSHIFT + P", then "SYMSHIFT + P", then RETURN) it more often than not interrupts the load routine. You then have to try again but hitting the RETURN key at the end of the sequence for as small a time as possible. Rinse and repeat until the load process starts. Clearly NOT ideal. * Audible 'popping' from the emulated buzzer after a load state operation From 38ee7147b3051cdba13b8dced7cb2e433ead52de Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 4 Dec 2017 09:42:08 +0000 Subject: [PATCH 018/105] _frameBuffer is now populated with correct data immediately, rather than converted during the IVideoProvider cycle --- .../SinclairSpectrum/Machine/SpectrumBase.Screen.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index bdada81b35..8a4bedf024 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -38,7 +38,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The main screen buffer /// - protected byte[] _frameBuffer; + protected int[] _frameBuffer; /// /// Pixel and attribute info stored while rendering the screen @@ -531,8 +531,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum protected virtual void SetPixels(int colorIndex1, int colorIndex2) { var pos = _yPos * ScreenWidth + _xPos; - _frameBuffer[pos++] = (byte)colorIndex1; - _frameBuffer[pos] = (byte)colorIndex2; + _frameBuffer[pos++] = ULAPalette[colorIndex1]; + _frameBuffer[pos] = ULAPalette[colorIndex2]; } /// @@ -785,7 +785,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //BorderDevice.Reset(); _flashPhase = false; - _frameBuffer = new byte[ScreenWidth * ScreenLines]; + _frameBuffer = new int[ScreenWidth * ScreenLines]; InitULACycleTable(); @@ -912,6 +912,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ public int[] GetVideoBuffer() { + return _frameBuffer; + // convert the generated _framebuffer into ARGB colours via the ULAPalette int[] trans = new int[_frameBuffer.Length]; for (int i = 0; i < _frameBuffer.Length; i++) From 6d22b06c21c549ff042c68ac8c5e8ed2cdfca8da Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 4 Dec 2017 14:05:04 +0000 Subject: [PATCH 019/105] Added virtual pad to UI --- .../BizHawk.Client.EmuHawk.csproj | 3 +- .../VirtualPads/schema/ZXSpectrumSchema.cs | 241 ++++++++++++++++++ .../ZXSpectrum.Controllers.cs | 12 +- 3 files changed, 249 insertions(+), 7 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 2a860dd082..fdce3477bf 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -1197,6 +1197,7 @@ + UserControl @@ -2173,4 +2174,4 @@ - + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs new file mode 100644 index 0000000000..fbcb9b9e57 --- /dev/null +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs @@ -0,0 +1,241 @@ +using System.Collections.Generic; +using System.Drawing; + +using BizHawk.Emulation.Common; +using System.Linq; + +namespace BizHawk.Client.EmuHawk +{ + [Schema("ZXSpectrum")] + class ZXSpectrumSchema : IVirtualPadSchema + { + public IEnumerable GetPadSchemas(IEmulator core) + { + yield return KempstonJoystick(1); + yield return Keyboard(); + yield return TapeDevice(); + } + + private static PadSchema KempstonJoystick(int controller) + { + return new PadSchema + { + DisplayName = "Player " + controller + " (Kempton Joystick)", + IsConsole = false, + DefaultSize = new Size(174, 74), + MaxSize = new Size(174, 74), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Up", + DisplayName = "", + Icon = Properties.Resources.BlueUp, + Location = new Point(23, 15), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Down", + DisplayName = "", + Icon = Properties.Resources.BlueDown, + Location = new Point(23, 36), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Left", + DisplayName = "", + Icon = Properties.Resources.Back, + Location = new Point(2, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Right", + DisplayName = "", + Icon = Properties.Resources.Forward, + Location = new Point(44, 24), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "P" + controller + " Button", + DisplayName = "B", + Location = new Point(124, 24), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + + private class ButtonLayout + { + public string Name { get; set; } + public string DisName { get; set; } + public double WidthFactor { get; set; } + public int Row { get; set; } + public bool IsActive = true; + } + + private static PadSchema Keyboard() + { + List bls = new List + { + new ButtonLayout { Name = "Key True Video", DisName = "TV", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Inv Video", DisName = "IV", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 1", DisName = "1", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 2", DisName = "2", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 3", DisName = "3", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 4", DisName = "4", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 5", DisName = "5", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 6", DisName = "6", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 7", DisName = "7", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 8", DisName = "8", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 9", DisName = "9", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key 0", DisName = "0", Row = 0, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Break", DisName = "BREAK", Row = 0, WidthFactor = 1.5 }, + + new ButtonLayout { Name = "Key Delete", DisName = "DEL", Row = 1, WidthFactor = 1.5 }, + new ButtonLayout { Name = "Key Graph", DisName = "GR", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Q", DisName = "Q", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key W", DisName = "W", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key E", DisName = "E", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key R", DisName = "R", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key T", DisName = "T", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Y", DisName = "Y", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key U", DisName = "U", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key I", DisName = "I", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key O", DisName = "O", Row = 1, WidthFactor = 1 }, + new ButtonLayout { Name = "Key P", DisName = "P", Row = 1, WidthFactor = 1 }, + + new ButtonLayout { Name = "Key Extend Mode", DisName = "EM", Row = 2, WidthFactor = 1.5 }, + new ButtonLayout { Name = "Key Edit", DisName = "ED", Row = 2, WidthFactor = 1.25}, + new ButtonLayout { Name = "Key A", DisName = "A", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key S", DisName = "S", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key D", DisName = "D", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key F", DisName = "F", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key G", DisName = "G", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key H", DisName = "H", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key J", DisName = "J", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key K", DisName = "K", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key L", DisName = "L", Row = 2, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Return", DisName = "ENTER", Row = 2, WidthFactor = 1.75 }, + + new ButtonLayout { Name = "Key Caps Shift", DisName = "CAPS-S", Row = 3, WidthFactor = 2.25 }, + new ButtonLayout { Name = "Key Caps Lock", DisName = "CL", Row = 3, WidthFactor = 1}, + new ButtonLayout { Name = "Key Z", DisName = "Z", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key X", DisName = "X", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key C", DisName = "C", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key V", DisName = "V", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key B", DisName = "B", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key N", DisName = "N", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key M", DisName = "M", Row = 3, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Period", DisName = ".", Row = 3, WidthFactor = 1}, + new ButtonLayout { Name = "Key Caps Shift", DisName = "CAPS-S", Row = 3, WidthFactor = 2.25 }, + + new ButtonLayout { Name = "Key Symbol Shift", DisName = "SS", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Semi-Colon", DisName = ";", Row = 4, WidthFactor = 1}, + new ButtonLayout { Name = "Key Quote", DisName = "\"", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Left Cursor", DisName = "←", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Right Cursor", DisName = "→", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Space", DisName = "SPACE", Row = 4, WidthFactor = 4.5 }, + new ButtonLayout { Name = "Key Up Cursor", DisName = "↑", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Down Cursor", DisName = "↓", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Comma", DisName = ",", Row = 4, WidthFactor = 1 }, + new ButtonLayout { Name = "Key Symbol Shift", DisName = "SS", Row = 4, WidthFactor = 1 }, + }; + + PadSchema ps = new PadSchema + { + DisplayName = "Keyboard", + IsConsole = false, + DefaultSize = new Size(500, 170) + }; + + List btns = new List(); + + int rowHeight = 29; //24 + int stdButtonWidth = 29; //24 + int yPos = 18; + int xPos = 22; + int currRow = 0; + + foreach (var b in bls) + { + if (b.Row > currRow) + { + currRow++; + yPos += rowHeight; + xPos = 22; + } + + int txtLength = b.DisName.Length; + int btnSize = System.Convert.ToInt32((double)stdButtonWidth * b.WidthFactor); + + + string disp = b.DisName; + if (txtLength == 1) + disp = " " + disp; + + switch(b.DisName) + { + case "SPACE": disp = " " + disp + " "; break; + case "I": disp = " " + disp + " "; break; + case "W": disp = b.DisName; break; + } + + + if (b.IsActive) + { + PadSchema.ButtonSchema btn = new PadSchema.ButtonSchema(); + btn.Name = b.Name; + btn.DisplayName = disp; + btn.Location = new Point(xPos, yPos); + btn.Type = PadSchema.PadInputType.Boolean; + btns.Add(btn); + } + + xPos += btnSize; + } + + ps.Buttons = btns.ToArray(); + return ps; + } + + private static PadSchema TapeDevice() + { + return new PadSchema + { + DisplayName = "DATACORDER", + IsConsole = false, + DefaultSize = new Size(174, 74), + MaxSize = new Size(174, 74), + Buttons = new[] + { + new PadSchema.ButtonSchema + { + Name = "Play Tape", + Icon = Properties.Resources.Play, + Location = new Point(23, 22), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "Stop Tape", + Icon = Properties.Resources.Stop, + Location = new Point(53, 22), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "RTZ Tape", + Icon = Properties.Resources.BackMore, + Location = new Point(83, 22), + Type = PadSchema.PadInputType.Boolean + } + } + }; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index d0f40166ec..018c9b67b1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -16,18 +16,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Name = "ZXSpectrum Controller", BoolButtons = { - // Joystick interface (not yet emulated) - Could be Kempston/Cursor/Protek + // Kempston Joystick (P1) "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", + "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", + "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", + "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",*/ + "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 Quote",*/ "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", /*"Key Comma",*/ + "Key Symbol Shift", "Key Semi-Colon", "Key Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", // Tape functions "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape" } From f92269657d5786abf2dd8cdf6c4d0dceaa93fb4a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 4 Dec 2017 15:40:27 +0000 Subject: [PATCH 020/105] Added core UI menu and fixed up settings / syncsettings --- BizHawk.Client.EmuHawk/MainForm.Designer.cs | 406 ++++++++++-------- BizHawk.Client.EmuHawk/MainForm.Events.cs | 20 +- BizHawk.Client.EmuHawk/MainForm.cs | 8 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 37 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 4 +- 5 files changed, 268 insertions(+), 207 deletions(-) diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index d2c636448a..dcb8272627 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -73,7 +73,7 @@ this.LoadCurrentSlotMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SaveRAMSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.FlushSaveRAMMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); + this.toolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); this.MovieSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.ReadonlyMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator15 = new System.Windows.Forms.ToolStripSeparator(); @@ -192,14 +192,13 @@ this.GbaCoreSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.VbaNextCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MgbaCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SGBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem(); - this.SgbBsnesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SgbBsnesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SgbSameBoyMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem(); - this.GBGambatteMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBGBHawkMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBCoreSubmenu = new System.Windows.Forms.ToolStripMenuItem(); + this.GBGambatteMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBGBHawkMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem16 = new System.Windows.Forms.ToolStripSeparator(); this.allowGameDBCoreOverridesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator8 = new System.Windows.Forms.ToolStripSeparator(); @@ -239,9 +238,8 @@ this.coreToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.quickNESToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.nesHawkToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator34 = new System.Windows.Forms.ToolStripSeparator(); - this.toolStripSeparator35 = new System.Windows.Forms.ToolStripSeparator(); - this.NESPPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator34 = new System.Windows.Forms.ToolStripSeparator(); + this.NESPPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.NESNametableViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.NESGameGenieCodesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.musicRipperToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -279,6 +277,12 @@ this.SMSdisplayNtscToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSdisplayPalToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSdisplayAutoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerStandardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerPaddleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerLightPhaserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerSportsPadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.SMSControllerKeyboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMStoolStripMenuItem2 = new System.Windows.Forms.ToolStripSeparator(); this.SMSenableBIOSToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.SMSEnableFMChipMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -303,14 +307,14 @@ this.AtariSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.A7800SubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.A7800ControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.A7800FilterSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.A7800FilterSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.GBcoreSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.LoadGBInSGBMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator28 = new System.Windows.Forms.ToolStripSeparator(); this.GBGPUViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBPrinterViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.GBGameGenieMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBGameGenieMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.GBPrinterViewerMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.GBASubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.GBACoreSelectionSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.GBAmGBAMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -332,6 +336,7 @@ this.SnesOptionsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ColecoSubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.ColecoControllerSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator35 = new System.Windows.Forms.ToolStripSeparator(); this.ColecoSkipBiosMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.N64SubMenu = new System.Windows.Forms.ToolStripMenuItem(); this.N64PluginSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -374,6 +379,8 @@ this.ForumsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainStatusBar = new StatusStripEx(); this.DumpStatusButton = new System.Windows.Forms.ToolStripDropDownButton(); this.EmuStatus = new System.Windows.Forms.ToolStripStatusLabel(); @@ -445,13 +452,8 @@ this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator(); this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); - this.SMSControllerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SMSControllerStandardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SMSControllerPaddleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SMSControllerLightPhaserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SMSControllerSportsPadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.SMSControllerKeyboardToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.MainformMenu.SuspendLayout(); + this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); + this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); this.SuspendLayout(); @@ -488,7 +490,8 @@ this.pCFXToolStripMenuItem, this.virtualBoyToolStripMenuItem, this.neoGeoPocketToolStripMenuItem, - this.HelpSubMenu}); + this.HelpSubMenu, + this.zXSpectrumToolStripMenuItem}); this.MainformMenu.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.Flow; this.MainformMenu.Location = new System.Drawing.Point(0, 0); this.MainformMenu.Name = "MainformMenu"; @@ -910,26 +913,26 @@ this.LoadCurrentSlotMenuItem.Size = new System.Drawing.Size(178, 22); this.LoadCurrentSlotMenuItem.Text = "Load Current Slot"; this.LoadCurrentSlotMenuItem.Click += new System.EventHandler(this.LoadCurrentSlotMenuItem_Click); - - // - // SaveRAMSubMenu - // - this.SaveRAMSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.FlushSaveRAMMenuItem}); - this.SaveRAMSubMenu.Name = "SaveRAMSubMenu"; - this.SaveRAMSubMenu.Size = new System.Drawing.Size(159, 22); - this.SaveRAMSubMenu.Text = "Save &RAM"; - this.SaveRAMSubMenu.DropDownOpened += new System.EventHandler(this.SaveRAMSubMenu_DropDownOpened); - // - // FlushSaveRAMMenuItem - // - this.FlushSaveRAMMenuItem.Name = "FlushSaveRAMMenuItem"; - this.FlushSaveRAMMenuItem.Size = new System.Drawing.Size(156, 22); - this.FlushSaveRAMMenuItem.Text = "&Flush Save Ram"; - this.FlushSaveRAMMenuItem.Click += new System.EventHandler(this.FlushSaveRAMMenuItem_Click); - // toolStripMenuItem2 - // - this.toolStripMenuItem2.Name = "toolStripMenuItem2"; + // + // SaveRAMSubMenu + // + this.SaveRAMSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.FlushSaveRAMMenuItem}); + this.SaveRAMSubMenu.Name = "SaveRAMSubMenu"; + this.SaveRAMSubMenu.Size = new System.Drawing.Size(159, 22); + this.SaveRAMSubMenu.Text = "Save &RAM"; + this.SaveRAMSubMenu.DropDownOpened += new System.EventHandler(this.SaveRAMSubMenu_DropDownOpened); + // + // FlushSaveRAMMenuItem + // + this.FlushSaveRAMMenuItem.Name = "FlushSaveRAMMenuItem"; + this.FlushSaveRAMMenuItem.Size = new System.Drawing.Size(156, 22); + this.FlushSaveRAMMenuItem.Text = "&Flush Save Ram"; + this.FlushSaveRAMMenuItem.Click += new System.EventHandler(this.FlushSaveRAMMenuItem_Click); + // + // toolStripMenuItem2 + // + this.toolStripMenuItem2.Name = "toolStripMenuItem2"; this.toolStripMenuItem2.Size = new System.Drawing.Size(156, 6); // // MovieSubMenu @@ -1124,7 +1127,7 @@ // this.RecordAVMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.RecordHS; this.RecordAVMenuItem.Name = "RecordAVMenuItem"; - this.RecordAVMenuItem.Size = new System.Drawing.Size(223, 22); + this.RecordAVMenuItem.Size = new System.Drawing.Size(225, 22); this.RecordAVMenuItem.Text = "&Record AVI/WAV"; this.RecordAVMenuItem.Click += new System.EventHandler(this.RecordAVMenuItem_Click); // @@ -1132,7 +1135,7 @@ // this.ConfigAndRecordAVMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.AVI; this.ConfigAndRecordAVMenuItem.Name = "ConfigAndRecordAVMenuItem"; - this.ConfigAndRecordAVMenuItem.Size = new System.Drawing.Size(223, 22); + this.ConfigAndRecordAVMenuItem.Size = new System.Drawing.Size(225, 22); this.ConfigAndRecordAVMenuItem.Text = "Config and Record AVI/WAV"; this.ConfigAndRecordAVMenuItem.Click += new System.EventHandler(this.ConfigAndRecordAVMenuItem_Click); // @@ -1140,26 +1143,26 @@ // this.StopAVIMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.Stop; this.StopAVIMenuItem.Name = "StopAVIMenuItem"; - this.StopAVIMenuItem.Size = new System.Drawing.Size(223, 22); + this.StopAVIMenuItem.Size = new System.Drawing.Size(225, 22); this.StopAVIMenuItem.Text = "&Stop AVI/WAV"; this.StopAVIMenuItem.Click += new System.EventHandler(this.StopAVMenuItem_Click); // // toolStripSeparator19 // this.toolStripSeparator19.Name = "toolStripSeparator19"; - this.toolStripSeparator19.Size = new System.Drawing.Size(220, 6); + this.toolStripSeparator19.Size = new System.Drawing.Size(222, 6); // // CaptureOSDMenuItem // this.CaptureOSDMenuItem.Name = "CaptureOSDMenuItem"; - this.CaptureOSDMenuItem.Size = new System.Drawing.Size(223, 22); + this.CaptureOSDMenuItem.Size = new System.Drawing.Size(225, 22); this.CaptureOSDMenuItem.Text = "Capture OSD"; this.CaptureOSDMenuItem.Click += new System.EventHandler(this.CaptureOSDMenuItem_Click); // // SynclessRecordingMenuItem // this.SynclessRecordingMenuItem.Name = "SynclessRecordingMenuItem"; - this.SynclessRecordingMenuItem.Size = new System.Drawing.Size(223, 22); + this.SynclessRecordingMenuItem.Size = new System.Drawing.Size(225, 22); this.SynclessRecordingMenuItem.Text = "S&yncless Recording Tools"; this.SynclessRecordingMenuItem.Click += new System.EventHandler(this.SynclessRecordingMenuItem_Click); // @@ -1814,8 +1817,8 @@ this.CoreSNESSubMenu, this.GbaCoreSubMenu, this.SGBCoreSubmenu, - this.GBCoreSubmenu, - this.GBInSGBMenuItem, + this.GBCoreSubmenu, + this.GBInSGBMenuItem, this.toolStripMenuItem16, this.allowGameDBCoreOverridesToolStripMenuItem, this.toolStripSeparator8, @@ -1898,12 +1901,6 @@ this.MgbaCoreMenuItem.Text = "mGBA"; this.MgbaCoreMenuItem.Click += new System.EventHandler(this.GbaCorePick_Click); // - // Atari7800HawkCoreMenuItem - // - this.Atari7800HawkCoreMenuItem.Name = "Atari7800HawkCoreMenuItem"; - this.Atari7800HawkCoreMenuItem.Size = new System.Drawing.Size(153, 22); - this.Atari7800HawkCoreMenuItem.Text = "Atari7800Hawk"; - // // SGBCoreSubmenu // this.SGBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1913,48 +1910,48 @@ this.SGBCoreSubmenu.Size = new System.Drawing.Size(239, 22); this.SGBCoreSubmenu.Text = "SGB"; this.SGBCoreSubmenu.DropDownOpened += new System.EventHandler(this.SGBCoreSubmenu_DropDownOpened); - // - // GBCoreSubmenu - // - this.GBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.GBGambatteMenuItem, - this.GBGBHawkMenuItem}); - this.GBCoreSubmenu.Name = "GBCoreSubmenu"; - this.GBCoreSubmenu.Size = new System.Drawing.Size(239, 22); - this.GBCoreSubmenu.Text = "GB"; - this.GBCoreSubmenu.DropDownOpened += new System.EventHandler(this.GBCoreSubmenu_DropDownOpened); - // - // SgbBsnesMenuItem - // - this.SgbBsnesMenuItem.Name = "SgbBsnesMenuItem"; - this.SgbBsnesMenuItem.Size = new System.Drawing.Size(152, 22); + // + // SgbBsnesMenuItem + // + this.SgbBsnesMenuItem.Name = "SgbBsnesMenuItem"; + this.SgbBsnesMenuItem.Size = new System.Drawing.Size(123, 22); this.SgbBsnesMenuItem.Text = "BSNES"; this.SgbBsnesMenuItem.Click += new System.EventHandler(this.SgbCorePick_Click); // // SgbSameBoyMenuItem // this.SgbSameBoyMenuItem.Name = "SgbSameBoyMenuItem"; - this.SgbSameBoyMenuItem.Size = new System.Drawing.Size(152, 22); + this.SgbSameBoyMenuItem.Size = new System.Drawing.Size(123, 22); this.SgbSameBoyMenuItem.Text = "SameBoy"; this.SgbSameBoyMenuItem.Click += new System.EventHandler(this.SgbCorePick_Click); - // - // GBGambatteMenuItem - // - this.GBGambatteMenuItem.Name = "GBGambatteMenuItem"; - this.GBGambatteMenuItem.Size = new System.Drawing.Size(152, 22); - this.GBGambatteMenuItem.Text = "Gambatte"; - this.GBGambatteMenuItem.Click += new System.EventHandler(this.GBCorePick_Click); - // - // GBGBHawkMenuItem - // - this.GBGBHawkMenuItem.Name = "GBGBHawkMenuItem"; - this.GBGBHawkMenuItem.Size = new System.Drawing.Size(152, 22); - this.GBGBHawkMenuItem.Text = "GBHawk"; - this.GBGBHawkMenuItem.Click += new System.EventHandler(this.GBCorePick_Click); - // - // GBInSGBMenuItem - // - this.GBInSGBMenuItem.Name = "GBInSGBMenuItem"; + // + // GBCoreSubmenu + // + this.GBCoreSubmenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.GBGambatteMenuItem, + this.GBGBHawkMenuItem}); + this.GBCoreSubmenu.Name = "GBCoreSubmenu"; + this.GBCoreSubmenu.Size = new System.Drawing.Size(239, 22); + this.GBCoreSubmenu.Text = "GB"; + this.GBCoreSubmenu.DropDownOpened += new System.EventHandler(this.GBCoreSubmenu_DropDownOpened); + // + // GBGambatteMenuItem + // + this.GBGambatteMenuItem.Name = "GBGambatteMenuItem"; + this.GBGambatteMenuItem.Size = new System.Drawing.Size(126, 22); + this.GBGambatteMenuItem.Text = "Gambatte"; + this.GBGambatteMenuItem.Click += new System.EventHandler(this.GBCorePick_Click); + // + // GBGBHawkMenuItem + // + this.GBGBHawkMenuItem.Name = "GBGBHawkMenuItem"; + this.GBGBHawkMenuItem.Size = new System.Drawing.Size(126, 22); + this.GBGBHawkMenuItem.Text = "GBHawk"; + this.GBGBHawkMenuItem.Click += new System.EventHandler(this.GBCorePick_Click); + // + // GBInSGBMenuItem + // + this.GBInSGBMenuItem.Name = "GBInSGBMenuItem"; this.GBInSGBMenuItem.Size = new System.Drawing.Size(239, 22); this.GBInSGBMenuItem.Text = "GB in SGB"; this.GBInSGBMenuItem.Click += new System.EventHandler(this.GbInSgbMenuItem_Click); @@ -2051,7 +2048,7 @@ this.batchRunnerToolStripMenuItem, this.ExperimentalToolsSubMenu}); this.ToolsSubMenu.Name = "ToolsSubMenu"; - this.ToolsSubMenu.Size = new System.Drawing.Size(47, 19); + this.ToolsSubMenu.Size = new System.Drawing.Size(48, 19); this.ToolsSubMenu.Text = "&Tools"; this.ToolsSubMenu.DropDownOpened += new System.EventHandler(this.ToolsSubMenu_DropDownOpened); // @@ -2440,7 +2437,7 @@ // this.PceControllerSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.GameController; this.PceControllerSettingsMenuItem.Name = "PceControllerSettingsMenuItem"; - this.PceControllerSettingsMenuItem.Size = new System.Drawing.Size(258, 22); + this.PceControllerSettingsMenuItem.Size = new System.Drawing.Size(259, 22); this.PceControllerSettingsMenuItem.Text = "Controller Settings"; this.PceControllerSettingsMenuItem.Click += new System.EventHandler(this.PceControllerSettingsMenuItem_Click); // @@ -2448,59 +2445,59 @@ // this.PCEGraphicsSettingsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.tvIcon; this.PCEGraphicsSettingsMenuItem.Name = "PCEGraphicsSettingsMenuItem"; - this.PCEGraphicsSettingsMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEGraphicsSettingsMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEGraphicsSettingsMenuItem.Text = "Graphics Settings"; this.PCEGraphicsSettingsMenuItem.Click += new System.EventHandler(this.PceGraphicsSettingsMenuItem_Click); // // toolStripSeparator32 // this.toolStripSeparator32.Name = "toolStripSeparator32"; - this.toolStripSeparator32.Size = new System.Drawing.Size(255, 6); + this.toolStripSeparator32.Size = new System.Drawing.Size(256, 6); // // PCEBGViewerMenuItem // this.PCEBGViewerMenuItem.Name = "PCEBGViewerMenuItem"; - this.PCEBGViewerMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEBGViewerMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEBGViewerMenuItem.Text = "&BG Viewer"; this.PCEBGViewerMenuItem.Click += new System.EventHandler(this.PceBgViewerMenuItem_Click); // // PCEtileViewerToolStripMenuItem // this.PCEtileViewerToolStripMenuItem.Name = "PCEtileViewerToolStripMenuItem"; - this.PCEtileViewerToolStripMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEtileViewerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEtileViewerToolStripMenuItem.Text = "&Tile Viewer"; this.PCEtileViewerToolStripMenuItem.Click += new System.EventHandler(this.PceTileViewerMenuItem_Click); // // PceSoundDebuggerToolStripMenuItem // this.PceSoundDebuggerToolStripMenuItem.Name = "PceSoundDebuggerToolStripMenuItem"; - this.PceSoundDebuggerToolStripMenuItem.Size = new System.Drawing.Size(258, 22); + this.PceSoundDebuggerToolStripMenuItem.Size = new System.Drawing.Size(259, 22); this.PceSoundDebuggerToolStripMenuItem.Text = "&Sound Debugger"; this.PceSoundDebuggerToolStripMenuItem.Click += new System.EventHandler(this.PceSoundDebuggerMenuItem_Click); // // toolStripSeparator25 // this.toolStripSeparator25.Name = "toolStripSeparator25"; - this.toolStripSeparator25.Size = new System.Drawing.Size(255, 6); + this.toolStripSeparator25.Size = new System.Drawing.Size(256, 6); // // PCEAlwaysPerformSpriteLimitMenuItem // this.PCEAlwaysPerformSpriteLimitMenuItem.Name = "PCEAlwaysPerformSpriteLimitMenuItem"; - this.PCEAlwaysPerformSpriteLimitMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEAlwaysPerformSpriteLimitMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEAlwaysPerformSpriteLimitMenuItem.Text = "Always Perform Sprite Limit"; this.PCEAlwaysPerformSpriteLimitMenuItem.Click += new System.EventHandler(this.PCEAlwaysPerformSpriteLimitMenuItem_Click); // // PCEAlwaysEqualizeVolumesMenuItem // this.PCEAlwaysEqualizeVolumesMenuItem.Name = "PCEAlwaysEqualizeVolumesMenuItem"; - this.PCEAlwaysEqualizeVolumesMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEAlwaysEqualizeVolumesMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEAlwaysEqualizeVolumesMenuItem.Text = "Always Equalize Volumes (PCE-CD)"; this.PCEAlwaysEqualizeVolumesMenuItem.Click += new System.EventHandler(this.PCEAlwaysEqualizeVolumesMenuItem_Click); // // PCEArcadeCardRewindEnableMenuItem // this.PCEArcadeCardRewindEnableMenuItem.Name = "PCEArcadeCardRewindEnableMenuItem"; - this.PCEArcadeCardRewindEnableMenuItem.Size = new System.Drawing.Size(258, 22); + this.PCEArcadeCardRewindEnableMenuItem.Size = new System.Drawing.Size(259, 22); this.PCEArcadeCardRewindEnableMenuItem.Text = "Arcade Card Rewind-Enable Hack"; this.PCEArcadeCardRewindEnableMenuItem.Click += new System.EventHandler(this.PCEArcadeCardRewindEnableMenuItem_Click); // @@ -2510,7 +2507,7 @@ this.SMSregionToolStripMenuItem, this.SMSdisplayToolStripMenuItem, this.SMSControllerToolStripMenuItem, - this.SMStoolStripMenuItem2, + this.SMStoolStripMenuItem2, this.SMSenableBIOSToolStripMenuItem, this.SMSEnableFMChipMenuItem, this.SMSOverclockMenuItem, @@ -2536,7 +2533,7 @@ this.SMSregionJapanToolStripMenuItem, this.SMSregionAutoToolStripMenuItem}); this.SMSregionToolStripMenuItem.Name = "SMSregionToolStripMenuItem"; - this.SMSregionToolStripMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSregionToolStripMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSregionToolStripMenuItem.Text = "Region"; // // SMSregionExportToolStripMenuItem @@ -2567,7 +2564,7 @@ this.SMSdisplayPalToolStripMenuItem, this.SMSdisplayAutoToolStripMenuItem}); this.SMSdisplayToolStripMenuItem.Name = "SMSdisplayToolStripMenuItem"; - this.SMSdisplayToolStripMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSdisplayToolStripMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSdisplayToolStripMenuItem.Text = "Display Type"; // // SMSdisplayNtscToolStripMenuItem @@ -2577,51 +2574,9 @@ this.SMSdisplayNtscToolStripMenuItem.Text = "NTSC"; this.SMSdisplayNtscToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayNTSC_Click); // - // SMSControllerToolStripMenuItem + // SMSdisplayPalToolStripMenuItem // - this.SMSControllerToolStripMenuItem.Name = "SMSControllerToolStripMenuItem"; - this.SMSControllerToolStripMenuItem.Text = "&Controller Type"; - this.SMSControllerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.SMSControllerStandardToolStripMenuItem, - this.SMSControllerPaddleToolStripMenuItem, - this.SMSControllerLightPhaserToolStripMenuItem, - this.SMSControllerSportsPadToolStripMenuItem, - this.SMSControllerKeyboardToolStripMenuItem}); - // - // SMSControllerStandardToolStripMenuItem - // - this.SMSControllerStandardToolStripMenuItem.Name = "SMSControllerStandardToolStripMenuItem"; - this.SMSControllerStandardToolStripMenuItem.Text = "Standard"; - this.SMSControllerStandardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerStandardToolStripMenuItem_Click); - // - // SMSControllerPaddleToolStripMenuItem - // - this.SMSControllerPaddleToolStripMenuItem.Name = "SMSControllerPaddleToolStripMenuItem"; - this.SMSControllerPaddleToolStripMenuItem.Text = "Paddle"; - this.SMSControllerPaddleToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerPaddleToolStripMenuItem_Click); - // - // SMSControllerLightPhaserToolStripMenuItem - // - this.SMSControllerLightPhaserToolStripMenuItem.Name = "SMSControllerLightPhaserToolStripMenuItem"; - this.SMSControllerLightPhaserToolStripMenuItem.Text = "Light Phaser"; - this.SMSControllerLightPhaserToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerLightPhaserToolStripMenuItem_Click); - // - // SMSControllerSportsPadToolStripMenuItem - // - this.SMSControllerSportsPadToolStripMenuItem.Name = "SMSControllerSportsPadToolStripMenuItem"; - this.SMSControllerSportsPadToolStripMenuItem.Text = "Sports Pad"; - this.SMSControllerSportsPadToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerSportsPadToolStripMenuItem_Click); - // - // SMSControllerKeyboardToolStripMenuItem - // - this.SMSControllerKeyboardToolStripMenuItem.Name = "SMSControllerKeyboardToolStripMenuItem"; - this.SMSControllerKeyboardToolStripMenuItem.Text = "Keyboard"; - this.SMSControllerKeyboardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerKeyboardToolStripMenuItem_Click); - - // - // SMSdisplayPalToolStripMenuItem - // - this.SMSdisplayPalToolStripMenuItem.Name = "SMSdisplayPalToolStripMenuItem"; + this.SMSdisplayPalToolStripMenuItem.Name = "SMSdisplayPalToolStripMenuItem"; this.SMSdisplayPalToolStripMenuItem.Size = new System.Drawing.Size(104, 22); this.SMSdisplayPalToolStripMenuItem.Text = "PAL"; this.SMSdisplayPalToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayPAL_Click); @@ -2633,97 +2588,144 @@ this.SMSdisplayAutoToolStripMenuItem.Text = "Auto"; this.SMSdisplayAutoToolStripMenuItem.Click += new System.EventHandler(this.SMS_DisplayAuto_Click); // + // SMSControllerToolStripMenuItem + // + this.SMSControllerToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.SMSControllerStandardToolStripMenuItem, + this.SMSControllerPaddleToolStripMenuItem, + this.SMSControllerLightPhaserToolStripMenuItem, + this.SMSControllerSportsPadToolStripMenuItem, + this.SMSControllerKeyboardToolStripMenuItem}); + this.SMSControllerToolStripMenuItem.Name = "SMSControllerToolStripMenuItem"; + this.SMSControllerToolStripMenuItem.Size = new System.Drawing.Size(278, 22); + this.SMSControllerToolStripMenuItem.Text = "&Controller Type"; + // + // SMSControllerStandardToolStripMenuItem + // + this.SMSControllerStandardToolStripMenuItem.Name = "SMSControllerStandardToolStripMenuItem"; + this.SMSControllerStandardToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.SMSControllerStandardToolStripMenuItem.Text = "Standard"; + this.SMSControllerStandardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerStandardToolStripMenuItem_Click); + // + // SMSControllerPaddleToolStripMenuItem + // + this.SMSControllerPaddleToolStripMenuItem.Name = "SMSControllerPaddleToolStripMenuItem"; + this.SMSControllerPaddleToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.SMSControllerPaddleToolStripMenuItem.Text = "Paddle"; + this.SMSControllerPaddleToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerPaddleToolStripMenuItem_Click); + // + // SMSControllerLightPhaserToolStripMenuItem + // + this.SMSControllerLightPhaserToolStripMenuItem.Name = "SMSControllerLightPhaserToolStripMenuItem"; + this.SMSControllerLightPhaserToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.SMSControllerLightPhaserToolStripMenuItem.Text = "Light Phaser"; + this.SMSControllerLightPhaserToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerLightPhaserToolStripMenuItem_Click); + // + // SMSControllerSportsPadToolStripMenuItem + // + this.SMSControllerSportsPadToolStripMenuItem.Name = "SMSControllerSportsPadToolStripMenuItem"; + this.SMSControllerSportsPadToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.SMSControllerSportsPadToolStripMenuItem.Text = "Sports Pad"; + this.SMSControllerSportsPadToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerSportsPadToolStripMenuItem_Click); + // + // SMSControllerKeyboardToolStripMenuItem + // + this.SMSControllerKeyboardToolStripMenuItem.Name = "SMSControllerKeyboardToolStripMenuItem"; + this.SMSControllerKeyboardToolStripMenuItem.Size = new System.Drawing.Size(139, 22); + this.SMSControllerKeyboardToolStripMenuItem.Text = "Keyboard"; + this.SMSControllerKeyboardToolStripMenuItem.Click += new System.EventHandler(this.SMSControllerKeyboardToolStripMenuItem_Click); + // // SMStoolStripMenuItem2 // this.SMStoolStripMenuItem2.Name = "SMStoolStripMenuItem2"; - this.SMStoolStripMenuItem2.Size = new System.Drawing.Size(274, 6); + this.SMStoolStripMenuItem2.Size = new System.Drawing.Size(275, 6); // // SMSenableBIOSToolStripMenuItem // this.SMSenableBIOSToolStripMenuItem.Name = "SMSenableBIOSToolStripMenuItem"; - this.SMSenableBIOSToolStripMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSenableBIOSToolStripMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSenableBIOSToolStripMenuItem.Text = "Enable BIOS (Must be Enabled for TAS)"; this.SMSenableBIOSToolStripMenuItem.Click += new System.EventHandler(this.SmsBiosMenuItem_Click); // // SMSEnableFMChipMenuItem // this.SMSEnableFMChipMenuItem.Name = "SMSEnableFMChipMenuItem"; - this.SMSEnableFMChipMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSEnableFMChipMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSEnableFMChipMenuItem.Text = "&Enable FM Chip"; this.SMSEnableFMChipMenuItem.Click += new System.EventHandler(this.SmsEnableFmChipMenuItem_Click); // // SMSOverclockMenuItem // this.SMSOverclockMenuItem.Name = "SMSOverclockMenuItem"; - this.SMSOverclockMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSOverclockMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSOverclockMenuItem.Text = "&Overclock when Known Safe"; this.SMSOverclockMenuItem.Click += new System.EventHandler(this.SMSOverclockMenuItem_Click); // // SMSForceStereoMenuItem // this.SMSForceStereoMenuItem.Name = "SMSForceStereoMenuItem"; - this.SMSForceStereoMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSForceStereoMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSForceStereoMenuItem.Text = "&Force Stereo Separation"; this.SMSForceStereoMenuItem.Click += new System.EventHandler(this.SMSForceStereoMenuItem_Click); // // SMSSpriteLimitMenuItem // this.SMSSpriteLimitMenuItem.Name = "SMSSpriteLimitMenuItem"; - this.SMSSpriteLimitMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSSpriteLimitMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSSpriteLimitMenuItem.Text = "Sprite &Limit"; this.SMSSpriteLimitMenuItem.Click += new System.EventHandler(this.SMSSpriteLimitMenuItem_Click); // // SMSDisplayOverscanMenuItem // this.SMSDisplayOverscanMenuItem.Name = "SMSDisplayOverscanMenuItem"; - this.SMSDisplayOverscanMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSDisplayOverscanMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSDisplayOverscanMenuItem.Text = "Display Overscan"; this.SMSDisplayOverscanMenuItem.Click += new System.EventHandler(this.SMSDisplayOverscanMenuItem_Click); // // SMSFix3DGameDisplayToolStripMenuItem // this.SMSFix3DGameDisplayToolStripMenuItem.Name = "SMSFix3DGameDisplayToolStripMenuItem"; - this.SMSFix3DGameDisplayToolStripMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSFix3DGameDisplayToolStripMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSFix3DGameDisplayToolStripMenuItem.Text = "Fix 3D Game Display"; this.SMSFix3DGameDisplayToolStripMenuItem.Click += new System.EventHandler(this.SMSFix3DDisplayMenuItem_Click); // // ShowClippedRegionsMenuItem // this.ShowClippedRegionsMenuItem.Name = "ShowClippedRegionsMenuItem"; - this.ShowClippedRegionsMenuItem.Size = new System.Drawing.Size(277, 22); + this.ShowClippedRegionsMenuItem.Size = new System.Drawing.Size(278, 22); this.ShowClippedRegionsMenuItem.Text = "&Show Clipped Regions"; this.ShowClippedRegionsMenuItem.Click += new System.EventHandler(this.ShowClippedRegionsMenuItem_Click); // // HighlightActiveDisplayRegionMenuItem // this.HighlightActiveDisplayRegionMenuItem.Name = "HighlightActiveDisplayRegionMenuItem"; - this.HighlightActiveDisplayRegionMenuItem.Size = new System.Drawing.Size(277, 22); + this.HighlightActiveDisplayRegionMenuItem.Size = new System.Drawing.Size(278, 22); this.HighlightActiveDisplayRegionMenuItem.Text = "&Highlight Active Display Region"; this.HighlightActiveDisplayRegionMenuItem.Click += new System.EventHandler(this.HighlightActiveDisplayRegionMenuItem_Click); // // SMSGraphicsSettingsMenuItem // this.SMSGraphicsSettingsMenuItem.Name = "SMSGraphicsSettingsMenuItem"; - this.SMSGraphicsSettingsMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSGraphicsSettingsMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSGraphicsSettingsMenuItem.Text = "&Graphics Settings..."; this.SMSGraphicsSettingsMenuItem.Click += new System.EventHandler(this.SMSGraphicsSettingsMenuItem_Click); // // toolStripSeparator24 // this.toolStripSeparator24.Name = "toolStripSeparator24"; - this.toolStripSeparator24.Size = new System.Drawing.Size(274, 6); + this.toolStripSeparator24.Size = new System.Drawing.Size(275, 6); // // SMSVDPViewerToolStripMenuItem // this.SMSVDPViewerToolStripMenuItem.Name = "SMSVDPViewerToolStripMenuItem"; - this.SMSVDPViewerToolStripMenuItem.Size = new System.Drawing.Size(277, 22); + this.SMSVDPViewerToolStripMenuItem.Size = new System.Drawing.Size(278, 22); this.SMSVDPViewerToolStripMenuItem.Text = "&VDP Viewer"; this.SMSVDPViewerToolStripMenuItem.Click += new System.EventHandler(this.SmsVdpViewerMenuItem_Click); // // GGGameGenieMenuItem // this.GGGameGenieMenuItem.Name = "GGGameGenieMenuItem"; - this.GGGameGenieMenuItem.Size = new System.Drawing.Size(277, 22); + this.GGGameGenieMenuItem.Size = new System.Drawing.Size(278, 22); this.GGGameGenieMenuItem.Text = "&Game Genie Encoder/Decoder"; this.GGGameGenieMenuItem.Click += new System.EventHandler(this.GGGameGenieMenuItem_Click); // @@ -2795,7 +2797,7 @@ // this.A7800SubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.A7800ControllerSettingsMenuItem, - this.A7800FilterSettingsMenuItem}); + this.A7800FilterSettingsMenuItem}); this.A7800SubMenu.Name = "A7800SubMenu"; this.A7800SubMenu.Size = new System.Drawing.Size(51, 19); this.A7800SubMenu.Text = "&A7800"; @@ -2804,26 +2806,26 @@ // A7800ControllerSettingsMenuItem // this.A7800ControllerSettingsMenuItem.Name = "A7800ControllerSettingsMenuItem"; - this.A7800ControllerSettingsMenuItem.Size = new System.Drawing.Size(125, 22); + this.A7800ControllerSettingsMenuItem.Size = new System.Drawing.Size(172, 22); this.A7800ControllerSettingsMenuItem.Text = "Controller Settings"; this.A7800ControllerSettingsMenuItem.Click += new System.EventHandler(this.A7800ControllerSettingsToolStripMenuItem_Click); - // - // A7800FilterSettingsMenuItem - // - this.A7800FilterSettingsMenuItem.Name = "A7800FilterSettingsMenuItem"; - this.A7800FilterSettingsMenuItem.Size = new System.Drawing.Size(125, 22); - this.A7800FilterSettingsMenuItem.Text = "Filter Settings"; - this.A7800FilterSettingsMenuItem.Click += new System.EventHandler(this.A7800FilterSettingsToolStripMenuItem_Click); - // - // GBSubMenu - // - this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + // + // A7800FilterSettingsMenuItem + // + this.A7800FilterSettingsMenuItem.Name = "A7800FilterSettingsMenuItem"; + this.A7800FilterSettingsMenuItem.Size = new System.Drawing.Size(172, 22); + this.A7800FilterSettingsMenuItem.Text = "Filter Settings"; + this.A7800FilterSettingsMenuItem.Click += new System.EventHandler(this.A7800FilterSettingsToolStripMenuItem_Click); + // + // GBSubMenu + // + this.GBSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.GBcoreSettingsToolStripMenuItem, this.LoadGBInSGBMenuItem, this.toolStripSeparator28, this.GBGPUViewerMenuItem, this.GBGameGenieMenuItem, - this.GBPrinterViewerMenuItem}); + this.GBPrinterViewerMenuItem}); this.GBSubMenu.Name = "GBSubMenu"; this.GBSubMenu.Size = new System.Drawing.Size(34, 19); this.GBSubMenu.Text = "&GB"; @@ -2861,17 +2863,17 @@ this.GBGameGenieMenuItem.Size = new System.Drawing.Size(233, 22); this.GBGameGenieMenuItem.Text = "&Game Genie Encoder/Decoder"; this.GBGameGenieMenuItem.Click += new System.EventHandler(this.GBGameGenieMenuItem_Click); - // - // GBPrinterViewerMenuItem - // - this.GBPrinterViewerMenuItem.Name = "GBPrinterViewerMenuItem"; - this.GBPrinterViewerMenuItem.Size = new System.Drawing.Size(233, 22); - this.GBPrinterViewerMenuItem.Text = "&Printer Viewer"; - this.GBPrinterViewerMenuItem.Click += new System.EventHandler(this.GBPrinterViewerMenuItem_Click); - // - // GBASubMenu - // - this.GBASubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + // + // GBPrinterViewerMenuItem + // + this.GBPrinterViewerMenuItem.Name = "GBPrinterViewerMenuItem"; + this.GBPrinterViewerMenuItem.Size = new System.Drawing.Size(233, 22); + this.GBPrinterViewerMenuItem.Text = "&Printer Viewer"; + this.GBPrinterViewerMenuItem.Click += new System.EventHandler(this.GBPrinterViewerMenuItem_Click); + // + // GBASubMenu + // + this.GBASubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.GBACoreSelectionSubMenu, this.GBAcoresettingsToolStripMenuItem1, this.toolStripSeparator33, @@ -3309,7 +3311,7 @@ // preferencesToolStripMenuItem1 // this.preferencesToolStripMenuItem1.Name = "preferencesToolStripMenuItem1"; - this.preferencesToolStripMenuItem1.Size = new System.Drawing.Size(144, 22); + this.preferencesToolStripMenuItem1.Size = new System.Drawing.Size(152, 22); this.preferencesToolStripMenuItem1.Text = "Preferences..."; this.preferencesToolStripMenuItem1.Click += new System.EventHandler(this.preferencesToolStripMenuItem1_Click); // @@ -3324,7 +3326,7 @@ // preferencesToolStripMenuItem2 // this.preferencesToolStripMenuItem2.Name = "preferencesToolStripMenuItem2"; - this.preferencesToolStripMenuItem2.Size = new System.Drawing.Size(144, 22); + this.preferencesToolStripMenuItem2.Size = new System.Drawing.Size(152, 22); this.preferencesToolStripMenuItem2.Text = "Preferences..."; this.preferencesToolStripMenuItem2.Click += new System.EventHandler(this.preferencesToolStripMenuItem2_Click); // @@ -3372,6 +3374,21 @@ this.AboutMenuItem.Text = "&About"; this.AboutMenuItem.Click += new System.EventHandler(this.AboutMenuItem_Click); // + // zXSpectrumToolStripMenuItem + // + this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.preferencesToolStripMenuItem4}); + this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem"; + this.zXSpectrumToolStripMenuItem.Size = new System.Drawing.Size(87, 19); + this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum"; + this.zXSpectrumToolStripMenuItem.DropDownOpened += new System.EventHandler(this.zXSpectrumToolStripMenuItem_DropDownOpened); + // + // Atari7800HawkCoreMenuItem + // + this.Atari7800HawkCoreMenuItem.Name = "Atari7800HawkCoreMenuItem"; + this.Atari7800HawkCoreMenuItem.Size = new System.Drawing.Size(153, 22); + this.Atari7800HawkCoreMenuItem.Text = "Atari7800Hawk"; + // // MainStatusBar // this.MainStatusBar.ClickThrough = true; @@ -3992,6 +4009,13 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // + // preferencesToolStripMenuItem4 + // + this.preferencesToolStripMenuItem4.Name = "preferencesToolStripMenuItem4"; + this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(152, 22); + this.preferencesToolStripMenuItem4.Text = "Preferences"; + this.preferencesToolStripMenuItem4.Click += new System.EventHandler(this.preferencesToolStripMenuItem4_Click); + // // MainForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -4454,5 +4478,7 @@ private System.Windows.Forms.ToolStripMenuItem SMSControllerLightPhaserToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSControllerSportsPadToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSControllerKeyboardToolStripMenuItem; - } + private System.Windows.Forms.ToolStripMenuItem zXSpectrumToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem4; + } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 3eb36838f1..7d8a4a3829 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2435,11 +2435,25 @@ namespace BizHawk.Client.EmuHawk new IntvControllerSettings().ShowDialog(); } - #endregion + #endregion - #region Help + #region ZXSpectrum - private void HelpSubMenu_DropDownOpened(object sender, EventArgs e) + private void zXSpectrumToolStripMenuItem_DropDownOpened(object sender, EventArgs e) + { + + } + + private void preferencesToolStripMenuItem4_Click(object sender, EventArgs e) + { + GenericCoreConfig.DoDialog(this, "ZXSpectrum Settings"); + } + + #endregion + + #region Help + + private void HelpSubMenu_DropDownOpened(object sender, EventArgs e) { FeaturesMenuItem.Visible = VersionInfo.DeveloperBuild; } diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index f52e3a64d5..92dee71736 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -1719,6 +1719,7 @@ namespace BizHawk.Client.EmuHawk sNESToolStripMenuItem.Visible = false; neoGeoPocketToolStripMenuItem.Visible = false; pCFXToolStripMenuItem.Visible = false; + zXSpectrumToolStripMenuItem.Visible = false; switch (system) { @@ -1816,6 +1817,9 @@ namespace BizHawk.Client.EmuHawk case "PCFX": pCFXToolStripMenuItem.Visible = true; break; + case "ZXSpectrum": + zXSpectrumToolStripMenuItem.Visible = true; + break; } } @@ -4295,7 +4299,9 @@ namespace BizHawk.Client.EmuHawk GenericCoreConfig.DoDialog(this, "PC-FX Settings"); } - private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording) + + + private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording) { var isRewinding = false; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 33dddae230..16643c8b84 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -30,24 +30,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public bool PutSyncSettings(ZXSpectrumSyncSettings o) { + bool ret = ZXSpectrumSyncSettings.NeedsReboot(SyncSettings, o); SyncSettings = o; - return false; + return ret; } 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("Auto-load tape")] + [Description("Auto or manual tape operation")] + [DefaultValue(true)] + public bool AutoLoadTape { get; set; } public ZXSpectrumSettings Clone() @@ -63,6 +58,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class ZXSpectrumSyncSettings { + [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)] @@ -72,6 +77,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { return (ZXSpectrumSyncSettings)MemberwiseClone(); } + + public ZXSpectrumSyncSettings() + { + SettingsUtil.SetDefaultValues(this); + } + + public static bool NeedsReboot(ZXSpectrumSyncSettings x, ZXSpectrumSyncSettings y) + { + return !DeepEquality.DeepEquals(x, y); + } } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 9add8bdeec..083da9edf6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -34,11 +34,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _file = file; - switch (Settings.MachineType) + switch (SyncSettings.MachineType) { case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum48, Settings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; default: throw new InvalidOperationException("Machine not yet emulated"); From 30019d68fc99925ba5aab7b4e86e0bfe8e9ed3e5 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 5 Dec 2017 10:02:57 +0000 Subject: [PATCH 021/105] Started Spectrum128 implementation --- .../Database/FirmwareDatabase.cs | 7 +- .../BizHawk.Emulation.Cores.csproj | 5 + .../SinclairSpectrum/Machine/MachineType.cs | 7 +- .../Machine/SpectrumBase.Memory.cs | 100 ++---- .../Machine/SpectrumBase.Port.cs | 140 +-------- .../Machine/SpectrumBase.Screen.cs | 3 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 8 +- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 284 ++++++++++++++++++ .../Machine/ZXSpectrum128K/ZX128.Port.cs | 188 ++++++++++++ .../Machine/ZXSpectrum128K/ZX128.cs | 57 ++++ .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 149 +++++++++ .../Machine/ZXSpectrum48K/ZX48.Port.cs | 161 ++++++++++ .../Machine/ZXSpectrum48K/ZX48.cs | 156 +--------- .../Computers/SinclairSpectrum/RomData.cs | 8 + .../Computers/SinclairSpectrum/ZXSpectrum.cs | 10 + 15 files changed, 905 insertions(+), 378 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 7d8eb5fe63..789c01138f 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -52,10 +52,11 @@ namespace BizHawk.Emulation.Common // ZX Spectrum FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); + FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); - // 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 - var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)"); + // 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 + var ss_100_j = File("2B8CB4F87580683EB4D760E4ED210813D667F0A2", 524288, "saturn-1.00-(J).bin", "Bios v1.00 (J)"); var ss_100_ue = File("FAA8EA183A6D7BBE5D4E03BB1332519800D3FBC3", 524288, "saturn-1.00-(U+E).bin", "Bios v1.00 (U+E)"); var ss_100a_ue = File("3BB41FEB82838AB9A35601AC666DE5AACFD17A58", 524288, "saturn-1.00a-(U+E).bin", "Bios v1.00a (U+E)"); // ?? is this size correct? var ss_101_j = File("DF94C5B4D47EB3CC404D88B33A8FDA237EAF4720", 524288, "saturn-1.01-(J).bin", "Bios v1.01 (J)"); // ?? is this size correct? diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 13af57d15a..830c6523cf 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -276,7 +276,11 @@ + + + + @@ -1377,6 +1381,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs index 829f1a0e5b..e70dc3ea4a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -11,6 +11,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Sinclair Spectrum 48K model /// - ZXSpectrum48 + ZXSpectrum48, + + /// + /// Sinclair Spectrum 128K model + /// + ZXSpectrum128 } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 5b371bbc7f..d58ef1f6eb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -44,28 +44,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public virtual byte ReadBus(ushort addr) + public abstract byte ReadBus(ushort addr); + + /// + /// Pushes a value onto the data bus that should be valid as long as the interrupt is true + /// + /// + /// + public virtual byte PushBus() { - throw new NotImplementedException("Must be overriden"); + return 0xFF; } - /// - /// Pushes a value onto the data bus that should be valid as long as the interrupt is true - /// - /// - /// - public virtual byte PushBus() - { - throw new NotImplementedException("Must be overriden"); - } - - /// - /// Simulates writing to the bus - /// Paging should be handled here - /// - /// - /// - public virtual void WriteBus(ushort addr, byte value) + /// + /// Simulates writing to the bus + /// Paging should be handled here + /// + /// + /// + public virtual void WriteBus(ushort addr, byte value) { throw new NotImplementedException("Must be overriden"); } @@ -76,23 +73,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public virtual byte ReadMemory(ushort addr) - { - throw new NotImplementedException("Must be overriden"); - } - /* - /// - /// Reads a byte of data from a specified memory address - /// (with no memory contention) - /// - /// - /// - public virtual byte PeekMemory(ushort addr) - { - var data = ReadBus(addr); - return data; - } - */ + public abstract byte ReadMemory(ushort addr); /// /// Writes a byte of data to a specified memory address @@ -100,51 +81,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public virtual void WriteMemory(ushort addr, byte value) - { - throw new NotImplementedException("Must be overriden"); - } - - /* - /// - /// Writes a byte of data to a specified memory address - /// (without contention) - /// - /// - /// - public virtual void PokeMemory(ushort addr, byte value) - { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - - WriteBus(addr, value); - } - */ - - /// - /// Fills memory from buffer - /// - /// - /// - public virtual void FillMemory(byte[] buffer, ushort startAddress) - { - //buffer?.CopyTo(RAM, startAddress); - } + public abstract void WriteMemory(ushort addr, byte value); /// /// Sets up the ROM /// /// /// - public virtual void InitROM(RomData romData) - { - RomData = romData; - // for 16/48k machines only ROM0 is used (no paging) - RomData.RomBytes?.CopyTo(ROM0, 0); - } + public abstract void InitROM(RomData romData); /// /// ULA reads the memory at the specified address @@ -154,7 +98,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual byte FetchScreenMemory(ushort addr) { - //var value = RAM0[(addr & 0x3FFF)];// + 0x4000]; var value = ReadBus((ushort)((addr & 0x3FFF) + 0x4000)); return value; } @@ -162,10 +105,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Helper function to refresh memory array (probably not the best way to do things) /// - public virtual void ReInitMemory() - { - throw new NotImplementedException("Must be overriden"); - } + public abstract void ReInitMemory(); /// /// Returns the memory contention value for the specified T-State (cycle) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index be31fead3f..c590901dac 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -22,150 +22,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public virtual byte ReadPort(ushort port) - { - int result = 0xFF; - - // Check whether the low bit is reset - // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x0001) == 0; - - ContendPort((ushort)port); - - // Kempston Joystick - if ((port & 0xe0) == 0 || (port & 0x20) == 0) - { - return (byte)KempstonDevice.JoyLine; - } - else 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 - */ - - if ((port & 0x8000) == 0) - result &= KeyboardDevice.KeyLine[7]; - - if ((port & 0x4000) == 0) - result &= KeyboardDevice.KeyLine[6]; - - if ((port & 0x2000) == 0) - result &= KeyboardDevice.KeyLine[5]; - - if ((port & 0x1000) == 0) - result &= KeyboardDevice.KeyLine[4]; - - if ((port & 0x800) == 0) - result &= KeyboardDevice.KeyLine[3]; - - if ((port & 0x400) == 0) - result &= KeyboardDevice.KeyLine[2]; - - if ((port & 0x200) == 0) - result &= KeyboardDevice.KeyLine[1]; - - if ((port & 0x100) == 0) - result &= KeyboardDevice.KeyLine[0]; - - result = result & 0x1f; //mask out lower 4 bits - result = result | 0xa0; //set bit 5 & 7 to 1 - - - if (TapeDevice.CurrentMode == TapeOperationMode.Load) - { - if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) - { - result &= ~(TAPE_BIT); // reset is EAR ON - } - else - { - result |= (TAPE_BIT); // set is EAR Off - } - } - else - { - if (KeyboardDevice.IsIssue2Keyboard) - { - if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - else - { - if ((LastULAOutByte & EAR_BIT) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - } - - } - 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 (byte)result; - } + public abstract byte ReadPort(ushort port); /// /// Writes a byte of data to a specified port address /// /// /// - public virtual void WritePort(ushort port, byte value) - { - // Check whether the low bit is reset - // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x01) == 0; - - ContendPort(port); - - // 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 - BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); - - // Tape - TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); - } - } + public abstract void WritePort(ushort port, byte value); /// /// Apply I/O contention if necessary diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 8a4bedf024..62bd4d98d4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -179,7 +179,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The time of displaying right part of the border. /// Given in Z80 clock cycles. /// - protected int BorderRightTime = 24; + protected int BorderRightTime = 24; /// /// The time used to render the nonvisible right part of the border. @@ -900,6 +900,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public int VsyncNumerator { get { return 3500000; } + set { } } public int VsyncDenominator diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 7cd878d1cb..cb1c9064f9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -10,7 +10,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// * Main properties / fields / contruction* /// public abstract partial class SpectrumBase - { + { + + public bool ROMPaged { get; set; } + public bool SHADOWPaged { get; set; } + public int RAMPaged { get; set; } + public bool PagingDisabled { get; set; } + /// /// The calling ZXSpectrum class (piped in via constructor) /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs new file mode 100644 index 0000000000..4502a98a51 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128 : SpectrumBase + { + /* 128k paging controlled by writes to port 0x7ffd + * + * + + #7FFD (32765) - decoded as A15=0, A1=0 and /IORQ=0. Bits 0..5 are latched. Bits 0..2 select RAM bank in secton D. Bit 3 selects RAM bank to dispay screen (0 - RAM5, 1 - RAM7). Bit 4 selects ROM bank (0 - ROM0, 1 - ROM1). Bit 5, when set locks future writing to #7FFD port until reset. Reading #7FFD port is the same as writing #FF into it. + #BFFD (49149) - write data byte into AY-3-8912 chip. + #FFFD (65533) - select AY-3-8912 addres (D4..D7 ignored) and reading data byte. + + * 0xffff +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 | + | | |(also at| | |(also at| | | + | | | 0x8000)| | | 0x4000)| | | + | | | | | | screen | | screen | + 0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 2 | Any one of these pages may be switched in. + | | + | | + | | + 0x8000 +--------+ + | Bank 5 | + | | + | | + | screen | + 0x4000 +--------+--------+ + | ROM 0 | ROM 1 | Either ROM may be switched in. + | | | + | | | + | | | + 0x0000 +--------+--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + byte result = 0xff; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + result = Memory[0][addr % 0x4000]; + else + result = Memory[1][addr % 0x4000]; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + result = Memory[7][addr % 0x4000]; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + result = Memory[4][addr % 0x4000]; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + result = Memory[2][addr % 0x4000]; + break; + case 1: + result = Memory[3][addr % 0x4000]; + break; + case 2: + result = Memory[4][addr % 0x4000]; + break; + case 3: + result = Memory[5][addr % 0x4000]; + break; + case 4: + result = Memory[6][addr % 0x4000]; + break; + case 5: + result = Memory[7][addr % 0x4000]; + break; + case 6: + result = Memory[8][addr % 0x4000]; + break; + case 7: + result = Memory[9][addr % 0x4000]; + break; + } + break; + default: + break; + } + + return result; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + Memory[0][addr % 0x4000] = value; + else + Memory[1][addr % 0x4000] = value; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + Memory[7][addr % 0x4000] = value; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + Memory[4][addr % 0x4000] = value; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + Memory[2][addr % 0x4000] = value; + break; + case 1: + Memory[3][addr % 0x4000] = value; + break; + case 2: + Memory[4][addr % 0x4000] = value; + break; + case 3: + Memory[5][addr % 0x4000] = value; + break; + case 4: + Memory[6][addr % 0x4000] = value; + break; + case 5: + Memory[7][addr % 0x4000] = value; + break; + case 6: + Memory[8][addr % 0x4000] = value; + break; + case 7: + Memory[9][addr % 0x4000] = value; + break; + } + break; + default: + break; + } + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = ROM1; + else + Memory.Add(1, ROM1); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM0; + else + Memory.Add(2, RAM0); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM1; + else + Memory.Add(3, RAM1); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM2; + else + Memory.Add(4, RAM2); + + if (Memory.ContainsKey(5)) + Memory[5] = RAM3; + else + Memory.Add(5, RAM3); + + if (Memory.ContainsKey(6)) + Memory[6] = RAM4; + else + Memory.Add(6, RAM4); + + if (Memory.ContainsKey(7)) + Memory[7] = RAM5; + else + Memory.Add(7, RAM5); + + if (Memory.ContainsKey(8)) + Memory[8] = RAM6; + else + Memory.Add(8, RAM6); + + if (Memory.ContainsKey(9)) + Memory[9] = RAM7; + else + Memory.Add(9, RAM7); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // 128k uses ROM0 and ROM1 + // 128k loader is in ROM0, and fallback 48k rom is in ROM1 + for (int i = 0; i < 0x4000; i++) + { + ROM0[i] = RomData.RomBytes[i]; + ROM1[i] = RomData.RomBytes[i + 0x4000]; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs new file mode 100644 index 0000000000..d381bef130 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128 : SpectrumBase + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public override byte ReadPort(ushort port) + { + int result = 0xFF; + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x0001) == 0; + + ContendPort((ushort)port); + + // Kempston Joystick + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + return (byte)KempstonDevice.JoyLine; + } + else 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 + */ + + if ((port & 0x8000) == 0) + result &= KeyboardDevice.KeyLine[7]; + + if ((port & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((port & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((port & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((port & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((port & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((port & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((port & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + + if (TapeDevice.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (KeyboardDevice.IsIssue2Keyboard) + { + if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + else + { + if ((LastULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + + } + 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 (byte)result; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public override void WritePort(ushort port, byte value) + { + // paging + if (port == 0x7ffd) + { + // Bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // ROM page + if ((value & 0x10) != 0) + { + // 48k ROM + ROMPaged = true; + } + else + { + ROMPaged = false; + } + + // Bit 5 signifies that paging is disabled until next reboot + if ((value & 0x20) != 0) + PagingDisabled = true; + + + return; + } + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x01) == 0; + + ContendPort(port); + + // 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 + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + + // Tape + TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs new file mode 100644 index 0000000000..93b7b58f35 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BizHawk.Emulation.Cores.Components.Z80A; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128 : SpectrumBase + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128(ZXSpectrum spectrum, Z80A cpu, byte[] file) + { + Spectrum = spectrum; + CPU = cpu; + + ROMPaged = false; + SHADOWPaged = false; + RAMPaged = 0; + PagingDisabled = false; + + // init addressable memory from ROM and RAM banks + ReInitMemory(); + + //RAM = new byte[0x4000 + 0xC000]; + + //DisplayLineTime = 132; + VsyncNumerator = 3546900; + + InitScreenConfig(); + InitScreen(); + + ResetULACycle(); + + BuzzerDevice = new Buzzer(this); + BuzzerDevice.Init(44100, UlaFrameCycleCount); + + KeyboardDevice = new Keyboard48(this); + KempstonDevice = new KempstonJoystick(this); + + TapeProvider = new DefaultTapeProvider(file); + + TapeDevice = new Tape(TapeProvider); + TapeDevice.Init(this); + } + + #endregion + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs new file mode 100644 index 0000000000..3cf3b09390 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX48 : SpectrumBase + { + /* 48K Spectrum has NO memory paging + * + * 0xffff +--------+ + | Bank 2 | + | | + | | + | | + 0xc000 +--------+ + | Bank 1 | + | | + | | + | | + 0x8000 +--------+ + | Bank 0 | + | | + | | + | screen | + 0x4000 +--------+ + | ROM 0 | + | | + | | + | | + 0x0000 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + return bank[index]; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + // paging logic goes here + + var bank = Memory[divisor]; + var index = addr % 0x4000; + bank[index] = value; + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = RAM0; + else + Memory.Add(1, RAM0); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM1; + else + Memory.Add(2, RAM1); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM2; + else + Memory.Add(3, RAM2); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM3; + else + Memory.Add(4, RAM3); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs new file mode 100644 index 0000000000..2a0080e1c8 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX48 : SpectrumBase + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public override byte ReadPort(ushort port) + { + int result = 0xFF; + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x0001) == 0; + + ContendPort((ushort)port); + + // Kempston Joystick + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + return (byte)KempstonDevice.JoyLine; + } + else 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 + */ + + if ((port & 0x8000) == 0) + result &= KeyboardDevice.KeyLine[7]; + + if ((port & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((port & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((port & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((port & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((port & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((port & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((port & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + + if (TapeDevice.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (KeyboardDevice.IsIssue2Keyboard) + { + if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + else + { + if ((LastULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + + } + 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 (byte)result; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public override void WritePort(ushort port, byte value) + { + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x01) == 0; + + ContendPort(port); + + // 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 + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + + // Tape + TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 3deae820b1..4979071d58 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { - public class ZX48 : SpectrumBase + public partial class ZX48 : SpectrumBase { #region Construction @@ -21,17 +21,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Spectrum = spectrum; CPU = cpu; - // init addressable memory from ROM and RAM banks - /* - Memory.Add(0, ROM0); - Memory.Add(1, RAM0); - Memory.Add(2, RAM1); - Memory.Add(3, RAM2); - */ ReInitMemory(); - - //RAM = new byte[0x4000 + 0xC000]; - + InitScreenConfig(); InitScreen(); @@ -50,148 +41,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } #endregion - - #region MemoryMapping - - /* 48K Spectrum has NO memory paging - * - * 0xffff +--------+ - | Bank 2 | - | | - | | - | | - 0xc000 +--------+ - | Bank 1 | - | | - | | - | | - 0x8000 +--------+ - | Bank 0 | - | | - | | - | screen | - 0x4000 +--------+ - | ROM 0 | - | | - | | - | | - 0x0000 +--------+ - */ - - /// - /// Simulates reading from the bus (no contention) - /// Paging should be handled here - /// - /// - /// - public override byte ReadBus(ushort addr) - { - int divisor = addr / 0x4000; - // paging logic goes here - - var bank = Memory[divisor]; - var index = addr % 0x4000; - return bank[index]; - } - - /// - /// Pushes a value onto the data bus that should be valid as long as the interrupt is true - /// - /// - /// - public override byte PushBus() - { - return 0xFF; - } - - /// - /// Simulates writing to the bus (no contention) - /// Paging should be handled here - /// - /// - /// - public override void WriteBus(ushort addr, byte value) - { - int divisor = addr / 0x4000; - // paging logic goes here - - var bank = Memory[divisor]; - var index = addr % 0x4000; - bank[index] = value; - } - - /// - /// Reads a byte of data from a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public override byte ReadMemory(ushort addr) - { - var data = ReadBus(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; - } - - /// - /// Writes a byte of data to a specified memory address - /// (with memory contention if appropriate) - /// - /// - /// - public override void WriteMemory(ushort addr, byte value) - { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - else if (addr < 0xC000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } - - WriteBus(addr, value); - } - - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = RAM0; - else - Memory.Add(1, RAM0); - - if (Memory.ContainsKey(2)) - Memory[2] = RAM1; - else - Memory.Add(2, RAM1); - - if (Memory.ContainsKey(3)) - Memory[3] = RAM2; - else - Memory.Add(3, RAM2); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM3; - else - Memory.Add(4, RAM3); - } - - - #endregion - - } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs index 97dffe206b..0f0f12110b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs @@ -71,6 +71,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RD.LoadBytesResumeAddress = 0x05E2; RD.LoadBytesInvalidHeaderAddress = 0x05B6; break; + + case MachineType.ZXSpectrum128: + RD.SaveBytesRoutineAddress = 0x04C2; + RD.SaveBytesResumeAddress = 0x0000; + RD.LoadBytesRoutineAddress = 0x0808; //0x0556; //0x056C; + RD.LoadBytesResumeAddress = 0x05E2; + RD.LoadBytesInvalidHeaderAddress = 0x05B6; + break; } return RD; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 083da9edf6..538b9c4cbe 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -40,6 +40,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; + case MachineType.ZXSpectrum128: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + break; default: throw new InvalidOperationException("Machine not yet emulated"); } @@ -110,6 +114,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var romData = RomData.InitROM(machineType, _systemRom); _machine.InitROM(romData); break; + case MachineType.ZXSpectrum128: + _machine = new ZX128(this, _cpu, file); + var _systemRom128 = GetFirmware(0x8000, "128ROM"); + var romData128 = RomData.InitROM(machineType, _systemRom128); + _machine.InitROM(romData128); + break; } } From 27ba7e0008a9692fe5ffd9ba5e15eb45a63bfdcb Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 5 Dec 2017 10:26:06 +0000 Subject: [PATCH 022/105] Started +2 implementation --- .../Database/FirmwareDatabase.cs | 1 + .../BizHawk.Emulation.Cores.csproj | 2 ++ .../SinclairSpectrum/Machine/MachineType.cs | 7 ++++- .../Machine/SpectrumBase.Screen.cs | 2 +- .../Machine/ZXSpectrum128K/ZX128.Screen.cs | 24 ++++++++++++++ .../Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs | 31 +++++++++++++++++++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 10 ++++++ 7 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 789c01138f..3d40ef5de1 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -53,6 +53,7 @@ namespace BizHawk.Emulation.Common // ZX Spectrum FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); + FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM"); // 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 diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 830c6523cf..2a18cd1e56 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -276,9 +276,11 @@ + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs index e70dc3ea4a..a29b745205 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -16,6 +16,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Sinclair Spectrum 128K model /// - ZXSpectrum128 + ZXSpectrum128, + + /// + /// Sinclair Spectrum 128 + 2 model + /// + ZXSpectrum128Plus2 } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 62bd4d98d4..3ab09352db 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -765,7 +765,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Initialises the screen configuration calculations /// - protected virtual void InitScreenConfig() + public virtual void InitScreenConfig() { ScreenLines = BorderTopLines + DisplayLines + BorderBottomLines; FirstDisplayLine = VerticalSyncLines + NonVisibleBorderTopLines + BorderTopLines; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs new file mode 100644 index 0000000000..541a370341 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128 : SpectrumBase + { + public override 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; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs new file mode 100644 index 0000000000..faaba1791f --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs @@ -0,0 +1,31 @@ +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 +{ + /// + /// The +2 is almost identical to the 128k from an emulation point of view + /// There are just a few small changes in the ROMs + /// + public partial class ZX128Plus2 : ZX128 + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, byte[] file) + : base(spectrum, cpu, file) + { + + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 538b9c4cbe..64ddcde13a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -44,6 +44,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; + case MachineType.ZXSpectrum128Plus2: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + break; default: throw new InvalidOperationException("Machine not yet emulated"); } @@ -120,6 +124,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var romData128 = RomData.InitROM(machineType, _systemRom128); _machine.InitROM(romData128); break; + case MachineType.ZXSpectrum128Plus2: + _machine = new ZX128Plus2(this, _cpu, file); + var _systemRomP2 = GetFirmware(0x8000, "PLUS2ROM"); + var romDataP2 = RomData.InitROM(machineType, _systemRomP2); + _machine.InitROM(romDataP2); + break; } } From 85d38a3379c6f61f4398f01dc78580ce29bec06c Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 5 Dec 2017 10:38:51 +0000 Subject: [PATCH 023/105] template for plus3 (but not implemented yet) --- .../Database/FirmwareDatabase.cs | 1 + .../BizHawk.Emulation.Cores.csproj | 3 + .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 284 ++++++++++++++++++ .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 188 ++++++++++++ .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 56 ++++ 5 files changed, 532 insertions(+) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 3d40ef5de1..9d9810b5e6 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -54,6 +54,7 @@ namespace BizHawk.Emulation.Common FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM"); + FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65563, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM"); // 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 diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 2a18cd1e56..439b004828 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -277,6 +277,9 @@ + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs new file mode 100644 index 0000000000..76dbb31891 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus3 : SpectrumBase + { + /* 128k paging controlled by writes to port 0x7ffd + * + * + + #7FFD (32765) - decoded as A15=0, A1=0 and /IORQ=0. Bits 0..5 are latched. Bits 0..2 select RAM bank in secton D. Bit 3 selects RAM bank to dispay screen (0 - RAM5, 1 - RAM7). Bit 4 selects ROM bank (0 - ROM0, 1 - ROM1). Bit 5, when set locks future writing to #7FFD port until reset. Reading #7FFD port is the same as writing #FF into it. + #BFFD (49149) - write data byte into AY-3-8912 chip. + #FFFD (65533) - select AY-3-8912 addres (D4..D7 ignored) and reading data byte. + + * 0xffff +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 | + | | |(also at| | |(also at| | | + | | | 0x8000)| | | 0x4000)| | | + | | | | | | screen | | screen | + 0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+ + | Bank 2 | Any one of these pages may be switched in. + | | + | | + | | + 0x8000 +--------+ + | Bank 5 | + | | + | | + | screen | + 0x4000 +--------+--------+ + | ROM 0 | ROM 1 | Either ROM may be switched in. + | | | + | | | + | | | + 0x0000 +--------+--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + byte result = 0xff; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + result = Memory[0][addr % 0x4000]; + else + result = Memory[1][addr % 0x4000]; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + result = Memory[7][addr % 0x4000]; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + result = Memory[4][addr % 0x4000]; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + result = Memory[2][addr % 0x4000]; + break; + case 1: + result = Memory[3][addr % 0x4000]; + break; + case 2: + result = Memory[4][addr % 0x4000]; + break; + case 3: + result = Memory[5][addr % 0x4000]; + break; + case 4: + result = Memory[6][addr % 0x4000]; + break; + case 5: + result = Memory[7][addr % 0x4000]; + break; + case 6: + result = Memory[8][addr % 0x4000]; + break; + case 7: + result = Memory[9][addr % 0x4000]; + break; + } + break; + default: + break; + } + + return result; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + switch (divisor) + { + // ROM 0x000 + case 0: + if (!ROMPaged) + Memory[0][addr % 0x4000] = value; + else + Memory[1][addr % 0x4000] = value; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + Memory[7][addr % 0x4000] = value; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + Memory[4][addr % 0x4000] = value; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + Memory[2][addr % 0x4000] = value; + break; + case 1: + Memory[3][addr % 0x4000] = value; + break; + case 2: + Memory[4][addr % 0x4000] = value; + break; + case 3: + Memory[5][addr % 0x4000] = value; + break; + case 4: + Memory[6][addr % 0x4000] = value; + break; + case 5: + Memory[7][addr % 0x4000] = value; + break; + case 6: + Memory[8][addr % 0x4000] = value; + break; + case 7: + Memory[9][addr % 0x4000] = value; + break; + } + break; + default: + break; + } + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(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; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr < 0xC000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = ROM1; + else + Memory.Add(1, ROM1); + + if (Memory.ContainsKey(2)) + Memory[2] = RAM0; + else + Memory.Add(2, RAM0); + + if (Memory.ContainsKey(3)) + Memory[3] = RAM1; + else + Memory.Add(3, RAM1); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM2; + else + Memory.Add(4, RAM2); + + if (Memory.ContainsKey(5)) + Memory[5] = RAM3; + else + Memory.Add(5, RAM3); + + if (Memory.ContainsKey(6)) + Memory[6] = RAM4; + else + Memory.Add(6, RAM4); + + if (Memory.ContainsKey(7)) + Memory[7] = RAM5; + else + Memory.Add(7, RAM5); + + if (Memory.ContainsKey(8)) + Memory[8] = RAM6; + else + Memory.Add(8, RAM6); + + if (Memory.ContainsKey(9)) + Memory[9] = RAM7; + else + Memory.Add(9, RAM7); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // 128k uses ROM0 and ROM1 + // 128k loader is in ROM0, and fallback 48k rom is in ROM1 + for (int i = 0; i < 0x4000; i++) + { + ROM0[i] = RomData.RomBytes[i]; + ROM1[i] = RomData.RomBytes[i + 0x4000]; + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs new file mode 100644 index 0000000000..35fe0c3391 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus3 : SpectrumBase + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public override byte ReadPort(ushort port) + { + int result = 0xFF; + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x0001) == 0; + + ContendPort((ushort)port); + + // Kempston Joystick + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + return (byte)KempstonDevice.JoyLine; + } + else 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 + */ + + if ((port & 0x8000) == 0) + result &= KeyboardDevice.KeyLine[7]; + + if ((port & 0x4000) == 0) + result &= KeyboardDevice.KeyLine[6]; + + if ((port & 0x2000) == 0) + result &= KeyboardDevice.KeyLine[5]; + + if ((port & 0x1000) == 0) + result &= KeyboardDevice.KeyLine[4]; + + if ((port & 0x800) == 0) + result &= KeyboardDevice.KeyLine[3]; + + if ((port & 0x400) == 0) + result &= KeyboardDevice.KeyLine[2]; + + if ((port & 0x200) == 0) + result &= KeyboardDevice.KeyLine[1]; + + if ((port & 0x100) == 0) + result &= KeyboardDevice.KeyLine[0]; + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + + if (TapeDevice.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (KeyboardDevice.IsIssue2Keyboard) + { + if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + else + { + if ((LastULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + + } + 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 (byte)result; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public override void WritePort(ushort port, byte value) + { + // paging + if (port == 0x7ffd) + { + // Bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // ROM page + if ((value & 0x10) != 0) + { + // 48k ROM + ROMPaged = true; + } + else + { + ROMPaged = false; + } + + // Bit 5 signifies that paging is disabled until next reboot + if ((value & 0x20) != 0) + PagingDisabled = true; + + + return; + } + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x01) == 0; + + ContendPort(port); + + // 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 + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + + // Tape + TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs new file mode 100644 index 0000000000..2a75a600f7 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -0,0 +1,56 @@ +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 partial class ZX128Plus3 : SpectrumBase + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, byte[] file) + { + Spectrum = spectrum; + CPU = cpu; + + ROMPaged = false; + SHADOWPaged = false; + RAMPaged = 0; + PagingDisabled = false; + + // init addressable memory from ROM and RAM banks + ReInitMemory(); + + //RAM = new byte[0x4000 + 0xC000]; + + //DisplayLineTime = 132; + VsyncNumerator = 3546900; + + InitScreenConfig(); + InitScreen(); + + ResetULACycle(); + + BuzzerDevice = new Buzzer(this); + BuzzerDevice.Init(44100, UlaFrameCycleCount); + + KeyboardDevice = new Keyboard48(this); + KempstonDevice = new KempstonJoystick(this); + + TapeProvider = new DefaultTapeProvider(file); + + TapeDevice = new Tape(TapeProvider); + TapeDevice.Init(this); + } + + #endregion + } +} From f82b1b83362d68b5ba81788da3e48679c8548afc Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 5 Dec 2017 13:08:47 +0000 Subject: [PATCH 024/105] Custom SoundProviderMixer implementation --- .../BizHawk.Emulation.Cores.csproj | 3 + .../SinclairSpectrum/Hardware/AYSound.cs | 407 ++++++++++++++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 10 +- .../Machine/ZXSpectrum128K/ZX128.cs | 2 + .../SinclairSpectrum/SoundProviderMixer.cs | 196 +++++++++ .../ZXSpectrum.ISoundProvider.cs | 16 + .../Computers/SinclairSpectrum/ZXSpectrum.cs | 7 +- 7 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 439b004828..8db1b196d4 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,6 +256,7 @@ + @@ -270,6 +271,7 @@ + @@ -307,6 +309,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs new file mode 100644 index 0000000000..3cd2c513dd --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs @@ -0,0 +1,407 @@ +using System; + +using BizHawk.Common; +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Components; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public class AYSound : ISoundProvider + { + private readonly BlipBuffer _blip = new BlipBuffer(4096); + private short[] _sampleBuffer = new short[0]; + + public AYSound() + { + _blip.SetRates(894866 / 4.0, 44100); + } + + public ushort[] Register = new ushort[16]; + + public int total_clock; // TODO: what is this used for? + + public void Reset() + { + clock_A = clock_B = clock_C = 0x1000; + noise_clock = 0x20; + + for (int i = 0; i < 16; i++) + { + Register[i] = 0x0000; + } + sync_psg_state(); + DiscardSamples(); + } + + public void DiscardSamples() + { + _blip.Clear(); + _sampleClock = 0; + } + + public void GetSamplesAsync(short[] samples) + { + throw new NotSupportedException("Async is not available"); + } + + 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 GetSamplesSync(out short[] samples, out int nsamp) + { + _blip.EndFrame((uint)_sampleClock); + _sampleClock = 0; + + nsamp = _blip.SamplesAvailable(); + int targetLength = nsamp * 2; + if (_sampleBuffer.Length != targetLength) + { + _sampleBuffer = new short[targetLength]; + } + + _blip.ReadSamplesLeft(_sampleBuffer, nsamp); + for (int i = 0; i < _sampleBuffer.Length; i += 2) + { + _sampleBuffer[i + 1] = _sampleBuffer[i]; + } + + samples = _sampleBuffer; + } + + public void GetSamples(short[] samples) + { + throw new Exception(); + } + + private static readonly int[] VolumeTable = + { + 0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA, + 0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA + }; + + private int _sampleClock; + private int _latchedSample; + + private int TotalExecutedCycles; + private int PendingCycles; + private int psg_clock; + private int sq_per_A, sq_per_B, sq_per_C; + private int clock_A, clock_B, clock_C; + private int vol_A, vol_B, vol_C; + private bool A_on, B_on, C_on; + private bool A_up, B_up, C_up; + private bool A_noise, B_noise, C_noise; + + private int env_per; + private int env_clock; + private int env_shape; + private int env_E; + private int E_up_down; + private int env_vol_A, env_vol_B, env_vol_C; + + private int noise_clock; + private int noise_per; + private int noise = 0x1; + + public Func ReadMemory; + public Func WriteMemory; + + public void SyncState(Serializer ser) + { + ser.BeginSection("PSG"); + + ser.Sync("Register", ref Register, false); + ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles); + ser.Sync("Pending_Cycles", ref PendingCycles); + + ser.Sync("psg_clock", ref psg_clock); + ser.Sync("clock_A", ref clock_A); + ser.Sync("clock_B", ref clock_B); + ser.Sync("clock_C", ref clock_C); + ser.Sync("noise_clock", ref noise_clock); + ser.Sync("env_clock", ref env_clock); + ser.Sync("A_up", ref A_up); + ser.Sync("B_up", ref B_up); + ser.Sync("C_up", ref C_up); + ser.Sync("noise", ref noise); + ser.Sync("env_E", ref env_E); + ser.Sync("E_up_down", ref E_up_down); + + sync_psg_state(); + + ser.EndSection(); + } + + public ushort? ReadPSG(ushort addr, bool peek) + { + if (addr >= 0x01F0 && addr <= 0x01FF) + { + return (ushort)(Register[addr - 0x01F0]); + } + + return null; + } + + private void sync_psg_state() + { + sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8)); + if (sq_per_A == 0) + { + sq_per_A = 0x1000; + } + + sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8)); + if (sq_per_B == 0) + { + sq_per_B = 0x1000; + } + + sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8)); + if (sq_per_C == 0) + { + sq_per_C = 0x1000; + } + + env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8)); + if (env_per == 0) + { + env_per = 0x10000; + } + + env_per *= 2; + + A_on = Register[8].Bit(0); + B_on = Register[8].Bit(1); + C_on = Register[8].Bit(2); + A_noise = Register[8].Bit(3); + B_noise = Register[8].Bit(4); + C_noise = Register[8].Bit(5); + + noise_per = Register[9] & 0x1F; + if (noise_per == 0) + { + noise_per = 0x20; + } + + var shape_select = Register[10] & 0xF; + + if (shape_select < 4) + env_shape = 0; + else if (shape_select < 8) + env_shape = 1; + else + env_shape = 2 + (shape_select - 8); + + vol_A = Register[11] & 0xF; + env_vol_A = (Register[11] >> 4) & 0x3; + + vol_B = Register[12] & 0xF; + env_vol_B = (Register[12] >> 4) & 0x3; + + vol_C = Register[13] & 0xF; + env_vol_C = (Register[13] >> 4) & 0x3; + } + + public bool WritePSG(ushort addr, ushort value, bool poke) + { + if (addr >= 0x01F0 && addr <= 0x01FF) + { + var reg = addr - 0x01F0; + + value &= 0xFF; + + if (reg == 4 || reg == 5 || reg == 6 || reg == 10) + value &= 0xF; + + if (reg == 9) + value &= 0x1F; + + if (reg == 11 || reg == 12 || reg == 13) + value &= 0x3F; + + Register[addr - 0x01F0] = value; + + sync_psg_state(); + + if (reg == 10) + { + env_clock = env_per; + + if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5) + { + env_E = 15; + E_up_down = -1; + } + else + { + env_E = 0; + E_up_down = 1; + } + } + + return true; + } + + return false; + } + + public void generate_sound(int cycles_to_do) + { + // there are 4 cpu cycles for every psg cycle + bool sound_out_A; + bool sound_out_B; + bool sound_out_C; + + for (int i = 0; i < cycles_to_do; i++) + { + psg_clock++; + + if (psg_clock == 4) + { + psg_clock = 0; + + total_clock++; + + clock_A--; + clock_B--; + clock_C--; + + noise_clock--; + env_clock--; + + // clock noise + if (noise_clock == 0) + { + noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0); + noise_clock = noise_per; + } + + if (env_clock == 0) + { + env_clock = env_per; + + env_E += E_up_down; + + if (env_E == 16 || env_E == -1) + { + + // we just completed a period of the envelope, determine what to do now based on the envelope shape + if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9) + { + E_up_down = 0; + env_E = 0; + } + else if (env_shape == 5 || env_shape == 7) + { + E_up_down = 0; + env_E = 15; + } + else if (env_shape == 4 || env_shape == 8) + { + if (env_E == 16) + { + env_E = 15; + E_up_down = -1; + } + else + { + env_E = 0; + E_up_down = 1; + } + } + else if (env_shape == 2) + { + env_E = 15; + } + else + { + env_E = 0; + } + } + } + + if (clock_A == 0) + { + A_up = !A_up; + clock_A = sq_per_A; + } + + if (clock_B == 0) + { + B_up = !B_up; + clock_B = sq_per_B; + } + + if (clock_C == 0) + { + C_up = !C_up; + clock_C = sq_per_C; + } + + + sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up); + sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up); + sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up); + + // now calculate the volume of each channel and add them together + int v; + + if (env_vol_A == 0) + { + v = (short)(sound_out_A ? VolumeTable[vol_A] : 0); + } + else + { + int shift_A = 3 - env_vol_A; + if (shift_A < 0) + shift_A = 0; + v = (short)(sound_out_A ? (VolumeTable[env_E] >> shift_A) : 0); + } + + if (env_vol_B == 0) + { + v += (short)(sound_out_B ? VolumeTable[vol_B] : 0); + + } + else + { + int shift_B = 3 - env_vol_B; + if (shift_B < 0) + shift_B = 0; + v += (short)(sound_out_B ? (VolumeTable[env_E] >> shift_B) : 0); + } + + if (env_vol_C == 0) + { + v += (short)(sound_out_C ? VolumeTable[vol_C] : 0); + } + else + { + int shift_C = 3 - env_vol_C; + if (shift_C < 0) + shift_C = 0; + v += (short)(sound_out_C ? (VolumeTable[env_E] >> shift_C) : 0); + } + + if (v != _latchedSample) + { + _blip.AddDelta((uint)_sampleClock, v - _latchedSample); + _latchedSample = v; + } + + _sampleClock++; + } + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index cb1c9064f9..f23173a259 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -37,6 +37,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public Buzzer BuzzerDevice { get; set; } + /// + /// Device representing the AY-3-8912 chip found in the 128k and up spectrums + /// + public AYSound AYDevice { get; set; } + /// /// The spectrum keyboard /// @@ -231,9 +236,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.SyncState(ser); TapeDevice.SyncState(ser); + if (AYDevice != null) + AYDevice.SyncState(ser); + ser.EndSection(); - ReInitMemory(); + //ReInitMemory(); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 93b7b58f35..fe3f08d651 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -42,6 +42,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, UlaFrameCycleCount); + AYDevice = new AYSound(); + KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs new file mode 100644 index 0000000000..57e37f3d77 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs @@ -0,0 +1,196 @@ +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// My attempt at mixing multiple ISoundProvider sources together and outputting another ISoundProvider + /// Currently only supports SyncSoundMode.Sync + /// Attached ISoundProvider sources must already be stereo 44.1khz + /// + internal sealed class SoundProviderMixer : ISoundProvider + { + private class Provider + { + public ISoundProvider SoundProvider { get; set; } + public int MaxVolume { get; set; } + public short[] Buffer { get; set; } + public int NSamp { get; set; } + } + + private readonly List SoundProviders; + + private short[] _buffer; + private int _nSamp; + + public SoundProviderMixer(params ISoundProvider[] soundProviders) + { + SoundProviders = new List(); + + foreach (var s in soundProviders) + { + SoundProviders.Add(new Provider + { + SoundProvider = s, + MaxVolume = short.MaxValue, + }); + } + + EqualizeVolumes(); + } + + public void AddSource(ISoundProvider source) + { + SoundProviders.Add(new Provider + { + SoundProvider = source, + MaxVolume = short.MaxValue + }); + + EqualizeVolumes(); + } + + public void DisableSource(ISoundProvider source) + { + var sp = SoundProviders.Where(a => a.SoundProvider == source); + if (sp.Count() == 1) + SoundProviders.Remove(sp.First()); + else if (sp.Count() > 1) + foreach (var s in sp) + SoundProviders.Remove(s); + } + + public void EqualizeVolumes() + { + if (SoundProviders.Count < 1) + return; + + int eachVolume = short.MaxValue / SoundProviders.Count; + foreach (var source in SoundProviders) + { + source.MaxVolume = eachVolume; + } + } + + #region ISoundProvider + + 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"); + } + + public void DiscardSamples() + { + foreach (var soundSource in SoundProviders) + { + soundSource.SoundProvider.DiscardSamples(); + } + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = null; + nsamp = 0; + + // get samples from all the providers + foreach (var sp in SoundProviders) + { + int sampCount; + short[] samp; + sp.SoundProvider.GetSamplesSync(out samp, out sampCount); + sp.NSamp = sampCount; + sp.Buffer = samp; + } + + // are all the sample lengths the same? + var firstEntry = SoundProviders.First(); + bool sameCount = SoundProviders.All(s => s.NSamp == firstEntry.NSamp); + + if (!sameCount) + { + int divisor = 1; + int highestCount = 0; + + // get the lowest divisor of all the soundprovider nsamps + for (int d = 2; d < 999; d++) + { + bool divFound = false; + foreach (var sp in SoundProviders) + { + if (sp.NSamp > highestCount) + highestCount = sp.NSamp; + + if (sp.NSamp % d == 0) + divFound = true; + else + divFound = false; + } + + if (divFound) + { + divisor = d; + break; + } + } + + // now we have the largest current number of samples among the providers + // along with a common divisor for all of them + nsamp = highestCount * divisor; + samples = new short[nsamp * 2]; + + // take a pass at populating the samples array for each provider + foreach (var sp in SoundProviders) + { + short sectorVal = 0; + int pos = 0; + for (int i = 0; i < sp.Buffer.Length; i++) + { + if (sp.Buffer[i] > sp.MaxVolume) + sectorVal = (short)sp.MaxVolume; + else + sectorVal = sp.Buffer[i]; + + for (int s = 0; s < divisor; s++) + { + samples[pos++] += sectorVal; + } + } + } + } + else + { + nsamp = firstEntry.NSamp; + samples = new short[nsamp * 2]; + + for (int i = 0; i < samples.Length; i++) + { + short sectorVal = 0; + foreach (var sp in SoundProviders) + { + if (sp.Buffer[i] > sp.MaxVolume) + sectorVal += (short)sp.MaxVolume; + else + sectorVal += sp.Buffer[i]; + } + + samples[i] = sectorVal; + } + } + } + + #endregion + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs new file mode 100644 index 0000000000..02f144350c --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs @@ -0,0 +1,16 @@ +using BizHawk.Emulation.Cores.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZXSpectrum + { + private FakeSyncSound _fakeSyncSound; + private IAsyncSoundProvider ActiveSoundProvider; + private SoundProviderMixer SoundMixer; + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 64ddcde13a..15052083da 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -69,7 +69,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Register(_tracer); ser.Register(_cpu); ser.Register(_machine); - ser.Register(_machine.BuzzerDevice); + + SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice); + if (_machine.AYDevice != null) + SoundMixer.AddSource(_machine.AYDevice); + + ser.Register(SoundMixer); HardReset(); From f0cef1cf0ddbc0aeade40910471717f3e08118a2 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 10:49:43 +0000 Subject: [PATCH 025/105] AY-3-8912 Implementation --- .../BizHawk.Emulation.Cores.csproj | 2 +- .../SinclairSpectrum/Hardware/AY38912.cs | 732 ++++++++++++++++++ .../SinclairSpectrum/Hardware/AYSound.cs | 407 ---------- .../SinclairSpectrum/Hardware/Buzzer.cs | 13 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 31 +- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 4 +- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 32 +- .../Machine/ZXSpectrum128K/ZX128.cs | 7 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 4 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 4 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 5 +- .../SinclairSpectrum/SoundProviderMixer.cs | 47 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 13 +- 13 files changed, 862 insertions(+), 439 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 8db1b196d4..274f62c6a4 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,7 +256,7 @@ - + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs new file mode 100644 index 0000000000..9babd6fbd0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs @@ -0,0 +1,732 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public class AY38912 : ISoundProvider + { + private int _tStatesPerFrame; + private int _sampleRate; + private int _samplesPerFrame; + private int _tStatesPerSample; + private int _sampleCounter; + const int AY_SAMPLE_RATE = 16; + private int _AYCyclesPerFrame; + private int _nsamp; + private int _AYCount; + + + /// + /// The final sample buffer + /// + private short[] _samples = new short[0]; + + /// + /// Number of samples in one frame + /// + public int SamplesPerFrame + { + get { return _samplesPerFrame; } + set { _samplesPerFrame = value; } + } + + /// + /// Number of TStates in each sample + /// + public int TStatesPerSample + { + get { return _tStatesPerSample; } + set { _tStatesPerSample = value; } + } + + #region Construction & Initialisation + + public AY38912() + { + Reset(); + } + + /// + /// Initialises the AY chip + /// + public void Init(int sampleRate, int tStatesPerFrame) + { + _sampleRate = sampleRate; + _tStatesPerFrame = tStatesPerFrame; + _tStatesPerSample = 79; + _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + _AYCyclesPerFrame = _tStatesPerFrame / AY_SAMPLE_RATE; + } + + #endregion + + public void UpdateSound(int currentFrameCycle) + { + //if (currentFrameCycle >= _tStatesPerFrame) + //currentFrameCycle = _tStatesPerFrame; + + for (int i = 0; i < (currentFrameCycle / AY_SAMPLE_RATE) - _AYCount; i++) + { + Update(); + SampleAY(); + _AYCount++; + } + + // calculate how many samples must be processed + int samplesToGenerate = (currentFrameCycle / _tStatesPerSample) - (_sampleCounter / 2); + + // begin generation + if (samplesToGenerate > 0) + { + // ensure the required resolution + while (soundSampleCounter < 4) + { + SampleAY(); + } + EndSampleAY(); + + // generate needed samples + for (int i = 0; i < samplesToGenerate; i++) + { + _samples[_sampleCounter++] = (short)(averagedChannelSamples[0]); + _samples[_sampleCounter++] = (short)(averagedChannelSamples[1]); + + samplesToGenerate--; + } + + averagedChannelSamples[0] = 0; + averagedChannelSamples[1] = 0; + averagedChannelSamples[2] = 0; + } + } + + public void StartFrame() + { + _AYCount = 0; + + // the stereo _samples buffer should already have been processed as a part of + // ISoundProvider at the end of the last frame + _samples = new short[_samplesPerFrame * 2]; + _nsamp = _samplesPerFrame; + _sampleCounter = 0; + + Init(44100, _tStatesPerFrame); + } + + public void EndFrame() + { + } + + + public void Reset() + { + // reset volumes + for (int i = 0; i < 16; i++) + AY_SpecVolumes[i] = (short)(AY_Volumes[i] * 8191); + + soundSampleCounter = 0; + regs[AY_NOISEPER] = 0xFF; + noiseOut = 0x01; + envelopeVolume = 0; + noiseCount = 0; + + // reset state of all channels + for (int f = 0; f < 3; f++) + { + channel_count[f] = 0; + channel_mix[f] = 0; + channel_out[f] = 0; + averagedChannelSamples[f] = 0; + } + + envelopeCount = 0; + randomSeed = 1; + selectedRegister = 0; + } + + #region IStatable + + public void SyncState(Serializer ser) + { + ser.BeginSection("AY38912"); + ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); + ser.Sync("_sampleRate", ref _sampleRate); + ser.Sync("_samplesPerFrame", ref _samplesPerFrame); + ser.Sync("_tStatesPerSample", ref _tStatesPerSample); + ser.Sync("_sampleCounter", ref _sampleCounter); + + ser.Sync("ChannelLeft", ref ChannelLeft); + ser.Sync("ChannelRight", ref ChannelRight); + ser.Sync("ChannelCenter", ref ChannelCenter); + ser.Sync("Regs", ref regs, false); + ser.Sync("NoiseOut", ref noiseOut); + ser.Sync("envelopeVolume", ref envelopeVolume); + ser.Sync("noiseCount", ref noiseCount); + ser.Sync("envelopeCount", ref envelopeCount); + ser.Sync("randomSeed", ref randomSeed); + ser.Sync("envelopeClock", ref envelopeClock); + ser.Sync("selectedRegister", ref selectedRegister); + ser.Sync("soundSampleCounter", ref soundSampleCounter); + ser.Sync("stereoSound", ref stereoSound); + ser.Sync("sustaining", ref sustaining); + ser.Sync("sustain", ref sustain); + ser.Sync("alternate", ref alternate); + ser.Sync("attack", ref attack); + ser.Sync("envelopeStep", ref envelopeStep); + + ser.Sync("channel_out", ref channel_out, false); + ser.Sync("channel_count", ref channel_count, false); + ser.Sync("averagedChannelSamples", ref averagedChannelSamples, false); + ser.Sync("channel_mix", ref channel_mix, false); + + ser.Sync("AY_SpecVolumes", ref AY_SpecVolumes, false); + + ser.Sync("_samples", ref _samples, false); + ser.Sync("_nsamp", ref _nsamp); + ser.EndSection(); + } + + #endregion + + #region AY Sound Implementation + + /* + Based on the AYSound class from ArjunNair's Zero-Emulator + https://github.com/ArjunNair/Zero-Emulator/ + *MIT LICENSED* + + The MIT License (MIT) + Copyright (c) 2009 Arjun Nair + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + /// + /// Register constants + /// + private const byte AY_A_FINE = 0; + private const byte AY_A_COARSE = 1; + private const byte AY_B_FINE = 2; + private const byte AY_B_COARSE = 3; + private const byte AY_C_FINE = 4; + private const byte AY_C_COARSE = 5; + private const byte AY_NOISEPER = 6; + private const byte AY_ENABLE = 7; + private const byte AY_A_VOL = 8; + private const byte AY_B_VOL = 9; + private const byte AY_C_VOL = 10; + private const byte AY_E_FINE = 11; + private const byte AY_E_COARSE = 12; + private const byte AY_E_SHAPE = 13; + private const byte AY_PORT_A = 14; + private const byte AY_PORT_B = 15; + + /// + /// Channels + /// + internal enum Channel + { + A, B, C + } + + /// + /// ACB configuration + /// + private int ChannelLeft = 0; + private int ChannelRight = 1; //2 if ABC + private int ChannelCenter = 2; //1 if ABC + + /// + /// Register storage + /// + private int[] regs = new int[16]; + + /// + /// State + /// + private int noiseOut; + private int envelopeVolume; + private int noiseCount; + private int envelopeCount; + private ulong randomSeed; + private byte envelopeClock = 0; + private int selectedRegister; + public ushort soundSampleCounter; + private bool stereoSound = true; + private bool sustaining; + private bool sustain; + private bool alternate; + private int attack; + private int envelopeStep; + + /// + /// Buffer arrays + /// + private int[] channel_out = new int[3]; + private int[] channel_count = new int[3]; + private int[] averagedChannelSamples = new int[3]; + private short[] channel_mix = new short[3]; + + /// + /// Measurements from comp.sys.sinclair (2001 Matthew Westcott) + /// + private float[] AY_Volumes = + { + 0.0000f, 0.0137f, 0.0205f, 0.0291f, + 0.0423f, 0.0618f, 0.0847f, 0.1369f, + 0.1691f, 0.2647f, 0.3527f, 0.4499f, + 0.5704f, 0.6873f, 0.8482f, 1.0000f + }; + + /// + /// Volume storage (short) + /// + private short[] AY_SpecVolumes = new short[16]; + + /// + /// Sets the ACB configuration + /// + /// + public void SetSpeakerACB(bool val) + { + // ACB + if (val) + { + ChannelCenter = 2; + ChannelRight = 1; + } + // ABC + else + { + ChannelCenter = 1; + ChannelRight = 2; + } + } + + /// + /// Set whether sound output is stereo or mono + /// + public bool StereoSound + { + get { return stereoSound; } + set { stereoSound = value; } + } + + /// + /// Utility method to set all registers externally + /// + /// + public void SetRegisters(byte[] _regs) + { + for (int f = 0; f < 16; f++) + regs[f] = _regs[f]; + } + + /// + /// Utility method to get all registers externally + /// + /// + public byte[] GetRegisters() + { + byte[] newArray = new byte[16]; + for (int f = 0; f < 16; f++) + newArray[f] = (byte)(regs[f] & 0xff); + return newArray; + } + + /// + /// Selected Register property + /// + public int SelectedRegister + { + get { return selectedRegister; } + set { if (value < 16) selectedRegister = value; } + } + + /// + /// Simulates a port write to the AY chip + /// + /// + public void PortWrite(int val) + { + switch (SelectedRegister) + { + // not implemented / necessary + case AY_A_FINE: + case AY_B_FINE: + case AY_C_FINE: + case AY_E_FINE: + case AY_E_COARSE: + break; + + case AY_A_COARSE: + case AY_B_COARSE: + case AY_C_COARSE: + val &= 0x0f; + break; + + case AY_NOISEPER: + case AY_A_VOL: + case AY_B_VOL: + case AY_C_VOL: + val &= 0x1f; + break; + + case AY_ENABLE: + /* + if ((lastEnable == -1) || ((lastEnable & 0x40) != (regs[AY_ENABLE] & 0x40))) { + SelectedRegister = ((regs[AY_ENABLE] & 0x40) > 0 ? regs[AY_PORT_B] : 0xff); + } + if ((lastEnable == -1) || ((lastEnable & 0x80) != (regs[AY_ENABLE] & 0x80))) { + PortWrite((regs[AY_ENABLE] & 0x80) > 0 ? regs[AY_PORT_B] : 0xff); + } + lastEnable = regs[AY_ENABLE];*/ + break; + + case AY_E_SHAPE: + val &= 0x0f; + attack = ((val & 0x04) != 0 ? 0x0f : 0x00); + // envelopeCount = 0; + if ((val & 0x08) == 0) + { + /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */ + sustain = true; + alternate = (attack != 0); + } + else + { + sustain = (val & 0x01) != 0; + alternate = (val & 0x02) != 0; + } + envelopeStep = 0x0f; + sustaining = false; + envelopeVolume = (envelopeStep ^ attack); + break; + + case AY_PORT_A: + /* + if ((regs[AY_ENABLE] & 0x40) > 0) { + selectedRegister = regs[AY_PORT_A]; + }*/ + break; + + case AY_PORT_B: + /* + if ((regs[AY_ENABLE] & 0x80) > 0) { + PortWrite(regs[AY_PORT_A]); + }*/ + break; + } + + regs[SelectedRegister] = val; + } + + /// + /// Simulates port reads from the AY chip + /// + /// + public int PortRead() + { + if (SelectedRegister == AY_PORT_B) + { + if ((regs[AY_ENABLE] & 0x80) == 0) + return 0xff; + else + return regs[AY_PORT_B]; + } + + return regs[selectedRegister]; + } + + private void EndSampleAY() + { + if (stereoSound) + { + averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); + averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); + averagedChannelSamples[2] = 0;// beeperSound; + } + else + { + averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter); + averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter); + averagedChannelSamples[2] = 0;// (averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter + beeperSound; + } + soundSampleCounter = 0; + } + + private void SampleAY() + { + int ah; + + ah = regs[AY_ENABLE]; + + channel_mix[(int)Channel.A] = MixChannel(ah, regs[AY_A_VOL], (int)Channel.A); + + ah >>= 1; + channel_mix[(int)Channel.B] = MixChannel(ah, regs[AY_B_VOL], (int)Channel.B); + + ah >>= 1; + channel_mix[(int)Channel.C] = MixChannel(ah, regs[AY_C_VOL], (int)Channel.C); + + averagedChannelSamples[0] += channel_mix[(int)Channel.A]; + averagedChannelSamples[1] += channel_mix[(int)Channel.B]; + averagedChannelSamples[2] += channel_mix[(int)Channel.C]; + soundSampleCounter++; + } + + private short MixChannel(int ah, int cl, int chan) + { + int al = channel_out[chan]; + int bl, bh; + bl = ah; + bh = ah; + bh &= 0x1; + bl >>= 3; + + al |= (bh); //Tone | AY_ENABLE + bl |= (noiseOut); //Noise | AY_ENABLE + al &= bl; + + if ((al != 0)) + { + if ((cl & 16) != 0) + cl = envelopeVolume; + + cl &= 15; + + //return (AY_Volumes[cl]); + return (AY_SpecVolumes[cl]); + } + return 0; + } + + /// + /// Gets the tone period for the specified channel + /// + /// + /// + private int TonePeriod(int channel) + { + return (regs[(channel) << 1] | ((regs[((channel) << 1) | 1] & 0x0f) << 8)); + } + + /// + /// Gets the noise period for the specified channel + /// + /// + private int NoisePeriod() + { + return (regs[AY_NOISEPER] & 0x1f); + } + + /// + /// Gets the envelope period for the specified channel + /// + /// + private int EnvelopePeriod() + { + return ((regs[AY_E_FINE] | (regs[AY_E_COARSE] << 8))); + } + + /// + /// Gets the noise enable value for the specified channel + /// + /// + /// + private int NoiseEnable(int channel) + { + return ((regs[AY_ENABLE] >> (3 + channel)) & 1); + } + + /// + /// Gets the tone enable value for the specified channel + /// + /// + /// + private int ToneEnable(int channel) + { + return ((regs[AY_ENABLE] >> (channel)) & 1); + } + + /// + /// Gets the tone envelope value for the specified channel + /// + /// + /// + private int ToneEnvelope(int channel) + { + //return ((regs[AY_A_VOL + channel] & 0x10) >> 4); + return ((regs[AY_A_VOL + channel] >> 4) & 0x1); + } + + /// + /// Updates noise + /// + private void UpdateNoise() + { + noiseCount++; + if (noiseCount >= NoisePeriod() && (noiseCount > 4)) + { + /* Is noise output going to change? */ + if (((randomSeed + 1) & 2) != 0) /* (bit0^bit1)? */ + { + noiseOut ^= 1; + } + + /* The Random Number Generator of the 8910 is a 17-bit shift */ + /* register. The input to the shift register is bit0 XOR bit3 */ + /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */ + + /* The following is a fast way to compute bit17 = bit0^bit3. */ + /* Instead of doing all the logic operations, we only check */ + /* bit0, relying on the fact that after three shifts of the */ + /* register, what now is bit3 will become bit0, and will */ + /* invert, if necessary, bit14, which previously was bit17. */ + if ((randomSeed & 1) != 0) + randomSeed ^= 0x24000; /* This version is called the "Galois configuration". */ + randomSeed >>= 1; + noiseCount = 0; + } + } + + /// + /// Updates envelope + /// + private void UpdateEnvelope() + { + /* update envelope */ + if (!sustaining) + { + envelopeCount++; + if ((envelopeCount >= EnvelopePeriod())) + { + envelopeStep--; + + /* check envelope current position */ + if (envelopeStep < 0) + { + if (sustain) + { + if (alternate) + attack ^= 0x0f; + sustaining = true; + envelopeStep = 0; + } + else + { + /* if CountEnv has looped an odd number of times (usually 1), */ + /* invert the output. */ + if (alternate && ((envelopeStep & (0x0f + 1)) != 0) && (envelopeCount > 4)) + attack ^= 0x0f; + + envelopeStep &= 0x0f; + } + } + envelopeCount = 0; + } + } + envelopeVolume = (envelopeStep ^ attack); + } + + + public void Update() + { + envelopeClock ^= 1; + + if (envelopeClock == 1) + { + envelopeCount++; + + //if ((((regs[AY_A_VOL + 0] & 0x10) >> 4) & (((regs[AY_A_VOL + 1] & 0x10) >> 4) & ((regs[AY_A_VOL + 2] & 0x10) >> 4))) != 1) + //if ((((regs[AY_A_VOL + 0] >> 4) & 0x1) & (((regs[AY_A_VOL + 1] >> 4) & 0x1) & ((regs[AY_A_VOL + 2] >> 4) & 0x1))) != 0) + if (((regs[AY_A_VOL + 0] & 0x10) & (regs[AY_A_VOL + 1] & 0x10) & (regs[AY_A_VOL + 2] & 0x10)) != 1) + { + // update envelope + if (!sustaining) + UpdateEnvelope(); + + envelopeVolume = (envelopeStep ^ attack); + } + } + + // update noise + if ((regs[AY_ENABLE] & 0x38) != 0x38) + { + UpdateNoise(); + } + + // update channels + channel_count[0]++; + int regs1 = (regs[1] & 0x0f) << 8; + if (((regs[0] | regs1) > 4) && (channel_count[0] >= (regs[0] | regs1))) + { + channel_out[0] ^= 1; + channel_count[0] = 0; + } + + int regs3 = (regs[3] & 0x0f) << 8; + channel_count[1]++; + if (((regs[2] | regs3) > 4) && (channel_count[1] >= (regs[2] | regs3))) + { + channel_out[1] ^= 1; + channel_count[1] = 0; + } + + int regs5 = (regs[5] & 0x0f) << 8; + channel_count[2]++; + if (((regs[4] | regs5) > 4) && (channel_count[2] >= (regs[4] | regs5))) + { + channel_out[2] ^= 1; + channel_count[2] = 0; + } + } + + + #endregion + + #region ISoundProvider + + 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"); + } + + public void DiscardSamples() + { + + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + samples = _samples; + nsamp = _nsamp; + } + + #endregion + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs deleted file mode 100644 index 3cd2c513dd..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AYSound.cs +++ /dev/null @@ -1,407 +0,0 @@ -using System; - -using BizHawk.Common; -using BizHawk.Common.NumberExtensions; -using BizHawk.Emulation.Common; -using BizHawk.Emulation.Cores.Components; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public class AYSound : ISoundProvider - { - private readonly BlipBuffer _blip = new BlipBuffer(4096); - private short[] _sampleBuffer = new short[0]; - - public AYSound() - { - _blip.SetRates(894866 / 4.0, 44100); - } - - public ushort[] Register = new ushort[16]; - - public int total_clock; // TODO: what is this used for? - - public void Reset() - { - clock_A = clock_B = clock_C = 0x1000; - noise_clock = 0x20; - - for (int i = 0; i < 16; i++) - { - Register[i] = 0x0000; - } - sync_psg_state(); - DiscardSamples(); - } - - public void DiscardSamples() - { - _blip.Clear(); - _sampleClock = 0; - } - - public void GetSamplesAsync(short[] samples) - { - throw new NotSupportedException("Async is not available"); - } - - 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 GetSamplesSync(out short[] samples, out int nsamp) - { - _blip.EndFrame((uint)_sampleClock); - _sampleClock = 0; - - nsamp = _blip.SamplesAvailable(); - int targetLength = nsamp * 2; - if (_sampleBuffer.Length != targetLength) - { - _sampleBuffer = new short[targetLength]; - } - - _blip.ReadSamplesLeft(_sampleBuffer, nsamp); - for (int i = 0; i < _sampleBuffer.Length; i += 2) - { - _sampleBuffer[i + 1] = _sampleBuffer[i]; - } - - samples = _sampleBuffer; - } - - public void GetSamples(short[] samples) - { - throw new Exception(); - } - - private static readonly int[] VolumeTable = - { - 0x0000, 0x0055, 0x0079, 0x00AB, 0x00F1, 0x0155, 0x01E3, 0x02AA, - 0x03C5, 0x0555, 0x078B, 0x0AAB, 0x0F16, 0x1555, 0x1E2B, 0x2AAA - }; - - private int _sampleClock; - private int _latchedSample; - - private int TotalExecutedCycles; - private int PendingCycles; - private int psg_clock; - private int sq_per_A, sq_per_B, sq_per_C; - private int clock_A, clock_B, clock_C; - private int vol_A, vol_B, vol_C; - private bool A_on, B_on, C_on; - private bool A_up, B_up, C_up; - private bool A_noise, B_noise, C_noise; - - private int env_per; - private int env_clock; - private int env_shape; - private int env_E; - private int E_up_down; - private int env_vol_A, env_vol_B, env_vol_C; - - private int noise_clock; - private int noise_per; - private int noise = 0x1; - - public Func ReadMemory; - public Func WriteMemory; - - public void SyncState(Serializer ser) - { - ser.BeginSection("PSG"); - - ser.Sync("Register", ref Register, false); - ser.Sync("Toal_executed_cycles", ref TotalExecutedCycles); - ser.Sync("Pending_Cycles", ref PendingCycles); - - ser.Sync("psg_clock", ref psg_clock); - ser.Sync("clock_A", ref clock_A); - ser.Sync("clock_B", ref clock_B); - ser.Sync("clock_C", ref clock_C); - ser.Sync("noise_clock", ref noise_clock); - ser.Sync("env_clock", ref env_clock); - ser.Sync("A_up", ref A_up); - ser.Sync("B_up", ref B_up); - ser.Sync("C_up", ref C_up); - ser.Sync("noise", ref noise); - ser.Sync("env_E", ref env_E); - ser.Sync("E_up_down", ref E_up_down); - - sync_psg_state(); - - ser.EndSection(); - } - - public ushort? ReadPSG(ushort addr, bool peek) - { - if (addr >= 0x01F0 && addr <= 0x01FF) - { - return (ushort)(Register[addr - 0x01F0]); - } - - return null; - } - - private void sync_psg_state() - { - sq_per_A = (Register[0] & 0xFF) | (((Register[4] & 0xF) << 8)); - if (sq_per_A == 0) - { - sq_per_A = 0x1000; - } - - sq_per_B = (Register[1] & 0xFF) | (((Register[5] & 0xF) << 8)); - if (sq_per_B == 0) - { - sq_per_B = 0x1000; - } - - sq_per_C = (Register[2] & 0xFF) | (((Register[6] & 0xF) << 8)); - if (sq_per_C == 0) - { - sq_per_C = 0x1000; - } - - env_per = (Register[3] & 0xFF) | (((Register[7] & 0xFF) << 8)); - if (env_per == 0) - { - env_per = 0x10000; - } - - env_per *= 2; - - A_on = Register[8].Bit(0); - B_on = Register[8].Bit(1); - C_on = Register[8].Bit(2); - A_noise = Register[8].Bit(3); - B_noise = Register[8].Bit(4); - C_noise = Register[8].Bit(5); - - noise_per = Register[9] & 0x1F; - if (noise_per == 0) - { - noise_per = 0x20; - } - - var shape_select = Register[10] & 0xF; - - if (shape_select < 4) - env_shape = 0; - else if (shape_select < 8) - env_shape = 1; - else - env_shape = 2 + (shape_select - 8); - - vol_A = Register[11] & 0xF; - env_vol_A = (Register[11] >> 4) & 0x3; - - vol_B = Register[12] & 0xF; - env_vol_B = (Register[12] >> 4) & 0x3; - - vol_C = Register[13] & 0xF; - env_vol_C = (Register[13] >> 4) & 0x3; - } - - public bool WritePSG(ushort addr, ushort value, bool poke) - { - if (addr >= 0x01F0 && addr <= 0x01FF) - { - var reg = addr - 0x01F0; - - value &= 0xFF; - - if (reg == 4 || reg == 5 || reg == 6 || reg == 10) - value &= 0xF; - - if (reg == 9) - value &= 0x1F; - - if (reg == 11 || reg == 12 || reg == 13) - value &= 0x3F; - - Register[addr - 0x01F0] = value; - - sync_psg_state(); - - if (reg == 10) - { - env_clock = env_per; - - if (env_shape == 0 || env_shape == 2 || env_shape == 3 || env_shape == 4 || env_shape == 5) - { - env_E = 15; - E_up_down = -1; - } - else - { - env_E = 0; - E_up_down = 1; - } - } - - return true; - } - - return false; - } - - public void generate_sound(int cycles_to_do) - { - // there are 4 cpu cycles for every psg cycle - bool sound_out_A; - bool sound_out_B; - bool sound_out_C; - - for (int i = 0; i < cycles_to_do; i++) - { - psg_clock++; - - if (psg_clock == 4) - { - psg_clock = 0; - - total_clock++; - - clock_A--; - clock_B--; - clock_C--; - - noise_clock--; - env_clock--; - - // clock noise - if (noise_clock == 0) - { - noise = (noise >> 1) ^ (noise.Bit(0) ? 0x10004 : 0); - noise_clock = noise_per; - } - - if (env_clock == 0) - { - env_clock = env_per; - - env_E += E_up_down; - - if (env_E == 16 || env_E == -1) - { - - // we just completed a period of the envelope, determine what to do now based on the envelope shape - if (env_shape == 0 || env_shape == 1 || env_shape == 3 || env_shape == 9) - { - E_up_down = 0; - env_E = 0; - } - else if (env_shape == 5 || env_shape == 7) - { - E_up_down = 0; - env_E = 15; - } - else if (env_shape == 4 || env_shape == 8) - { - if (env_E == 16) - { - env_E = 15; - E_up_down = -1; - } - else - { - env_E = 0; - E_up_down = 1; - } - } - else if (env_shape == 2) - { - env_E = 15; - } - else - { - env_E = 0; - } - } - } - - if (clock_A == 0) - { - A_up = !A_up; - clock_A = sq_per_A; - } - - if (clock_B == 0) - { - B_up = !B_up; - clock_B = sq_per_B; - } - - if (clock_C == 0) - { - C_up = !C_up; - clock_C = sq_per_C; - } - - - sound_out_A = (noise.Bit(0) | A_noise) & (A_on | A_up); - sound_out_B = (noise.Bit(0) | B_noise) & (B_on | B_up); - sound_out_C = (noise.Bit(0) | C_noise) & (C_on | C_up); - - // now calculate the volume of each channel and add them together - int v; - - if (env_vol_A == 0) - { - v = (short)(sound_out_A ? VolumeTable[vol_A] : 0); - } - else - { - int shift_A = 3 - env_vol_A; - if (shift_A < 0) - shift_A = 0; - v = (short)(sound_out_A ? (VolumeTable[env_E] >> shift_A) : 0); - } - - if (env_vol_B == 0) - { - v += (short)(sound_out_B ? VolumeTable[vol_B] : 0); - - } - else - { - int shift_B = 3 - env_vol_B; - if (shift_B < 0) - shift_B = 0; - v += (short)(sound_out_B ? (VolumeTable[env_E] >> shift_B) : 0); - } - - if (env_vol_C == 0) - { - v += (short)(sound_out_C ? VolumeTable[vol_C] : 0); - } - else - { - int shift_C = 3 - env_vol_C; - if (shift_C < 0) - shift_C = 0; - v += (short)(sound_out_C ? (VolumeTable[env_E] >> shift_C) : 0); - } - - if (v != _latchedSample) - { - _blip.AddDelta((uint)_sampleClock, v - _latchedSample); - _latchedSample = v; - } - - _sampleClock++; - } - } - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs index ace668868f..f815a80c1e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs @@ -94,6 +94,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _sampleRate = sampleRate; _tStatesPerFrame = tStatesPerFrame; + _tStatesPerSample = 79; + _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + + /* // get divisors var divs = from a in Enumerable.Range(2, _tStatesPerFrame / 2) @@ -105,6 +109,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // get _samplesPerFrame _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + */ Pulses = new List(1000); } @@ -265,13 +270,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum short[] stereoBuffer = new short[soundBufferContains * 2]; int index = 0; for (int i = 0; i < soundBufferContains; i++) - { - stereoBuffer[index++] = soundBuffer[i]; + { stereoBuffer[index++] = soundBuffer[i]; + stereoBuffer[index++] = soundBuffer[i]; } - + samples = stereoBuffer; - nsamp = soundBufferContains; + nsamp = _samplesPerFrame; // soundBufferContains; } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index f23173a259..a700bf98ac 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -12,10 +12,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public abstract partial class SpectrumBase { - public bool ROMPaged { get; set; } - public bool SHADOWPaged { get; set; } - public int RAMPaged { get; set; } - public bool PagingDisabled { get; set; } + protected int ROMPaged = 0; + protected bool SHADOWPaged; + protected int RAMPaged; + protected bool PagingDisabled; /// /// The calling ZXSpectrum class (piped in via constructor) @@ -40,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Device representing the AY-3-8912 chip found in the 128k and up spectrums /// - public AYSound AYDevice { get; set; } + public AY38912 AYDevice { get; set; } /// /// The spectrum keyboard @@ -100,6 +100,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum protected const int MIC_BIT = 0x08; protected const int TAPE_BIT = 0x40; + protected const int AY_SAMPLE_RATE = 16; + /// /// Executes a single frame /// @@ -107,9 +109,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { FrameCompleted = false; BuzzerDevice.StartFrame(); - + if (AYDevice != null) + AYDevice.StartFrame(); PollInput(); + var curr = CPU.TotalExecutedCycles; + while (CurrentFrameCycle <= UlaFrameCycleCount) { // check for interrupt @@ -122,14 +127,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var lastCycle = CurrentFrameCycle; RenderScreen(LastRenderedULACycle + 1, lastCycle); LastRenderedULACycle = lastCycle; - - } + // update AY + if (AYDevice != null) + AYDevice.UpdateSound(CurrentFrameCycle); + } + // we have reached the end of a frame LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow; LastRenderedULACycle = OverFlow; - BuzzerDevice.EndFrame(); + BuzzerDevice.EndFrame(); TapeDevice.CPUFrameCompleted(); @@ -234,14 +242,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RomData.SyncState(ser); KeyboardDevice.SyncState(ser); BuzzerDevice.SyncState(ser); - TapeDevice.SyncState(ser); if (AYDevice != null) AYDevice.SyncState(ser); + TapeDevice.SyncState(ser); + ser.EndSection(); - //ReInitMemory(); + ReInitMemory(); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index 4502a98a51..5b8ef31e19 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - if (!ROMPaged) + if (ROMPaged == 0) result = Memory[0][addr % 0x4000]; else result = Memory[1][addr % 0x4000]; @@ -119,7 +119,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - if (!ROMPaged) + if (ROMPaged == 0) Memory[0][addr % 0x4000] = value; else Memory[1][addr % 0x4000] = value; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index d381bef130..67e667defb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -8,6 +8,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZX128 : SpectrumBase { + private int AYTStates = 0; + /// /// Reads a byte of data from a specified port address /// @@ -112,12 +114,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // (e.g. the AY sound chip in a 128k spectrum // AY register activate - // Kemptson Mouse + if ((port & 0xc002) == 0xc000) + { + result = (int)AYDevice.PortRead(); + } + + // Kempston Mouse // if unused port the floating memory bus should be returned (still todo) } + CPU.TotalExecutedCycles += 3; + return (byte)result; } @@ -128,6 +137,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + int currT = CPU.TotalExecutedCycles; + // paging if (port == 0x7ffd) { @@ -140,11 +151,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if ((value & 0x10) != 0) { // 48k ROM - ROMPaged = true; + ROMPaged = 1; } else { - ROMPaged = false; + ROMPaged = 0; } // Bit 5 signifies that paging is disabled until next reboot @@ -183,6 +194,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Tape TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } + + // Active AY Register + if ((port & 0xc002) == 0xc000) + { + var reg = value & 0x0f; + AYDevice.SelectedRegister = reg; + CPU.TotalExecutedCycles += 3; + } + + // AY Write + if ((port & 0xc002) == 0x8000) + { + AYDevice.PortWrite(value); + CPU.TotalExecutedCycles += 3; + } } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index fe3f08d651..b40df6e8a9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Spectrum = spectrum; CPU = cpu; - ROMPaged = false; + ROMPaged = 0; SHADOWPaged = false; RAMPaged = 0; PagingDisabled = false; @@ -29,8 +29,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - //RAM = new byte[0x4000 + 0xC000]; - //DisplayLineTime = 132; VsyncNumerator = 3546900; @@ -42,7 +40,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, UlaFrameCycleCount); - AYDevice = new AYSound(); + AYDevice = new AY38912(); + AYDevice.Init(44100, UlaFrameCycleCount); KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index 76dbb31891..27d74882fb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - if (!ROMPaged) + if (ROMPaged == 0) result = Memory[0][addr % 0x4000]; else result = Memory[1][addr % 0x4000]; @@ -119,7 +119,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - if (!ROMPaged) + if (ROMPaged == 0) Memory[0][addr % 0x4000] = value; else Memory[1][addr % 0x4000] = value; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 35fe0c3391..9b6b458e4b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -140,11 +140,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if ((value & 0x10) != 0) { // 48k ROM - ROMPaged = true; + ROMPaged = 1; } else { - ROMPaged = false; + ROMPaged = 0; } // Bit 5 signifies that paging is disabled until next reboot diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 2a75a600f7..199beb7d8d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Spectrum = spectrum; CPU = cpu; - ROMPaged = false; + ROMPaged = 0; SHADOWPaged = false; RAMPaged = 0; PagingDisabled = false; @@ -42,6 +42,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, UlaFrameCycleCount); + AYDevice = new AY38912(); + AYDevice.Init(44100, UlaFrameCycleCount); + KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs index 57e37f3d77..16d9fa9c52 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs @@ -60,6 +60,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else if (sp.Count() > 1) foreach (var s in sp) SoundProviders.Remove(s); + + EqualizeVolumes(); } public void EqualizeVolumes() @@ -119,6 +121,38 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (!sameCount) { + // get the highest number of samples + int max = SoundProviders.Aggregate((i, j) => i.Buffer.Length > j.Buffer.Length ? i : j).Buffer.Length; + + nsamp = max; + samples = new short[nsamp * 2]; + + // take a pass at populating the samples array for each provider + foreach (var sp in SoundProviders) + { + short sectorVal = 0; + int pos = 0; + for (int i = 0; i < sp.Buffer.Length; i++) + { + if (sp.Buffer[i] > sp.MaxVolume) + sectorVal = (short)sp.MaxVolume; + else + { + if (sp.SoundProvider is AY38912) + { + // boost audio + sectorVal += (short)(sp.Buffer[i] * 2); + } + else + { + sectorVal += sp.Buffer[i]; + } + } + + samples[pos++] += sectorVal; + } + } + /* int divisor = 1; int highestCount = 0; @@ -167,6 +201,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } + */ } else { @@ -181,7 +216,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (sp.Buffer[i] > sp.MaxVolume) sectorVal += (short)sp.MaxVolume; else - sectorVal += sp.Buffer[i]; + { + if (sp.SoundProvider is AY38912) + { + // boost audio + sectorVal += (short)(sp.Buffer[i] * 2); + } + else + { + sectorVal += sp.Buffer[i]; + } + } } samples[i] = sectorVal; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 15052083da..81d2a965fb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -74,7 +74,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - ser.Register(SoundMixer); + //SoundMixer.DisableSource(_machine.BuzzerDevice); + + dcf = new DCFilter(SoundMixer, 1024); + + + + ser.Register(dcf); + //ser.Register(_machine.AYDevice); + + HardReset(); @@ -89,6 +98,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public IController _controller; private SpectrumBase _machine; + private DCFilter dcf; + private byte[] _file; From 43ed79cd645a285c154c97e3e1b418710b1797ec Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 13:09:53 +0000 Subject: [PATCH 026/105] Mixer balancing and stereo output toggle --- .../SinclairSpectrum/Hardware/AY38912.cs | 26 +---- .../SinclairSpectrum/Hardware/Buzzer.cs | 11 +-- .../SinclairSpectrum/SoundProviderMixer.cs | 96 +++++++++++-------- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 17 +++- 4 files changed, 80 insertions(+), 70 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs index 9babd6fbd0..8dcde1e0ee 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs @@ -265,7 +265,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private byte envelopeClock = 0; private int selectedRegister; public ushort soundSampleCounter; - private bool stereoSound = true; + private bool stereoSound = false; private bool sustaining; private bool sustain; private bool alternate; @@ -316,15 +316,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } - /// - /// Set whether sound output is stereo or mono - /// - public bool StereoSound - { - get { return stereoSound; } - set { stereoSound = value; } - } - /// /// Utility method to set all registers externally /// @@ -453,18 +444,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private void EndSampleAY() { - if (stereoSound) - { - averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); - averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); - averagedChannelSamples[2] = 0;// beeperSound; - } - else - { - averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter); - averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter); - averagedChannelSamples[2] = 0;// (averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] + averagedChannelSamples[ChannelRight]) / soundSampleCounter + beeperSound; - } + averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); + averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); + soundSampleCounter = 0; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs index f815a80c1e..d751189fae 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs @@ -206,12 +206,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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]); - - } - + if (_tapeMode) + samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 6) : (short)0; + else + samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 2) : (short)0; + } currentEnd += pulse.Length; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs index 16d9fa9c52..1b2e37b835 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs @@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// My attempt at mixing multiple ISoundProvider sources together and outputting another ISoundProvider /// Currently only supports SyncSoundMode.Sync - /// Attached ISoundProvider sources must already be stereo 44.1khz + /// Attached ISoundProvider sources must already be stereo 44.1khz and ideally sound buffers should be the same length /// internal sealed class SoundProviderMixer : ISoundProvider { @@ -20,11 +20,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public int NSamp { get; set; } } + private bool _stereo = true; + public bool Stereo + { + get { return _stereo; } + set { _stereo = value; } + } + private readonly List SoundProviders; - - private short[] _buffer; - private int _nSamp; - + public SoundProviderMixer(params ISoundProvider[] soundProviders) { SoundProviders = new List(); @@ -119,40 +123,51 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var firstEntry = SoundProviders.First(); bool sameCount = SoundProviders.All(s => s.NSamp == firstEntry.NSamp); - if (!sameCount) + + if (sameCount) { - // get the highest number of samples - int max = SoundProviders.Aggregate((i, j) => i.Buffer.Length > j.Buffer.Length ? i : j).Buffer.Length; - - nsamp = max; + nsamp = firstEntry.NSamp; samples = new short[nsamp * 2]; - // take a pass at populating the samples array for each provider - foreach (var sp in SoundProviders) + if (_stereo) { - short sectorVal = 0; - int pos = 0; - for (int i = 0; i < sp.Buffer.Length; i++) + for (int i = 0; i < samples.Length; i++) { - if (sp.Buffer[i] > sp.MaxVolume) - sectorVal = (short)sp.MaxVolume; - else + short sectorVal = 0; + foreach (var sp in SoundProviders) { - if (sp.SoundProvider is AY38912) - { - // boost audio - sectorVal += (short)(sp.Buffer[i] * 2); - } + if (sp.Buffer[i] > sp.MaxVolume) + sectorVal += (short)sp.MaxVolume; else { sectorVal += sp.Buffer[i]; } } - samples[pos++] += sectorVal; + samples[i] = sectorVal; } } - /* + else + { + // convert to mono + for (int i = 0; i < samples.Length; i += 2) + { + short s = 0; + foreach (var sp in SoundProviders) + { + s += (short)((sp.Buffer[i] + sp.Buffer[i + 1]) / 2); + } + + samples[i] = s; + samples[i + 1] = s; + } + } + } + + else if (!sameCount) + { + // this is a pretty poor implementation that doesnt work very well + // ideally soundproviders should ensure that their number of samples is identical int divisor = 1; int highestCount = 0; @@ -194,27 +209,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum sectorVal = (short)sp.MaxVolume; else sectorVal = sp.Buffer[i]; - + for (int s = 0; s < divisor; s++) { samples[pos++] += sectorVal; } } } - */ - } - else - { - nsamp = firstEntry.NSamp; + + /* + // get the highest number of samples + int max = SoundProviders.Aggregate((i, j) => i.Buffer.Length > j.Buffer.Length ? i : j).Buffer.Length; + + nsamp = max; samples = new short[nsamp * 2]; - for (int i = 0; i < samples.Length; i++) + // take a pass at populating the samples array for each provider + foreach (var sp in SoundProviders) { short sectorVal = 0; - foreach (var sp in SoundProviders) + int pos = 0; + for (int i = 0; i < sp.Buffer.Length; i++) { if (sp.Buffer[i] > sp.MaxVolume) - sectorVal += (short)sp.MaxVolume; + sectorVal = (short)sp.MaxVolume; else { if (sp.SoundProvider is AY38912) @@ -225,12 +243,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else { sectorVal += sp.Buffer[i]; - } - } - } + } + } - samples[i] = sectorVal; + samples[pos++] += sectorVal; + } } + */ + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 16643c8b84..f68aa408e4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -24,7 +24,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public bool PutSettings(ZXSpectrumSettings o) { + if (SoundMixer != null) + SoundMixer.Stereo = o.StereoSound; + Settings = o; + return false; } @@ -39,11 +43,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class ZXSpectrumSettings { - [DisplayName("Auto-load tape")] - [Description("Auto or manual tape operation")] + [DisplayName("Stereo Sound")] + [Description("Turn stereo sound on or off")] [DefaultValue(true)] - public bool AutoLoadTape { get; set; } - + public bool StereoSound { get; set; } + public ZXSpectrumSettings Clone() { @@ -73,6 +77,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DefaultValue(TapeLoadSpeed.Accurate)] public TapeLoadSpeed TapeLoadSpeed { get; set; } + [DisplayName("Auto-load tape")] + [Description("Auto or manual tape operation")] + [DefaultValue(true)] + public bool AutoLoadTape { get; set; } + public ZXSpectrumSyncSettings Clone() { return (ZXSpectrumSyncSettings)MemberwiseClone(); From eff8ce69b44b9c451454c4881519aef17e4121b2 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 15:43:28 +0000 Subject: [PATCH 027/105] More +3 stuff (still not working) --- .../Database/FirmwareDatabase.cs | 2 +- .../BizHawk.Emulation.Cores.csproj | 1 + .../SinclairSpectrum/Machine/MachineType.cs | 9 +- .../Machine/SpectrumBase.Screen.cs | 43 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 12 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 455 ++++++++++++------ .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 209 ++++++-- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 2 - .../ZXSpectrum.ISoundProvider.cs | 2 - .../ZXSpectrum.IVideoProvider.cs | 13 + .../Computers/SinclairSpectrum/ZXSpectrum.cs | 10 + 11 files changed, 543 insertions(+), 215 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 9d9810b5e6..7755353463 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -54,7 +54,7 @@ namespace BizHawk.Emulation.Common FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM"); - FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65563, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM"); + FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65536, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM"); // 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 diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 274f62c6a4..70231ad7e7 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1391,6 +1391,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs index a29b745205..cb895cf171 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -19,8 +19,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ZXSpectrum128, /// - /// Sinclair Spectrum 128 + 2 model + /// Sinclair Spectrum 128 +2 model /// - ZXSpectrum128Plus2 + ZXSpectrum128Plus2, + + /// + /// Sinclair Spectrum 128 +3 model + /// + ZXSpectrum128Plus3 } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 3ab09352db..69bdc62382 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -913,13 +913,44 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ public int[] GetVideoBuffer() { - return _frameBuffer; + /* + switch(Spectrum.SyncSettings.BorderType) + { + case ZXSpectrum.BorderType.Full: + return _frameBuffer; - // 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; + case ZXSpectrum.BorderType.Small: + // leave only 10 border units all around + int[] smlBuff = new int[(ScreenWidth - 30) * (DisplayLines - 30)]; + int index = 0; + int brdCount = 0; + // skip top and bottom + for (int i = ScreenWidth * 30; i < smlBuff.Length - ScreenWidth * 30; i++) + { + if (brdCount < 30) + { + brdCount++; + continue; + } + if (brdCount > ScreenWidth - 30) + { + brdCount++; + continue; + } + + smlBuff[index] = _frameBuffer[i]; + index++; + brdCount++; + } + + return smlBuff; + + case ZXSpectrum.BorderType.Medium: + break; + } + */ + return _frameBuffer; + } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index a700bf98ac..8de871a5e1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -11,12 +11,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public abstract partial class SpectrumBase { - + // 128 and up only protected int ROMPaged = 0; protected bool SHADOWPaged; protected int RAMPaged; protected bool PagingDisabled; + // +3/+2A only + protected bool SpecialPagingMode; + protected int PagingConfiguration; + /// /// The calling ZXSpectrum class (piped in via constructor) /// @@ -238,6 +242,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("RAM5", ref RAM5, false); ser.Sync("RAM6", ref RAM6, false); ser.Sync("RAM7", ref RAM7, false); + ser.Sync("ROMPaged", ref ROMPaged); + ser.Sync("SHADOWPaged", ref SHADOWPaged); + ser.Sync("RAMPaged", ref RAMPaged); + ser.Sync("PagingDisabled", ref PagingDisabled); + ser.Sync("SpecialPagingMode", ref SpecialPagingMode); + ser.Sync("PagingConfiguration", ref PagingConfiguration); RomData.SyncState(ser); KeyboardDevice.SyncState(ser); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index 27d74882fb..fca4961bd9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,35 +9,50 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZX128Plus3 : SpectrumBase { - /* 128k paging controlled by writes to port 0x7ffd - * - * - - #7FFD (32765) - decoded as A15=0, A1=0 and /IORQ=0. Bits 0..5 are latched. Bits 0..2 select RAM bank in secton D. Bit 3 selects RAM bank to dispay screen (0 - RAM5, 1 - RAM7). Bit 4 selects ROM bank (0 - ROM0, 1 - ROM1). Bit 5, when set locks future writing to #7FFD port until reset. Reading #7FFD port is the same as writing #FF into it. - #BFFD (49149) - write data byte into AY-3-8912 chip. - #FFFD (65533) - select AY-3-8912 addres (D4..D7 ignored) and reading data byte. + /* http://www.worldofspectrum.org/faq/reference/128kreference.htm + * + * Port 0x7ffd behaves in the almost exactly the same way as on the 128K/+2, with two exceptions: - * 0xffff +--------+--------+--------+--------+--------+--------+--------+--------+ - | Bank 0 | Bank 1 | Bank 2 | Bank 3 | Bank 4 | Bank 5 | Bank 6 | Bank 7 | - | | |(also at| | |(also at| | | - | | | 0x8000)| | | 0x4000)| | | - | | | | | | screen | | screen | - 0xc000 +--------+--------+--------+--------+--------+--------+--------+--------+ - | Bank 2 | Any one of these pages may be switched in. - | | - | | - | | - 0x8000 +--------+ - | Bank 5 | - | | - | | - | screen | - 0x4000 +--------+--------+ - | ROM 0 | ROM 1 | Either ROM may be switched in. - | | | - | | | - | | | - 0x0000 +--------+--------+ + Bit 4 is now the low bit of the ROM selection. + The partial decoding used is now slightly different: the hardware will respond only to those port addresses with bit 1 reset, bit 14 set and bit 15 reset (as opposed to just bits 1 and 15 reset on the 128K/+2). + The extra paging features of the +2A/+3 are controlled by port 0x1ffd (again, partial decoding applies here: the hardware will respond to all port addresses with bit 1 reset, bit 12 set and bits 13, 14 and 15 reset). This port is also write-only, and its last value should be saved at 0x5b67 (23399). + + Port 0x1ffd responds as follows: + + Bit 0: Paging mode. 0=normal, 1=special + Bit 1: In normal mode, ignored. + Bit 2: In normal mode, high bit of ROM selection. The four ROMs are: + ROM 0: 128k editor, menu system and self-test program + ROM 1: 128k syntax checker + ROM 2: +3DOS + ROM 3: 48 BASIC + Bit 3: Disk motor; 1=on, 0=off + Bit 4: Printer port strobe. + When special mode is selected, the memory map changes to one of four configurations specified in bits 1 and 2 of port 0x1ffd: + Bit 2 =0 Bit 2 =0 Bit 2 =1 Bit 2 =1 + Bit 1 =0 Bit 1 =1 Bit 1 =0 Bit 1 =1 + 0xffff +--------+ +--------+ +--------+ +--------+ + | Bank 3 | | Bank 7 | | Bank 3 | | Bank 3 | + | | | | | | | | + | | | | | | | | + | | | screen | | | | | + 0xc000 +--------+ +--------+ +--------+ +--------+ + | Bank 2 | | Bank 6 | | Bank 6 | | Bank 6 | + | | | | | | | | + | | | | | | | | + | | | | | | | | + 0x8000 +--------+ +--------+ +--------+ +--------+ + | Bank 1 | | Bank 5 | | Bank 5 | | Bank 7 | + | | | | | | | | + | | | | | | | | + | | | screen | | screen | | screen | + 0x4000 +--------+ +--------+ +--------+ +--------+ + | Bank 0 | | Bank 4 | | Bank 4 | | Bank 4 | + | | | | | | | | + | | | | | | | | + | | | | | | | | + 0x0000 +--------+ +--------+ +--------+ +--------+ + RAM banks 1,3,4 and 6 are used for the disc cache and RAMdisc, while Bank 7 contains editor scratchpads and +3DOS workspace. */ /// @@ -49,58 +65,120 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { int divisor = addr / 0x4000; byte result = 0xff; - switch (divisor) + + // special paging + if (SpecialPagingMode) { - // ROM 0x000 - case 0: - if (ROMPaged == 0) - result = Memory[0][addr % 0x4000]; - else - result = Memory[1][addr % 0x4000]; - break; + switch (divisor) + { + case 0: + switch (PagingConfiguration) + { + case 0: + result = Memory[4][addr % 0x4000]; + break; + case 1: + case 2: + case 3: + result = Memory[8][addr % 0x4000]; + break; + } + break; + case 1: + switch (PagingConfiguration) + { + case 0: + result = Memory[5][addr % 0x4000]; + break; + case 1: + case 2: + result = Memory[9][addr % 0x4000]; + break; + case 3: + result = Memory[11][addr % 0x4000]; + break; + } + break; + case 2: + switch (PagingConfiguration) + { + case 0: + result = Memory[6][addr % 0x4000]; + break; + case 1: + case 2: + case 3: + result = Memory[10][addr % 0x4000]; + break; + } + break; + case 3: + switch (PagingConfiguration) + { + case 0: + case 2: + case 3: + result = Memory[7][addr % 0x4000]; + break; + case 1: + result = Memory[11][addr % 0x4000]; + break; + } + break; + } + } + else + { + switch (divisor) + { + // ROM 0x000 + case 0: + result = Memory[ROMPaged][addr % 0x4000]; + break; - // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) - case 1: - result = Memory[7][addr % 0x4000]; - break; + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + result = Memory[9][addr % 0x4000]; + break; - // RAM 0x8000 (RAM2 - Bank2) - case 2: - result = Memory[4][addr % 0x4000]; - break; + // RAM 0x8000 (RAM2 - Bank2) + case 2: + result = Memory[6][addr % 0x4000]; + break; - // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) - case 3: - switch (RAMPaged) - { - case 0: - result = Memory[2][addr % 0x4000]; - break; - case 1: - result = Memory[3][addr % 0x4000]; - break; - case 2: - result = Memory[4][addr % 0x4000]; - break; - case 3: - result = Memory[5][addr % 0x4000]; - break; - case 4: - result = Memory[6][addr % 0x4000]; - break; - case 5: - result = Memory[7][addr % 0x4000]; - break; - case 6: - result = Memory[8][addr % 0x4000]; - break; - case 7: - result = Memory[9][addr % 0x4000]; - break; - } - break; - default: - break; + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + result = Memory[4][addr % 0x4000]; + break; + case 1: + result = Memory[5][addr % 0x4000]; + break; + case 2: + result = Memory[6][addr % 0x4000]; + break; + case 3: + result = Memory[7][addr % 0x4000]; + break; + case 4: + result = Memory[8][addr % 0x4000]; + break; + case 5: + result = Memory[9][addr % 0x4000]; + break; + case 6: + result = Memory[10][addr % 0x4000]; + break; + case 7: + result = Memory[11][addr % 0x4000]; + break; + } + break; + default: + break; + } } return result; @@ -115,59 +193,121 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override void WriteBus(ushort addr, byte value) { int divisor = addr / 0x4000; - switch (divisor) + + // special paging + if (SpecialPagingMode) { - // ROM 0x000 - case 0: - if (ROMPaged == 0) - Memory[0][addr % 0x4000] = value; - else - Memory[1][addr % 0x4000] = value; - break; - - // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) - case 1: - Memory[7][addr % 0x4000] = value; - break; - - // RAM 0x8000 (RAM2 - Bank2) - case 2: - Memory[4][addr % 0x4000] = value; - break; - - // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) - case 3: - switch (RAMPaged) - { - case 0: - Memory[2][addr % 0x4000] = value; - break; - case 1: - Memory[3][addr % 0x4000] = value; - break; - case 2: - Memory[4][addr % 0x4000] = value; - break; - case 3: - Memory[5][addr % 0x4000] = value; - break; - case 4: - Memory[6][addr % 0x4000] = value; - break; - case 5: - Memory[7][addr % 0x4000] = value; - break; - case 6: - Memory[8][addr % 0x4000] = value; - break; - case 7: - Memory[9][addr % 0x4000] = value; - break; - } - break; - default: - break; + switch (divisor) + { + case 0: + switch (PagingConfiguration) + { + case 0: + Memory[4][addr % 0x4000] = value; + break; + case 1: + case 2: + case 3: + Memory[8][addr % 0x4000] = value; + break; + } + break; + case 1: + switch (PagingConfiguration) + { + case 0: + Memory[5][addr % 0x4000] = value; + break; + case 1: + case 2: + Memory[9][addr % 0x4000] = value; + break; + case 3: + Memory[11][addr % 0x4000] = value; + break; + } + break; + case 2: + switch (PagingConfiguration) + { + case 0: + Memory[6][addr % 0x4000] = value; + break; + case 1: + case 2: + case 3: + Memory[10][addr % 0x4000] = value; + break; + } + break; + case 3: + switch (PagingConfiguration) + { + case 0: + case 2: + case 3: + Memory[7][addr % 0x4000] = value; + break; + case 1: + Memory[11][addr % 0x4000] = value; + break; + } + break; + } } + else + { + switch (divisor) + { + // ROM 0x000 + case 0: + Memory[ROMPaged][addr % 0x4000] = value; + break; + + // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + case 1: + Memory[9][addr % 0x4000] = value; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + Memory[6][addr % 0x4000] = value; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + Memory[4][addr % 0x4000] = value; + break; + case 1: + Memory[5][addr % 0x4000] = value; + break; + case 2: + Memory[6][addr % 0x4000] = value; + break; + case 3: + Memory[7][addr % 0x4000] = value; + break; + case 4: + Memory[8][addr % 0x4000] = value; + break; + case 5: + Memory[9][addr % 0x4000] = value; + break; + case 6: + Memory[10][addr % 0x4000] = value; + break; + case 7: + Memory[11][addr % 0x4000] = value; + break; + } + break; + default: + break; + } + } } /// @@ -224,44 +364,54 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Memory.Add(1, ROM1); if (Memory.ContainsKey(2)) - Memory[2] = RAM0; + Memory[2] = ROM2; else - Memory.Add(2, RAM0); + Memory.Add(2, ROM2); if (Memory.ContainsKey(3)) - Memory[3] = RAM1; + Memory[3] = ROM3; else - Memory.Add(3, RAM1); + Memory.Add(3, ROM3); if (Memory.ContainsKey(4)) - Memory[4] = RAM2; + Memory[4] = RAM0; else - Memory.Add(4, RAM2); + Memory.Add(4, RAM0); if (Memory.ContainsKey(5)) - Memory[5] = RAM3; + Memory[5] = RAM1; else - Memory.Add(5, RAM3); + Memory.Add(5, RAM1); if (Memory.ContainsKey(6)) - Memory[6] = RAM4; + Memory[6] = RAM2; else - Memory.Add(6, RAM4); + Memory.Add(6, RAM2); if (Memory.ContainsKey(7)) - Memory[7] = RAM5; + Memory[7] = RAM3; else - Memory.Add(7, RAM5); + Memory.Add(7, RAM3); if (Memory.ContainsKey(8)) - Memory[8] = RAM6; + Memory[8] = RAM4; else - Memory.Add(8, RAM6); + Memory.Add(8, RAM4); if (Memory.ContainsKey(9)) - Memory[9] = RAM7; + Memory[9] = RAM5; else - Memory.Add(9, RAM7); + Memory.Add(9, RAM5); + + if (Memory.ContainsKey(10)) + Memory[10] = RAM6; + else + Memory.Add(10, RAM6); + + if (Memory.ContainsKey(11)) + Memory[11] = RAM7; + else + Memory.Add(11, RAM7); } /// @@ -272,13 +422,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override void InitROM(RomData romData) { RomData = romData; - // 128k uses ROM0 and ROM1 - // 128k loader is in ROM0, and fallback 48k rom is in ROM1 - for (int i = 0; i < 0x4000; i++) - { - ROM0[i] = RomData.RomBytes[i]; - ROM1[i] = RomData.RomBytes[i + 0x4000]; - } + // +3 uses ROM0, ROM1, ROM2 & ROM3 + /* ROM 0: 128k editor, menu system and self-test program + ROM 1: 128k syntax checker + ROM 2: +3DOS + ROM 3: 48 BASIC + */ + Stream stream = new MemoryStream(RomData.RomBytes); + stream.Read(ROM0, 0, 16384); + stream.Read(ROM1, 0, 16384); + stream.Read(ROM2, 0, 16384); + stream.Read(ROM3, 0, 16384); + stream.Dispose(); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 9b6b458e4b..e7f2071fca 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -79,30 +79,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result |= (TAPE_BIT); // set is EAR Off } } + else if ((LastULAOutByte & 0x10) == 0) + { + result &= ~(0x40); + } else { - if (KeyboardDevice.IsIssue2Keyboard) - { - if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - else - { - if ((LastULAOutByte & EAR_BIT) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } + result |= 0x40; } } @@ -111,9 +94,35 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // devices other than the ULA will respond here // (e.g. the AY sound chip in a 128k spectrum - // AY register activate - // Kemptson Mouse + // AY register activate - on +3/2a both FFFD and BFFD active AY + if ((port & 0xc002) == 0xc000) + { + result = (int)AYDevice.PortRead(); + } + else if ((port & 0xc002) == 0x8000) + { + result = (int)AYDevice.PortRead(); + } + // Kempston Mouse + + + else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + //result = udpDrive.DiskStatusRead(); + } + else if ((port & 0xF002) == 0x3000) + { + //result = udpDrive.DiskReadByte(); + } + + else if ((port & 0xF002) == 0x0) + { + if (PagingDisabled) + result = 0x1; + else + result = 0xff; + } // if unused port the floating memory bus should be returned (still todo) } @@ -128,33 +137,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { - // paging - if (port == 0x7ffd) - { - // Bits 0, 1, 2 select the RAM page - var rp = value & 0x07; - if (rp < 8) - RAMPaged = rp; - - // ROM page - if ((value & 0x10) != 0) - { - // 48k ROM - ROMPaged = 1; - } - else - { - ROMPaged = 0; - } - - // Bit 5 signifies that paging is disabled until next reboot - if ((value & 0x20) != 0) - PagingDisabled = true; - - - return; - } - // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x01) == 0; @@ -183,6 +165,131 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Tape TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } + + else + { + // AY Register activation + if ((port & 0xc002) == 0xc000) + { + var reg = value & 0x0f; + AYDevice.SelectedRegister = reg; + CPU.TotalExecutedCycles += 3; + } + else + { + if ((port & 0xC002) == 0x8000) + { + AYDevice.PortWrite(value); + CPU.TotalExecutedCycles += 3; + } + else + { + if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set? + { + // memory paging activate + if (PagingDisabled) + return; + + // bit 5 handles paging disable (48k mode, persistent until next reboot) + if ((value & 0x20) != 0) + { + PagingDisabled = true; + } + + // shadow screen + if ((value & 0x08) != 0) + { + SHADOWPaged = true; + } + else + { + SHADOWPaged = false; + } + } + else + { + //Extra Memory Paging feature activate + if ((port & 0xF002) == 0x1000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + if (PagingDisabled) + return; + + // set disk motor state + //todo + + if ((value & 0x08) != 0) + { + //diskDriveState |= (1 << 4); + } + else + { + //diskDriveState &= ~(1 << 4); + } + + if ((value & 0x1) != 0) + { + // activate special paging mode + SpecialPagingMode = true; + PagingConfiguration = (value & 0x6 >> 1); + } + else + { + // normal paging mode + SpecialPagingMode = false; + } + } + else + { + // disk write port + if ((port & 0xF002) == 0x3000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + //udpDrive.DiskWriteByte((byte)(val & 0xff)); + } + } + } + } + } + } + + + // paging + if (port == 0x7ffd) + { + if (PagingDisabled) + return; + + LastULAOutByte = value; + + + + + + + // Bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // ROM page + if ((value & 0x10) != 0) + { + // 48k ROM + ROMPaged = 1; + } + else + { + ROMPaged = 0; + } + + // Bit 5 signifies that paging is disabled until next reboot + if ((value & 0x20) != 0) + PagingDisabled = true; + + + return; + } + + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 199beb7d8d..cd5e8f81b2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -29,8 +29,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - //RAM = new byte[0x4000 + 0xC000]; - //DisplayLineTime = 132; VsyncNumerator = 3546900; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs index 02f144350c..402b7f292d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs @@ -9,8 +9,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZXSpectrum { - private FakeSyncSound _fakeSyncSound; - private IAsyncSoundProvider ActiveSoundProvider; private SoundProviderMixer SoundMixer; } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs new file mode 100644 index 0000000000..073236a69d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs @@ -0,0 +1,13 @@ +using System; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Main IVideoProvider implementation is inside the machine classes + /// This is just some helper functions + /// + public partial class ZXSpectrum + { + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 81d2a965fb..ac08f1bd3a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -48,6 +48,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); break; + case MachineType.ZXSpectrum128Plus3: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + break; default: throw new InvalidOperationException("Machine not yet emulated"); } @@ -146,6 +150,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var romDataP2 = RomData.InitROM(machineType, _systemRomP2); _machine.InitROM(romDataP2); break; + case MachineType.ZXSpectrum128Plus3: + _machine = new ZX128Plus3(this, _cpu, file); + var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); + var romDataP3 = RomData.InitROM(machineType, _systemRomP3); + _machine.InitROM(romDataP3); + break; } } From fc8b89c837dd767e45e98bdf0709451515d1c867 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 16:03:23 +0000 Subject: [PATCH 028/105] Added original 16k speccy (even though it sucks) --- .../BizHawk.Emulation.Cores.csproj | 1 + .../SinclairSpectrum/Machine/MachineType.cs | 5 + .../Machine/ZXSpectrum16K/ZX16.cs | 169 ++++++++++++++++++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 10 ++ 4 files changed, 185 insertions(+) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 70231ad7e7..244c3ae233 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -271,6 +271,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs index cb895cf171..478b821a1a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -8,6 +8,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public enum MachineType { + /// + /// Original Sinclair Spectrum 16K model + /// + ZXSpectrum16, + /// /// Sinclair Spectrum 48K model /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs new file mode 100644 index 0000000000..d810cf7255 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -0,0 +1,169 @@ +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 ZX16 : ZX48 + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX16(ZXSpectrum spectrum, Z80A cpu, byte[] file) + : base(spectrum, cpu, file) + { + + } + + #endregion + + + #region Memory + + /* 48K Spectrum has NO memory paging + * + * + | Bank 0 | + | | + | | + | screen | + 0x4000 +--------+ + | ROM 0 | + | | + | | + | | + 0x0000 +--------+ + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + // paging logic goes here + + if (divisor > 1) + { + // memory does not exist + return 0xff; + } + + var bank = Memory[divisor]; + var index = addr % 0x4000; + return bank[index]; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + // paging logic goes here + + if (divisor > 1) + { + // memory does not exist + return; + } + + var bank = Memory[divisor]; + var index = addr % 0x4000; + bank[index] = value; + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + var data = ReadBus(addr); + if ((addr & 0xC000) == 0x4000) + { + // addr is in RAM not ROM - apply memory contention if neccessary + if (addr >= 0x8000) + { + data = 0xFF; + } + else + { + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + } + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + if (addr < 0x4000) + { + // Do nothing - we cannot write to ROM + return; + } + else if (addr >= 0x8000) + { + // memory does not exist + return; + } + else if (addr < 0x8000) + { + // possible contended RAM + var delay = GetContentionValue(CurrentFrameCycle); + CPU.TotalExecutedCycles += delay; + } + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = RAM1; + else + Memory.Add(1, RAM1); + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // for 16/48k machines only ROM0 is used (no paging) + RomData.RomBytes?.CopyTo(ROM0, 0); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index ac08f1bd3a..036247f341 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -36,6 +36,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (SyncSettings.MachineType) { + case MachineType.ZXSpectrum16: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + break; case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); @@ -132,6 +136,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // setup the emulated model based on the MachineType switch (machineType) { + case MachineType.ZXSpectrum16: + _machine = new ZX16(this, _cpu, file); + var _systemRom16 = GetFirmware(0x4000, "48ROM"); + var romData16 = RomData.InitROM(machineType, _systemRom16); + _machine.InitROM(romData16); + break; case MachineType.ZXSpectrum48: _machine = new ZX48(this, _cpu, file); var _systemRom = GetFirmware(0x4000, "48ROM"); From 0cd8af59745910f6e1ac40b451226488aa00f986 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 17:24:30 +0000 Subject: [PATCH 029/105] SyncSettings option for widescreen mode (remove top and bottom borders) --- .../Machine/SpectrumBase.Screen.cs | 25 ++++++++++++++++--- .../Machine/ZXSpectrum128K/ZX128.Screen.cs | 12 --------- .../Machine/ZXSpectrum128K/ZX128.cs | 6 ++--- .../Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs | 4 +-- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 6 ++--- .../Machine/ZXSpectrum16K/ZX16.cs | 4 +-- .../Machine/ZXSpectrum48K/ZX48.cs | 4 +-- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 3 +-- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 11 ++++---- 9 files changed, 40 insertions(+), 35 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 69bdc62382..65df1938cc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -151,7 +151,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The number of border pixels to the right of the display /// protected int BorderRightPixels = 48; - + /// /// The total width of the screen in pixels /// @@ -765,8 +765,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Initialises the screen configuration calculations /// - public virtual void InitScreenConfig() + public virtual void InitScreenConfig(ZXSpectrum.BorderType border_type) { + switch (border_type) + { + case ZXSpectrum.BorderType.Full: + BorderTopLines = 48; + BorderBottomLines = 48; + NonVisibleBorderTopLines = 8; + NonVisibleBorderBottomLines = 8; + break; + + case ZXSpectrum.BorderType.Widescreen: + BorderTopLines = 0; + BorderBottomLines = 0; + NonVisibleBorderTopLines = 8 + 48; + NonVisibleBorderBottomLines = 8 + 48; + break; + } + ScreenLines = BorderTopLines + DisplayLines + BorderBottomLines; FirstDisplayLine = VerticalSyncLines + NonVisibleBorderTopLines + BorderTopLines; LastDisplayLine = FirstDisplayLine + DisplayLines - 1; @@ -907,11 +924,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { get { return UlaFrameCycleCount; } } - /* + /* public int VsyncNumerator => NullVideo.DefaultVsyncNum; public int VsyncDenominator => NullVideo.DefaultVsyncDen; */ - public int[] GetVideoBuffer() + public int[] GetVideoBuffer() { /* switch(Spectrum.SyncSettings.BorderType) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs index 541a370341..00b952614b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs @@ -8,17 +8,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZX128 : SpectrumBase { - public override 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; - } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index b40df6e8a9..9a8dfbc55b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128(ZXSpectrum spectrum, Z80A cpu, byte[] file) + public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) { Spectrum = spectrum; CPU = cpu; @@ -30,9 +30,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ReInitMemory(); //DisplayLineTime = 132; - VsyncNumerator = 3546900; + //VsyncNumerator = 3546900 * 2; - InitScreenConfig(); + InitScreenConfig(borderType); InitScreen(); ResetULACycle(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs index faaba1791f..b88bcb3e58 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs @@ -20,8 +20,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, byte[] file) - : base(spectrum, cpu, file) + public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) + : base(spectrum, cpu, borderType, file) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index cd5e8f81b2..31f71db4c0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, byte[] file) + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) { Spectrum = spectrum; CPU = cpu; @@ -30,9 +30,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ReInitMemory(); //DisplayLineTime = 132; - VsyncNumerator = 3546900; + //VsyncNumerator = 3546900; - InitScreenConfig(); + InitScreenConfig(borderType); InitScreen(); ResetULACycle(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index d810cf7255..5fc4d6badd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -16,8 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX16(ZXSpectrum spectrum, Z80A cpu, byte[] file) - : base(spectrum, cpu, file) + public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) + : base(spectrum, cpu, borderType, file) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 4979071d58..81e1b07258 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -16,14 +16,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX48(ZXSpectrum spectrum, Z80A cpu, byte[] file) + public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) { Spectrum = spectrum; CPU = cpu; ReInitMemory(); - InitScreenConfig(); + InitScreenConfig(borderType); InitScreen(); ResetULACycle(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index f68aa408e4..3d12898e0e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -104,8 +104,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public enum BorderType { Full, - Medium, - Small + Widescreen, } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 036247f341..5fff81c5be 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -137,34 +137,35 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (machineType) { case MachineType.ZXSpectrum16: - _machine = new ZX16(this, _cpu, file); + _machine = new ZX16(this, _cpu, borderType, file); var _systemRom16 = GetFirmware(0x4000, "48ROM"); var romData16 = RomData.InitROM(machineType, _systemRom16); _machine.InitROM(romData16); break; case MachineType.ZXSpectrum48: - _machine = new ZX48(this, _cpu, file); + _machine = new ZX48(this, _cpu, borderType, file); var _systemRom = GetFirmware(0x4000, "48ROM"); var romData = RomData.InitROM(machineType, _systemRom); _machine.InitROM(romData); break; case MachineType.ZXSpectrum128: - _machine = new ZX128(this, _cpu, file); + _machine = new ZX128(this, _cpu, borderType, file); var _systemRom128 = GetFirmware(0x8000, "128ROM"); var romData128 = RomData.InitROM(machineType, _systemRom128); _machine.InitROM(romData128); break; case MachineType.ZXSpectrum128Plus2: - _machine = new ZX128Plus2(this, _cpu, file); + _machine = new ZX128Plus2(this, _cpu, borderType, file); var _systemRomP2 = GetFirmware(0x8000, "PLUS2ROM"); var romDataP2 = RomData.InitROM(machineType, _systemRomP2); _machine.InitROM(romDataP2); break; case MachineType.ZXSpectrum128Plus3: - _machine = new ZX128Plus3(this, _cpu, file); + _machine = new ZX128Plus3(this, _cpu, borderType, file); var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); var romDataP3 = RomData.InitROM(machineType, _systemRomP3); _machine.InitROM(romDataP3); + System.Windows.Forms.MessageBox.Show("+3 is not working at all yet :/"); break; } } From b6ddf03c9666131def1eb084eb9c78e6d3e4946d Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 7 Dec 2017 17:34:02 +0000 Subject: [PATCH 030/105] Some comments --- .../Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 3d12898e0e..f64d476d6d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -103,7 +103,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public enum BorderType { + /// + /// How it was originally back in the day + /// Full, + + /// + /// Top and bottom border removed so that the result is *almost* 16:9 + /// Widescreen, } From e155bb05fce0ca50c1b553cb3e21fd21f1cb839a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 09:05:12 +0000 Subject: [PATCH 031/105] Embedded ZX Roms (allowed for distribution from AMSTRAD) --- BizHawk.Emulation.Common/Database/Database.cs | 1 + .../Database/FirmwareDatabase.cs | 2 + .../BizHawk.Emulation.Cores.csproj | 4 ++ .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 2 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 1 + .../Computers/SinclairSpectrum/ZXSpectrum.cs | 30 ++++++++++++- .../Properties/Resources.Designer.cs | 40 ++++++++++++++++++ .../Properties/Resources.resx | 12 ++++++ BizHawk.Emulation.Cores/Resources/128.ROM.gz | Bin 0 -> 24229 bytes BizHawk.Emulation.Cores/Resources/48.ROM.gz | Bin 0 -> 12246 bytes .../Resources/plus2.rom.gz | Bin 0 -> 24158 bytes .../Resources/plus3.rom.gz | Bin 0 -> 38413 bytes 12 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Resources/128.ROM.gz create mode 100644 BizHawk.Emulation.Cores/Resources/48.ROM.gz create mode 100644 BizHawk.Emulation.Cores/Resources/plus2.rom.gz create mode 100644 BizHawk.Emulation.Cores/Resources/plus3.rom.gz diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index d2d2bfccee..9364aac02d 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -308,6 +308,7 @@ namespace BizHawk.Emulation.Common break; case ".TAP": + byte[] head = File.ReadAllBytes(fileName).Take(8).ToArray(); if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE")) game.System = "C64"; diff --git a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs index 7755353463..ba32833137 100644 --- a/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs +++ b/BizHawk.Emulation.Common/Database/FirmwareDatabase.cs @@ -51,10 +51,12 @@ namespace BizHawk.Emulation.Common FirmwareAndOption("D3B78C3DBAC55F5199F33F3FE0036439811F7FB3", 16384, "C64", "Drive1541II", "drive-1541ii.bin", "1541-II Disk Drive Rom"); // ZX Spectrum + /* These are now shipped with bizhawk FirmwareAndOption("5EA7C2B824672E914525D1D5C419D71B84A426A2", 16384, "ZXSpectrum", "48ROM", "48.ROM", "Spectrum 48K ROM"); FirmwareAndOption("16375D42EA109B47EDDED7A16028DE7FDB3013A1", 32768, "ZXSpectrum", "128ROM", "128.ROM", "Spectrum 128K ROM"); FirmwareAndOption("8CAFB292AF58617907B9E6B9093D3588A75849B8", 32768, "ZXSpectrum", "PLUS2ROM", "PLUS2.ROM", "Spectrum 128K +2 ROM"); FirmwareAndOption("929BF1A5E5687EBD8D7245F9B513A596C0EC21A4", 65536, "ZXSpectrum", "PLUS3ROM", "PLUS3.ROM", "Spectrum 128K +3 ROM"); + */ // 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 diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 244c3ae233..638bf87f74 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1399,8 +1399,12 @@ + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs index 8b10763146..b7a1a07d82 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public string SystemId => "ZXSpectrum"; - public bool DeterministicEmulation => true; + public bool DeterministicEmulation => false; public void ResetCounters() { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index f64d476d6d..ba6a944346 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -116,6 +116,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The speed at which the tape is loaded + /// NOT IN USE YET /// public enum TapeLoadSpeed { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 5fff81c5be..75bb2fcca0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -1,6 +1,8 @@ -using BizHawk.Emulation.Common; +using BizHawk.Common; +using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components; using BizHawk.Emulation.Cores.Components.Z80A; +using BizHawk.Emulation.Cores.Properties; using System; using System.Collections.Generic; using System.IO; @@ -121,6 +123,32 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return rom; } + // Amstrad licensed ROMs are free to distribute and shipped with BizHawk + byte[] embeddedRom = new byte[length]; + bool embeddedFound = true; + switch (names.FirstOrDefault()) + { + case "48ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_48_ROM)); + break; + case "128ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_128_ROM)); + break; + case "PLUS2ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus2_rom)); + break; + case "PLUS3ROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus3_rom)); + break; + default: + embeddedFound = false; + break; + } + + if (embeddedFound) + return embeddedRom; + + // Embedded ROM not found, maybe this is a peripheral ROM? var result = names.Select(n => CoreComm.CoreFileProvider.GetFirmware("ZXSpectrum", n, false)).FirstOrDefault(b => b != null && b.Length == length); if (result == null) { diff --git a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs index 5a3bbcce9a..312ea05d92 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs +++ b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs @@ -89,5 +89,45 @@ namespace BizHawk.Emulation.Cores.Properties { return ((byte[])(obj)); } } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_128_ROM { + get { + object obj = ResourceManager.GetObject("ZX_128_ROM", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_48_ROM { + get { + object obj = ResourceManager.GetObject("ZX_48_ROM", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_plus2_rom { + get { + object obj = ResourceManager.GetObject("ZX_plus2_rom", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] ZX_plus3_rom { + get { + object obj = ResourceManager.GetObject("ZX_plus3_rom", resourceCulture); + return ((byte[])(obj)); + } + } } } diff --git a/BizHawk.Emulation.Cores/Properties/Resources.resx b/BizHawk.Emulation.Cores/Properties/Resources.resx index bdb07b9a9f..4699ab819d 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.resx +++ b/BizHawk.Emulation.Cores/Properties/Resources.resx @@ -127,4 +127,16 @@ ..\Resources\sgb-cart-present.spc.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\128.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\48.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\plus2.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\plus3.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Resources/128.ROM.gz b/BizHawk.Emulation.Cores/Resources/128.ROM.gz new file mode 100644 index 0000000000000000000000000000000000000000..697dc6e8723129a24edd92a430dbff2b9cec8690 GIT binary patch literal 24229 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 literal 0 HcmV?d00001 From 7532b4be8c3b1507f7843003b0744d491da3ad9a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 09:09:05 +0000 Subject: [PATCH 032/105] Updated readme with progress info --- .../Computers/SinclairSpectrum/readme.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index bbced33297..ce9e736487 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -4,19 +4,22 @@ At this moment this is still *very* experimental and needs a lot more work. ### Implemented and sorta working * IEmulator -* ZX Spectrum 48k model +* ZX Spectrum 48k, 128k & Plus2 models * ULA video output (implementing IVideoProvider) * ULA Mode 1 VBLANK interrupt generation * IM2 Interrupts and DataBus implementation (thanks Aloysha) * Beeper/Buzzer output (implementing ISoundProvider) +* AY-3-8912 sound chip implementation * Keyboard input (implementing IInputPollable) * Kempston joystick (mapped to J1 currently) * Tape device that will load spectrum games in realtime (*.tzx and *.tap) * IStatable (although this is not currently working/implemented properly during tape load operations) +* ISettable core settings * IMemoryDomains (I think) -### Some progress -* ISettable - There are some Settings and SyncSettings instantiated, although they are not really used and I haven't yet figured out how to wire these up to the front-end yet +### Work in progress +* Exact emulator timings +* Floating memory bus emulation ### Not working * IDebuggable From 2b988954eea486964169acfb0aa1faab5d8123f5 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 12:54:48 +0000 Subject: [PATCH 033/105] Started implementing new ULA implemetation (far more performant) --- .../BizHawk.Emulation.Cores.csproj | 2 + .../SinclairSpectrum/Machine/SpectrumBase.cs | 17 +- .../SinclairSpectrum/Machine/ULABase.cs | 571 ++++++++++++++++++ .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 10 + .../Machine/ZXSpectrum48K/ZX48.Port.cs | 10 +- .../Machine/ZXSpectrum48K/ZX48.ULA.cs | 184 ++++++ .../Machine/ZXSpectrum48K/ZX48.cs | 4 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- .../Computers/SinclairSpectrum/readme.md | 3 + 9 files changed, 795 insertions(+), 8 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 638bf87f74..da148d52e1 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -271,7 +271,9 @@ + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 8de871a5e1..220c6c1e38 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -36,6 +36,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public RomData RomData { get; set; } + /// + /// The emulated ULA device + /// + public ULABase ULADevice { get; set; } + /// /// The spectrum buzzer/beeper /// @@ -119,18 +124,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var curr = CPU.TotalExecutedCycles; - while (CurrentFrameCycle <= UlaFrameCycleCount) + while (CurrentFrameCycle <= ULADevice.FrameLength) // UlaFrameCycleCount) { // check for interrupt - CheckForInterrupt(CurrentFrameCycle); + ULADevice.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; + */ // update AY if (AYDevice != null) @@ -141,6 +148,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow; LastRenderedULACycle = OverFlow; + ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); + BuzzerDevice.EndFrame(); TapeDevice.CPUFrameCompleted(); @@ -149,7 +158,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // setup for next frame OverFlow = CurrentFrameCycle % UlaFrameCycleCount; - ResetInterrupt(); + ULADevice.ResetInterrupt(); FrameCompleted = true; if (FrameCount % FlashToggleFrames == 0) @@ -157,7 +166,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _flashPhase = !_flashPhase; } - RenderScreen(0, OverFlow); + //RenderScreen(0, OverFlow); } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs new file mode 100644 index 0000000000..015980b038 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -0,0 +1,571 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Another ULA implementation (maybe it will be more performant & accurate) + /// + public abstract class ULABase : IVideoProvider + { + #region ULA Configuration + + #region General + + /// + /// Length of the frame in T-States + /// + public int FrameLength; + + /// + /// Emulated clock speed + /// + public int ClockSpeed; + + /// + /// Whether machine is late or early timing model + /// + public bool LateTiming; //currently not implemented + + /// + /// The current cycle within the current frame + /// + public int CurrentTStateInFrame; + + + protected SpectrumBase _machine; + + #endregion + + #region Palettes + + /// + /// The standard ULA palette + /// + 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 Interrupts + + /// + /// The number of T-States that the INT pin is simulated to be held low + /// + public int InterruptPeriod; + + #endregion + + #region Contention + + /// + /// T-State at which to start applying contention + /// + protected int contentionStartPeriod; + /// + /// T-State at which to end applying contention + /// + protected int contentionEndPeriod; + /// + /// T-State memory contention delay mapping + /// + public byte[] contentionTable; + + #endregion + + #region Screen Rendering + + /// + /// Video output buffer + /// + public int[] ScreenBuffer; + /// + /// Display memory + /// + protected byte[] screen; + /// + /// Attribute memory lookup (mapped 1:1 to screen for convenience) + /// + protected short[] attr; + /// + /// T-State display mapping + /// + protected short[] tstateToDisp; + /// + /// Table that stores T-State to screen/attribute address values + /// + protected short[] floatingBusTable; + /// + /// Cycle at which the last render update took place + /// + protected int lastTState; + /// + /// T-States elapsed since last render update + /// + protected int elapsedTStates; + /// + /// T-State of top left raster pixel + /// + protected int actualULAStart; + /// + /// Offset into display memory based on current T-State + /// + protected int screenByteCtr; + /// + /// Offset into current pixel of rasterizer + /// + protected int ULAByteCtr; + /// + /// The current border colour + /// + public int borderColour; + /// + /// Signs whether the colour flash is ON or OFF + /// + protected bool flashOn = false; + + /// + /// Last 8-bit bitmap read from display memory + /// (Floating bus implementation) + /// + protected int lastPixelValue; + /// + /// Last 8-bit attr val read from attribute memory + /// (Floating bus implementation) + /// + protected int lastAttrValue; + /// + /// Last 8-bit bitmap read from display memory+1 + /// (Floating bus implementation) + /// + protected int lastPixelValuePlusOne; + /// + /// Last 8-bit attr val read from attribute memory+1 + /// (Floating bus implementation) + /// + protected int lastAttrValuePlusOne; + + /// + /// Used to create the non-border display area + /// + protected int TtateAtLeft; + protected int TstateWidth; + protected int TstateAtTop; + protected int TstateHeight; + protected int TstateAtRight; + protected int TstateAtBottom; + + /// + /// Total T-States in one scanline + /// + protected int TstatesPerScanline; + /// + /// Total pixels in one scanline + /// + protected int ScanLineWidth; + /// + /// Total chars in one PRINT row + /// + protected int CharRows; + /// + /// Total chars in one PRINT column + /// + protected int CharCols; + /// + /// Total pixels in one display row + /// + protected int ScreenWidth; + /// + /// Total pixels in one display column + /// + protected int ScreenHeight; + /// + /// Total pixels in top border + /// + protected int BorderTopHeight; + /// + /// Total pixels in bottom border + /// + protected int BorderBottomHeight; + /// + /// Total pixels in left border width + /// + protected int BorderLeftWidth; + /// + /// Total pixels in right border width + /// + protected int BorderRightWidth; + /// + /// Memory address of display start + /// + protected int DisplayStart; + /// + /// Total number of bytes of display memory + /// + protected int DisplayLength; + /// + /// Memory address of attribute start + /// + protected int AttributeStart; + /// + /// Total number of bytes of attribute memory + /// + protected int AttributeLength; + + #endregion + + #region Interrupt + + /// + /// The longest instruction cycle count + /// + protected const int LONGEST_OP_CYCLES = 23; + + /// + /// Signs that an interrupt has been raised in this frame. + /// + protected bool InterruptRaised; + + /// + /// Signs that the interrupt signal has been revoked + /// + protected bool InterruptRevoked; + + /// + /// Resets the interrupt - this should happen every frame in order to raise + /// the VBLANK interrupt in the proceding frame + /// + public virtual void ResetInterrupt() + { + InterruptRaised = false; + InterruptRevoked = false; + } + + /// + /// Generates an interrupt in the current phase if needed + /// + /// + public virtual void CheckForInterrupt(int currentCycle) + { + if (InterruptRevoked) + { + // interrupt has already been handled + return; + } + + if (currentCycle < InterruptPeriod) + { + // interrupt does not need to be raised yet + return; + } + + if (currentCycle > InterruptPeriod + 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; + _machine.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; + _machine.CPU.FlagI = true; + //FrameCount++; + ULAUpdateStart(); + + } + + #endregion + + #endregion + + #region Construction & Initialisation + + public ULABase(SpectrumBase machine) + { + _machine = machine; + } + + public virtual void Init() + { + + } + + #endregion + + #region Methods + + /// + /// Resets the ULA chip + /// + public abstract void Reset(); + + /// + /// Builds the contention table for the emulated model + /// + public abstract void BuildContentionTable(); + + /// + /// Returns true if the given memory address should be contended + /// + /// + /// + public abstract bool IsContended(int addr); + + /// + /// Contends the machine for a given address + /// + /// + public virtual void Contend(ushort addr) + { + if (IsContended(addr) && !(_machine is ZX128Plus3)) + { + _machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame]; + } + } + + public virtual void Contend(int addr, int time, int count) + { + if (IsContended(addr) && !(_machine is ZX128Plus3)) + { + for (int f = 0; f < count; f++) + { + _machine.CPU.TotalExecutedCycles += contentionTable[CurrentTStateInFrame] + time; + } + } + else + _machine.CPU.TotalExecutedCycles += count * time; + } + + /// + /// Resets render state once interrupt is generated + /// + public void ULAUpdateStart() + { + ULAByteCtr = 0; + screenByteCtr = DisplayStart; + lastTState = actualULAStart; + } + + /// + /// Builds the T-State to attribute map used with the floating bus + /// + public void BuildAttributeMap() + { + int start = DisplayStart; + + for (int f = 0; f < DisplayLength; f++, start++) + { + int addrH = start >> 8; //div by 256 + int addrL = start % 256; + + int pixelY = (addrH & 0x07); + pixelY |= (addrL & (0xE0)) >> 2; + pixelY |= (addrH & (0x18)) << 3; + + int attrIndex_Y = AttributeStart + ((pixelY >> 3) << 5);// pixel/8 * 32 + + addrL = start % 256; + int pixelX = addrL & (0x1F); + + attr[f] = (short)(attrIndex_Y + pixelX); + } + } + + public virtual void UpdateScreenBuffer(int _tstates) + { + if (_tstates < actualULAStart) + { + return; + } + else if (_tstates >= FrameLength) + { + _tstates = FrameLength - 1; + } + + //the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna + elapsedTStates = (_tstates + 1 - lastTState); + + //It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state. + + int numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0); + + int pixelData; + int pixel2Data = 0xff; + int attrData; + int attr2Data; + int bright; + int ink; + int paper; + int flash; + + for (int i = 0; i < numBytes; i++) + { + if (tstateToDisp[lastTState] > 1) + { + screenByteCtr = tstateToDisp[lastTState] - 16384; //adjust for actual screen offset + + pixelData = _machine.FetchScreenMemory((ushort)screenByteCtr); //screen[screenByteCtr]; + attrData = _machine.FetchScreenMemory((ushort)(attr[screenByteCtr] - 16384)); //screen[attr[screenByteCtr] - 16384]; + + lastPixelValue = pixelData; + lastAttrValue = attrData; + + bright = (attrData & 0x40) >> 3; + flash = (attrData & 0x80) >> 7; + ink = (attrData & 0x07); + paper = ((attrData >> 3) & 0x7); + int paletteInk = ULAPalette[ink + bright]; + int palettePaper = ULAPalette[paper + bright]; + + if (flashOn && (flash != 0)) //swap paper and ink when flash is on + { + int temp = paletteInk; + paletteInk = palettePaper; + palettePaper = temp; + } + + for (int a = 0; a < 8; ++a) + { + if ((pixelData & 0x80) != 0) + { + ScreenBuffer[ULAByteCtr++] = paletteInk; + lastAttrValue = ink; + //pixelIsPaper = false; + } + else + { + ScreenBuffer[ULAByteCtr++] = palettePaper; + lastAttrValue = paper; + } + pixelData <<= 1; + } + } + else if (tstateToDisp[lastTState] == 1) + { + int bor = ULAPalette[borderColour]; + + for (int g = 0; g < 8; g++) + ScreenBuffer[ULAByteCtr++] = bor; + } + lastTState += 4; + } + } + + #endregion + + #region IVideoProvider + + private int _virtualWidth; + private int _virtualHeight; + private int _bufferWidth; + private int _bufferHeight; + + public int BackgroundColor + { + get { return ULAPalette[borderColour]; } + } + + public int VirtualWidth + { + get { return _virtualWidth; } + set { _virtualWidth = value; } + } + + public int VirtualHeight + { + get { return _virtualHeight; } + set { _virtualHeight = value; } + } + + public int BufferWidth + { + get { return _bufferWidth; } + set { _bufferWidth = value; } + } + + public int BufferHeight + { + get { return _bufferHeight; } + set { _bufferHeight = value; } + } + + public int VsyncNumerator + { + get { return ClockSpeed; } + set { } + } + + public int VsyncDenominator + { + get { return FrameLength; } + } + + public int[] GetVideoBuffer() + { + return ScreenBuffer; + } + + #endregion + + + #region Attribution + + /* + * Based on code from ArjunNair's Zero emulator (MIT Licensed) + * https://github.com/ArjunNair/Zero-Emulator + + The MIT License (MIT) + + Copyright (c) 2009 Arjun Nair + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 3cf3b09390..8ec377b16a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -63,6 +63,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var bank = Memory[divisor]; var index = addr % 0x4000; bank[index] = value; + + // update ULA screen buffer if necessary + if ((addr & 49152) == 16384) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } /// @@ -96,13 +100,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Do nothing - we cannot write to ROM return; } + /* else if (addr < 0xC000) { // possible contended RAM var delay = GetContentionValue(CurrentFrameCycle); CPU.TotalExecutedCycles += delay; } + */ + // apply contention if necessry + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + WriteBus(addr, value); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 2a0080e1c8..aa92a35464 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -132,13 +132,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x01) == 0; - ContendPort(port); + ULADevice.Contend(port); // Only even addresses address the ULA if (lowBitReset) { // store the last OUT byte LastULAOutByte = value; + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; /* Bit 7 6 5 4 3 2 1 0 @@ -148,13 +149,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ // Border - LSB 3 bits hold the border colour - BorderColour = value & BORDER_BIT; + if (ULADevice.borderColour != (value & BORDER_BIT)) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + + ULADevice.borderColour = value & BORDER_BIT; // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); // Tape TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + + CPU.TotalExecutedCycles += 3; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs new file mode 100644 index 0000000000..d6ca8b4565 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -0,0 +1,184 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + class ULA48 : ULABase + { + #region Construction + + public ULA48(SpectrumBase machine) + : base(machine) + { + InterruptPeriod = 32; + FrameLength = 69888; + ClockSpeed = 3500000; + + contentionTable = new byte[70930]; + floatingBusTable = new short[70930]; + for (int f = 0; f < 70930; f++) + floatingBusTable[f] = -1; + + CharRows = 24; + CharCols = 32; + ScreenWidth = 256; + ScreenHeight = 192; + BorderTopHeight = 48; + BorderBottomHeight = 56; + BorderLeftWidth = 48; + BorderRightWidth = 48; + DisplayStart = 16384; + DisplayLength = 6144; + AttributeStart = 22528; + AttributeLength = 768; + borderColour = 7; + ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth; + + TstatesPerScanline = 224; + TstateAtTop = BorderTopHeight * TstatesPerScanline; + TstateAtBottom = BorderBottomHeight * TstatesPerScanline; + tstateToDisp = new short[FrameLength]; + + ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border + + ScanLineWidth * ScreenHeight //border + main + border of 192 lines + + ScanLineWidth * BorderBottomHeight]; //56 lines of border + + attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped + + BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; + BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + + + + Reset(); + } + + #endregion + + #region Misc Operations + + public override void Reset() + { + contentionStartPeriod = 14335; // + LateTiming; + contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); + screen = _machine.Memory[1]; + screenByteCtr = DisplayStart; + ULAByteCtr = 0; + actualULAStart = 14340 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; + lastTState = actualULAStart; + BuildAttributeMap(); + BuildContentionTable(); + } + + #endregion + + #region Contention Methods + + public override bool IsContended(int addr) + { + if ((addr & 49152) == 16384) + return true; + return false; + } + + public override void BuildContentionTable() + { + int t = contentionStartPeriod; + while (t < contentionEndPeriod) + { + //for 128 t-states + for (int i = 0; i < 128; i += 8) + { + contentionTable[t++] = 6; + contentionTable[t++] = 5; + contentionTable[t++] = 4; + contentionTable[t++] = 3; + contentionTable[t++] = 2; + contentionTable[t++] = 1; + contentionTable[t++] = 0; + contentionTable[t++] = 0; + } + t += (TstatesPerScanline - 128); //24 tstates of right border + left border + 48 tstates of retrace + } + + //build top half of tstateToDisp table + //vertical retrace period + for (t = 0; t < actualULAStart; t++) + tstateToDisp[t] = 0; + + //next 48 are actual border + while (t < actualULAStart + (TstateAtTop)) + { + //border(24t) + screen (128t) + border(24t) = 176 tstates + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + //horizontal retrace + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + + //build middle half of display + int _x = 0; + int _y = 0; + int scrval = 2; + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline)) + { + //left border + for (int g = 0; g < 24; g++) + tstateToDisp[t++] = 1; + + //screen + for (int g = 24; g < 24 + 128; g++) + { + //Map screenaddr to tstate + if (g % 4 == 0) + { + scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2)); + _x += 8; + } + tstateToDisp[t++] = (short)scrval; + } + _y++; + + //right border + for (int g = 24 + 128; g < 24 + 128 + 24; g++) + tstateToDisp[t++] = 1; + + //horizontal retrace + for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 48; g++) + tstateToDisp[t++] = 0; + } + + int h = contentionStartPeriod + 3; + while (h < contentionEndPeriod + 3) + { + for (int j = 0; j < 128; j += 8) + { + floatingBusTable[h] = tstateToDisp[h + 2]; //screen address + floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; //attr address + floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; //screen address + 1 + floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; //attr address + 1 + h += 8; + } + h += TstatesPerScanline - 128; + } + + //build bottom border + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom)) + { + //border(24t) + screen (128t) + border(24t) = 176 tstates + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + //horizontal retrace + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + } + + #endregion + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 81e1b07258..42dbbeaf49 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -22,7 +22,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CPU = cpu; ReInitMemory(); - + + ULADevice = new ULA48(this); + InitScreenConfig(borderType); InitScreen(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 75bb2fcca0..ac8fd29229 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Register(_tracer); ser.Register(_cpu); - ser.Register(_machine); + ser.Register(_machine.ULADevice); SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice); if (_machine.AYDevice != null) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index ce9e736487..c0147c1d3a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -23,9 +23,12 @@ At this moment this is still *very* experimental and needs a lot more work. ### Not working * IDebuggable +* ZX Spectrum Plus3 emulation * Default keyboard keymappings (you have to configure yourself in the core controller settings) * Manual tape device control (at the moment the tape device detects when the spectrum goes into 'loadbytes' mode and auto-plays the tape. This is not ideal and manual control should be implemented so the user can start/stop manually, return to zero etc..) * Only standard spectrum tape blocks currently work. Any fancy SpeedLock encoded (and similar) blocks do not ### Known bugs * Audible 'popping' from the emulated buzzer after a load state operation + +-Asnivor From a5b50fe5471e59d656421b9df0baaf3b321ecf42 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 14:08:00 +0000 Subject: [PATCH 034/105] 48k - new ULA implementation - 80% faster --- .../Machine/SpectrumBase.Memory.cs | 13 +-- .../Machine/SpectrumBase.Port.cs | 24 +---- .../Machine/SpectrumBase.Screen.cs | 7 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 78 +++------------- .../SinclairSpectrum/Machine/ULABase.cs | 90 +++++++++++++------ .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 27 +++--- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 18 ++-- .../Machine/ZXSpectrum128K/ZX128.cs | 12 +-- .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 31 +++---- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 9 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 12 +-- .../Machine/ZXSpectrum16K/ZX16.cs | 39 +++----- .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 28 ++---- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 5 +- .../Machine/ZXSpectrum48K/ZX48.cs | 7 +- 15 files changed, 141 insertions(+), 259 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index d58ef1f6eb..81ee1c819f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -106,17 +106,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Helper function to refresh memory array (probably not the best way to do things) /// public abstract void ReInitMemory(); - - /// - /// 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 - /// - /// - /// - public virtual byte GetContentionValue(int cycle) - { - var val = _renderingCycleTable[cycle % UlaFrameCycleCount].ContentionDelay; - return val; - } + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index c590901dac..a508f9fa8e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -30,28 +30,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// public abstract void WritePort(ushort port, byte value); - - /// - /// Apply I/O contention if necessary - /// - /// - public virtual void ContendPort(ushort port) - { - var lowBit = (port & 0x0001) != 0; - var ulaHigh = (port & 0xc000) == 0x4000; - var cfc = CurrentFrameCycle; - if (cfc < 1) - cfc = 1; - - if (ulaHigh) - { - CPU.TotalExecutedCycles += GetContentionValue(cfc - 1); - } - else - { - if (!lowBit) - CPU.TotalExecutedCycles += GetContentionValue(cfc); - } - } + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs index 65df1938cc..1d3d1b0895 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs @@ -27,6 +27,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum copies or substantial portions of the Software. */ + /* + /// /// The abstract class that all emulated models will inherit from /// * Screen * @@ -927,7 +929,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /* public int VsyncNumerator => NullVideo.DefaultVsyncNum; public int VsyncDenominator => NullVideo.DefaultVsyncDen; - */ + public int[] GetVideoBuffer() { /* @@ -965,7 +967,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case ZXSpectrum.BorderType.Medium: break; } - */ + return _frameBuffer; } @@ -973,4 +975,5 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion } + */ } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 220c6c1e38..a999933720 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -120,10 +120,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.StartFrame(); if (AYDevice != null) AYDevice.StartFrame(); + PollInput(); - - var curr = CPU.TotalExecutedCycles; - + while (CurrentFrameCycle <= ULADevice.FrameLength) // UlaFrameCycleCount) { // check for interrupt @@ -132,13 +131,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // 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; - */ - // update AY if (AYDevice != null) AYDevice.UpdateSound(CurrentFrameCycle); @@ -146,9 +138,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // we have reached the end of a frame LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow; - LastRenderedULACycle = OverFlow; - ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); + // paint the buffer if needed + if (ULADevice.needsPaint) + ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); BuzzerDevice.EndFrame(); @@ -157,16 +150,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum FrameCount++; // setup for next frame - OverFlow = CurrentFrameCycle % UlaFrameCycleCount; ULADevice.ResetInterrupt(); FrameCompleted = true; - - if (FrameCount % FlashToggleFrames == 0) - { - _flashPhase = !_flashPhase; - } - - //RenderScreen(0, OverFlow); } /// @@ -174,8 +159,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual void HardReset() { - ResetBorder(); - ResetInterrupt(); + //ResetBorder(); + ULADevice.ResetInterrupt(); } /// @@ -183,8 +168,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual void SoftReset() { - ResetBorder(); - ResetInterrupt(); + //ResetBorder(); + ULADevice.ResetInterrupt(); } public void SyncState(Serializer ser) @@ -195,50 +180,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("FrameCount", ref FrameCount); ser.Sync("_frameCycles", ref _frameCycles); ser.Sync("LastFrameStartCPUTick", ref LastFrameStartCPUTick); - ser.Sync("LastULAOutByte", ref LastULAOutByte); - ser.Sync("_flashPhase", ref _flashPhase); - ser.Sync("_frameBuffer", ref _frameBuffer, false); - ser.Sync("_flashOffColors", ref _flashOffColors, false); - ser.Sync("_flashOnColors", ref _flashOnColors, false); - ser.Sync("InterruptCycle", ref InterruptCycle); - ser.Sync("InterruptRaised", ref InterruptRaised); - ser.Sync("InterruptRevoked", ref InterruptRevoked); - ser.Sync("UlaFrameCycleCount", ref UlaFrameCycleCount); - ser.Sync("FirstScreenPixelCycle", ref FirstScreenPixelCycle); - ser.Sync("FirstDisplayPixelCycle", ref FirstDisplayPixelCycle); - ser.Sync("FirstPixelCycleInLine", ref FirstPixelCycleInLine); - ser.Sync("AttributeDataPrefetchTime", ref AttributeDataPrefetchTime); - ser.Sync("PixelDataPrefetchTime", ref PixelDataPrefetchTime); - ser.Sync("ScreenLineTime", ref ScreenLineTime); - ser.Sync("NonVisibleBorderRightTime", ref NonVisibleBorderRightTime); - ser.Sync("BorderRightTime", ref BorderRightTime); - ser.Sync("DisplayLineTime", ref DisplayLineTime); - ser.Sync("BorderLeftTime", ref BorderLeftTime); - ser.Sync("HorizontalBlankingTime", ref HorizontalBlankingTime); - ser.Sync("ScreenWidth", ref ScreenWidth); - ser.Sync("BorderRightPixels", ref BorderRightPixels); - ser.Sync("BorderLeftPixels", ref BorderLeftPixels); - ser.Sync("FirstDisplayLine", ref FirstDisplayLine); - ser.Sync("ScreenLines", ref ScreenLines); - ser.Sync("NonVisibleBorderBottomLines", ref NonVisibleBorderBottomLines); - ser.Sync("BorderBottomLines", ref BorderBottomLines); - ser.Sync("BorderTopLines", ref BorderTopLines); - ser.Sync("NonVisibleBorderTopLines", ref NonVisibleBorderTopLines); - ser.Sync("VerticalSyncLines", ref VerticalSyncLines); - ser.Sync("FlashToggleFrames", ref FlashToggleFrames); - ser.Sync("DisplayLines", ref DisplayLines); - ser.Sync("DisplayWidth", ref DisplayWidth); - ser.Sync("_pixelByte1", ref _pixelByte1); - ser.Sync("_pixelByte2", ref _pixelByte2); - ser.Sync("_attrByte1", ref _attrByte1); - ser.Sync("_attrByte2", ref _attrByte2); - ser.Sync("_xPos", ref _xPos); - ser.Sync("_yPos", ref _yPos); - ser.Sync("DisplayWidth", ref DisplayWidth); - ser.Sync("DisplayWidth", ref DisplayWidth); - ser.Sync("DisplayWidth", ref DisplayWidth); - ser.Sync("DisplayWidth", ref DisplayWidth); - ser.Sync("_borderColour", ref _borderColour); + ser.Sync("LastULAOutByte", ref LastULAOutByte); ser.Sync("ROM0", ref ROM0, false); ser.Sync("ROM1", ref ROM1, false); ser.Sync("ROM2", ref ROM2, false); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 015980b038..9bebc31eac 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -6,11 +6,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// /// Another ULA implementation (maybe it will be more performant & accurate) + /// -edit: it is :) /// public abstract class ULABase : IVideoProvider { - #region ULA Configuration - #region General /// @@ -65,15 +64,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion - #region Interrupts - - /// - /// The number of T-States that the INT pin is simulated to be held low - /// - public int InterruptPeriod; - - #endregion - #region Contention /// @@ -142,6 +132,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// protected bool flashOn = false; + + protected int flashCounter; + public int FlashCounter + { + get { return flashCounter; } + set + { + flashCounter = value; + } + } + + + /// + /// Internal frame counter used for flasher operations + /// + protected int frameCounter = 0; + /// /// Last 8-bit bitmap read from display memory /// (Floating bus implementation) @@ -230,10 +237,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// protected int AttributeLength; + /// + /// Raised when ULA has finished painting the entire screen + /// + public bool needsPaint = false; + #endregion #region Interrupt + /// + /// The number of T-States that the INT pin is simulated to be held low + /// + public int InterruptPeriod; + /// /// The longest instruction cycle count /// @@ -282,9 +299,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // 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; _machine.CPU.FlagI = false; - //CPU.NonMaskableInterruptPending = true; } @@ -295,23 +310,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum 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; _machine.CPU.FlagI = true; - //FrameCount++; - ULAUpdateStart(); + // Signal the start of ULA processing + ULAUpdateStart(); } - #endregion - - #endregion + #endregion #region Construction & Initialisation @@ -320,11 +327,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine = machine; } - public virtual void Init() - { - - } - #endregion #region Methods @@ -379,6 +381,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ULAByteCtr = 0; screenByteCtr = DisplayStart; lastTState = actualULAStart; + needsPaint = true; + + flashCounter++; + + if (flashCounter > 15) + { + flashOn = !flashOn; + flashCounter = 0; + } } /// @@ -406,6 +417,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + /// + /// Updates the screen buffer based on the number of T-States supplied + /// + /// public virtual void UpdateScreenBuffer(int _tstates) { if (_tstates < actualULAStart) @@ -415,6 +430,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else if (_tstates >= FrameLength) { _tstates = FrameLength - 1; + + needsPaint = true; } //the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna @@ -542,6 +559,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + #region IStatable + + public void SyncState(Serializer ser) + { + ser.BeginSection("ULA"); + ser.Sync("ScreenBuffer", ref ScreenBuffer, false); + ser.Sync("FrameLength", ref FrameLength); + ser.Sync("ClockSpeed", ref ClockSpeed); + ser.Sync("LateTiming", ref LateTiming); + ser.Sync("borderColour", ref borderColour); + ser.EndSection(); + } + + #endregion + #region Attribution diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index 5b8ef31e19..f1a63b0b24 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -168,6 +168,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum default: break; } + + // update ULA screen buffer if necessary + if ((addr & 49152) == 16384) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } /// @@ -178,13 +182,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadMemory(ushort addr) { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + var data = ReadBus(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; } @@ -196,17 +197,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WriteMemory(ushort addr, byte value) { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - else if (addr < 0xC000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; WriteBus(addr, value); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 67e667defb..0997bf3947 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -8,8 +8,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZX128 : SpectrumBase { - private int AYTStates = 0; - /// /// Reads a byte of data from a specified port address /// @@ -23,7 +21,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ContendPort((ushort)port); + ULADevice.Contend(port); + CPU.TotalExecutedCycles++; // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) @@ -124,9 +123,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // if unused port the floating memory bus should be returned (still todo) } - - CPU.TotalExecutedCycles += 3; - + return (byte)result; } @@ -170,13 +167,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x01) == 0; - ContendPort(port); + ULADevice.Contend(port); // Only even addresses address the ULA if (lowBitReset) { // store the last OUT byte LastULAOutByte = value; + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; /* Bit 7 6 5 4 3 2 1 0 @@ -186,13 +184,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ // Border - LSB 3 bits hold the border colour - BorderColour = value & BORDER_BIT; + if (ULADevice.borderColour != (value & BORDER_BIT)) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + + ULADevice.borderColour = value & BORDER_BIT; // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); // Tape TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } // Active AY Register diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 9a8dfbc55b..65117acdfe 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -29,19 +29,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - //DisplayLineTime = 132; - //VsyncNumerator = 3546900 * 2; - - InitScreenConfig(borderType); - InitScreen(); - - ResetULACycle(); + ULADevice = new ULA48(this); BuzzerDevice = new Buzzer(this); - BuzzerDevice.Init(44100, UlaFrameCycleCount); + BuzzerDevice.Init(44100, ULADevice.FrameLength); AYDevice = new AY38912(); - AYDevice.Init(44100, UlaFrameCycleCount); + AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index fca4961bd9..d236edc82e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -307,7 +307,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum default: break; } - } + } + + // update ULA screen buffer if necessary + if ((addr & 49152) == 16384) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } /// @@ -318,13 +322,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadMemory(ushort addr) { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + var data = ReadBus(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; } @@ -336,18 +337,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WriteMemory(ushort addr, byte value) { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - else if (addr < 0xC000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } - + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + WriteBus(addr, value); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index e7f2071fca..4a5df60bc8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ContendPort((ushort)port); + ULADevice.Contend(port); // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) @@ -141,7 +141,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x01) == 0; - ContendPort(port); + ULADevice.Contend(port); // Only even addresses address the ULA if (lowBitReset) @@ -157,7 +157,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ // Border - LSB 3 bits hold the border colour - BorderColour = value & BORDER_BIT; + if (ULADevice.borderColour != (value & BORDER_BIT)) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + + ULADevice.borderColour = value & BORDER_BIT; // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 31f71db4c0..5904dc41de 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -29,19 +29,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - //DisplayLineTime = 132; - //VsyncNumerator = 3546900; - - InitScreenConfig(borderType); - InitScreen(); - - ResetULACycle(); + ULADevice = new ULA48(this); BuzzerDevice = new Buzzer(this); - BuzzerDevice.Init(44100, UlaFrameCycleCount); + BuzzerDevice.Init(44100, ULADevice.FrameLength); AYDevice = new AY38912(); - AYDevice.Init(44100, UlaFrameCycleCount); + AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index 5fc4d6badd..b828eb9b48 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -94,20 +94,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadMemory(ushort addr) { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + CPU.TotalExecutedCycles += 3; + var data = ReadBus(addr); - if ((addr & 0xC000) == 0x4000) - { - // addr is in RAM not ROM - apply memory contention if neccessary - if (addr >= 0x8000) - { - data = 0xFF; - } - else - { - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } - } return data; } @@ -119,22 +111,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WriteMemory(ushort addr, byte value) { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - else if (addr >= 0x8000) - { - // memory does not exist - return; - } - else if (addr < 0x8000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + CPU.TotalExecutedCycles += 3; WriteBus(addr, value); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 8ec377b16a..0a1efe104e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -77,13 +77,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadMemory(ushort addr) { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(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; } @@ -95,24 +93,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WriteMemory(ushort addr, byte value) { - if (addr < 0x4000) - { - // Do nothing - we cannot write to ROM - return; - } - /* - else if (addr < 0xC000) - { - // possible contended RAM - var delay = GetContentionValue(CurrentFrameCycle); - CPU.TotalExecutedCycles += delay; - } - */ - - // apply contention if necessry + // apply contention if necessary if (ULADevice.IsContended(addr)) CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; - + WriteBus(addr, value); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index aa92a35464..c5bfd6f4d8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -21,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ContendPort((ushort)port); + ULADevice.Contend(port); // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) @@ -159,8 +159,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Tape TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); - - CPU.TotalExecutedCycles += 3; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 42dbbeaf49..f7a447dc35 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -25,13 +25,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ULADevice = new ULA48(this); - InitScreenConfig(borderType); - InitScreen(); - - ResetULACycle(); - BuzzerDevice = new Buzzer(this); - BuzzerDevice.Init(44100, UlaFrameCycleCount); + BuzzerDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); From 12f5df2b058de04d0f44c4b02151a8d36a445c17 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 14:33:47 +0000 Subject: [PATCH 035/105] Added new ULA implementation for 128k and plus2 --- .../BizHawk.Emulation.Cores.csproj | 3 +- .../Machine/SpectrumBase.Screen.cs | 979 ------------------ .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 +- .../SinclairSpectrum/Machine/ULABase.cs | 2 +- .../Machine/ZXSpectrum128K/ZX128.Screen.cs | 12 - .../Machine/ZXSpectrum128K/ZX128.ULA.cs | 194 ++++ 6 files changed, 197 insertions(+), 995 deletions(-) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index da148d52e1..10ba63cfb3 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -272,6 +272,7 @@ + @@ -288,7 +289,6 @@ - @@ -1390,7 +1390,6 @@ - diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs deleted file mode 100644 index 1d3d1b0895..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Screen.cs +++ /dev/null @@ -1,979 +0,0 @@ -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 -{ - /* - * Much of the SCREEN implementation has been taken from: https://github.com/Dotneteer/spectnetide - * - * MIT License - - Copyright (c) 2017 Istvan Novak - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - */ - - /* - - /// - /// The abstract class that all emulated models will inherit from - /// * Screen * - /// - public abstract partial class SpectrumBase : IVideoProvider - { - #region State - - /// - /// The main screen buffer - /// - protected int[] _frameBuffer; - - /// - /// Pixel and attribute info stored while rendering the screen - /// - 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 - - /// - /// The standard ULA palette - /// - 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 - - /// - /// The number of displayed pixels in a display row - /// - protected int DisplayWidth = 256; - - /// - /// Number of display lines - /// - protected int DisplayLines = 192; - - /// - /// The number of frames after the flash is toggled - /// - protected int FlashToggleFrames = 25; - - /// - /// Number of lines used for vertical sync - /// - protected int VerticalSyncLines = 8; - - /// - /// The number of top border lines that are not visible - /// when rendering the screen - /// - protected int NonVisibleBorderTopLines = 8; - - /// - /// The number of border lines before the display - /// - protected int BorderTopLines = 48; - - /// - /// The number of border lines after the display - /// - protected int BorderBottomLines = 48; - - /// - /// The number of bottom border lines that are not visible - /// when rendering the screen - /// - protected int NonVisibleBorderBottomLines = 8; - - /// - /// The total number of lines in the screen - /// - protected int ScreenLines; - - /// - /// The first screen line that contains the top left display pixel - /// - protected int FirstDisplayLine; - - /// - /// The last screen line that contains the bottom right display pixel - /// - protected int LastDisplayLine; - - /// - /// The number of border pixels to the left of the display - /// - protected int BorderLeftPixels = 48; - - /// - /// The number of border pixels to the right of the display - /// - protected int BorderRightPixels = 48; - - /// - /// The total width of the screen in pixels - /// - protected int ScreenWidth; - - /// - /// Horizontal blanking time (HSync+blanking). - /// Given in Z80 clock cycles. - /// - protected int HorizontalBlankingTime = 40; - - /// - /// The time of displaying left part of the border. - /// Given in Z80 clock cycles. - /// - protected int BorderLeftTime = 24; - - /// - /// The time of displaying a pixel row. - /// Given in Z80 clock cycles. - /// - protected int DisplayLineTime = 128; - - /// - /// The time of displaying right part of the border. - /// Given in Z80 clock cycles. - /// - protected int BorderRightTime = 24; - - /// - /// The time used to render the nonvisible right part of the border. - /// Given in Z80 clock cycles. - /// - protected int NonVisibleBorderRightTime = 8; - - /// - /// The time of displaying a full screen line. - /// Given in Z80 clock cycles. - /// - protected int ScreenLineTime; - - /// - /// The time the data of a particular pixel should be prefetched - /// before displaying it. - /// Given in Z80 clock cycles. - /// - protected int PixelDataPrefetchTime = 2; - - /// - /// The time the data of a particular pixel attribute should be prefetched - /// before displaying it. - /// Given in Z80 clock cycles. - /// - protected int AttributeDataPrefetchTime = 1; - - /// - /// The tact within the line that should display the first pixel. - /// Given in Z80 clock cycles. - /// - protected int FirstPixelCycleInLine; - - /// - /// The tact in which the top left pixel should be displayed. - /// Given in Z80 clock cycles. - /// - protected int FirstDisplayPixelCycle; - - /// - /// The tact in which the top left screen pixel (border) should be displayed - /// - protected int FirstScreenPixelCycle; - - /// - /// Defines the number of Z80 clock cycles used for the full rendering - /// of the screen. - /// - public int UlaFrameCycleCount; - - /// - /// The last rendered ULA cycle - /// - public int LastRenderedULACycle; - - - /// - /// This structure defines information related to a particular T-State - /// (cycle) of ULA screen rendering - /// - [StructLayout(LayoutKind.Explicit)] - public struct ScreenRenderingCycle - { - /// - /// Tha rendering phase to be applied for the particular tact - /// - [FieldOffset(0)] - public ScreenRenderingPhase Phase; - - /// - /// Display memory contention delay - /// - [FieldOffset(1)] - public byte ContentionDelay; - - /// - /// Display memory address used in the particular tact - /// - [FieldOffset(2)] - public ushort PixelByteToFetchAddress; - - /// - /// Display memory address used in the particular tact - /// - [FieldOffset(4)] - public ushort AttributeToFetchAddress; - - /// - /// Pixel X coordinate - /// - [FieldOffset(6)] - public ushort XPos; - - /// - /// Pixel Y coordinate - /// - [FieldOffset(8)] - public ushort YPos; - } - - /// - /// This enumeration defines the particular phases of ULA rendering - /// - public enum ScreenRenderingPhase : byte - { - /// - /// The ULA does not do any rendering - /// - None, - - /// - /// The ULA simple sets the border color to display the current pixel. - /// - Border, - - /// - /// 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. - /// - BorderAndFetchPixelByte, - - /// - /// 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. - /// - BorderAndFetchPixelAttribute, - - /// - /// The ULA displays the next two pixels of Byte1 sequentially during a - /// single Z80 clock cycle. - /// - DisplayByte1, - - /// - /// 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. - /// - DisplayByte1AndFetchByte2, - - /// - /// 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. - /// - DisplayByte1AndFetchAttribute2, - - /// - /// The ULA displays the next two pixels of Byte2 sequentially during a - /// single Z80 clock cycle. - /// - DisplayByte2, - - /// - /// 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. - /// - DisplayByte2AndFetchByte1, - - /// - /// 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. - /// - DisplayByte2AndFetchAttribute1 - } - - #endregion - - #region Border - - private int _borderColour; - - /// - /// Gets or sets the ULA border color - /// - public int BorderColour - { - get { return _borderColour; } - set { _borderColour = value & 0x07; } - } - - protected virtual void ResetBorder() - { - BorderColour = 0; - } - - #endregion - - #region Screen Methods - - /// - /// ULA renders the screen between two specified T-States (cycles) - /// - /// - /// - 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; - } - } - } - - /// - /// Tests whether the specified cycle is in the visible area of the screen. - /// - /// Line index - /// Tacts index within the line - /// - /// True, if the tact is visible on the screen; otherwise, false - /// - 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; - } - - /// - /// Tests whether the cycle is in the display area of the screen. - /// - /// Line index - /// Tacts index within the line - /// - /// True, if the tact is within the display area of the screen; otherwise, false. - /// - public virtual bool IsCycleInDisplayArea(int line, int cycleInLine) - { - return line >= FirstDisplayLine - && line <= LastDisplayLine - && cycleInLine >= FirstPixelCycleInLine - && cycleInLine < FirstPixelCycleInLine + DisplayLineTime; - } - - /// - /// Sets the two adjacent screen pixels belonging to the specified cycle to the given - /// color - /// - /// Color index of the first pixel - /// Color index of the second pixel - protected virtual void SetPixels(int colorIndex1, int colorIndex2) - { - var pos = _yPos * ScreenWidth + _xPos; - _frameBuffer[pos++] = ULAPalette[colorIndex1]; - _frameBuffer[pos] = ULAPalette[colorIndex2]; - } - - /// - /// Gets the color index for the specified pixel value according - /// to the given color attribute - /// - /// 0 for paper pixel, non-zero for ink pixel - /// Color attribute - /// - protected virtual int GetColor(int pixelValue, byte attr) - { - var offset = (pixelValue == 0 ? 0 : 0x100) + attr; - return _flashPhase - ? _flashOnColors[offset] - : _flashOffColors[offset]; - } - - /// - /// Resets the ULA cycle to start screen rendering from the beginning - /// - protected virtual void ResetULACycle() - { - LastRenderedULACycle = -1; - } - - /// - /// Initializes the ULA cycle table - /// - 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; - } - } - - /// - /// Calculates the pixel address for the specified line and tact within - /// the line - /// - /// Line index - /// Tacts index within the line - /// ZX spectrum screen memory address - /// - /// 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 | - /// ================================================================= - /// - 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 - } - - /// - /// Calculates the pixel attribute address for the specified line and - /// tact within the line - /// - /// Line index - /// Tacts index within the line - /// ZX spectrum screen memory address - /// - /// 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 | - /// ================================================================= - /// - 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 - - /// - /// Initialises the screen configuration calculations - /// - public virtual void InitScreenConfig(ZXSpectrum.BorderType border_type) - { - switch (border_type) - { - case ZXSpectrum.BorderType.Full: - BorderTopLines = 48; - BorderBottomLines = 48; - NonVisibleBorderTopLines = 8; - NonVisibleBorderBottomLines = 8; - break; - - case ZXSpectrum.BorderType.Widescreen: - BorderTopLines = 0; - BorderBottomLines = 0; - NonVisibleBorderTopLines = 8 + 48; - NonVisibleBorderBottomLines = 8 + 48; - break; - } - - 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; - } - - /// - /// Inits the screen - /// - protected virtual void InitScreen() - { - //BorderDevice.Reset(); - _flashPhase = false; - - _frameBuffer = new int[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 - - /// - /// The longest instruction cycle count - /// - protected const int LONGEST_OP_CYCLES = 23; - - /// - /// The ULA cycle to raise the interrupt at - /// - protected int InterruptCycle = 32; - - /// - /// Signs that an interrupt has been raised in this frame. - /// - protected bool InterruptRaised; - - /// - /// Signs that the interrupt signal has been revoked - /// - protected bool InterruptRevoked; - - /// - /// Resets the interrupt - this should happen every frame in order to raise - /// the VBLANK interrupt in the proceding frame - /// - public virtual void ResetInterrupt() - { - InterruptRaised = false; - InterruptRevoked = false; - } - - /// - /// Generates an interrupt in the current phase if needed - /// - /// - 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; } - set { } - } - - public int VsyncDenominator - { - get { return UlaFrameCycleCount; } - } - /* - public int VsyncNumerator => NullVideo.DefaultVsyncNum; - public int VsyncDenominator => NullVideo.DefaultVsyncDen; - - public int[] GetVideoBuffer() - { - /* - switch(Spectrum.SyncSettings.BorderType) - { - case ZXSpectrum.BorderType.Full: - return _frameBuffer; - - case ZXSpectrum.BorderType.Small: - // leave only 10 border units all around - int[] smlBuff = new int[(ScreenWidth - 30) * (DisplayLines - 30)]; - int index = 0; - int brdCount = 0; - // skip top and bottom - for (int i = ScreenWidth * 30; i < smlBuff.Length - ScreenWidth * 30; i++) - { - if (brdCount < 30) - { - brdCount++; - continue; - } - if (brdCount > ScreenWidth - 30) - { - brdCount++; - continue; - } - - smlBuff[index] = _frameBuffer[i]; - index++; - brdCount++; - } - - return smlBuff; - - case ZXSpectrum.BorderType.Medium: - break; - } - - return _frameBuffer; - - } - - #endregion - - } - */ -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index a999933720..b9fad2fe5a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // 128 and up only protected int ROMPaged = 0; protected bool SHADOWPaged; - protected int RAMPaged; + public int RAMPaged; protected bool PagingDisabled; // +3/+2A only diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 9bebc31eac..9bdab42b2f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -514,7 +514,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public int BackgroundColor { - get { return ULAPalette[borderColour]; } + get { return ULAPalette[7]; } //ULAPalette[borderColour]; } } public int VirtualWidth diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs deleted file mode 100644 index 00b952614b..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Screen.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public partial class ZX128 : SpectrumBase - { - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs new file mode 100644 index 0000000000..090cd9c7be --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -0,0 +1,194 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + class ULA128 : ULABase + { + #region Construction + + public ULA128(SpectrumBase machine) + : base(machine) + { + InterruptPeriod = 36; + FrameLength = 70908; + ClockSpeed = 3546900; + + contentionTable = new byte[70930]; + floatingBusTable = new short[70930]; + for (int f = 0; f < 70930; f++) + floatingBusTable[f] = -1; + + CharRows = 24; + CharCols = 32; + ScreenWidth = 256; + ScreenHeight = 192; + BorderTopHeight = 48; + BorderBottomHeight = 56; + BorderLeftWidth = 48; + BorderRightWidth = 48; + DisplayStart = 16384; + DisplayLength = 6144; + AttributeStart = 22528; + AttributeLength = 768; + borderColour = 7; + ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth; + + TstatesPerScanline = 228; + TstateAtTop = BorderTopHeight * TstatesPerScanline; + TstateAtBottom = BorderBottomHeight * TstatesPerScanline; + tstateToDisp = new short[FrameLength]; + + ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border + + ScanLineWidth * ScreenHeight //border + main + border of 192 lines + + ScanLineWidth * BorderBottomHeight]; //56 lines of border + + attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped + + BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; + BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + + Reset(); + } + + #endregion + + #region Misc Operations + + public override void Reset() + { + contentionStartPeriod = 14361; // + LateTiming; + contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); + screen = _machine.Memory[1]; + screenByteCtr = DisplayStart; + ULAByteCtr = 0; + actualULAStart = 14366 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; + lastTState = actualULAStart; + BuildAttributeMap(); + BuildContentionTable(); + } + + #endregion + + #region Contention Methods + + public override bool IsContended(int addr) + { + addr = addr & 0xc000; + + if (addr == 0x4000) + { + // low port contention + return true; + } + + if (addr == 0xc000) + { + // high port contention - check for contended bank paged in + switch (_machine.RAMPaged) + { + case 1: + case 3: + case 5: + case 7: + return true; + } + } + + return false; + } + + public override void BuildContentionTable() + { + int t = contentionStartPeriod; + while (t < contentionEndPeriod) + { + //for 128 t-states + for (int i = 0; i < 128; i += 8) + { + contentionTable[t++] = 6; + contentionTable[t++] = 5; + contentionTable[t++] = 4; + contentionTable[t++] = 3; + contentionTable[t++] = 2; + contentionTable[t++] = 1; + contentionTable[t++] = 0; + contentionTable[t++] = 0; + } + t += (TstatesPerScanline - 128); + } + + //build top half of tstateToDisp table + //vertical retrace period + for (t = 0; t < actualULAStart; t++) + tstateToDisp[t] = 0; + + //next 48 are actual border + while (t < actualULAStart + (TstateAtTop)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + + //build middle half + int _x = 0; + int _y = 0; + int scrval = 2; + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline)) + { + for (int g = 0; g < 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24; g < 24 + 128; g++) + { + //Map screenaddr to tstate + if (g % 4 == 0) + { + scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2)); + _x += 8; + } + tstateToDisp[t++] = (short)scrval; + } + _y++; + + for (int g = 24 + 128; g < 24 + 128 + 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++) + tstateToDisp[t++] = 0; + } + + int h = contentionStartPeriod + 3; + while (h < contentionEndPeriod + 3) + { + for (int j = 0; j < 128; j += 8) + { + floatingBusTable[h] = tstateToDisp[h + 2]; + floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; + floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; + floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; + h += 8; + } + h += TstatesPerScanline - 128; + } + + //build bottom half + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + } + + + #endregion + + + } +} From a9d179d83a0decae226ab3eb1c222a2716e3ebf4 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 14:35:27 +0000 Subject: [PATCH 036/105] Added ULA state serialization --- .../Computers/SinclairSpectrum/Machine/SpectrumBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index b9fad2fe5a..516b7d4b4d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -203,6 +203,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RomData.SyncState(ser); KeyboardDevice.SyncState(ser); BuzzerDevice.SyncState(ser); + ULADevice.SyncState(ser); if (AYDevice != null) AYDevice.SyncState(ser); From 2759f65b1af2b0c35b906db73da3bec6e94f15de Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 16:05:36 +0000 Subject: [PATCH 037/105] Added more border configuration options --- .../SinclairSpectrum/Machine/ULABase.cs | 150 ++++++++++++++++++ .../Machine/ZXSpectrum128K/ZX128.ULA.cs | 5 +- .../Machine/ZXSpectrum128K/ZX128.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.ULA.cs | 7 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 15 ++ 5 files changed, 168 insertions(+), 11 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 9bdab42b2f..657a255adf 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -325,6 +325,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public ULABase(SpectrumBase machine) { _machine = machine; + borderType = _machine.Spectrum.SyncSettings.BorderType; } #endregion @@ -554,9 +555,158 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public int[] GetVideoBuffer() { + switch (borderType) + { + // Full side borders, no top or bottom border (giving *almost* 16:9 output) + case ZXSpectrum.BorderType.Widescreen: + // we are cropping out the top and bottom borders + var startPixelsToCrop = ScanLineWidth * BorderTopHeight; + var endPixelsToCrop = ScanLineWidth * BorderBottomHeight; + int index = 0; + for (int i = startPixelsToCrop; i < ScreenBuffer.Length - endPixelsToCrop; i++) + { + croppedBuffer[index++] = ScreenBuffer[i]; + } + return croppedBuffer; + + // The full spectrum border + case ZXSpectrum.BorderType.Full: + return ScreenBuffer; + + case ZXSpectrum.BorderType.Medium: + // all border sizes now 24 + var lR = BorderLeftWidth - 24; + var rR = BorderRightWidth - 24; + var tR = BorderTopHeight - 24; + var bR = BorderBottomHeight - 24; + var startP = ScanLineWidth * tR; + var endP = ScanLineWidth * bR; + + int index2 = 0; + // line by line + for (int i = startP; i < ScreenBuffer.Length - endP; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) + { + // each pixel in each line + for (int p = lR; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR; p++) + { + if (index2 == croppedBuffer.Length) + break; + croppedBuffer[index2++] = ScreenBuffer[i + p]; + } + } + + return croppedBuffer; + + case ZXSpectrum.BorderType.Small: + // all border sizes now 24 + var lR_ = BorderLeftWidth - 10; + var rR_ = BorderRightWidth - 10; + var tR_ = BorderTopHeight - 10; + var bR_ = BorderBottomHeight - 10; + var startP_ = ScanLineWidth * tR_; + var endP_ = ScanLineWidth * bR_; + + int index2_ = 0; + // line by line + for (int i = startP_; i < ScreenBuffer.Length - endP_; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) + { + // each pixel in each line + for (int p = lR_; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR_; p++) + { + if (index2_ == croppedBuffer.Length) + break; + croppedBuffer[index2_++] = ScreenBuffer[i + p]; + } + } + + return croppedBuffer; + + case ZXSpectrum.BorderType.None: + // all border sizes now 24 + var lR__ = BorderLeftWidth; + var rR__ = BorderRightWidth; + var tR__ = BorderTopHeight; + var bR__ = BorderBottomHeight; + var startP__ = ScanLineWidth * tR__; + var endP__ = ScanLineWidth * bR__; + + int index2__ = 0; + // line by line + for (int i = startP__; i < ScreenBuffer.Length - endP__; i += ScreenWidth + BorderLeftWidth + BorderRightWidth) + { + // each pixel in each line + for (int p = lR__; p < ScreenWidth + BorderLeftWidth + BorderRightWidth - rR__; p++) + { + if (index2__ == croppedBuffer.Length) + break; + croppedBuffer[index2__++] = ScreenBuffer[i + p]; + } + } + + return croppedBuffer; + } + return ScreenBuffer; } + protected void SetupScreenSize() + { + switch (borderType) + { + case ZXSpectrum.BorderType.Full: + BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; + BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + ScreenBuffer = new int[BufferWidth * BufferHeight]; + break; + + case ZXSpectrum.BorderType.Widescreen: + BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; + BufferHeight = ScreenHeight; + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + croppedBuffer = new int[BufferWidth * BufferHeight]; + break; + + case ZXSpectrum.BorderType.Medium: + BufferWidth = ScreenWidth + (24) + (24); + BufferHeight = ScreenHeight + (24) + (24); + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + croppedBuffer = new int[BufferWidth * BufferHeight]; + break; + + case ZXSpectrum.BorderType.Small: + BufferWidth = ScreenWidth + (10) + (10); + BufferHeight = ScreenHeight + (10) + (10); + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + croppedBuffer = new int[BufferWidth * BufferHeight]; + break; + + case ZXSpectrum.BorderType.None: + BufferWidth = ScreenWidth; + BufferHeight = ScreenHeight; + VirtualHeight = BufferHeight; + VirtualWidth = BufferWidth; + croppedBuffer = new int[BufferWidth * BufferHeight]; + break; + } + } + + protected int[] croppedBuffer; + + private ZXSpectrum.BorderType _borderType; + + public ZXSpectrum.BorderType borderType + { + get { return _borderType; } + set { _borderType = value; } + } + + + #endregion #region IStatable diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs index 090cd9c7be..d7cc8f4941 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -43,10 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped - BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; - BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; - VirtualHeight = BufferHeight; - VirtualWidth = BufferWidth; + SetupScreenSize(); Reset(); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 65117acdfe..74df0bba39 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - ULADevice = new ULA48(this); + ULADevice = new ULA128(this); BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs index d6ca8b4565..a093a33aad 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -43,12 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped - BufferWidth = ScreenWidth + BorderLeftWidth + BorderRightWidth; - BufferHeight = ScreenHeight + BorderTopHeight + BorderBottomHeight; - VirtualHeight = BufferHeight; - VirtualWidth = BufferWidth; - - + SetupScreenSize(); Reset(); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index ba6a944346..6266d7e021 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -108,6 +108,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Full, + /// + /// All borders 24px + /// + Medium, + + /// + /// All borders 10px + /// + Small, + + /// + /// No border at all + /// + None, + /// /// Top and bottom border removed so that the result is *almost* 16:9 /// From 3d508455ec4fead9416e39d0db254eb1eaf16155 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 11 Dec 2017 18:00:59 +0000 Subject: [PATCH 038/105] Some floating bus work (although still not working) --- .../SinclairSpectrum/Machine/ULABase.cs | 6 ++--- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 657a255adf..d6e3b1ef66 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -69,11 +69,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// T-State at which to start applying contention /// - protected int contentionStartPeriod; + public int contentionStartPeriod; /// /// T-State at which to end applying contention /// - protected int contentionEndPeriod; + public int contentionEndPeriod; /// /// T-State memory contention delay mapping /// @@ -102,7 +102,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Table that stores T-State to screen/attribute address values /// - protected short[] floatingBusTable; + public short[] floatingBusTable; /// /// Cycle at which the last render update took place /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index c5bfd6f4d8..c8ad708a67 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -30,6 +30,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else if (lowBitReset) { + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + // Even I/O address so get input // The high byte indicates which half-row of keys is being polled /* @@ -115,7 +117,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Kemptson Mouse - // if unused port the floating memory bus should be returned (still todo) + // if unused port the floating memory bus should be returned + + // Floating bus is read on the previous cycle + int _tStates = CurrentFrameCycle - 1; + + // if we are on the top or bottom border return 0xff + if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) + { + result = 0xff; + } + else + { + if (ULADevice.floatingBusTable[_tStates] < 0) + { + result = 0xff; + } + else + { + result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]); + } + } } return (byte)result; From 1fb10f3d9c6ede9601d882ab1064a8576636edca Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 15 Jan 2018 12:50:07 +0000 Subject: [PATCH 039/105] Some TapeDevice serialization --- .../BizHawk.Emulation.Cores.csproj | 2 - .../Interfaces/ISupportsTapeBlockPlayback.cs | 6 +- .../SinclairSpectrum/Hardware/Tape.cs | 3 - .../Hardware/TapeBlockSetPlayer.cs | 53 ++++++++++-- .../Hardware/TapeDataBlockPlayer.cs | 81 ++++++++++++++++--- .../Hardware/TapeFilePlayer.cs | 13 ++- .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 +- .../Media/Tape/TAP/TapDataBlock.cs | 17 +++- .../Media/Tape/TAP/TapPlayer.cs | 11 +++ .../Media/Tape/TZX/DataBlocks.cs | 26 +++++- .../Media/Tape/TZX/TzxPlayer.cs | 13 ++- .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 2 +- .../ZXSpectrum.ISoundProvider.cs | 14 ---- .../ZXSpectrum.IVideoProvider.cs | 13 --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 15 +--- 15 files changed, 204 insertions(+), 67 deletions(-) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 10ba63cfb3..ff25860ed9 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -312,7 +312,6 @@ - @@ -1393,7 +1392,6 @@ - diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs index 969944fa92..8d1c47645b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -35,5 +36,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The EAR bit value to play back /// bool GetEarBit(long currentCycle); + + + void SyncState(Serializer ser); } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs index 203fef91b2..fd29eb3855 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs @@ -526,9 +526,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.SyncEnum("_currentMode", ref _currentMode); ser.SyncEnum("_savePhase", ref _savePhase); ser.SyncEnum("_prevDataPulse", ref _prevDataPulse); - /* - private TapeFilePlayer _tapePlayer; - */ ser.EndSection(); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs index c08b488acf..6d5b149f9e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using BizHawk.Common; +using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -15,27 +16,56 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Signs that the player completed playing back the file /// - public bool Eof { get; private set; } + private bool eof; + public bool Eof + { + get { return eof; } + set { eof = value; } + } + /// /// Gets the currently playing block's index /// - public int CurrentBlockIndex { get; private set; } + private int currentBlockIndex; + public int CurrentBlockIndex + { + get { return currentBlockIndex; } + set { currentBlockIndex = value; } + } /// /// The current playable block /// - public ISupportsTapeBlockPlayback CurrentBlock => DataBlocks[CurrentBlockIndex]; + private ISupportsTapeBlockPlayback currentBlock; + public ISupportsTapeBlockPlayback CurrentBlock + { + get { return DataBlocks[CurrentBlockIndex]; } + //set { currentBlock = value; } + } + /// /// The current playing phase /// - public PlayPhase PlayPhase { get; private set; } + private PlayPhase playPhase; + public PlayPhase PlayPhase + { + get { return playPhase; } + set { playPhase = value; } + } + /// /// The cycle count of the CPU when playing starts /// - public long StartCycle { get; private set; } + private long startCycle; + public long StartCycle + { + get { return startCycle; } + set { startCycle = value; } + } + public TapeBlockSetPlayer(List dataBlocks) { @@ -98,5 +128,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CurrentBlockIndex++; CurrentBlock.InitPlay(currentCycle); } + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapeBlockSetPlayer"); + ser.Sync("eof", ref eof); + ser.Sync("currentBlockIndex", ref currentBlockIndex); + ser.SyncEnum("playPhase", ref playPhase); + ser.Sync("startCycle", ref startCycle); + currentBlock.SyncState(ser); + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs index 547d7698b9..0c7dae4f14 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs @@ -1,4 +1,6 @@  +using BizHawk.Common; + namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// @@ -9,20 +11,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Pause after this block (default: 1000ms) /// - public ushort PauseAfter { get; } + private ushort pauseAfter; + public ushort PauseAfter + { + get { return pauseAfter; } + } /// /// Block Data /// - public byte[] Data { get; } + private byte[] data; + public byte[] Data + { + get { return data; } + } /// /// Initializes a new instance /// - public TapeDataBlockPlayer(byte[] data, ushort pauseAfter) + public TapeDataBlockPlayer(byte[] _data, ushort _pauseAfter) { - PauseAfter = pauseAfter; - Data = data; + pauseAfter = _pauseAfter; + data = _data; } /// @@ -82,27 +92,52 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The index of the currently playing byte /// - public int ByteIndex { get; private set; } + private int byteIndex; + public int ByteIndex + { + get { return byteIndex; } + set { byteIndex = value; } + } /// /// The mask of the currently playing bit in the current byte /// - public byte BitMask { get; private set; } + private byte bitMask; + public byte BitMask + { + get { return bitMask; } + set { bitMask = value; } + } /// /// The current playing phase /// - public PlayPhase PlayPhase { get; private set; } + private PlayPhase playPhase; + public PlayPhase PlayPhase + { + get { return playPhase; } + set { playPhase = value; } + } /// /// The cycle count of the CPU when playing starts /// - public long StartCycle { get; private set; } + private long startCycle; + public long StartCycle + { + get { return startCycle; } + set { startCycle = value; } + } /// /// Last cycle queried /// - public long LastCycle { get; private set; } + private long lastCycle; + public long LastCycle + { + get { return lastCycle; } + set { lastCycle = value; } + } /// /// Initializes the player @@ -214,5 +249,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } return true; } + + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapeDataBlockPlayer"); + + ser.Sync("pauseAfter", ref pauseAfter); + ser.Sync("data", ref data, false); + + ser.Sync("_pilotEnds", ref _pilotEnds); + ser.Sync("_sync1Ends", ref _sync1Ends); + ser.Sync("_sync2Ends", ref _sync2Ends); + ser.Sync("_bitStarts", ref _bitStarts); + ser.Sync("_bitPulseLength", ref _bitPulseLength); + ser.Sync("_currentBit", ref _currentBit); + ser.Sync("_termSyncEnds", ref _termSyncEnds); + ser.Sync("_pauseEnds", ref _pauseEnds); + + ser.Sync("byteIndex", ref byteIndex); + ser.Sync("bitMask", ref bitMask); + ser.SyncEnum("playPhase", ref playPhase); + ser.Sync("startCycle", ref startCycle); + ser.Sync("lastCycle", ref lastCycle); + + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs index 25c75df0f8..92c32dbc95 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -13,6 +14,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { private readonly BinaryReader _reader; private TapeBlockSetPlayer _player; + private int _numberOfDataBlocks; /// /// Data blocks to play back @@ -113,5 +115,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Tacts time to start the next block public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapeFilePlayer"); + ReadContent(); + ser.Sync("_numberOfDataBlocks", ref _numberOfDataBlocks); + _player.SyncState(ser); + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 516b7d4b4d..a67333aa03 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -142,7 +142,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // paint the buffer if needed if (ULADevice.needsPaint) ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); - + BuzzerDevice.EndFrame(); TapeDevice.CPUFrameCompleted(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs index 3c4858b533..0682b32752 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs @@ -1,4 +1,5 @@  +using BizHawk.Common; using System.IO; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum @@ -16,7 +17,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Block Data /// - public byte[] Data { get; private set; } + private byte[] data; + public byte[] Data + { + get { return data; } + set { data = value; } + } /// /// Pause after this block (given in milliseconds) @@ -86,5 +92,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The EAR bit value to play back /// public bool GetEarBit(long currentTact) => _player.GetEarBit(currentTact); + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapDataBlock"); + + ser.Sync("data", ref data, false); + + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs index faa7d23c02..0c24566e37 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs @@ -1,4 +1,5 @@  +using BizHawk.Common; using System.Collections.Generic; using System.IO; using System.Linq; @@ -81,5 +82,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Tacts time to start the next block public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); + + + public void SyncState(Serializer ser) + { + ser.BeginSection("TapePlayer"); + + _player.SyncState(ser); + + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs index 18ce828a09..cfd694d544 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs @@ -1,4 +1,5 @@  +using BizHawk.Common; using System.IO; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum @@ -1144,12 +1145,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Lenght of block data /// - public ushort DataLength { get; set; } + private ushort dataLength; + public ushort DataLength + { + get { return dataLength; } + set { dataLength = value; } + } /// /// Block Data /// - public byte[] Data { get; set; } + private byte[] data; + public byte[] Data + { + get { return data; } + set { data = value; } + } + /// /// The ID of the block @@ -1222,6 +1234,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The EAR bit value to play back /// public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); + + public void SyncState(Serializer ser) + { + ser.BeginSection("TzxStandardSpeedDataBlock"); + + ser.Sync("dataLength", ref dataLength); + ser.Sync("data", ref data, false); + + ser.EndSection(); + } } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs index 55797b80df..a9ac846467 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs @@ -1,4 +1,5 @@ -using System.IO; +using BizHawk.Common; +using System.IO; using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum @@ -79,5 +80,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Tacts time to start the next block public void NextBlock(long currentTact) => _player.NextBlock(currentTact); + + + public void SyncState(Serializer ser) + { + ser.BeginSection("TzxPlayer"); + + _player.SyncState(ser); + + ser.EndSection(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs index b7a1a07d82..8b10763146 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public string SystemId => "ZXSpectrum"; - public bool DeterministicEmulation => false; + public bool DeterministicEmulation => true; public void ResetCounters() { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs deleted file mode 100644 index 402b7f292d..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISoundProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using BizHawk.Emulation.Cores.Components; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public partial class ZXSpectrum - { - private SoundProviderMixer SoundMixer; - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs deleted file mode 100644 index 073236a69d..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IVideoProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Main IVideoProvider implementation is inside the machine classes - /// This is just some helper functions - /// - public partial class ZXSpectrum - { - - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index ac8fd29229..6694a7e97d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -60,9 +60,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum break; default: throw new InvalidOperationException("Machine not yet emulated"); - } - - + } _cpu.MemoryCallbacks = MemoryCallbacks; @@ -84,14 +82,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - //SoundMixer.DisableSource(_machine.BuzzerDevice); - - dcf = new DCFilter(SoundMixer, 1024); - - - + dcf = new DCFilter(SoundMixer, 256); ser.Register(dcf); - //ser.Register(_machine.AYDevice); @@ -108,11 +100,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public IController _controller; private SpectrumBase _machine; + private SoundProviderMixer SoundMixer; + private DCFilter dcf; private byte[] _file; - public bool DiagRom = false; private byte[] GetFirmware(int length, params string[] names) From d534ee3f5fe7a3d64d97dad382714d4968d9f4de Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Feb 2018 17:22:03 +0000 Subject: [PATCH 040/105] Small settings change --- BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj | 8 ++++++++ .../Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs | 4 ++-- .../Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 537435d739..f8cad71c73 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -258,6 +258,14 @@ + + + + + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 6266d7e021..df56bd1b5b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -136,8 +136,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public enum TapeLoadSpeed { Accurate, - Fast, - Fastest + //Fast, + //Fastest } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index a9c407ca14..372cb8ed54 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -53,7 +53,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.SyncState(ser); ser.BeginSection("ZXSpectrum"); - _cpu.SyncState(ser); + //_cpu.SyncState(ser); _machine.SyncState(ser); ser.Sync("Frame", ref _machine.FrameCount); ser.Sync("LagCount", ref _lagCount); From 181a6ba2ab93269e094598fbf85dee90fb9ffdd6 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Feb 2018 17:23:45 +0000 Subject: [PATCH 041/105] fix deleted files --- BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index f8cad71c73..537435d739 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -258,14 +258,6 @@ - - - - - - - - From f9e93cfa2a66762b405c5304e10f65c126c34a0e Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Feb 2018 15:29:21 +0000 Subject: [PATCH 042/105] Starting new tape implementation --- .../BizHawk.Emulation.Cores.csproj | 4 + .../Media/MediaSerializationType.cs | 18 +++ .../SinclairSpectrum/Media/MediaSerializer.cs | 114 ++++++++++++++++++ .../Media/Tape/TapeCommand.cs | 21 ++++ .../Media/Tape/TapeDataBlock.cs | 65 ++++++++++ 5 files changed, 222 insertions(+) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 537435d739..8d0bceb934 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -275,6 +275,10 @@ + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs new file mode 100644 index 0000000000..def321b245 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializationType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents the different types of media serializer avaiable + /// + public enum MediaSerializationType + { + NONE, + TZX, + TAP + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs new file mode 100644 index 0000000000..b20a6aa8b1 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Abtract class that represents all Media Serializers + /// + public abstract class MediaSerializer + { + /// + /// The type of serializer + /// + public abstract MediaSerializationType FormatType { get; } + + /// + /// Signs whether this class can be used to serialize + /// + public virtual bool IsSerializer + { + get + { + return false; + } + } + + /// + /// Signs whether this class can be used to de-serialize + /// + public virtual bool IsDeSerializer + { + get + { + return false; + } + } + + /// + /// Serialization method + /// + /// + public virtual void Serialize(byte[] data) + { + throw new NotImplementedException(this.GetType().ToString() + + "Serialize operation is not implemented for this serializer"); + } + + /// + /// DeSerialization method + /// + /// + public virtual void DeSerialize(byte[] data) + { + throw new NotImplementedException(this.GetType().ToString() + + "DeSerialize operation is not implemented for this serializer"); + } + + #region Static Tools + + /// + /// Converts an int32 value into a byte array + /// + /// + /// + protected static byte[] GetBytes(int value) + { + byte[] buf = new byte[4]; + buf[0] = (byte)value; + buf[1] = (byte)(value >> 8); + buf[2] = (byte)(value >> 16); + buf[3] = (byte)(value >> 24); + return buf; + } + + /// + /// Returns an int32 from a byte array based on offset + /// + /// + /// + /// + protected static int GetInt32(byte[] buf, int offsetIndex) + { + return buf[offsetIndex] | buf[offsetIndex + 1] << 8 | buf[offsetIndex + 2] << 16 | buf[offsetIndex + 3] << 24; + } + + /// + /// Returns an uint16 from a byte array based on offset + /// + /// + /// + /// + protected static ushort GetUInt16(byte[] buf, int offsetIndex) + { + return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8); + } + + /// + /// Updates a byte array with a uint16 value based on offset + /// + /// + /// + /// + protected static void setUint16(byte[] buf, int offsetIndex, ushort value) + { + buf[offsetIndex] = (byte)value; + buf[offsetIndex + 1] = (byte)(value >> 8); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs new file mode 100644 index 0000000000..14fc54a460 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeCommand.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents the possible commands that can be raised from each tape block + /// + public enum TapeCommand + { + NONE, + STOP_THE_TAPE, + STOP_THE_TAPE_48K, + BEGIN_GROUP, + END_GROUP, + SHOW_MESSAGE, + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs new file mode 100644 index 0000000000..796bee81d9 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a tape block + /// + public class TapeDataBlock + { + /// + /// Either the TZX block ID, or -1 in the case of non-tzx blocks + /// + public int BlockID = -1; + + /// + /// Description of the block + /// + public string BlockDescription { get; set; } + + /// + /// Byte array containing the raw block data + /// + public byte[] BlockData = null; + + /// + /// List containing the pulse timing values + /// + public List DataPeriods = new List(); + + /// + /// Command that is raised by this data block + /// (that may or may not need to be acted on) + /// + public TapeCommand Command = TapeCommand.NONE; + + /// + /// Returns the data periods as an array + /// (primarily to aid in bizhawk state serialization) + /// + /// + public int[] GetDataPeriodsArray() + { + return DataPeriods.ToArray(); + } + + /// + /// Accepts an array of data periods and updates the DataPeriods list accordingly + /// (primarily to aid in bizhawk state serialization) + /// + /// + public void SetDataPeriodsArray(int[] periodArray) + { + DataPeriods = new List(); + + if (periodArray == null) + return; + + DataPeriods = periodArray.ToList(); + } + } +} From 42b5f5dc5d2419d392ad7c6508c6545df269580e Mon Sep 17 00:00:00 2001 From: Asnivor Date: Wed, 14 Feb 2018 12:21:02 +0000 Subject: [PATCH 043/105] Tape device re-write and TAP format reading done. Loading state is now fully serializable --- .../BizHawk.Emulation.Cores.csproj | 2 + .../Hardware/Datacorder/DatacorderDevice.cs | 393 ++++++++++++++++++ .../Machine/SpectrumBase.Input.cs | 6 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 6 +- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 4 +- .../Machine/ZXSpectrum128K/ZX128.cs | 5 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 4 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 5 +- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 4 +- .../Machine/ZXSpectrum48K/ZX48.cs | 5 +- .../Media/Tape/TapSerializer.cs | 301 ++++++++++++++ .../Media/Tape/TapeDataBlock.cs | 60 ++- 12 files changed, 772 insertions(+), 23 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 8d0bceb934..3b65b45bb8 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -258,6 +258,7 @@ + @@ -279,6 +280,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs new file mode 100644 index 0000000000..8018ac23cc --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -0,0 +1,393 @@ +using BizHawk.Common; +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 +{ + /// + /// Represents the tape device (or build-in datacorder as it was called +2 and above) + /// + public class DatacorderDevice + { + #region Construction + + private SpectrumBase _machine { get; set; } + private Z80A _cpu { get; set; } + private Buzzer _buzzer { get; set; } + + /// + /// Default constructor + /// + public DatacorderDevice() + { + + } + + /// + /// Initializes the datacorder device + /// + /// + public void Init(SpectrumBase machine) + { + _machine = machine; + _cpu = _machine.CPU; + _buzzer = machine.BuzzerDevice; + } + + #endregion + + #region State Information + + /// + /// The index of the current tape data block that is loaded + /// + private int _currentDataBlockIndex = 0; + public int CurrentDataBlockIndex + { + get + { + if (_dataBlocks.Count() > 0) { return _currentDataBlockIndex; } + else { return -1; } + } + set + { + if (value == _currentDataBlockIndex) { return; } + if (value < _dataBlocks.Count() && value >= 0) + { + _currentDataBlockIndex = value; + _position = 0; + } + } + } + + /// + /// The current position within the current data block + /// + private int _position = 0; + public int Position + { + get + { + if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count) { return 0; } + else { return _position; } + } + } + + /// + /// Signs whether the tape is currently playing or not + /// + private bool _tapeIsPlaying = false; + public bool TapeIsPlaying + { + get { return _tapeIsPlaying; } + } + + /// + /// A list of the currently loaded data blocks + /// + private List _dataBlocks = new List(); + public List DataBlocks + { + get { return _dataBlocks; } + set { _dataBlocks = value; } + } + + /// + /// Stores the last CPU t-state value + /// + private long _lastCycle = 0; + + /// + /// + /// + private int _waitEdge = 0; + + /// + /// + /// + private bool currentState = false; + + #endregion + + #region Datacorder Device Settings + + /// + /// Signs whether the device should autodetect when the Z80 has entered into + /// 'load' mode and auto-play the tape if neccesary + /// + public bool AutoPlay { get; set; } + + #endregion + + #region Tape Controls + + /// + /// Starts the tape playing from the beginning of the current block + /// + public void Play() + { + if (_tapeIsPlaying) + return; + + // update the lastCycle + _lastCycle = _cpu.TotalExecutedCycles; + + // reset waitEdge and position + _waitEdge = 0; + _position = 0; + + if ( + _dataBlocks.Count > 0 && // data blocks are present && + _currentDataBlockIndex >= 0 // the current data block index is 1 or greater + ) + { + while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count) + { + // we are at the end of a data block - move to the next + _position = 0; + _currentDataBlockIndex++; + + // are we at the end of the tape? + if (_currentDataBlockIndex >= _dataBlocks.Count) + { + break; + } + } + + // check for end of tape + if (_currentDataBlockIndex >= _dataBlocks.Count) + { + // end of tape reached. Rewind to beginning + RTZ(); + return; + } + + // update waitEdge with the current position in the current block + _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position]; + + // sign that the tape is now playing + _tapeIsPlaying = true; + } + } + + /// + /// Stops the tape + /// (should move to the beginning of the next block) + /// + public void Stop() + { + if (!_tapeIsPlaying) + return; + + // sign that the tape is no longer playing + _tapeIsPlaying = false; + + if ( + _currentDataBlockIndex >= 0 && // we are at datablock 1 or above + _position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count - 1 // the block is still playing back + ) + { + // move to the next block + _currentDataBlockIndex++; + + if (_currentDataBlockIndex >= _dataBlocks.Count()) + { + _currentDataBlockIndex = -1; + } + + // reset waitEdge and position + _waitEdge = 0; + _position = 0; + + if ( + _currentDataBlockIndex < 0 && // block index is -1 + _dataBlocks.Count() > 0 // number of blocks is greater than 0 + ) + { + // move the index on to 0 + _currentDataBlockIndex = 0; + } + } + + // update the lastCycle + _lastCycle = _cpu.TotalExecutedCycles; + + } + + /// + /// Rewinds the tape to it's beginning (return to zero) + /// + public void RTZ() + { + Stop(); + _currentDataBlockIndex = 0; + } + + /// + /// Inserts a new tape and sets up the tape device accordingly + /// + /// + public void LoadTape(byte[] tapeData) + { + // attempt TAP deserialization + TapSerializer tapSer = new TapSerializer(this); + + try + { + tapSer.DeSerialize(tapeData); + } + catch (Exception ex) + { + // TAP format not detected + var e = ex; + } + } + + /// + /// Resets the tape + /// + public void Reset() + { + RTZ(); + } + + #endregion + + #region Tape Device Methods + + /// + /// Simulates the spectrum 'EAR' input reading data from the tape + /// + /// + /// + public bool GetEarBit(long cpuCycle) + { + // decide how many cycles worth of data we are capturing + int cycles = Convert.ToInt32(cpuCycle - _lastCycle); + + // check whether tape is actually playing + if (_tapeIsPlaying == false) + { + // it's not playing. Update lastCycle and return + _lastCycle = cpuCycle; + return false; + } + + // check for end of tape + if (_currentDataBlockIndex < 0) + { + // end of tape reached - RTZ (and stop) + RTZ(); + return currentState; + } + + // process the cycles based on the waitEdge + while (cycles >= _waitEdge) + { + // decrement cycles + cycles -= _waitEdge; + + // flip the current state + currentState = !currentState; + + // increment the current period position + _position++; + + if (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count()) + { + // we have reached the end of the current block + + // skip any empty blocks + while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count()) + { + _position = 0; + _currentDataBlockIndex++; + if (_currentDataBlockIndex >= _dataBlocks.Count()) + { + break; + } + } + + // check for end of tape + if (_currentDataBlockIndex >= _dataBlocks.Count()) + { + _currentDataBlockIndex = -1; + RTZ(); + return currentState; + } + } + + // update waitEdge with current position within the current block + _waitEdge = _dataBlocks[_currentDataBlockIndex].DataPeriods[_position]; + + } + + // update lastCycle and return currentstate + _lastCycle = cpuCycle - (long)cycles; + + // play the buzzer + _buzzer.ProcessPulseValue(true, currentState); + + return currentState; + } + + #endregion + + #region Media Serialization + + + + #endregion + + #region State Serialization + + private int _tempBlockCount; + + /// + /// Bizhawk state serialization + /// + /// + public void SyncState(Serializer ser) + { + ser.BeginSection("DatacorderDevice"); + + ser.Sync("_currentDataBlockIndex", ref _currentDataBlockIndex); + ser.Sync("_position", ref _position); + ser.Sync("_tapeIsPlaying", ref _tapeIsPlaying); + ser.Sync("_lastCycle", ref _lastCycle); + ser.Sync("_waitEdge", ref _waitEdge); + ser.Sync("currentState", ref currentState); + + //_dataBlocks + ser.BeginSection("Datablocks"); + + if (ser.IsWriter) + { + _tempBlockCount = _dataBlocks.Count(); + ser.Sync("_tempBlockCount", ref _tempBlockCount); + + for (int i = 0; i < _tempBlockCount; i++) + { + _dataBlocks[i].SyncState(ser, i); + } + } + else + { + ser.Sync("_tempBlockCount", ref _tempBlockCount); + } + + + + ser.EndSection(); + + + ser.EndSection(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 9f0c736069..85bac061d8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -55,15 +55,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Tape control if (Spectrum._controller.IsPressed(Play)) { - + TapeDevice.Play(); } if (Spectrum._controller.IsPressed(Stop)) { - + TapeDevice.Stop(); } if (Spectrum._controller.IsPressed(RTZ)) { - + TapeDevice.RTZ(); } if (Spectrum._controller.IsPressed(Record)) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index a67333aa03..43c0ec8b7a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -59,12 +59,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The spectrum datacorder device /// - public virtual Tape TapeDevice { get; set; } + public virtual DatacorderDevice TapeDevice { get; set; } /// /// The tape provider /// - public virtual ITapeProvider TapeProvider { get; set; } + //public virtual ITapeProvider TapeProvider { get; set; } /// /// Kempston joystick @@ -145,7 +145,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.EndFrame(); - TapeDevice.CPUFrameCompleted(); + //TapeDevice.CPUFrameCompleted(); FrameCount++; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 0997bf3947..64c2cff470 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -69,7 +69,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result | 0xa0; //set bit 5 & 7 to 1 - if (TapeDevice.CurrentMode == TapeOperationMode.Load) + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) { @@ -193,7 +193,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); // Tape - TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 74df0bba39..5188a658a5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -40,10 +40,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - TapeProvider = new DefaultTapeProvider(file); + //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new Tape(TapeProvider); + TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); + TapeDevice.LoadTape(file); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 4a5df60bc8..556a5f1d23 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -68,7 +68,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result | 0xa0; //set bit 5 & 7 to 1 - if (TapeDevice.CurrentMode == TapeOperationMode.Load) + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) { @@ -166,7 +166,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); // Tape - TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 5904dc41de..e2723dab2d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -40,10 +40,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - TapeProvider = new DefaultTapeProvider(file); + //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new Tape(TapeProvider); + TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); + TapeDevice.LoadTape(file); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index c8ad708a67..3d891fb9e7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -70,7 +70,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result | 0xa0; //set bit 5 & 7 to 1 - if (TapeDevice.CurrentMode == TapeOperationMode.Load) + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) { @@ -180,7 +180,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); // Tape - TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index f7a447dc35..deddf9bda0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -31,10 +31,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - TapeProvider = new DefaultTapeProvider(file); + //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new Tape(TapeProvider); + TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); + TapeDevice.LoadTape(file); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs new file mode 100644 index 0000000000..39bc2853b5 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs @@ -0,0 +1,301 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Reponsible for TAP format serializaton + /// + public class TapSerializer : MediaSerializer + { + /// + /// The type of serializer + /// + private MediaSerializationType _formatType = MediaSerializationType.TAP; + public override MediaSerializationType FormatType + { + get + { + return _formatType; + } + } + + /// + /// Signs whether this class can be used to serialize + /// + public override bool IsSerializer { get { return false; } } + + /// + /// Signs whether this class can be used to de-serialize + /// + public override bool IsDeSerializer { get { return true; } } + + #region Construction + + private DatacorderDevice _datacorder; + + public TapSerializer(DatacorderDevice _tapeDevice) + { + _datacorder = _tapeDevice; + } + + #endregion + + + #region TAP Format Constants + + /// + /// Pilot pulse length + /// + public const int PILOT_PL = 2168; + + /// + /// Pilot pulses in the ROM header block + /// + public const int HEADER_PILOT_COUNT = 8063; + + /// + /// Pilot pulses in the ROM data block + /// + public const int DATA_PILOT_COUNT = 3223; + + /// + /// Sync 1 pulse length + /// + public const int SYNC_1_PL = 667; + + /// + /// Sync 2 pulse lenth + /// + public const int SYNC_2_PL = 735; + + /// + /// Bit 0 pulse length + /// + public const int BIT_0_PL = 855; + + /// + /// Bit 1 pulse length + /// + public const int BIT_1_PL = 1710; + + /// + /// End sync pulse length + /// + public const int TERM_SYNC = 947; + + /// + /// 1 millisecond pause + /// + public const int PAUSE_MS = 3500; + + /// + /// Used bit count in last byte + /// + public const int BIT_COUNT_IN_LAST = 8; + + #endregion + + /// + /// DeSerialization method + /// + /// + public override void DeSerialize(byte[] data) + { + /* + The .TAP files contain blocks of tape-saved data. All blocks start with two bytes specifying how many bytes will follow (not counting the two length bytes). Then raw tape data follows, including the flag and checksum bytes. The checksum is the bitwise XOR of all bytes including the flag byte. For example, when you execute the line SAVE "ROM" CODE 0,2 this will result: + + |------ Spectrum-generated data -------| |---------| + + 13 00 00 03 52 4f 4d 7x20 02 00 00 00 00 80 f1 04 00 ff f3 af a3 + + ^^^^^...... first block is 19 bytes (17 bytes+flag+checksum) + ^^... flag byte (A reg, 00 for headers, ff for data blocks) + ^^ first byte of header, indicating a code block + + file name ..^^^^^^^^^^^^^ + header info ..............^^^^^^^^^^^^^^^^^ + checksum of header .........................^^ + length of second block ........................^^^^^ + flag byte ............................................^^ + first two bytes of rom .................................^^^^^ + checksum (checkbittoggle would be a better name!).............^^ + */ + + + // convert bytearray to memory stream + MemoryStream stream = new MemoryStream(data); + + // the first 2 bytes of the TAP file designate the length of the first data block + // this (I think) should always be 17 bytes (as this is the tape header) + byte[] blockLengthData = new byte[2]; + + // we are now going to stream through the entire file processing a block at a time + while (stream.Position < stream.Length) + { + // read and calculate the length of the block + stream.Read(blockLengthData, 0, 2); + int blockSize = BitConverter.ToUInt16(blockLengthData, 0); + if (blockSize == 0) + { + // block size is 0 - this is probably invalid (but I guess could be EoF in some situations) + break; + } + + // copy the entire block into a new bytearray + byte[] blockdata = new byte[blockSize]; + stream.Read(blockdata, 0, blockSize); + + // create and populate a new tapedatablock object + TapeDataBlock tdb = new TapeDataBlock(); + + // ascertain the block description + string description = string.Empty; + byte crc = 0; + byte crcValue = 0; + byte crcFile = 0; + byte[] programData = new byte[10]; + + // calculate block checksum value + for (int i = 0; i < blockSize; i++) + { + crc ^= blockdata[i]; + if (i < blockSize - 1) + { + crcValue = crc; + } + else + { + crcFile = blockdata[i]; + } + } + + // process the flag byte + /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file. + A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal. + If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) + and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds + the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.) + */ + + if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3) + { + // This is the PROGRAM header + // take the 10 filename bytes (that start at offset 2) + programData = blockdata.Skip(2).Take(10).ToArray(); + + // get the filename as a string (with padding removed) + string fileName = Encoding.ASCII.GetString(programData).Trim(); + + // get the type + string type = ""; + if (blockdata[0] == 0x00) + { + type = "Program"; + } + else + { + type = "Bytes"; + } + + // now build the description string + StringBuilder sb = new StringBuilder(); + sb.Append(type + ": "); + sb.Append(fileName + " "); + sb.Append(GetUInt16(blockdata, 14)); + sb.Append(":"); + sb.Append(GetUInt16(blockdata, 12)); + description = sb.ToString(); + } + else if (blockdata[0] == 0xFF) + { + // this is a data block + description = "Data Block " + (blockSize - 2) + "bytes"; + } + else + { + // other type + description = string.Format("#{0} block, {1} bytes", blockdata[0].ToString("X2"), blockSize - 2); + description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok")); + } + + tdb.BlockDescription = description; + + // calculate the data periods for this block + int pilotLength = 0; + + // work out pilot length + if (blockdata[0] < 4) + { + pilotLength = 8064; + } + else + { + pilotLength = 3220; + } + + // create a list to hold the data periods + List dataPeriods = new List(); + + // generate pilot pulses + for (int i = 0; i < pilotLength; i++) + { + dataPeriods.Add(PILOT_PL); + } + + // add syncro pulses + dataPeriods.Add(SYNC_1_PL); + dataPeriods.Add(SYNC_2_PL); + + int pos = 0; + + // add bit0 and bit1 periods + for (int i = 0; i < blockSize - 1; i++, pos++) + { + for (byte b = 0x80; b != 0; b >>= 1) + { + if ((blockdata[i] & b) != 0) + dataPeriods.Add(BIT_1_PL); + else + dataPeriods.Add(BIT_0_PL); + if ((blockdata[i] & b) != 0) + dataPeriods.Add(BIT_1_PL); + else + dataPeriods.Add(BIT_0_PL); + } + } + + // add the last byte + for (byte c = 0x80; c != (byte)(0x80 >> BIT_COUNT_IN_LAST); c >>= 1) + { + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(BIT_1_PL); + else + dataPeriods.Add(BIT_0_PL); + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(BIT_1_PL); + else + dataPeriods.Add(BIT_0_PL); + } + + // add block pause + int actualPause = PAUSE_MS * 1000; + dataPeriods.Add(actualPause); + + // add to the tapedatablock object + tdb.DataPeriods = dataPeriods; + + // add the raw data + tdb.BlockData = blockdata; + + // add block to the tape + _datacorder.DataBlocks.Add(tdb); + + } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs index 796bee81d9..c75906b496 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -14,17 +15,32 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Either the TZX block ID, or -1 in the case of non-tzx blocks /// - public int BlockID = -1; + private int _blockID = -1; + public int BlockID + { + get { return _blockID; } + set { _blockID = value; } + } /// /// Description of the block /// - public string BlockDescription { get; set; } + private string _blockDescription; + public string BlockDescription + { + get { return _blockDescription; } + set { _blockDescription = value; } + } /// /// Byte array containing the raw block data /// - public byte[] BlockData = null; + private byte[] _blockData; + public byte[] BlockData + { + get { return _blockData; } + set { _blockData = value; } + } /// /// List containing the pulse timing values @@ -35,7 +51,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Command that is raised by this data block /// (that may or may not need to be acted on) /// - public TapeCommand Command = TapeCommand.NONE; + private TapeCommand _command = TapeCommand.NONE; + public TapeCommand Command + { + get { return _command; } + set { _command = value; } + } /// /// Returns the data periods as an array @@ -61,5 +82,34 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum DataPeriods = periodArray.ToList(); } + + /// + /// Bizhawk state serialization + /// + /// + public void SyncState(Serializer ser, int blockPosition) + { + ser.BeginSection("DataBlock" + blockPosition); + + ser.Sync("_blockID", ref _blockID); + ser.SyncFixedString("_blockDescription", ref _blockDescription, 50); + ser.Sync("_blockData", ref _blockData, true); + ser.SyncEnum("_command", ref _command); + + int[] tempArray = null; + + if (ser.IsWriter) + { + tempArray = GetDataPeriodsArray(); + ser.Sync("_periods", ref tempArray, true); + } + else + { + ser.Sync("_periods", ref tempArray, true); + SetDataPeriodsArray(tempArray); + } + + ser.EndSection(); + } } } From b9729d0dc26ccfc6cb67ee40f85a54344436f9e9 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 15 Feb 2018 14:37:22 +0000 Subject: [PATCH 044/105] TZX tape format handling re-write nearly complete (supporting advanced protection/loader schemes) --- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/Datacorder/DatacorderDevice.cs | 15 +- .../SinclairSpectrum/Media/MediaSerializer.cs | 4 +- .../Media/Tape/TapSerializer.cs | 10 +- .../Media/Tape/TapeDataBlock.cs | 163 +- .../Media/Tape/TzxSerializer.cs | 1630 +++++++++++++++++ 6 files changed, 1809 insertions(+), 14 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 3b65b45bb8..aefb1900ae 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -281,6 +281,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 8018ac23cc..ddc7598f03 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -233,12 +233,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public void LoadTape(byte[] tapeData) { + // attempt TZX deserialization + TzxSerializer tzxSer = new TzxSerializer(this); + try + { + tzxSer.DeSerialize(tapeData); + return; + } + catch (Exception ex) + { + // TAP format not detected + var e = ex; + } + // attempt TAP deserialization TapSerializer tapSer = new TapSerializer(this); - try { tapSer.DeSerialize(tapeData); + return; } catch (Exception ex) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs index b20a6aa8b1..44c99c28ff 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/MediaSerializer.cs @@ -92,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - protected static ushort GetUInt16(byte[] buf, int offsetIndex) + protected static ushort GetWordValue(byte[] buf, int offsetIndex) { return (ushort)(buf[offsetIndex] | buf[offsetIndex + 1] << 8); } @@ -103,7 +103,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - protected static void setUint16(byte[] buf, int offsetIndex, ushort value) + protected static void SetWordValue(byte[] buf, int offsetIndex, ushort value) { buf[offsetIndex] = (byte)value; buf[offsetIndex + 1] = (byte)(value >> 8); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs index 39bc2853b5..01e9bee86c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapSerializer.cs @@ -126,6 +126,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum checksum (checkbittoggle would be a better name!).............^^ */ + // clear existing tape blocks + _datacorder.DataBlocks.Clear(); // convert bytearray to memory stream MemoryStream stream = new MemoryStream(data); @@ -174,7 +176,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } - // process the flag byte + // process the type byte /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file. A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal. If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) @@ -206,9 +208,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum StringBuilder sb = new StringBuilder(); sb.Append(type + ": "); sb.Append(fileName + " "); - sb.Append(GetUInt16(blockdata, 14)); + sb.Append(GetWordValue(blockdata, 14)); sb.Append(":"); - sb.Append(GetUInt16(blockdata, 12)); + sb.Append(GetWordValue(blockdata, 12)); description = sb.ToString(); } else if (blockdata[0] == 0xFF) @@ -223,7 +225,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok")); } - tdb.BlockDescription = description; + tdb.BlockDescription = BlockType.Standard_Speed_Data_Block; // calculate the data periods for this block int pilotLength = 0; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs index c75906b496..f714d21385 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TapeDataBlock.cs @@ -19,17 +19,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public int BlockID { get { return _blockID; } - set { _blockID = value; } + set { + _blockID = value; + + if (MetaData == null) + MetaData = new Dictionary(); + + AddMetaData(BlockDescriptorTitle.Block_ID, value.ToString()); + } } /// - /// Description of the block + /// The block type /// - private string _blockDescription; - public string BlockDescription + private BlockType _blockType; + public BlockType BlockDescription { - get { return _blockDescription; } - set { _blockDescription = value; } + get { return _blockType; } + set { + _blockType = value; + if (MetaData == null) + MetaData = new Dictionary(); + } } /// @@ -42,6 +53,63 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum set { _blockData = value; } } + /// + /// An array of bytearray encoded strings (stored in this format for easy Bizhawk serialization) + /// Its basically tape information + /// + private byte[][] _tapeDescriptionData; + + /// + /// Returns the Tape Description Data in a human readable format + /// + public List TapeDescriptionData + { + get + { + List data = new List(); + + foreach (byte[] b in _tapeDescriptionData) + { + data.Add(Encoding.ASCII.GetString(b)); + } + + return data; + } + } + + + #region Block Meta Data + + /// + /// Dictionary of block related data + /// + public Dictionary MetaData { get; set; } + + /// + /// Adds a single metadata item to the Dictionary + /// + /// + /// + public void AddMetaData(BlockDescriptorTitle descriptor, string data) + { + // check whether entry already exists + bool check = MetaData.ContainsKey(descriptor); + if (check) + { + // already exists - update + MetaData[descriptor] = data; + } + else + { + // create new + MetaData.Add(descriptor, data); + } + } + + #endregion + + + /// /// List containing the pulse timing values /// @@ -92,7 +160,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.BeginSection("DataBlock" + blockPosition); ser.Sync("_blockID", ref _blockID); - ser.SyncFixedString("_blockDescription", ref _blockDescription, 50); + //ser.SyncFixedString("_blockDescription", ref _blockDescription, 200); + ser.SyncEnum("_blockType", ref _blockType); ser.Sync("_blockData", ref _blockData, true); ser.SyncEnum("_command", ref _command); @@ -112,4 +181,84 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.EndSection(); } } + + /// + /// The types of TZX blocks + /// + public enum BlockType + { + Standard_Speed_Data_Block = 0x10, + Turbo_Speed_Data_Block = 0x11, + Pure_Tone = 0x12, + Pulse_Sequence = 0x13, + Pure_Data_Block = 0x14, + Direct_Recording = 0x15, + CSW_Recording = 0x18, + Generalized_Data_Block = 0x19, + Pause_or_Stop_the_Tape = 0x20, + Group_Start = 0x21, + Group_End = 0x22, + Jump_to_Block = 0x23, + Loop_Start = 0x24, + Loop_End = 0x25, + Call_Sequence = 0x26, + Return_From_Sequence = 0x27, + Select_Block = 0x28, + Stop_the_Tape_48K = 0x2A, + Set_Signal_Level = 0x2B, + Text_Description = 0x30, + Message_Block = 0x31, + Archive_Info = 0x32, + Hardware_Type = 0x33, + Custom_Info_Block = 0x35, + Glue_Block = 0x5A, + + // depreciated blocks + C64_ROM_Type_Data_Block = 0x16, + C64_Turbo_Tape_Data_Block = 0x17, + Emulation_Info = 0x34, + Snapshot_Block = 0x40, + + // unsupported / undetected + Unsupported + } + + /// + /// Different title possibilities + /// + public enum BlockDescriptorTitle + { + Undefined, + Block_ID, + Program, + Data_Bytes, + Bytes, + + Pilot_Pulse_Length, + Pilot_Pulse_Count, + First_Sync_Length, + Second_Sync_Length, + Zero_Bit_Length, + One_Bit_Length, + Data_Length, + Bits_In_Last_Byte, + Pause_After_Data, + + Pulse_Length, + Pulse_Count, + + Text_Description, + Title, + Publisher, + Author, + Year, + Language, + Type, + Price, + Protection, + Origin, + Comments, + + Needs_Parsing + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs new file mode 100644 index 0000000000..76c1b1fd97 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs @@ -0,0 +1,1630 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Reponsible for TZX format serializaton + /// + public class TzxSerializer : MediaSerializer + { + /// + /// The type of serializer + /// + private MediaSerializationType _formatType = MediaSerializationType.TZX; + public override MediaSerializationType FormatType + { + get + { + return _formatType; + } + } + + /// + /// Signs whether this class can be used to serialize + /// + public override bool IsSerializer { get { return false; } } + + /// + /// Signs whether this class can be used to de-serialize + /// + public override bool IsDeSerializer { get { return true; } } + + /// + /// Working list of generated tape data blocks + /// + private List _blocks = new List(); + + /// + /// Position counter + /// + private int _position = 0; + + /// + /// Object to keep track of loops - this assumes there is only one loop at a time + /// + private List> _loopCounter = new List>(); + + #region Construction + + private DatacorderDevice _datacorder; + + public TzxSerializer(DatacorderDevice _tapeDevice) + { + _datacorder = _tapeDevice; + } + + #endregion + + /// + /// DeSerialization method + /// + /// + public override void DeSerialize(byte[] data) + { + // clear existing tape blocks + _datacorder.DataBlocks.Clear(); + +/* + // TZX Header + length: 10 bytes + Offset Value Type Description + 0x00 "ZXTape!" ASCII[7] TZX signature + 0x07 0x1A BYTE End of text file marker + 0x08 1 BYTE TZX major revision number + 0x09 20 BYTE TZX minor revision number +*/ + + // check whether this is a valid tzx format file by looking at the identifier in the header + // (first 7 bytes of the file) + string ident = Encoding.ASCII.GetString(data, 0, 7); + // and 'end of text' marker + byte eotm = data[7]; + + // version info + int majorVer = data[8]; + int minorVer = data[9]; + + if (ident != "ZXTape!" || eotm != 0x1A) + { + // this is not a valid TZX format file + throw new Exception(this.GetType().ToString() + + "This is not a valid TZX format file"); + } + + // iterate through each block + _position = 10; + while (_position < data.Length) + { + // block ID is the first byte in a new block + int ID = data[_position++]; + + // process the data + ProcessBlock(data, ID); + } + + } + + /// + /// Processes a TZX block + /// + /// + /// + private void ProcessBlock(byte[] data, int id) + { + // process based on detected block ID + switch (id) + { + // ID 10 - Standard Speed Data Block + case 0x10: + ProcessBlockID10(data); + break; + // ID 11 - Turbo Speed Data Block + case 0x11: + ProcessBlockID11(data); + break; + // ID 12 - Pure Tone + case 0x12: + ProcessBlockID12(data); + break; + // ID 13 - Pulse sequence + case 0x13: + ProcessBlockID13(data); + break; + // ID 14 - Pure Data Block + case 0x14: + ProcessBlockID14(data); + break; + // ID 15 - Direct Recording + case 0x15: + ProcessBlockID15(data); + break; + // ID 18 - CSW Recording + case 0x18: + ProcessBlockID18(data); + break; + // ID 19 - Generalized Data Block + case 0x19: + ProcessBlockID19(data); + break; + // ID 20 - Pause (silence) or 'Stop the Tape' command + case 0x20: + ProcessBlockID20(data); + break; + // ID 21 - Group start + case 0x21: + ProcessBlockID21(data); + break; + // ID 22 - Group end + case 0x22: + ProcessBlockID22(data); + break; + // ID 23 - Jump to block + case 0x23: + ProcessBlockID23(data); + break; + // ID 24 - Loop start + case 0x24: + ProcessBlockID24(data); + break; + // ID 25 - Loop end + case 0x25: + ProcessBlockID25(data); + break; + // ID 26 - Call sequence + case 0x26: + ProcessBlockID26(data); + break; + // ID 27 - Return from sequence + case 0x27: + ProcessBlockID27(data); + break; + // ID 28 - Select block + case 0x28: + ProcessBlockID28(data); + break; + // ID 2A - Stop the tape if in 48K mode + case 0x2A: + ProcessBlockID2A(data); + break; + // ID 2B - Set signal level + case 0x2B: + ProcessBlockID2B(data); + break; + // ID 30 - Text description + case 0x30: + ProcessBlockID30(data); + break; + // ID 31 - Message block + case 0x31: + ProcessBlockID31(data); + break; + // ID 32 - Archive info + case 0x32: + ProcessBlockID32(data); + break; + // ID 33 - Hardware type + case 0x33: + ProcessBlockID33(data); + break; + // ID 35 - Custom info block + case 0x35: + ProcessBlockID35(data); + break; + // ID 5A - "Glue" block + case 0x5A: + ProcessBlockID5A(data); + break; + + #region Depreciated Blocks + + // ID 16 - C64 ROM Type Data Block + case 0x16: + ProcessBlockID16(data); + break; + // ID 17 - C64 Turbo Tape Data Block + case 0x17: + ProcessBlockID17(data); + break; + // ID 34 - Emulation info + case 0x34: + ProcessBlockID34(data); + break; + // ID 40 - Snapshot block + case 0x40: + ProcessBlockID40(data); + break; + + #endregion + + default: + ProcessUnidentifiedBlock(data); + break; + } + } + + #region TZX Block Processors + + #region ID 10 - Standard Speed Data Block +/* length: [02,03]+04 + Offset Value Type Description + 0x00 - WORD Pause after this block (ms.) {1000} + 0x02 N WORD Length of data that follow + 0x04 - BYTE[N] Data as in .TAP files + + This block must be replayed with the standard Spectrum ROM timing values - see the values in + curly brackets in block ID 11. The pilot tone consists in 8063 pulses if the first data byte + (flag byte) is < 128, 3223 otherwise. This block can be used for the ROM loading routines AND + for custom loading routines that use the same timings as ROM ones do. */ + private void ProcessBlockID10(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x10; + t.BlockDescription = BlockType.Standard_Speed_Data_Block; + t.DataPeriods = new List(); + + int pauseLen = GetWordValue(data, _position); + int blockLen = GetWordValue(data, _position + 2); + + _position += 4; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Standard, 1000); + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 11 - Turbo Speed Data Block +/* length: [0F,10,11]+12 + Offset Value Type Description + 0x00 - WORD Length of PILOT pulse {2168} + 0x02 - WORD Length of SYNC first pulse {667} + 0x04 - WORD Length of SYNC second pulse {735} + 0x06 - WORD Length of ZERO bit pulse {855} + 0x08 - WORD Length of ONE bit pulse {1710} + 0x0A - WORD Length of PILOT tone (number of pulses) {8063 header (flag<128), 3223 data (flag>=128)} + 0x0C - BYTE Used bits in the last byte (other bits should be 0) {8} + (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00, + where MSb is the leftmost bit, LSb is the rightmost bit) + 0x0D - WORD Pause after this block (ms.) {1000} + 0x0F N BYTE[3] Length of data that follow + 0x12 - BYTE[N] Data as in .TAP files + + This block is very similar to the normal TAP block but with some additional info on the timings and other important + differences. The same tape encoding is used as for the standard speed data block. If a block should use some non-standard + sync or pilot tones (i.e. all sorts of protection schemes) then use the next three blocks to describe it.*/ + private void ProcessBlockID11(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x11; + t.BlockDescription = BlockType.Turbo_Speed_Data_Block; + t.DataPeriods = new List(); + + int pilotPL = GetWordValue(data, _position); + int sync1P = GetWordValue(data, _position + 2); + int sync2P = GetWordValue(data, _position + 4); + int bit0P = GetWordValue(data, _position + 6); + int bit1P = GetWordValue(data, _position + 8); + int pilotTL = GetWordValue(data, _position + 10); + int bitinbyte = data[_position + 12]; + int pause = GetWordValue(data, _position + 13); + + int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x0F); + + _position += 0x12; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Turbo, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte); + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 12 - Pure Tone +/* length: 04 + Offset Value Type Description + 0x00 - WORD Length of one pulse in T-states + 0x02 - WORD Number of pulses + + This will produce a tone which is basically the same as the pilot tone in the ID 10, ID 11 blocks. You can define how + long the pulse is and how many pulses are in the tone. */ + private void ProcessBlockID12(byte[] data) + { + int blockLen = 4; + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x12; + t.BlockDescription = BlockType.Pure_Tone; + t.DataPeriods = new List(); + + // get values + int pulseLength = GetWordValue(data, _position); + int pulseCount = GetWordValue(data, _position + 2); + + t.AddMetaData(BlockDescriptorTitle.Pulse_Length, pulseLength.ToString() + " T-States"); + t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString()); + + // build period information + for (int p = 0; p < pulseCount; p++) + { + t.DataPeriods.Add(pulseLength); + } + + // add the block + _datacorder.DataBlocks.Add(t); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 13 - Pulse sequence +/* length: [00]*02+01 + Offset Value Type Description + 0x00 N BYTE Number of pulses + 0x01 - WORD[N] Pulses' lengths + + This will produce N pulses, each having its own timing. Up to 255 pulses can be stored in this block; this is useful for non-standard + sync tones used by some protection schemes. */ + private void ProcessBlockID13(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x13; + t.BlockDescription = BlockType.Pulse_Sequence; + t.DataPeriods = new List(); + + // get pulse count + int pulseCount = data[_position]; + t.AddMetaData(BlockDescriptorTitle.Pulse_Count, pulseCount.ToString()); + _position++; + + // build period information + for (int p = 0; p < pulseCount; p++, _position += 2) + { + // get pulse length + int pulseLength = GetWordValue(data, _position); + t.AddMetaData(BlockDescriptorTitle.Needs_Parsing, "Pulse " + p + " Length\t" + pulseLength.ToString() + " T-States"); + t.DataPeriods.Add(pulseLength); + } + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 14 - Pure Data Block +/* length: [07,08,09]+0A + Offset Value Type Description + 0x00 - WORD Length of ZERO bit pulse + 0x02 - WORD Length of ONE bit pulse + 0x04 - BYTE Used bits in last byte (other bits should be 0) + (e.g. if this is 6, then the bits used (x) in the last byte are: xxxxxx00, + where MSb is the leftmost bit, LSb is the rightmost bit) + 0x05 - WORD Pause after this block (ms.) + 0x07 N BYTE[3] Length of data that follow + 0x0A - BYTE[N] Data as in .TAP files + + This is the same as in the turbo loading data block, except that it has no pilot or sync pulses. */ + private void ProcessBlockID14(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x14; + t.BlockDescription = BlockType.Pure_Data_Block; + t.DataPeriods = new List(); + + int pilotPL = 0; + int sync1P = 0; + int sync2P = 0; + int bit0P = GetWordValue(data, _position + 0); + int bit1P = GetWordValue(data, _position + 2); + int pilotTL = 0; + int bitinbyte = data[_position + 4]; + int pause = GetWordValue(data, _position + 5); + + int blockLen = 0xFFFFFF & GetInt32(data, _position + 0x07); + + _position += 0x0A; + + byte[] tmp = new byte[blockLen]; + tmp = data.Skip(_position).Take(blockLen).ToArray(); + + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Pure, pause, pilotTL, pilotPL, sync1P, sync2P, bit0P, bit1P, bitinbyte); + + // add the block + _datacorder.DataBlocks.Add(t2); + + // advance the position to the next block + _position += blockLen; + } + #endregion + + #region ID 15 - Direct Recording +/* length: [05,06,07]+08 + Offset Value Type Description + 0x00 - WORD Number of T-states per sample (bit of data) + 0x02 - WORD Pause after this block in milliseconds (ms.) + 0x04 - BYTE Used bits (samples) in last byte of data (1-8) + (e.g. if this is 2, only first two samples of the last byte will be played) + 0x05 N BYTE[3] Length of samples' data + 0x08 - BYTE[N] Samples data. Each bit represents a state on the EAR port (i.e. one sample). + MSb is played first. + + This block is used for tapes which have some parts in a format such that the turbo loader block cannot be used. + This is not like a VOC file, since the information is much more compact. Each sample value is represented by one bit only + (0 for low, 1 for high) which means that the block will be at most 1/8 the size of the equivalent VOC. + The preferred sampling frequencies are 22050 or 44100 Hz (158 or 79 T-states/sample). + Please, if you can, don't use other sampling frequencies. + Please use this block only if you cannot use any other block. */ + private void ProcessBlockID15(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x15; + t.BlockDescription = BlockType.Direct_Recording; + t.DataPeriods = new List(); + + // get values + int samLen = GetInt32(data, _position + 5); + int samSize = 0xFFFFFF & samLen; + + int tStatesPerSample = GetWordValue(data, _position); + int pauseAfterBlock = GetWordValue(data, _position + 2); + int usedBitsInLastByte = data[_position + 4]; + + // skip to samples data + _position += 8; + + int pulseLength = 0; + int pulseCount = 0; + + // ascertain the pulse count + for (int i = 0; i < samSize; i++) + { + for (int p = 0x80; p != 0; p >>= 1) + { + if (((data[_position + i] ^ pulseLength) & p) != 0) + { + pulseCount++; + pulseLength ^= -1; + } + } + } + + // get the pulses + t.DataPeriods = new List(pulseCount + 2); + int tStateCount = 0; + pulseLength = 0; + for (int i = 1; i < samSize; i++) + { + for (int p = 0x80; p != 0; p >>= 1) + { + tStateCount += tStatesPerSample; + if (((data[_position] ^ pulseLength) & p) != 0) + { + t.DataPeriods.Add(tStateCount); + pulseLength ^= -1; + tStateCount = 0; + } + } + + // incrememt position + _position++; + } + + // get the pulses in the last byte of data + for (int p = 0x80; p != (byte)(0x80 >> usedBitsInLastByte); p >>= 1) + { + tStateCount += tStatesPerSample; + if (((data[_position] ^ pulseLength) & p) != 0) + { + t.DataPeriods.Add(tStateCount); + pulseLength ^= -1; + tStateCount = 0; + } + } + + // add final pulse + t.DataPeriods.Add(tStateCount); + + // add end of block pause + if (pauseAfterBlock > 0) + { + t.DataPeriods.Add(3500 * pauseAfterBlock); + } + + // increment position + _position++; + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 18 - CSW Recording +/* length: [00,01,02,03]+04 + Offset Value Type Description + 0x00 10+N DWORD Block length (without these four bytes) + 0x04 - WORD Pause after this block (in ms). + 0x06 - BYTE[3] Sampling rate + 0x09 - BYTE Compression type + 0x01: RLE + 0x02: Z-RLE + 0x0A - DWORD Number of stored pulses (after decompression, for validation purposes) + 0x0E - BYTE[N] CSW data, encoded according to the CSW file format specification. + + This block contains a sequence of raw pulses encoded in CSW format v2 (Compressed Square Wave). */ + private void ProcessBlockID18(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x18; + t.BlockDescription = BlockType.CSW_Recording; + t.DataPeriods = new List(); + + // add the block + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 19 - Generalized Data Block +/* length: [00,01,02,03]+04 + Offset Value Type Description + 0x00 - DWORD Block length (without these four bytes) + 0x04 - WORD Pause after this block (ms) + 0x06 TOTP DWORD Total number of symbols in pilot/sync block (can be 0) + 0x0A NPP BYTE Maximum number of pulses per pilot/sync symbol + 0x0B ASP BYTE Number of pilot/sync symbols in the alphabet table (0=256) + 0x0C TOTD DWORD Total number of symbols in data stream (can be 0) + 0x10 NPD BYTE Maximum number of pulses per data symbol + 0x11 ASD BYTE Number of data symbols in the alphabet table (0=256) + 0x12 - SYMDEF[ASP] Pilot and sync symbols definition table + This field is present only if TOTP>0 + 0x12+ + (2*NPP+1)*ASP - PRLE[TOTP] Pilot and sync data stream + This field is present only if TOTP>0 + 0x12+ + (TOTP>0)*((2*NPP+1)*ASP)+ + TOTP*3 - SYMDEF[ASD] Data symbols definition table + This field is present only if TOTD>0 + 0x12+ + (TOTP>0)*((2*NPP+1)*ASP)+ + TOTP*3+ + (2*NPD+1)*ASD - BYTE[DS] Data stream + This field is present only if TOTD>0 + + This block has been specifically developed to represent an extremely wide range of data encoding techniques. + The basic idea is that each loading component (pilot tone, sync pulses, data) is associated to a specific sequence + of pulses, where each sequence (wave) can contain a different number of pulses from the others. + In this way we can have a situation where bit 0 is represented with 4 pulses and bit 1 with 8 pulses. + + ---- + SYMDEF structure format + Offset Value Type Description + 0x00 - BYTE Symbol flags + b0-b1: starting symbol polarity + 00: opposite to the current level (make an edge, as usual) - default + 01: same as the current level (no edge - prolongs the previous pulse) + 10: force low level + 11: force high level + 0x01 - WORD[MAXP] Array of pulse lengths. + + The alphabet is stored using a table where each symbol is a row of pulses. The number of columns (i.e. pulses) of the table is the + length of the longest sequence amongst all (MAXP=NPP or NPD, for pilot/sync or data blocks respectively); shorter waves are terminated by a + zero-length pulse in the sequence. + Any number of data symbols is allowed, so we can have more than two distinct waves; for example, imagine a loader which writes two bits at a + time by encoding them with four distinct pulse lengths: this loader would have an alphabet of four symbols, each associated to a specific + sequence of pulses (wave). + ---- + ---- + PRLE structure format + Offset Value Type Description + 0x00 - BYTE Symbol to be represented + 0x01 - WORD Number of repetitions + + Most commonly, pilot and sync are repetitions of the same pulse, thus they are represented using a very simple RLE encoding structure which stores + the symbol and the number of times it must be repeated. + Each symbol in the data stream is represented by a string of NB bits of the block data, where NB = ceiling(Log2(ASD)). + Thus the length of the whole data stream in bits is NB*TOTD, or in bytes DS=ceil(NB*TOTD/8). + ---- */ + private void ProcessBlockID19(byte[] data) + { + string test = "dgfg"; + } + #endregion + + #region ID 20 - Pause (silence) or 'Stop the Tape' command +/* length: 02 + Offset Value Type Description + 0x00 - WORD Pause duration (ms.) + + This will make a silence (low amplitude level (0)) for a given time in milliseconds. If the value is 0 then the + emulator or utility should (in effect) STOP THE TAPE, i.e. should not continue loading until the user or emulator requests it. */ + private void ProcessBlockID20(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x20; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Pause_or_Stop_the_Tape; + + int pauseDuration = GetWordValue(data, _position); + if (pauseDuration != 0) + { + //t.BlockDescription = "Pause: " + pauseDuration + " ms"; + } + else + { + //t.BlockDescription = "[STOP THE TAPE]"; + } + + if (pauseDuration == 0) + { + // issue stop the tape command + t.Command = TapeCommand.STOP_THE_TAPE; + // add 1ms period + t.DataPeriods.Add(3500); + pauseDuration = -1; + } + else + { + // this is actually just a pause + pauseDuration = 3500 * pauseDuration; + } + + // add end of block pause + t.DataPeriods.Add(pauseDuration); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advanced position to next block + _position += 2; + } + #endregion + + #region ID 21 - Group start +/* length: [00]+01 + Offset Value Type Description + 0x00 L BYTE Length of the group name string + 0x01 - CHAR[L] Group name in ASCII format (please keep it under 30 characters long) + + This block marks the start of a group of blocks which are to be treated as one single (composite) block. + This is very handy for tapes that use lots of subblocks like Bleepload (which may well have over 160 custom loading blocks). + You can also give the group a name (example 'Bleepload Block 1'). + For each group start block, there must be a group end block. Nesting of groups is not allowed. */ + private void ProcessBlockID21(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x21; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Group_Start; + + int nameLength = data[_position]; + _position++; + + string name = Encoding.ASCII.GetString(data, _position, nameLength); + //t.BlockDescription = "[GROUP: " + name + "]"; + t.Command = TapeCommand.BEGIN_GROUP; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += nameLength; + } + #endregion + + #region ID 22 - Group end +/* length: 00 + + This indicates the end of a group. This block has no body. */ + private void ProcessBlockID22(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x22; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Group_End; + t.Command = TapeCommand.END_GROUP; + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 23 - Jump to block +/* length: 02 + Offset Value Type Description + 0x00 - WORD Relative jump value + + This block will enable you to jump from one block to another within the file. The value is a signed short word + (usually 'signed short' in C); Some examples: + Jump 0 = 'Loop Forever' - this should never happen + Jump 1 = 'Go to the next block' - it is like NOP in assembler ;) + Jump 2 = 'Skip one block' + Jump -1 = 'Go to the previous block' + All blocks are included in the block count!. */ + private void ProcessBlockID23(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x23; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Jump_to_Block; + + int relativeJumpValue = GetWordValue(data, _position); + string result = string.Empty; + + switch(relativeJumpValue) + { + case 0: + result = "Loop Forever"; + break; + case 1: + result = "To Next Block"; + break; + case 2: + result = "Skip One Block"; + break; + case -1: + result = "Go to Previous Block"; + break; + } + + //t.BlockDescription = "[JUMP BLOCK - " + result +"]"; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 2; + } + #endregion + + #region ID 24 - Loop start +/* length: 02 + Offset Value Type Description + 0x00 - WORD Number of repetitions (greater than 1) + + If you have a sequence of identical blocks, or of identical groups of blocks, you can use this block to tell how many times they should + be repeated. This block is the same as the FOR statement in BASIC. + For simplicity reasons don't nest loop blocks! */ + private void ProcessBlockID24(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x24; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Loop_Start; + + // loop should start from the next block + int loopStart = _datacorder.DataBlocks.Count() + 1; + + int numberOfRepetitions = GetWordValue(data, _position); + + // update loop counter + _loopCounter.Add( + new KeyValuePair( + loopStart, + numberOfRepetitions)); + + // update description + //t.BlockDescription = "[LOOP START - " + numberOfRepetitions + " times]"; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 2; + } + #endregion + + #region ID 25 - Loop end +/* length: 00 + + This is the same as BASIC's NEXT statement. It means that the utility should jump back to the start of the loop if it hasn't + been run for the specified number of times. + This block has no body. */ + private void ProcessBlockID25(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x25; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Loop_End; + + // get the most recent loop info + var loop = _loopCounter.LastOrDefault(); + + int loopStart = loop.Key; + int numberOfRepetitions = loop.Value; + + if (numberOfRepetitions == 0) + { + return; + } + + // get the number of blocks to loop + int blockCnt = _datacorder.DataBlocks.Count() - loopStart; + + // loop through each group to repeat + for (int b = 0; b < numberOfRepetitions; b++) + { + TapeDataBlock repeater = new TapeDataBlock(); + //repeater.BlockDescription = "[LOOP REPEAT - " + (b + 1) + "]"; + repeater.DataPeriods = new List(); + + // add the repeat block + _datacorder.DataBlocks.Add(repeater); + + // now iterate through and add the blocks to be repeated + for (int i = 0; i < blockCnt; i++) + { + var block = _datacorder.DataBlocks[loopStart + i]; + _datacorder.DataBlocks.Add(block); + } + } + } + #endregion + + #region ID 26 - Call sequence +/* length: [00,01]*02+02 + Offset Value Type Description + 0x00 N WORD Number of calls to be made + 0x02 - WORD[N] Array of call block numbers (relative-signed offsets) + + This block is an analogue of the CALL Subroutine statement. It basically executes a sequence of blocks that are somewhere else and + then goes back to the next block. Because more than one call can be normally used you can include a list of sequences to be called. + The 'nesting' of call blocks is also not allowed for the simplicity reasons. You can, of course, use the CALL blocks in the LOOP + sequences and vice versa. The value is relative for the obvious reasons - so that you can add some blocks in the beginning of the + file without disturbing the call values. Please take a look at 'Jump To Block' for reference on the values. */ + private void ProcessBlockID26(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x26; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Call_Sequence; + + int blockSize = 2 + 2 * GetWordValue(data, _position); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 27 - Return from sequence +/* length: 00 + + This block indicates the end of the Called Sequence. The next block played will be the block after the last CALL block (or the next Call, + if the Call block had multiple calls). + Again, this block has no body. */ + private void ProcessBlockID27(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x27; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Return_From_Sequence; + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 28 - Select block +/* length: [00,01]+02 + Offset Value Type Description + 0x00 - WORD Length of the whole block (without these two bytes) + 0x02 N BYTE Number of selections + 0x03 - SELECT[N] List of selections + + ---- + SELECT structure format + Offset Value Type Description + 0x00 - WORD Relative Offset + 0x02 L BYTE Length of description text + 0x03 - CHAR[L] Description text (please use single line and max. 30 chars) + ---- + + This block is useful when the tape consists of two or more separately-loadable parts. With this block, you are able to select + one of the parts and the utility/emulator will start loading from that block. For example you can use it when the game has a + separate Trainer or when it is a multiload. Of course, to make some use of it the emulator/utility has to show a menu with the + selections when it encounters such a block. All offsets are relative signed words. */ + private void ProcessBlockID28(byte[] data) + { + // block processing not implemented for this - just gets added for informational purposes only + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x28; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Select_Block; + + int blockSize = 2 + GetWordValue(data, _position); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 2A - Stop the tape if in 48K mode +/* length: 04 + Offset Value Type Description + 0x00 0 DWORD Length of the block without these four bytes (0) + + When this block is encountered, the tape will stop ONLY if the machine is an 48K Spectrum. This block is to be used for + multiloading games that load one level at a time in 48K mode, but load the entire tape at once if in 128K mode. + This block has no body of its own, but follows the extension rule. */ + private void ProcessBlockID2A(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x2A; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Stop_the_Tape_48K; + t.Command = TapeCommand.STOP_THE_TAPE_48K; + + int blockSize = 4 + GetWordValue(data, _position); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockSize; + } + #endregion + + #region ID 2B - Set signal level +/* length: 05 + Offset Value Type Description + 0x00 1 DWORD Block length (without these four bytes) + 0x04 - BYTE Signal level (0=low, 1=high) + + This block sets the current signal level to the specified value (high or low). It should be used whenever it is necessary to avoid any + ambiguities, e.g. with custom loaders which are level-sensitive. */ + private void ProcessBlockID2B(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x2B; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Set_Signal_Level; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 5; + } + #endregion + + #region ID 30 - Text description +/* length: [00]+01 + Offset Value Type Description + 0x00 N BYTE Length of the text description + 0x01 - CHAR[N] Text description in ASCII format + + This is meant to identify parts of the tape, so you know where level 1 starts, where to rewind to when the game ends, etc. + This description is not guaranteed to be shown while the tape is playing, but can be read while browsing the tape or changing + the tape pointer. + The description can be up to 255 characters long but please keep it down to about 30 so the programs can show it in one line + (where this is appropriate). + Please use 'Archive Info' block for title, authors, publisher, etc. */ + private void ProcessBlockID30(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x30; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Text_Description; + + int textLen = data[_position]; + _position++; + + string desc = Encoding.ASCII.GetString(data, _position, textLen); + + //t.BlockDescription = "[" + desc + "]"; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += textLen; + } + #endregion + + #region ID 31 - Message block +/* length: [01]+02 + Offset Value Type Description + 0x00 - BYTE Time (in seconds) for which the message should be displayed + 0x01 N BYTE Length of the text message + 0x02 - CHAR[N] Message that should be displayed in ASCII format + + This will enable the emulators to display a message for a given time. This should not stop the tape and it should not make silence. + If the time is 0 then the emulator should wait for the user to press a key. + The text message should: + stick to a maximum of 30 chars per line; + use single 0x0D (13 decimal) to separate lines; + stick to a maximum of 8 lines. + If you do not obey these rules, emulators may display your message in any way they like. */ + private void ProcessBlockID31(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x31; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Message_Block; + + _position++; + + int msgLen = data[_position]; + _position++; + + string desc = Encoding.ASCII.GetString(data, _position, msgLen); + + t.Command = TapeCommand.SHOW_MESSAGE; + + //t.BlockDescription = "[MESSAGE: " + desc + "]"; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += msgLen; + } + #endregion + + #region ID 32 - Archive info +/* length: [00,01]+02 + Offset Value Type Description + 0x00 - WORD Length of the whole block (without these two bytes) + 0x02 N BYTE Number of text strings + 0x03 - TEXT[N] List of text strings + + ---- + TEXT structure format + Offset Value Type Description + 0x00 - BYTE Text identification byte: + 00 - Full title + 01 - Software house/publisher + 02 - Author(s) + 03 - Year of publication + 04 - Language + 05 - Game/utility type + 06 - Price + 07 - Protection scheme/loader + 08 - Origin + FF - Comment(s) + 0x01 L BYTE Length of text string + 0x02 - CHAR[L] Text string in ASCII format + ---- + + Use this block at the beginning of the tape to identify the title of the game, author, publisher, year of publication, price (including + the currency), type of software (arcade adventure, puzzle, word processor, ...), protection scheme it uses (Speedlock 1, Alkatraz, ...) + and its origin (Original, Budget re-release, ...), etc. This block is built in a way that allows easy future expansion. + The block consists of a series of text strings. Each text has its identification number (which tells us what the text means) and then + the ASCII text. To make it possible to skip this block, if needed, the length of the whole block is at the beginning of it. + If all texts on the tape are in English language then you don't have to supply the 'Language' field + The information about what hardware the tape uses is in the 'Hardware Type' block, so no need for it here. */ + private void ProcessBlockID32(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x32; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Archive_Info; + + int blockLen = GetWordValue(data, 0); + _position += 2; + int stringCount = data[_position++]; + + // iterate through each string + for (int s = 0; s < stringCount; s++) + { + // identify the type of text + int type = data[_position++]; + + // get text length + int strLen = data[_position++]; + + string title = "Info: "; + + switch (type) + { + case 0x00: + title = "Full Title: "; + break; + case 0x01: + title = "Software House/Publisher: "; + break; + case 0x02: + title = "Author(s): "; + break; + case 0x03: + title = "Year of Publication: "; + break; + case 0x04: + title = "Language: "; + break; + case 0x05: + title = "Game/Utility Type: "; + break; + case 0x06: + title = "Price: "; + break; + case 0x07: + title = "Protection Scheme/Loader: "; + break; + case 0x08: + title = "Origin: "; + break; + case 0xFF: + title = "Comment(s): "; + break; + default: + break; + } + + // add title to description + //t.BlockDescription += title; + + // get string data + string val = Encoding.ASCII.GetString(data, _position, strLen); + //t.BlockDescription += val + " \n"; + + // advance to next string block + _position += strLen; + } + + // add to tape + _datacorder.DataBlocks.Add(t); + } + #endregion + + #region ID 33 - Hardware type +/* length: [00]*03+01 + Offset Value Type Description + 0x00 N BYTE Number of machines and hardware types for which info is supplied + 0x01 - HWINFO[N] List of machines and hardware + + ---- + HWINFO structure format + Offset Value Type Description + 0x00 - BYTE Hardware type + 0x01 - BYTE Hardware ID + 0x02 - BYTE Hardware information: + 00 - The tape RUNS on this machine or with this hardware, + but may or may not use the hardware or special features of the machine. + 01 - The tape USES the hardware or special features of the machine, + such as extra memory or a sound chip. + 02 - The tape RUNS but it DOESN'T use the hardware + or special features of the machine. + 03 - The tape DOESN'T RUN on this machine or with this hardware. + ---- + + This blocks contains information about the hardware that the programs on this tape use. Please include only machines and hardware for + which you are 100% sure that it either runs (or doesn't run) on or with, or you know it uses (or doesn't use) the hardware or special + features of that machine. + If the tape runs only on the ZX81 (and TS1000, etc.) then it clearly won't work on any Spectrum or Spectrum variant, so there's no + need to list this information. + If you are not sure or you haven't tested a tape on some particular machine/hardware combination then do not include it in the list. + The list of hardware types and IDs is somewhat large, and may be found at the end of the format description. */ + private void ProcessBlockID33(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x33; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Hardware_Type; + + _position += 2; + int blockLen = GetWordValue(data, 0); + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #region ID 35 - Custom info block +/* length: [10,11,12,13]+14 + Offset Value Type Description + 0x00 - CHAR[10] Identification string (in ASCII) + 0x10 L DWORD Length of the custom info + 0x14 - BYTE[L] Custom info + + This block can be used to save any information you want. For example, it might contain some information written by a utility, + extra settings required by a particular emulator, or even poke data. */ + private void ProcessBlockID35(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x35; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Custom_Info_Block; + + string info = Encoding.ASCII.GetString(data, _position, 0x10); + //t.BlockDescription = "[CUSTOM INFO: " + info + "]"; + _position += 0x10; + + int blockLen = BitConverter.ToInt32(data, _position); + _position += 4; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #region ID 5A - "Glue" block +/* length: 09 + Offset Value Type Description + 0x00 - BYTE[9] Value: { "XTape!",0x1A,MajR,MinR } + Just skip these 9 bytes and you will end up on the next ID. + + This block is generated when you merge two ZX Tape files together. It is here so that you can easily copy the files together and use + them. Of course, this means that resulting file would be 10 bytes longer than if this block was not used. All you have to do + if you encounter this block ID is to skip next 9 bytes. + If you can avoid using this block for this purpose, then do so; it is preferable to use a utility to join the two files and + ensure that they are both of the higher version number. */ + private void ProcessBlockID5A(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x5A; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Glue_Block; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 9; + } + #endregion + + #region UnDetected Blocks + + private void ProcessUnidentifiedBlock(byte[] data) + { + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = -2; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Unsupported; + //t.BlockDescription = "[UNSUPPORTED - 0x" + data[_position - 1] + "]"; + + _position += GetInt32(data, _position) & 0xFFFFFF; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 4; + } + + #endregion + + #region Depreciated Blocks + + // These mostly should be ignored by ZXHawk - here for completeness + + #region ID 16 - C64 ROM Type Data Block + private void ProcessBlockID16(byte[] data) + { + + } + #endregion + + #region ID 17 - C64 Turbo Tape Data Block + private void ProcessBlockID17(byte[] data) + { + + } + #endregion + + #region ID 34 - Emulation info + private void ProcessBlockID34(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x34; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Emulation_Info; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += 8; + } + #endregion + + #region ID 40 - Snapshot block + /* length: [01,02,03]+04 + Offset Value Type Description + 0x00 - BYTE Snapshot type: + 00: .Z80 format + 01: .SNA format + 0x01 L BYTE[3] Snapshot length + 0x04 - BYTE[L] Snapshot itself + + This would enable one to snapshot the game at the start and still have all the tape blocks (level data, etc.) in the same file. + Only .Z80 and .SNA snapshots are supported for compatibility reasons! + The emulator should take care of that the snapshot is not taken while the actual Tape loading is taking place (which doesn't do much sense). + And when an emulator encounters the snapshot block it should load it and then continue with the next block. */ + private void ProcessBlockID40(byte[] data) + { + // currently not implemented properly in ZXHawk + + TapeDataBlock t = new TapeDataBlock(); + t.BlockID = 0x40; + t.DataPeriods = new List(); + t.BlockDescription = BlockType.Snapshot_Block; + + _position++; + + int blockLen = data[_position] | + data[_position + 1] << 8 | + data[_position + 2] << 16; + _position += 3; + + // add to tape + _datacorder.DataBlocks.Add(t); + + // advance to next block + _position += blockLen; + } + #endregion + + #endregion + + #endregion + + #region DataBlockDecoder + + /// + /// Used to process either a standard or turbo data block + /// + /// + /// + /// + private TapeDataBlock DecodeDataBlock + ( + TapeDataBlock block, + byte[] blockdata, + DataBlockType dataBlockType, + int pauseAfterBlock, + int pilotCount, + + int pilotToneLength = 2168, + int sync1PulseLength = 667, + int sync2PulseLength = 735, + int bit0PulseLength = 855, + int bit1PulseLength = 1710, + int bitsInLastByte = 8 + ) + { + // first get the block description + string description = string.Empty; + + // process the type byte + /* (The type is 0,1,2 or 3 for a Program, Number array, Character array or Code file. + A SCREEN$ file is regarded as a Code file with start address 16384 and length 6912 decimal. + If the file is a Program file, parameter 1 holds the autostart line number (or a number >=32768 if no LINE parameter was given) + and parameter 2 holds the start of the variable area relative to the start of the program. If it's a Code file, parameter 1 holds + the start of the code block when saved, and parameter 2 holds 32768. For data files finally, the byte at position 14 decimal holds the variable name.) + */ + + int blockSize = blockdata.Length; + + // dont get description info for Pure Data Blocks + if (dataBlockType != DataBlockType.Pure) + { + if (blockdata[0] == 0x00 && blockSize == 19 && (blockdata[1] == 0x00) || blockdata[1] == 3) + { + // This is the program header + string fileName = Encoding.ASCII.GetString(blockdata.Skip(2).Take(10).ToArray()).Trim(); + + string type = ""; + if (blockdata[0] == 0x00) + { + type = "Program"; + block.AddMetaData(BlockDescriptorTitle.Program, fileName); + } + else + { + type = "Bytes"; + block.AddMetaData(BlockDescriptorTitle.Bytes, fileName); + } + + // now build the description string + StringBuilder sb = new StringBuilder(); + sb.Append(type + ": "); + sb.Append(fileName + " "); + sb.Append(GetWordValue(blockdata, 14)); + sb.Append(":"); + sb.Append(GetWordValue(blockdata, 12)); + description = sb.ToString(); + } + else if (blockdata[0] == 0xFF) + { + // this is a data block + description = "Data Block " + (blockSize - 2) + "bytes"; + block.AddMetaData(BlockDescriptorTitle.Data_Bytes, (blockSize - 2).ToString() + " Bytes"); + } + else + { + // other type + description = string.Format("#{0} block, {1} bytes", blockdata[0].ToString("X2"), blockSize - 2); + //description += string.Format(", crc {0}", ((crc != 0) ? string.Format("bad (#{0:X2}!=#{1:X2})", crcFile, crcValue) : "ok")); + block.AddMetaData(BlockDescriptorTitle.Undefined, description); + } + } + + // update metadata + switch (dataBlockType) + { + case DataBlockType.Standard: + case DataBlockType.Turbo: + + if (dataBlockType == DataBlockType.Standard) + block.BlockDescription = BlockType.Standard_Speed_Data_Block; + if (dataBlockType == DataBlockType.Turbo) + block.BlockDescription = BlockType.Turbo_Speed_Data_Block; + + block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Length, pilotToneLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Pilot_Pulse_Count, pilotCount.ToString() + " Pulses"); + block.AddMetaData(BlockDescriptorTitle.First_Sync_Length, sync1PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Second_Sync_Length, sync2PulseLength.ToString() + " T-States"); + break; + + case DataBlockType.Pure: + block.BlockDescription = BlockType.Pure_Data_Block; + break; + } + + block.AddMetaData(BlockDescriptorTitle.Zero_Bit_Length, bit0PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.One_Bit_Length, bit1PulseLength.ToString() + " T-States"); + block.AddMetaData(BlockDescriptorTitle.Data_Length, blockSize.ToString() + " Bytes"); + block.AddMetaData(BlockDescriptorTitle.Bits_In_Last_Byte, bitsInLastByte.ToString() + " Bits"); + block.AddMetaData(BlockDescriptorTitle.Pause_After_Data, pauseAfterBlock.ToString() + " ms"); + + // calculate period information + List dataPeriods = new List(); + + // generate pilot pulses + + if (pilotCount > 0) + { + for (int i = 0; i < pilotCount; i++) + { + dataPeriods.Add(pilotToneLength); + } + + // add syncro pulses + dataPeriods.Add(sync1PulseLength); + dataPeriods.Add(sync2PulseLength); + } + + int pos = 0; + + // add bit0 and bit1 periods + for (int i = 0; i < blockSize - 1; i++, pos++) + { + for (byte b = 0x80; b != 0; b >>= 1) + { + if ((blockdata[i] & b) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + if ((blockdata[i] & b) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + } + } + + // add the last byte + for (byte c = 0x80; c != (byte)(0x80 >> bitsInLastByte); c >>= 1) + { + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + if ((blockdata[pos] & c) != 0) + dataPeriods.Add(bit1PulseLength); + else + dataPeriods.Add(bit0PulseLength); + } + + // add block pause if pause is not 0 + if (pauseAfterBlock != 0) + { + int actualPause = pauseAfterBlock * 3500; + dataPeriods.Add(actualPause); + } + + // add to the tapedatablock object + block.DataPeriods = dataPeriods; + + // add the raw data + block.BlockData = blockdata; + + return block; + } + + /// + /// Used to process either a standard or turbo data block + /// + /// + /// + /// + private TapeDataBlock DecodeDataBlock + ( + TapeDataBlock block, + byte[] blockData, + DataBlockType dataBlockType, + int pauseAfterBlock, + + int pilotToneLength = 2168, + int sync1PulseLength = 667, + int sync2PulseLength = 735, + int bit0PulseLength = 855, + int bit1PulseLength = 1710, + int bitsInLastByte = 8 + ) + { + + // pilot count needs to be ascertained from flag byte + int pilotCount; + if (blockData[0] < 128) + pilotCount = 8063; + else + pilotCount = 3223; + + // now we can decode + var nBlock = DecodeDataBlock + ( + block, + blockData, + dataBlockType, + pauseAfterBlock, + pilotCount, + pilotToneLength, + sync1PulseLength, + sync2PulseLength, + bit0PulseLength, + bit1PulseLength, + bitsInLastByte + ); + + + return nBlock; + } + + #endregion + } + + public enum DataBlockType + { + Standard, + Turbo, + Pure + } +} From ec7445669c75d391119e421ed90a923eb84e422a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 15 Feb 2018 18:16:12 +0000 Subject: [PATCH 045/105] Fixed integer overflow bug in the tape device --- .../Hardware/Datacorder/DatacorderDevice.cs | 27 ++++++++++++++++++- .../Computers/SinclairSpectrum/readme.md | 5 ++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index ddc7598f03..0080872421 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -280,7 +280,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public bool GetEarBit(long cpuCycle) { // decide how many cycles worth of data we are capturing - int cycles = Convert.ToInt32(cpuCycle - _lastCycle); + long cycles = cpuCycle - _lastCycle; // check whether tape is actually playing if (_tapeIsPlaying == false) @@ -314,6 +314,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // we have reached the end of the current block + // check for any commands + var command = _dataBlocks[_currentDataBlockIndex].Command; + var block = _dataBlocks[_currentDataBlockIndex]; + switch (command) + { + // Stop the tape command found - if this is the end of the tape RTZ + // otherwise just STOP and move to the next block + case TapeCommand.STOP_THE_TAPE: + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + break; + case TapeCommand.STOP_THE_TAPE_48K: + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + break; + } + // skip any empty blocks while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count()) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index c0147c1d3a..afcf6b122f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -13,20 +13,19 @@ At this moment this is still *very* experimental and needs a lot more work. * Keyboard input (implementing IInputPollable) * Kempston joystick (mapped to J1 currently) * Tape device that will load spectrum games in realtime (*.tzx and *.tap) -* IStatable (although this is not currently working/implemented properly during tape load operations) +* IStatable * ISettable core settings * IMemoryDomains (I think) ### Work in progress * Exact emulator timings * Floating memory bus emulation +* Tape auto-loading routines (currently you have to manually start and stop the virtual tape device) ### Not working * IDebuggable * ZX Spectrum Plus3 emulation * Default keyboard keymappings (you have to configure yourself in the core controller settings) -* Manual tape device control (at the moment the tape device detects when the spectrum goes into 'loadbytes' mode and auto-plays the tape. This is not ideal and manual control should be implemented so the user can start/stop manually, return to zero etc..) -* Only standard spectrum tape blocks currently work. Any fancy SpeedLock encoded (and similar) blocks do not ### Known bugs * Audible 'popping' from the emulated buzzer after a load state operation From c8ea81bfd858224a7823a31505c05cbdd29244ea Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Feb 2018 08:49:41 +0000 Subject: [PATCH 046/105] Fixed off-by-one-tstate frame timing issue --- .../Computers/SinclairSpectrum/Machine/SpectrumBase.cs | 2 +- .../Computers/SinclairSpectrum/Machine/ULABase.cs | 8 ++++---- .../SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs | 1 + .../SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 43c0ec8b7a..11ec6ae96b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -123,7 +123,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum PollInput(); - while (CurrentFrameCycle <= ULADevice.FrameLength) // UlaFrameCycleCount) + while (CurrentFrameCycle < ULADevice.FrameLength) // UlaFrameCycleCount) { // check for interrupt ULADevice.CheckForInterrupt(CurrentFrameCycle); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index d6e3b1ef66..1604006525 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -254,7 +254,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The longest instruction cycle count /// - protected const int LONGEST_OP_CYCLES = 23; + protected int LongestOperationCycles = 23; /// /// Signs that an interrupt has been raised in this frame. @@ -288,19 +288,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return; } - if (currentCycle < InterruptPeriod) + if (currentCycle < LongestOperationCycles)// InterruptPeriod) { // interrupt does not need to be raised yet return; } - if (currentCycle > InterruptPeriod + LONGEST_OP_CYCLES) + if (currentCycle >= InterruptPeriod + LongestOperationCycles) { // 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; _machine.CPU.FlagI = false; - + return; } if (InterruptRaised) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs index d7cc8f4941..92789fa82c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -9,6 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum : base(machine) { InterruptPeriod = 36; + LongestOperationCycles = 23; FrameLength = 70908; ClockSpeed = 3546900; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs index a093a33aad..f72bc8bde1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -9,6 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum : base(machine) { InterruptPeriod = 32; + LongestOperationCycles = 23; FrameLength = 69888; ClockSpeed = 3500000; From a3dc506c06de18877ecbc288204012283b9db5d4 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Feb 2018 09:51:00 +0000 Subject: [PATCH 047/105] Another timing fix --- .../SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs | 2 +- .../SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 2 +- .../SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs index 92789fa82c..1ac1ccf326 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum : base(machine) { InterruptPeriod = 36; - LongestOperationCycles = 23; + LongestOperationCycles = 64 + 2; FrameLength = 70908; ClockSpeed = 3546900; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index e2723dab2d..36380c17b3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - ULADevice = new ULA48(this); + ULADevice = new ULA128(this); BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs index f72bc8bde1..61c22499ea 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum : base(machine) { InterruptPeriod = 32; - LongestOperationCycles = 23; + LongestOperationCycles = 64; FrameLength = 69888; ClockSpeed = 3500000; From 50d28c9627e80b0103bc91769d5eb7494129017f Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Feb 2018 10:14:02 +0000 Subject: [PATCH 048/105] file reorganisation and removal of obsolete stuff --- .../BizHawk.Emulation.Cores.csproj | 36 +- .../Hardware/DefaultTapeProvider.cs | 135 -- .../{Interfaces => Input}/IKeyboard.cs | 0 .../Hardware/{ => Input}/KempstonJoystick.cs | 0 .../Interfaces/ISaveToTapeProvider.cs | 35 - .../Interfaces/ISupportsTapeBlockPlayback.cs | 43 - .../ISupportsTapeBlockSetPlayback.cs | 21 - .../Interfaces/ITapeContentProvider.cs | 25 - .../Hardware/Interfaces/ITapeData.cs | 24 - .../Interfaces/ITapeDataSerialization.cs | 27 - .../Hardware/Interfaces/ITapeProvider.cs | 53 - .../{ => Hardware/Rom}/RomData.cs | 0 .../Hardware/{ => SoundOuput}/AY38912.cs | 0 .../Hardware/{ => SoundOuput}/Buzzer.cs | 0 .../{ => Hardware/SoundOuput}/Pulse.cs | 0 .../SinclairSpectrum/Hardware/Tape.cs | 814 ---------- .../Hardware/TapeBlockSetPlayer.cs | 143 -- .../Hardware/TapeDataBlockPlayer.cs | 279 ---- .../Hardware/TapeFilePlayer.cs | 128 -- .../Machine/SpectrumBase.Sound.cs | 17 - .../Media/Tape/TAP/TapDataBlock.cs | 105 -- .../Media/Tape/TAP/TapPlayer.cs | 96 -- .../Media/Tape/TAP/TapReader.cs | 52 - .../Media/Tape/TZX/BlockAbstraction.cs | 172 -- .../Media/Tape/TZX/DataBlocks.cs | 1433 ----------------- .../SinclairSpectrum/Media/Tape/TZX/Info.cs | 250 --- .../SinclairSpectrum/Media/Tape/TZX/Types.cs | 282 ---- .../Media/Tape/TZX/TzxException.cs | 28 - .../Media/Tape/TZX/TzxHeader.cs | 68 - .../Media/Tape/TZX/TzxPlayer.cs | 94 -- .../Media/Tape/TZX/TzxReader.cs | 125 -- .../Computers/SinclairSpectrum/readme.md | 10 +- 32 files changed, 12 insertions(+), 4483 deletions(-) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{Interfaces => Input}/IKeyboard.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{ => Input}/KempstonJoystick.cs (100%) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/{ => Hardware/Rom}/RomData.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{ => SoundOuput}/AY38912.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{ => SoundOuput}/Buzzer.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/{ => Hardware/SoundOuput}/Pulse.cs (100%) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index aefb1900ae..f108d03e54 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,22 +256,11 @@ - - + + - - - - - - - - - - - - - + + @@ -288,7 +277,6 @@ - @@ -298,20 +286,8 @@ - - - - - - - - - - - - - - + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs deleted file mode 100644 index 2ee8ef6f63..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/DefaultTapeProvider.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public class DefaultTapeProvider : ITapeProvider - { - public const string RESOURCE_FOLDER = "TzxResources"; - public const string DEFAULT_SAVE_FILE_DIR = @"C:\Temp\ZxSpectrumSavedFiles"; - public const string DEFAULT_NAME = "SavedFile"; - public const string DEFAULT_EXT = ".tzx"; - private string _suggestedName; - private string _fullFileName; - private int _dataBlockCount; - - private byte[] _file; - - /// - /// The directory files should be saved to - /// - public string SaveFileFolder { get; } - - - - public DefaultTapeProvider(byte[] file, string saveFolder = null) - { - SaveFileFolder = string.IsNullOrWhiteSpace(saveFolder) - ? DEFAULT_SAVE_FILE_DIR - : saveFolder; - - _file = file; - } - - /// - /// The component provider should be able to reset itself - /// - /// - - public void Reset() - { - _dataBlockCount = 0; - _suggestedName = null; - _fullFileName = null; - } - - - /// - /// Tha tape set to load the content from - /// - public string TapeSetName { get; set; } - - /// - /// Gets a binary reader that provider TZX content - /// - /// BinaryReader instance to obtain the content from - public BinaryReader GetTapeContent() - { - Stream stream = new MemoryStream(_file); - var reader = new BinaryReader(stream); - return reader; - } - - /// - /// Creates a tape file with the specified name - /// - /// - public void CreateTapeFile() - { - //Reset(); - } - - /// - /// This method sets the name of the file according to the - /// Spectrum SAVE HEADER information - /// - /// - public void SetName(string name) - { - _suggestedName = name; - } - - /// - /// Appends the TZX block to the tape file - /// - /// - public void SaveTapeBlock(ITapeDataSerialization block) - { - if (_dataBlockCount == 0) - { - if (!Directory.Exists(SaveFileFolder)) - { - Directory.CreateDirectory(SaveFileFolder); - } - var baseFileName = $"{_suggestedName ?? DEFAULT_NAME}_{DateTime.Now:yyyyMMdd_HHmmss}{DEFAULT_EXT}"; - _fullFileName = Path.Combine(SaveFileFolder, baseFileName); - using (var writer = new BinaryWriter(File.Create(_fullFileName))) - { - var header = new TzxHeader(); - header.WriteTo(writer); - } - } - _dataBlockCount++; - - var stream = File.Open(_fullFileName, FileMode.Append); - using (var writer = new BinaryWriter(stream)) - { - block.WriteTo(writer); - } - } - - /// - /// The tape provider can finalize the tape when all - /// TZX blocks are written. - /// - public void FinalizeTapeFile() - { - } - - /// - /// Obtains the specified resource stream ot the given assembly - /// - /// Assembly to get the resource stream from - /// Resource name - private static Stream GetFileResource(Assembly asm, string resourceName) - { - var resourceFullName = $"{asm.GetName().Name}.{RESOURCE_FOLDER}.{resourceName}"; - return asm.GetManifestResourceStream(resourceFullName); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IKeyboard.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/IKeyboard.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IKeyboard.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/KempstonJoystick.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs deleted file mode 100644 index 05a4de9e06..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISaveToTapeProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This interface describes the behavior of an object that - /// provides tape content - /// - public interface ISaveToTapeProvider - { - /// - /// Creates a tape file with the specified name - /// - /// - void CreateTapeFile(); - - /// - /// This method sets the name of the file according to the - /// Spectrum SAVE HEADER information - /// - /// - void SetName(string name); - - /// - /// Appends the tape block to the tape file - /// - /// - void SaveTapeBlock(ITapeDataSerialization block); - - /// - /// The tape provider can finalize the tape when all - /// tape blocks are written. - /// - void FinalizeTapeFile(); - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs deleted file mode 100644 index 8d1c47645b..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockPlayback.cs +++ /dev/null @@ -1,43 +0,0 @@ -using BizHawk.Common; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This interface represents that the implementing class supports - /// emulating tape playback of a single tape block - /// - public interface ISupportsTapeBlockPlayback - { - /// - /// The current playing phase - /// - PlayPhase PlayPhase { get; } - - /// - /// The tact count of the CPU when playing starts - /// - long StartCycle { get; } - - /// - /// Initializes the player - /// - void InitPlay(long startCycle); - - /// - /// Gets the EAR bit value for the specified tact - /// - /// Tacts to retrieve the EAR bit - /// - /// The EAR bit value to play back - /// - bool GetEarBit(long currentCycle); - - - void SyncState(Serializer ser); - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs deleted file mode 100644 index 1312761e51..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ISupportsTapeBlockSetPlayback.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This interface represents that the implementing class supports - /// emulating tape playback of a set of subsequent tape blocks - /// - public interface ISupportsTapeBlockSetPlayback : ISupportsTapeBlockPlayback - { - /// - /// Moves the player to the next playable block - /// - /// Tacts time to start the next block - void NextBlock(long currentCycle); - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs deleted file mode 100644 index 62f8c6f814..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeContentProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ - -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This interface describes the behavior of an object that - /// provides tape content - /// - public interface ITapeContentProvider - { - /// - /// Tha tape set to load the content from - /// - string TapeSetName { get; set; } - - /// - /// Gets a binary reader that provides tape content - /// - /// BinaryReader instance to obtain the content from - BinaryReader GetTapeContent(); - - void Reset(); - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs deleted file mode 100644 index 5879c57536..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeData.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Represetns the data in the tape - /// - public interface ITapeData - { - /// - /// Block Data - /// - byte[] Data { get; } - - /// - /// Pause after this block (given in milliseconds) - /// - ushort PauseAfter { get; } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs deleted file mode 100644 index 9f48e9097e..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeDataSerialization.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Defines the serialization operations of a TZX record - /// - public interface ITapeDataSerialization - { - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - void ReadFrom(BinaryReader reader); - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - void WriteTo(BinaryWriter writer); - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs deleted file mode 100644 index 91a4457a31..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Interfaces/ITapeProvider.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This interface describes the behavior of an object that - /// provides TZX tape content - /// - public interface ITapeProvider - { - /// - /// Tha tape set to load the content from - /// - string TapeSetName { get; set; } - - /// - /// Gets a binary reader that provider TZX content - /// - /// BinaryReader instance to obtain the content from - BinaryReader GetTapeContent(); - - /// - /// Creates a tape file with the specified name - /// - /// - void CreateTapeFile(); - - /// - /// This method sets the name of the file according to the - /// Spectrum SAVE HEADER information - /// - /// - void SetName(string name); - - /// - /// Appends the TZX block to the tape file - /// - /// - void SaveTapeBlock(ITapeDataSerialization block); - - /// - /// The tape provider can finalize the tape when all - /// TZX blocks are written. - /// - void FinalizeTapeFile(); - - /// - /// Provider can reset itself - /// - void Reset(); - - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Rom/RomData.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/RomData.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Rom/RomData.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/AY38912.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Buzzer.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Pulse.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs deleted file mode 100644 index fd29eb3855..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Tape.cs +++ /dev/null @@ -1,814 +0,0 @@ -using BizHawk.Common; -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 -{ - /* - * Much of the TAPE implementation has been taken from: https://github.com/Dotneteer/spectnetide - * - * MIT License - - Copyright (c) 2017 Istvan Novak - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - */ - - /// - /// Represents the tape device (or DATACORDER as AMSTRAD liked to call it) - /// - public class Tape - { - private SpectrumBase _machine { get; set; } - private Z80A _cpu { get; set; } - private Buzzer _buzzer { get; set; } - - private TapeOperationMode _currentMode; - private TapeFilePlayer _tapePlayer; - private bool _micBitState; - private long _lastMicBitActivityCycle; - private SavePhase _savePhase; - private int _pilotPulseCount; - private int _bitOffset; - private byte _dataByte; - private int _dataLength; - private byte[] _dataBuffer; - private int _dataBlockCount; - private MicPulseType _prevDataPulse; - - /// - /// Number of tacts after save mod can be exited automatically - /// - public const int SAVE_STOP_SILENCE = 17500000; - - /// - /// The address of the ERROR routine in the Spectrum ROM - /// - public const ushort ERROR_ROM_ADDRESS = 0x0008; - - /// - /// The maximum distance between two scans of the EAR bit - /// - public const int MAX_TACT_JUMP = 10000; - - /// - /// The width tolerance of save pulses - /// - public const int SAVE_PULSE_TOLERANCE = 24; - - /// - /// Minimum number of pilot pulses before SYNC1 - /// - public const int MIN_PILOT_PULSE_COUNT = 3000; - - /// - /// Lenght of the data buffer to allocate for the SAVE operation - /// - public const int DATA_BUFFER_LENGTH = 0x10000; - - /// - /// Gets the tape content provider - /// - public ITapeProvider TapeProvider { get; } - - /// - /// The TapeFilePlayer that can playback tape content - /// - public TapeFilePlayer TapeFilePlayer => _tapePlayer; - - /// - /// The current operation mode of the tape - /// - public TapeOperationMode CurrentMode => _currentMode; - - - private bool _fastLoad = false; - - - public virtual void Init(SpectrumBase machine) - { - _machine = machine; - _cpu = _machine.CPU; - _buzzer = machine.BuzzerDevice; - Reset(); - } - - public Tape(ITapeProvider tapeProvider) - { - TapeProvider = tapeProvider; - } - - public virtual void Reset() - { - TapeProvider?.Reset(); - _tapePlayer = null; - _currentMode = TapeOperationMode.Passive; - _savePhase = SavePhase.None; - _micBitState = true; - } - - public void CPUFrameCompleted() - { - SetTapeMode(); - if (CurrentMode == TapeOperationMode.Load - && _fastLoad - && TapeFilePlayer != null - && TapeFilePlayer.PlayPhase != PlayPhase.Completed - && _cpu.RegPC == 1529) //_machine.RomData.LoadBytesRoutineAddress) - { - - if (FastLoadFromTzx()) - { - //FastLoadCompleted?.Invoke(this, EventArgs.Empty); - } - - } - } - - /// - /// Sets the current tape mode according to the current PC register - /// and the MIC bit state - /// - public void SetTapeMode() - { - switch (_currentMode) - { - case TapeOperationMode.Passive: - if (_cpu.RegPC == 1523) // _machine.RomData.LoadBytesRoutineAddress) //1529 - { - EnterLoadMode(); - } - else if (_cpu.RegPC == 2416) // _machine.RomData.SaveBytesRoutineAddress) - { - EnterSaveMode(); - } - - var res = _cpu.RegPC; - var res2 = _machine.Spectrum.RegPC; - - return; - case TapeOperationMode.Save: - if (_cpu.RegPC == ERROR_ROM_ADDRESS - || (int)(_cpu.TotalExecutedCycles - _lastMicBitActivityCycle) > SAVE_STOP_SILENCE) - { - LeaveSaveMode(); - } - return; - case TapeOperationMode.Load: - if ((_tapePlayer?.Eof ?? false) || _cpu.RegPC == ERROR_ROM_ADDRESS) - { - LeaveLoadMode(); - } - return; - } - } - - /// - /// Puts the device in save mode. From now on, every MIC pulse is recorded - /// - private void EnterSaveMode() - { - _currentMode = TapeOperationMode.Save; - _savePhase = SavePhase.None; - _micBitState = true; - _lastMicBitActivityCycle = _cpu.TotalExecutedCycles; - _pilotPulseCount = 0; - _prevDataPulse = MicPulseType.None; - _dataBlockCount = 0; - TapeProvider?.CreateTapeFile(); - } - - /// - /// Leaves the save mode. Stops recording MIC pulses - /// - private void LeaveSaveMode() - { - _currentMode = TapeOperationMode.Passive; - TapeProvider?.FinalizeTapeFile(); - } - - /// - /// Puts the device in load mode. From now on, EAR pulses are played by a device - /// - private void EnterLoadMode() - { - _currentMode = TapeOperationMode.Load; - - var contentReader = TapeProvider?.GetTapeContent(); - if (contentReader == null) return; - - // --- Play the content - _tapePlayer = new TapeFilePlayer(contentReader); - _tapePlayer.ReadContent(); - _tapePlayer.InitPlay(_cpu.TotalExecutedCycles); - _buzzer.SetTapeMode(true); - } - - /// - /// Leaves the load mode. Stops the device that playes EAR pulses - /// - private void LeaveLoadMode() - { - _currentMode = TapeOperationMode.Passive; - _tapePlayer = null; - TapeProvider?.Reset(); - _buzzer.SetTapeMode(false); - } - - /// - /// Loads the next TZX player block instantly without emulation - /// EAR bit processing - /// - /// True, if fast load is operative - private bool FastLoadFromTzx() - { - var c = _machine.Spectrum; - - var blockType = TapeFilePlayer.CurrentBlock.GetType(); - bool canFlash = TapeFilePlayer.CurrentBlock is ITapeData; - - // --- Check, if we can load the current block in a fast way - if (!(TapeFilePlayer.CurrentBlock is ITapeData) - || TapeFilePlayer.PlayPhase == PlayPhase.Completed) - { - // --- We cannot play this block - return false; - } - - var currentData = TapeFilePlayer.CurrentBlock as ITapeData; - - var regs = _cpu.Regs; - - //regs.AF = regs._AF_; - //c.Set16BitAF(c.Get16BitAF_()); - _cpu.A = _cpu.A_s; - _cpu.F = _cpu.F_s; - - // --- Check if the operation is LOAD or VERIFY - var isVerify = (c.RegAF & 0xFF01) == 0xFF00; - - // --- At this point IX contains the address to load the data, - // --- DE shows the #of bytes to load. A contains 0x00 for header, - // --- 0xFF for data block - var data = currentData.Data; - if (data[0] != regs[_cpu.A]) - { - // --- This block has a different type we're expecting - regs[_cpu.A] = (byte)(regs[_cpu.A] ^ regs[_cpu.L]); - regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.Z; - regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; - c.RegPC = _machine.RomData.LoadBytesInvalidHeaderAddress; - - // --- Get the next block - TapeFilePlayer.NextBlock(_cpu.TotalExecutedCycles); - return true; - } - - // --- It is time to load the block - var curIndex = 1; - //var memory = _machine.me MemoryDevice; - regs[_cpu.H] = regs[_cpu.A]; - while (c.RegDE > 0) - { - var de16 = c.RegDE; - var ix16 = c.RegIX; - if (curIndex > data.Length - 1) - { - // --- No more data to read - //break; - } - - regs[_cpu.L] = data[curIndex]; - if (isVerify && regs[_cpu.L] != _machine.ReadBus(c.RegIX)) - { - // --- Verify failed - regs[_cpu.A] = (byte)(_machine.ReadBus(c.RegIX) ^ regs[_cpu.L]); - regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.Z; - regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; - c.RegPC = _machine.RomData.LoadBytesInvalidHeaderAddress; - return true; - } - - // --- Store the loaded data byte - _machine.WriteBus(c.RegIX, (byte)regs[_cpu.L]); - regs[_cpu.H] ^= regs[_cpu.L]; - curIndex++; - //regs.IX++; - //c.Set16BitIX((ushort)((int)c.Get16BitIX() + 1)); - c.RegIX++; - //regs.DE--; - //c.Set16BitDE((ushort)((int)c.Get16BitDE() - 1)); - //_cpu.Regs[_cpu.E]--; - c.RegDE--; - var te = c.RegDE; - } - - // --- Check the parity byte at the end of the data stream - if (curIndex > data.Length - 1 || regs[_cpu.H] != data[curIndex]) - { - // --- Carry is reset to sign an error - regs[_cpu.F] &= (byte)ZXSpectrum.FlagsResetMask.C; - } - else - { - // --- Carry is set to sign success - regs[_cpu.F] |= (byte)ZXSpectrum.FlagsSetMask.C; - } - c.RegPC = _machine.RomData.LoadBytesResumeAddress; - - // --- Get the next block - TapeFilePlayer.NextBlock(_cpu.TotalExecutedCycles); - return true; - } - - - /// - /// the EAR bit read from tape - /// - /// - /// - public virtual bool GetEarBit(int cpuCycles) - { - if (_currentMode != TapeOperationMode.Load) - { - return true; - } - - var earBit = _tapePlayer?.GetEarBit(cpuCycles) ?? true; - _buzzer.ProcessPulseValue(true, earBit); - return earBit; - } - - /// - /// Processes the mic bit change - /// - /// - public virtual void ProcessMicBit(bool micBit) - { - if (_currentMode != TapeOperationMode.Save - || _micBitState == micBit) - { - return; - } - - var length = _cpu.TotalExecutedCycles - _lastMicBitActivityCycle; - - // --- Classify the pulse by its width - var pulse = MicPulseType.None; - if (length >= TapeDataBlockPlayer.BIT_0_PL - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.BIT_0_PL + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.Bit0; - } - else if (length >= TapeDataBlockPlayer.BIT_1_PL - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.BIT_1_PL + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.Bit1; - } - if (length >= TapeDataBlockPlayer.PILOT_PL - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.PILOT_PL + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.Pilot; - } - else if (length >= TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.SYNC_1_PL + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.Sync1; - } - else if (length >= TapeDataBlockPlayer.SYNC_2_PL - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.SYNC_2_PL + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.Sync2; - } - else if (length >= TapeDataBlockPlayer.TERM_SYNC - SAVE_PULSE_TOLERANCE - && length <= TapeDataBlockPlayer.TERM_SYNC + SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.TermSync; - } - else if (length < TapeDataBlockPlayer.SYNC_1_PL - SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.TooShort; - } - else if (length > TapeDataBlockPlayer.PILOT_PL + 2 * SAVE_PULSE_TOLERANCE) - { - pulse = MicPulseType.TooLong; - } - - _micBitState = micBit; - _lastMicBitActivityCycle = _cpu.TotalExecutedCycles; - - // --- Lets process the pulse according to the current SAVE phase and pulse width - var nextPhase = SavePhase.Error; - switch (_savePhase) - { - case SavePhase.None: - if (pulse == MicPulseType.TooShort || pulse == MicPulseType.TooLong) - { - nextPhase = SavePhase.None; - } - else if (pulse == MicPulseType.Pilot) - { - _pilotPulseCount = 1; - nextPhase = SavePhase.Pilot; - } - break; - case SavePhase.Pilot: - if (pulse == MicPulseType.Pilot) - { - _pilotPulseCount++; - nextPhase = SavePhase.Pilot; - } - else if (pulse == MicPulseType.Sync1 && _pilotPulseCount >= MIN_PILOT_PULSE_COUNT) - { - nextPhase = SavePhase.Sync1; - } - break; - case SavePhase.Sync1: - if (pulse == MicPulseType.Sync2) - { - nextPhase = SavePhase.Sync2; - } - break; - case SavePhase.Sync2: - if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) - { - // --- Next pulse starts data, prepare for receiving it - _prevDataPulse = pulse; - nextPhase = SavePhase.Data; - _bitOffset = 0; - _dataByte = 0; - _dataLength = 0; - _dataBuffer = new byte[DATA_BUFFER_LENGTH]; - } - break; - case SavePhase.Data: - if (pulse == MicPulseType.Bit0 || pulse == MicPulseType.Bit1) - { - if (_prevDataPulse == MicPulseType.None) - { - // --- We are waiting for the second half of the bit pulse - _prevDataPulse = pulse; - nextPhase = SavePhase.Data; - } - else if (_prevDataPulse == pulse) - { - // --- We received a full valid bit pulse - nextPhase = SavePhase.Data; - _prevDataPulse = MicPulseType.None; - - // --- Add this bit to the received data - _bitOffset++; - _dataByte = (byte)(_dataByte * 2 + (pulse == MicPulseType.Bit0 ? 0 : 1)); - if (_bitOffset == 8) - { - // --- We received a full byte - _dataBuffer[_dataLength++] = _dataByte; - _dataByte = 0; - _bitOffset = 0; - } - } - } - else if (pulse == MicPulseType.TermSync) - { - // --- We received the terminating pulse, the datablock has been completed - nextPhase = SavePhase.None; - _dataBlockCount++; - - // --- Create and save the data block - var dataBlock = new TzxStandardSpeedDataBlock - { - Data = _dataBuffer, - DataLength = (ushort)_dataLength - }; - - // --- If this is the first data block, extract the name from the header - if (_dataBlockCount == 1 && _dataLength == 0x13) - { - // --- It's a header! - var sb = new StringBuilder(16); - for (var i = 2; i <= 11; i++) - { - sb.Append((char)_dataBuffer[i]); - } - var name = sb.ToString().TrimEnd(); - TapeProvider?.SetName(name); - } - TapeProvider?.SaveTapeBlock(dataBlock); - } - break; - } - _savePhase = nextPhase; - } - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapeDevice"); - ser.Sync("_micBitState", ref _micBitState); - ser.Sync("_lastMicBitActivityCycle", ref _lastMicBitActivityCycle); - ser.Sync("_pilotPulseCount", ref _pilotPulseCount); - ser.Sync("_bitOffset", ref _bitOffset); - ser.Sync("_dataByte", ref _dataByte); - ser.Sync("_dataLength", ref _dataLength); - ser.Sync("_dataBlockCount", ref _dataBlockCount); - ser.Sync("_dataBuffer", ref _dataBuffer, false); - ser.SyncEnum("_currentMode", ref _currentMode); - ser.SyncEnum("_savePhase", ref _savePhase); - ser.SyncEnum("_prevDataPulse", ref _prevDataPulse); - - ser.EndSection(); - } - } - - /// - /// This enum represents the operation mode of the tape - /// - public enum TapeOperationMode : byte - { - /// - /// The tape device is passive - /// - Passive = 0, - - /// - /// The tape device is saving information (MIC pulses) - /// - Save, - - /// - /// The tape device generates EAR pulses from a player - /// - Load - } - - /// - /// This class represents a spectrum tape header - /// - public class SpectrumTapeHeader - { - private const int HEADER_LEN = 19; - private const int TYPE_OFFS = 1; - private const int NAME_OFFS = 2; - private const int NAME_LEN = 10; - private const int DATA_LEN_OFFS = 12; - private const int PAR1_OFFS = 14; - private const int PAR2_OFFS = 16; - private const int CHK_OFFS = 18; - - /// - /// The bytes of the header - /// - public byte[] HeaderBytes { get; } - - /// - /// Initializes a new instance of the class. - /// - public SpectrumTapeHeader() - { - HeaderBytes = new byte[HEADER_LEN]; - for (var i = 0; i < HEADER_LEN; i++) HeaderBytes[i] = 0x00; - CalcChecksum(); - } - - /// - /// Initializes a new instance with the specified header data. - /// - /// Header data - public SpectrumTapeHeader(byte[] header) - { - if (header == null) throw new ArgumentNullException(nameof(header)); - if (header.Length != HEADER_LEN) - { - throw new ArgumentException($"Header must be exactly {HEADER_LEN} bytes long"); - } - HeaderBytes = new byte[HEADER_LEN]; - header.CopyTo(HeaderBytes, 0); - CalcChecksum(); - } - - /// - /// Gets or sets the type of the header - /// - public byte Type - { - get { return HeaderBytes[TYPE_OFFS]; } - set - { - HeaderBytes[TYPE_OFFS] = (byte)(value & 0x03); - CalcChecksum(); - } - } - - /// - /// Gets or sets the program name - /// - public string Name - { - get - { - var name = new StringBuilder(NAME_LEN + 4); - for (var i = NAME_OFFS; i < NAME_OFFS + NAME_LEN; i++) - { - name.Append((char)HeaderBytes[i]); - } - return name.ToString().TrimEnd(); - } - set - { - if (value == null) throw new ArgumentNullException(nameof(value)); - if (value.Length > NAME_LEN) value = value.Substring(0, NAME_LEN); - else if (value.Length < NAME_LEN) value = value.PadRight(NAME_LEN, ' '); - for (var i = NAME_OFFS; i < NAME_OFFS + NAME_LEN; i++) - { - HeaderBytes[i] = (byte)value[i - NAME_OFFS]; - } - CalcChecksum(); - } - } - - /// - /// Gets or sets the Data Length - /// - public ushort DataLength - { - get { return GetWord(DATA_LEN_OFFS); } - set { SetWord(DATA_LEN_OFFS, value); } - } - - /// - /// Gets or sets Parameter1 - /// - public ushort Parameter1 - { - get { return GetWord(PAR1_OFFS); } - set { SetWord(PAR1_OFFS, value); } - } - - /// - /// Gets or sets Parameter2 - /// - public ushort Parameter2 - { - get { return GetWord(PAR2_OFFS); } - set { SetWord(PAR2_OFFS, value); } - } - - /// - /// Gets the value of checksum - /// - public byte Checksum => HeaderBytes[CHK_OFFS]; - - /// - /// Calculate the checksum - /// - private void CalcChecksum() - { - var chk = 0x00; - for (var i = 0; i < HEADER_LEN - 1; i++) chk ^= HeaderBytes[i]; - HeaderBytes[CHK_OFFS] = (byte)chk; - } - - /// - /// Gets the word value from the specified offset - /// - private ushort GetWord(int offset) => - (ushort)(HeaderBytes[offset] + 256 * HeaderBytes[offset + 1]); - - /// - /// Sets the word value at the specified offset - /// - private void SetWord(int offset, ushort value) - { - HeaderBytes[offset] = (byte)(value & 0xff); - HeaderBytes[offset + 1] = (byte)(value >> 8); - CalcChecksum(); - } - } - - /// - /// This enum defines the MIC pulse types according to their widths - /// - public enum MicPulseType : byte - { - /// - /// No pulse information - /// - None = 0, - - /// - /// Too short to be a valid pulse - /// - TooShort, - - /// - /// Too long to be a valid pulse - /// - TooLong, - - /// - /// PILOT pulse (Length: 2168 cycles) - /// - Pilot, - - /// - /// SYNC1 pulse (Length: 667 cycles) - /// - Sync1, - - /// - /// SYNC2 pulse (Length: 735 cycles) - /// - Sync2, - - /// - /// BIT0 pulse (Length: 855 cycles) - /// - Bit0, - - /// - /// BIT1 pulse (Length: 1710 cycles) - /// - Bit1, - - /// - /// TERM_SYNC pulse (Length: 947 cycles) - /// - TermSync - } - - /// - /// Represents the playing phase of the current block - /// - public enum PlayPhase - { - /// - /// The player is passive - /// - None = 0, - - /// - /// Pilot signals - /// - Pilot, - - /// - /// Sync signals at the end of the pilot - /// - Sync, - - /// - /// Bits in the data block - /// - Data, - - /// - /// Short terminating sync signal before pause - /// - TermSync, - - /// - /// Pause after the data block - /// - Pause, - - /// - /// The entire block has been played back - /// - Completed - } - - /// - /// This enumeration defines the phases of the SAVE operation - /// - public enum SavePhase : byte - { - /// No SAVE operation is in progress - None = 0, - - /// Emitting PILOT impulses - Pilot, - - /// Emitting SYNC1 impulse - Sync1, - - /// Emitting SYNC2 impulse - Sync2, - - /// Emitting BIT0/BIT1 impulses - Data, - - /// Unexpected pulse detected - Error - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs deleted file mode 100644 index 6d5b149f9e..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeBlockSetPlayer.cs +++ /dev/null @@ -1,143 +0,0 @@ -using BizHawk.Common; -using System.Collections.Generic; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class is responsible to "play" a tape file. - /// - public class TapeBlockSetPlayer : ISupportsTapeBlockSetPlayback - { - /// - /// All data blocks that can be played back - /// - public List DataBlocks { get; } - - /// - /// Signs that the player completed playing back the file - /// - private bool eof; - public bool Eof - { - get { return eof; } - set { eof = value; } - } - - - /// - /// Gets the currently playing block's index - /// - private int currentBlockIndex; - public int CurrentBlockIndex - { - get { return currentBlockIndex; } - set { currentBlockIndex = value; } - } - - /// - /// The current playable block - /// - private ISupportsTapeBlockPlayback currentBlock; - public ISupportsTapeBlockPlayback CurrentBlock - { - get { return DataBlocks[CurrentBlockIndex]; } - //set { currentBlock = value; } - } - - - /// - /// The current playing phase - /// - private PlayPhase playPhase; - public PlayPhase PlayPhase - { - get { return playPhase; } - set { playPhase = value; } - } - - - /// - /// The cycle count of the CPU when playing starts - /// - private long startCycle; - public long StartCycle - { - get { return startCycle; } - set { startCycle = value; } - } - - - public TapeBlockSetPlayer(List dataBlocks) - { - DataBlocks = dataBlocks; - Eof = dataBlocks.Count == 0; - } - - /// - /// Initializes the player - /// - public void InitPlay(long startTact) - { - CurrentBlockIndex = -1; - NextBlock(startTact); - PlayPhase = PlayPhase.None; - StartCycle = startTact; - } - - /// - /// Gets the EAR bit value for the specified cycle - /// - /// Cycles to retrieve the EAR bit - /// - /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block - /// - public bool GetEarBit(long currentCycle) - { - // --- Check for EOF - if (CurrentBlockIndex == DataBlocks.Count - 1 - && (CurrentBlock.PlayPhase == PlayPhase.Pause || CurrentBlock.PlayPhase == PlayPhase.Completed)) - { - Eof = true; - } - if (CurrentBlockIndex >= DataBlocks.Count || CurrentBlock == null) - { - // --- After all playable block played back, there's nothing more to do - PlayPhase = PlayPhase.Completed; - return true; - } - var earbit = CurrentBlock.GetEarBit(currentCycle); - if (CurrentBlock.PlayPhase == PlayPhase.Completed) - { - NextBlock(currentCycle); - } - return earbit; - } - - /// - /// Moves the current block index to the next playable block - /// - /// Cycles time to start the next block - public void NextBlock(long currentCycle) - { - if (CurrentBlockIndex >= DataBlocks.Count - 1) - { - PlayPhase = PlayPhase.Completed; - Eof = true; - return; - } - CurrentBlockIndex++; - CurrentBlock.InitPlay(currentCycle); - } - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapeBlockSetPlayer"); - ser.Sync("eof", ref eof); - ser.Sync("currentBlockIndex", ref currentBlockIndex); - ser.SyncEnum("playPhase", ref playPhase); - ser.Sync("startCycle", ref startCycle); - currentBlock.SyncState(ser); - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs deleted file mode 100644 index 0c7dae4f14..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeDataBlockPlayer.cs +++ /dev/null @@ -1,279 +0,0 @@ - -using BizHawk.Common; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Represents the standard speed data block in a tape file - /// - public class TapeDataBlockPlayer : ISupportsTapeBlockPlayback, ITapeData - { - /// - /// Pause after this block (default: 1000ms) - /// - private ushort pauseAfter; - public ushort PauseAfter - { - get { return pauseAfter; } - } - - /// - /// Block Data - /// - private byte[] data; - public byte[] Data - { - get { return data; } - } - - /// - /// Initializes a new instance - /// - public TapeDataBlockPlayer(byte[] _data, ushort _pauseAfter) - { - pauseAfter = _pauseAfter; - data = _data; - } - - /// - /// Pilot pulse length - /// - public const int PILOT_PL = 2168; - - /// - /// Pilot pulses in the ROM header block - /// - public const int HEADER_PILOT_COUNT = 8063; - - /// - /// Pilot pulses in the ROM data block - /// - public const int DATA_PILOT_COUNT = 3223; - - /// - /// Sync 1 pulse length - /// - public const int SYNC_1_PL = 667; - - /// - /// Sync 2 pulse lenth - /// - public const int SYNC_2_PL = 735; - - /// - /// Bit 0 pulse length - /// - public const int BIT_0_PL = 855; - - /// - /// Bit 1 pulse length - /// - public const int BIT_1_PL = 1710; - - /// - /// End sync pulse length - /// - public const int TERM_SYNC = 947; - - /// - /// 1 millisecond pause - /// - public const int PAUSE_MS = 3500; - - private int _pilotEnds; - private int _sync1Ends; - private int _sync2Ends; - private int _bitStarts; - private int _bitPulseLength; - private bool _currentBit; - private long _termSyncEnds; - private long _pauseEnds; - - /// - /// The index of the currently playing byte - /// - private int byteIndex; - public int ByteIndex - { - get { return byteIndex; } - set { byteIndex = value; } - } - - /// - /// The mask of the currently playing bit in the current byte - /// - private byte bitMask; - public byte BitMask - { - get { return bitMask; } - set { bitMask = value; } - } - - /// - /// The current playing phase - /// - private PlayPhase playPhase; - public PlayPhase PlayPhase - { - get { return playPhase; } - set { playPhase = value; } - } - - /// - /// The cycle count of the CPU when playing starts - /// - private long startCycle; - public long StartCycle - { - get { return startCycle; } - set { startCycle = value; } - } - - /// - /// Last cycle queried - /// - private long lastCycle; - public long LastCycle - { - get { return lastCycle; } - set { lastCycle = value; } - } - - /// - /// Initializes the player - /// - public void InitPlay(long startTact) - { - PlayPhase = PlayPhase.Pilot; - StartCycle = LastCycle = startTact; - _pilotEnds = ((Data[0] & 0x80) == 0 ? HEADER_PILOT_COUNT : DATA_PILOT_COUNT) * PILOT_PL; - _sync1Ends = _pilotEnds + SYNC_1_PL; - _sync2Ends = _sync1Ends + SYNC_2_PL; - ByteIndex = 0; - BitMask = 0x80; - } - - /// - /// Gets the EAR bit value for the specified cycle - /// - /// Tacts to retrieve the EAR bit - /// - /// The EAR bit value to play back - /// - public bool GetEarBit(long currentCycle) - { - var pos = (int)(currentCycle - StartCycle); - LastCycle = currentCycle; - - if (PlayPhase == PlayPhase.Pilot || PlayPhase == PlayPhase.Sync) - { - // --- Generate the appropriate pilot or sync EAR bit - if (pos <= _pilotEnds) - { - // --- Alternating pilot pulses - return (pos / PILOT_PL) % 2 == 0; - } - if (pos <= _sync1Ends) - { - // --- 1st sync pulse - PlayPhase = PlayPhase.Sync; - return false; - } - if (pos <= _sync2Ends) - { - // --- 2nd sync pulse - PlayPhase = PlayPhase.Sync; - return true; - } - PlayPhase = PlayPhase.Data; - _bitStarts = _sync2Ends; - _currentBit = (Data[ByteIndex] & BitMask) != 0; - _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; - } - if (PlayPhase == PlayPhase.Data) - { - // --- Data block playback - // --- Generate current bit pulse - var bitPos = pos - _bitStarts; - if (bitPos < _bitPulseLength) - { - // --- First pulse of the bit - return false; - } - if (bitPos < 2 * _bitPulseLength) - { - // --- Second pulse of the bit - return true; - } - - // --- Move to the next bit, or byte - if ((BitMask >>= 1) == 0) - { - BitMask = 0x80; - ByteIndex++; - } - - // --- Prepare the next bit - if (ByteIndex < Data.Length) - { - _bitStarts += 2 * _bitPulseLength; - _currentBit = (Data[ByteIndex] & BitMask) != 0; - _bitPulseLength = _currentBit ? BIT_1_PL : BIT_0_PL; - // --- We're in the first pulse of the next bit - return false; - } - - // --- We've played back all data bytes, send terminating pulse - PlayPhase = PlayPhase.TermSync; - _termSyncEnds = currentCycle + TERM_SYNC; - return false; - } - - if (PlayPhase == PlayPhase.TermSync) - { - if (currentCycle < _termSyncEnds) - { - return false; - } - - // --- We've played back all data, not, it's pause time - PlayPhase = PlayPhase.Pause; - _pauseEnds = currentCycle + PAUSE_MS * PauseAfter; - return true; - } - - // --- We need to produce pause signs - if (currentCycle > _pauseEnds) - { - PlayPhase = PlayPhase.Completed; - } - return true; - } - - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapeDataBlockPlayer"); - - ser.Sync("pauseAfter", ref pauseAfter); - ser.Sync("data", ref data, false); - - ser.Sync("_pilotEnds", ref _pilotEnds); - ser.Sync("_sync1Ends", ref _sync1Ends); - ser.Sync("_sync2Ends", ref _sync2Ends); - ser.Sync("_bitStarts", ref _bitStarts); - ser.Sync("_bitPulseLength", ref _bitPulseLength); - ser.Sync("_currentBit", ref _currentBit); - ser.Sync("_termSyncEnds", ref _termSyncEnds); - ser.Sync("_pauseEnds", ref _pauseEnds); - - ser.Sync("byteIndex", ref byteIndex); - ser.Sync("bitMask", ref bitMask); - ser.SyncEnum("playPhase", ref playPhase); - ser.Sync("startCycle", ref startCycle); - ser.Sync("lastCycle", ref lastCycle); - - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs deleted file mode 100644 index 92c32dbc95..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/TapeFilePlayer.cs +++ /dev/null @@ -1,128 +0,0 @@ -using BizHawk.Common; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class recognizes .TZX and .TAP files, and playes back - /// the content accordingly. - /// - public class TapeFilePlayer : ISupportsTapeBlockPlayback - { - private readonly BinaryReader _reader; - private TapeBlockSetPlayer _player; - private int _numberOfDataBlocks; - - /// - /// Data blocks to play back - /// - public List DataBlocks { get; private set; } - - /// - /// Signs that the player completed playing back the file - /// - public bool Eof => _player.Eof; - - /// - /// Initializes the player from the specified reader - /// - /// BinaryReader instance to get tape file data from - public TapeFilePlayer(BinaryReader reader) - { - _reader = reader; - } - - /// - /// Reads in the content of the tape file so that it can be played - /// - /// True, if read was successful; otherwise, false - public bool ReadContent() - { - // --- First try TzxReader - var tzxReader = new TzxReader(_reader); - var readerFound = false; - try - { - readerFound = tzxReader.ReadContent(); - } - catch (Exception) - { - // --- This exception is intentionally ingnored - } - - if (readerFound) - { - // --- This is a .TZX format - DataBlocks = tzxReader.DataBlocks.Where(b => b is ISupportsTapeBlockPlayback) - .Cast() - .ToList(); - _player = new TapeBlockSetPlayer(DataBlocks); - return true; - } - - // --- Let's assume .TAP tap format - _reader.BaseStream.Seek(0, SeekOrigin.Begin); - var tapReader = new TapReader(_reader); - readerFound = tapReader.ReadContent(); - DataBlocks = tapReader.DataBlocks.Cast() - .ToList(); - _player = new TapeBlockSetPlayer(DataBlocks); - return readerFound; - } - - /// - /// Gets the currently playing block's index - /// - public int CurrentBlockIndex => _player.CurrentBlockIndex; - - /// - /// The current playable block - /// - public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; - - /// - /// The current playing phase - /// - public PlayPhase PlayPhase => _player.PlayPhase; - - /// - /// The tact count of the CPU when playing starts - /// - public long StartCycle => _player.StartCycle; - - /// - /// Initializes the player - /// - public void InitPlay(long startCycle) - { - _player.InitPlay(startCycle); - } - - /// - /// Gets the EAR bit value for the specified cycle - /// - /// Tacts to retrieve the EAR bit - /// - /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block - /// - public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); - - /// - /// Moves the current block index to the next playable block - /// - /// Tacts time to start the next block - public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapeFilePlayer"); - ReadContent(); - ser.Sync("_numberOfDataBlocks", ref _numberOfDataBlocks); - _player.SyncState(ser); - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs deleted file mode 100644 index 994e32df80..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Sound.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// The abstract class that all emulated models will inherit from - /// * Sound * - /// - public abstract partial class SpectrumBase - { - - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs deleted file mode 100644 index 0682b32752..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapDataBlock.cs +++ /dev/null @@ -1,105 +0,0 @@ - -using BizHawk.Common; -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class describes a TAP Block - /// - public sealed class TapDataBlock : - ITapeData, - ITapeDataSerialization, - ISupportsTapeBlockPlayback - { - private TapeDataBlockPlayer _player; - - /// - /// Block Data - /// - private byte[] data; - public byte[] Data - { - get { return data; } - set { data = value; } - } - - /// - /// Pause after this block (given in milliseconds) - /// - public ushort PauseAfter => 1000; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public void ReadFrom(BinaryReader reader) - { - var length = reader.ReadUInt16(); - Data = reader.ReadBytes(length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public void WriteTo(BinaryWriter writer) - { - writer.Write((ushort)Data.Length); - writer.Write(Data); - } - - /// - /// The index of the currently playing byte - /// - /// This proprty is made public for test purposes - public int ByteIndex => _player.ByteIndex; - - /// - /// The mask of the currently playing bit in the current byte - /// - public byte BitMask => _player.BitMask; - - /// - /// The current playing phase - /// - public PlayPhase PlayPhase => _player.PlayPhase; - - /// - /// The tact count of the CPU when playing starts - /// - public long StartCycle => _player.StartCycle; - - /// - /// Last tact queried - /// - public long LastCycle => _player.LastCycle; - - /// - /// Initializes the player - /// - public void InitPlay(long startTact) - { - _player = new TapeDataBlockPlayer(Data, PauseAfter); - _player.InitPlay(startTact); - } - - /// - /// Gets the EAR bit value for the specified tact - /// - /// Tacts to retrieve the EAR bit - /// - /// The EAR bit value to play back - /// - public bool GetEarBit(long currentTact) => _player.GetEarBit(currentTact); - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapDataBlock"); - - ser.Sync("data", ref data, false); - - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs deleted file mode 100644 index 0c24566e37..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapPlayer.cs +++ /dev/null @@ -1,96 +0,0 @@ - -using BizHawk.Common; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class is responsible to "play" a TAP file. - /// - public class TapPlayer : TapReader, ISupportsTapeBlockPlayback - { - private TapeBlockSetPlayer _player; - - /// - /// Signs that the player completed playing back the file - /// - public bool Eof => _player.Eof; - - /// - /// Initializes the player from the specified reader - /// - /// BinaryReader instance to get TZX file data from - public TapPlayer(BinaryReader reader) : base(reader) - { - } - - /// - /// Reads in the content of the TZX file so that it can be played - /// - /// True, if read was successful; otherwise, false - public override bool ReadContent() - { - var success = base.ReadContent(); - - var blocks = DataBlocks.Cast() - .ToList(); - _player = new TapeBlockSetPlayer(blocks); - return success; - } - - /// - /// Gets the currently playing block's index - /// - public int CurrentBlockIndex => _player.CurrentBlockIndex; - - /// - /// The current playable block - /// - public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; - - /// - /// The current playing phase - /// - public PlayPhase PlayPhase => _player.PlayPhase; - - /// - /// The tact count of the CPU when playing starts - /// - public long StartCycle => _player.StartCycle; - - /// - /// Initializes the player - /// - public void InitPlay(long startCycle) - { - _player.InitPlay(startCycle); - } - - /// - /// Gets the EAR bit value for the specified tact - /// - /// Tacts to retrieve the EAR bit - /// - /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block - /// - public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); - - /// - /// Moves the current block index to the next playable block - /// - /// Tacts time to start the next block - public void NextBlock(long currentCycle) => _player.NextBlock(currentCycle); - - - public void SyncState(Serializer ser) - { - ser.BeginSection("TapePlayer"); - - _player.SyncState(ser); - - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs deleted file mode 100644 index b915daf0fc..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TAP/TapReader.cs +++ /dev/null @@ -1,52 +0,0 @@ - -using System.Collections.Generic; -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class reads a TAP file - /// - public class TapReader - { - private readonly BinaryReader _reader; - - /// - /// Data blocks of this TZX file - /// - public IList DataBlocks { get; } - - /// - /// Initializes the player from the specified reader - /// - /// - public TapReader(BinaryReader reader) - { - _reader = reader; - DataBlocks = new List(); - } - - /// - /// Reads in the content of the TZX file so that it can be played - /// - /// True, if read was successful; otherwise, false - public virtual bool ReadContent() - { - try - { - while (_reader.BaseStream.Position != _reader.BaseStream.Length) - { - var tapBlock = new TapDataBlock(); - tapBlock.ReadFrom(_reader); - DataBlocks.Add(tapBlock); - } - return true; - } - catch - { - // --- This exception is intentionally ignored - return false; - } - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs deleted file mode 100644 index a5145928e9..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/BlockAbstraction.cs +++ /dev/null @@ -1,172 +0,0 @@ - -using System; -using System.IO; -using System.Text; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class describes a TZX Block - /// - public abstract class TzxDataBlockBase : ITapeDataSerialization - { - /// - /// The ID of the block - /// - public abstract int BlockId { get; } - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public abstract void ReadFrom(BinaryReader reader); - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public abstract void WriteTo(BinaryWriter writer); - - /// - /// Override this method to check the content of the block - /// - public virtual bool IsValid => true; - - /// - /// Reads the specified number of words from the reader. - /// - /// Reader to obtain the input from - /// Number of words to get - /// Word array read from the input - public static ushort[] ReadWords(BinaryReader reader, int count) - { - var result = new ushort[count]; - var bytes = reader.ReadBytes(2 * count); - for (var i = 0; i < count; i++) - { - result[i] = (ushort)(bytes[i * 2] + bytes[i * 2 + 1] << 8); - } - return result; - } - - /// - /// Writes the specified array of words to the writer - /// - /// Output - /// Word array - public static void WriteWords(BinaryWriter writer, ushort[] words) - { - foreach (var word in words) - { - writer.Write(word); - } - } - - /// - /// Converts the provided bytes to an ASCII string - /// - /// Bytes to convert - /// First byte offset - /// Number of bytes - /// ASCII string representation - public static string ToAsciiString(byte[] bytes, int offset = 0, int count = -1) - { - if (count < 0) count = bytes.Length - offset; - var sb = new StringBuilder(); - for (var i = offset; i < count; i++) - { - sb.Append(Convert.ToChar(bytes[i])); - } - return sb.ToString(); - } - } - - /// - /// Base class for all TZX block type with data length of 3 bytes - /// - public abstract class Tzx3ByteDataBlockBase : TzxDataBlockBase - { - /// - /// Used bits in the last byte (other bits should be 0) - /// - /// - /// (e.g. if this is 6, then the bits used(x) in the last byte are: - /// xxxxxx00, where MSb is the leftmost bit, LSb is the rightmost bit) - /// - public byte LastByteUsedBits { get; set; } - - /// - /// Lenght of block data - /// - public byte[] DataLength { get; set; } - - /// - /// Block Data - /// - public byte[] Data { get; set; } - - /// - /// Override this method to check the content of the block - /// - public override bool IsValid => GetLength() == Data.Length; - - /// - /// Calculates data length - /// - protected int GetLength() - { - return DataLength[0] + DataLength[1] << 8 + DataLength[2] << 16; - } - } - - /// - /// This class represents a TZX data block with empty body - /// - public abstract class TzxBodylessDataBlockBase : TzxDataBlockBase - { - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - } - } - - /// - /// This class represents a deprecated block - /// - public abstract class TzxDeprecatedDataBlockBase : TzxDataBlockBase - { - /// - /// Reads through the block infromation, and does not store it - /// - /// Stream to read the block from - public abstract void ReadThrough(BinaryReader reader); - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - throw new InvalidOperationException("Deprecated TZX data blocks cannot be written."); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs deleted file mode 100644 index cfd694d544..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/DataBlocks.cs +++ /dev/null @@ -1,1433 +0,0 @@ - -using BizHawk.Common; -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxArchiveInfoDataBlock : Tzx3ByteDataBlockBase - { - /// - /// Length of the whole block (without these two bytes) - /// - public ushort Length { get; set; } - - /// - /// Number of text strings - /// - public byte StringCount { get; set; } - - /// - /// List of text strings - /// - public TzxText[] TextStrings { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x32; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Length = reader.ReadUInt16(); - StringCount = reader.ReadByte(); - TextStrings = new TzxText[StringCount]; - for (var i = 0; i < StringCount; i++) - { - var text = new TzxText(); - text.ReadFrom(reader); - TextStrings[i] = text; - } - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Length); - writer.Write(StringCount); - foreach (var text in TextStrings) - { - text.WriteTo(writer); - } - } - } - - /// - /// This block was created to support the Commodore 64 standard - /// ROM and similar tape blocks. - /// - public class TzxC64RomTypeDataBlock : TzxDeprecatedDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x16; - - /// - /// Reads through the block infromation, and does not store it - /// - /// Stream to read the block from - public override void ReadThrough(BinaryReader reader) - { - var length = reader.ReadInt32(); - reader.ReadBytes(length - 4); - } - } - - /// - /// This block is made to support another type of encoding that is - /// commonly used by the C64. - /// - public class TzxC64TurboTapeDataBlock : TzxDeprecatedDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x17; - - /// - /// Reads through the block infromation, and does not store it - /// - /// Stream to read the block from - public override void ReadThrough(BinaryReader reader) - { - var length = reader.ReadInt32(); - reader.ReadBytes(length - 4); - } - } - - /// - /// This block is an analogue of the CALL Subroutine statement. - /// - /// - /// It basically executes a sequence of blocks that are somewhere - /// else and then goes back to the next block. Because more than - /// one call can be normally used you can include a list of sequences - /// to be called. The 'nesting' of call blocks is also not allowed - /// for the simplicity reasons. You can, of course, use the CALL - /// blocks in the LOOP sequences and vice versa. - /// - public class TzxCallSequenceDataBlock : TzxDataBlockBase - { - /// - /// Number of group name - /// - public byte NumberOfCalls { get; set; } - - /// - /// Group name bytes - /// - public ushort[] BlockOffsets { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x26; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - NumberOfCalls = reader.ReadByte(); - BlockOffsets = ReadWords(reader, NumberOfCalls); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(NumberOfCalls); - WriteWords(writer, BlockOffsets); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxCswRecordingDataBlock : TzxDataBlockBase - { - /// - /// Block length (without these four bytes) - /// - public uint BlockLength { get; set; } - - /// - /// Pause after this block - /// - public ushort PauseAfter { get; set; } - - /// - /// Sampling rate - /// - public byte[] SamplingRate { get; set; } - - /// - /// Compression type - /// - /// - /// 0x01=RLE, 0x02=Z-RLE - /// - public byte CompressionType { get; set; } - - /// - /// Number of stored pulses (after decompression, for validation purposes) - /// - public uint PulseCount { get; set; } - - /// - /// CSW data, encoded according to the CSW file format specification - /// - public byte[] Data { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x18; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - BlockLength = reader.ReadUInt32(); - PauseAfter = reader.ReadUInt16(); - SamplingRate = reader.ReadBytes(3); - CompressionType = reader.ReadByte(); - PulseCount = reader.ReadUInt32(); - var length = (int)BlockLength - 4 /* PauseAfter*/ - 3 /* SamplingRate */ - - 1 /* CompressionType */ - 4 /* PulseCount */; - Data = reader.ReadBytes(length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(BlockLength); - writer.Write(PauseAfter); - writer.Write(SamplingRate); - writer.Write(CompressionType); - writer.Write(PulseCount); - writer.Write(Data); - } - - /// - /// Override this method to check the content of the block - /// - public override bool IsValid => BlockLength == 4 + 3 + 1 + 4 + Data.Length; - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxCustomInfoDataBlock : Tzx3ByteDataBlockBase - { - /// - /// Identification string (in ASCII) - /// - public byte[] Id { get; set; } - - /// - /// String representation of the ID - /// - public string IdText => ToAsciiString(Id); - - /// - /// Length of the custom info - /// - public uint Length { get; set; } - - /// - /// Custom information - /// - public byte[] CustomInfo { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x35; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Id = reader.ReadBytes(10); - Length = reader.ReadUInt32(); - CustomInfo = reader.ReadBytes((int)Length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Id); - writer.Write(Length); - writer.Write(CustomInfo); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxDirectRecordingDataBlock : Tzx3ByteDataBlockBase - { - /// - /// Number of T-states per sample (bit of data) - /// - public ushort TactsPerSample { get; set; } - - /// - /// Pause after this block - /// - public ushort PauseAfter { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x15; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - TactsPerSample = reader.ReadUInt16(); - PauseAfter = reader.ReadUInt16(); - LastByteUsedBits = reader.ReadByte(); - DataLength = reader.ReadBytes(3); - Data = reader.ReadBytes(GetLength()); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(TactsPerSample); - writer.Write(PauseAfter); - writer.Write(LastByteUsedBits); - writer.Write(DataLength); - writer.Write(Data); - } - } - - /// - /// This is a special block that would normally be generated only by emulators. - /// - public class TzxEmulationInfoDataBlock : TzxDeprecatedDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x34; - - /// - /// Reads through the block infromation, and does not store it - /// - /// Stream to read the block from - public override void ReadThrough(BinaryReader reader) - { - reader.ReadBytes(8); - } - } - - /// - /// Represents a generalized data block in a TZX file - /// - public class TzxGeneralizedDataBlock : TzxDataBlockBase - { - /// - /// Block length (without these four bytes) - /// - public uint BlockLength { get; set; } - - /// - /// Pause after this block - /// - public ushort PauseAfter { get; set; } - - /// - /// Total number of symbols in pilot/sync block (can be 0) - /// - public uint Totp { get; set; } - - /// - /// Maximum number of pulses per pilot/sync symbol - /// - public byte Npp { get; set; } - - /// - /// Number of pilot/sync symbols in the alphabet table (0=256) - /// - public byte Asp { get; set; } - - /// - /// Total number of symbols in data stream (can be 0) - /// - public uint Totd { get; set; } - - /// - /// Maximum number of pulses per data symbol - /// - public byte Npd { get; set; } - - /// - /// Number of data symbols in the alphabet table (0=256) - /// - public byte Asd { get; set; } - - /// - /// Pilot and sync symbols definition table - /// - /// - /// This field is present only if Totp > 0 - /// - public TzxSymDef[] PilotSymDef { get; set; } - - /// - /// Pilot and sync data stream - /// - /// - /// This field is present only if Totd > 0 - /// - public TzxPrle[] PilotStream { get; set; } - - /// - /// Data symbols definition table - /// - /// - /// This field is present only if Totp > 0 - /// - public TzxSymDef[] DataSymDef { get; set; } - - /// - /// Data stream - /// - /// - /// This field is present only if Totd > 0 - /// - public TzxPrle[] DataStream { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x19; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - BlockLength = reader.ReadUInt32(); - PauseAfter = reader.ReadUInt16(); - Totp = reader.ReadUInt32(); - Npp = reader.ReadByte(); - Asp = reader.ReadByte(); - Totd = reader.ReadUInt32(); - Npd = reader.ReadByte(); - Asd = reader.ReadByte(); - - PilotSymDef = new TzxSymDef[Asp]; - for (var i = 0; i < Asp; i++) - { - var symDef = new TzxSymDef(Npp); - symDef.ReadFrom(reader); - PilotSymDef[i] = symDef; - } - - PilotStream = new TzxPrle[Totp]; - for (var i = 0; i < Totp; i++) - { - PilotStream[i].Symbol = reader.ReadByte(); - PilotStream[i].Repetitions = reader.ReadUInt16(); - } - - DataSymDef = new TzxSymDef[Asd]; - for (var i = 0; i < Asd; i++) - { - var symDef = new TzxSymDef(Npd); - symDef.ReadFrom(reader); - DataSymDef[i] = symDef; - } - - DataStream = new TzxPrle[Totd]; - for (var i = 0; i < Totd; i++) - { - DataStream[i].Symbol = reader.ReadByte(); - DataStream[i].Repetitions = reader.ReadUInt16(); - } - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(BlockLength); - writer.Write(PauseAfter); - writer.Write(Totp); - writer.Write(Npp); - writer.Write(Asp); - writer.Write(Totd); - writer.Write(Npd); - writer.Write(Asd); - for (var i = 0; i < Asp; i++) - { - PilotSymDef[i].WriteTo(writer); - } - for (var i = 0; i < Totp; i++) - { - writer.Write(PilotStream[i].Symbol); - writer.Write(PilotStream[i].Repetitions); - } - - for (var i = 0; i < Asd; i++) - { - DataSymDef[i].WriteTo(writer); - } - - for (var i = 0; i < Totd; i++) - { - writer.Write(DataStream[i].Symbol); - writer.Write(DataStream[i].Repetitions); - } - } - } - - /// - /// This block is generated when you merge two ZX Tape files together. - /// - /// - /// It is here so that you can easily copy the files together and use - /// them. Of course, this means that resulting file would be 10 bytes - /// longer than if this block was not used. All you have to do if - /// you encounter this block ID is to skip next 9 bytes. - /// - public class TzxGlueDataBlock : TzxDataBlockBase - { - /// - /// Value: { "XTape!", 0x1A, MajorVersion, MinorVersion } - /// - /// - /// Just skip these 9 bytes and you will end up on the next ID. - /// - public byte[] Glue { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x5A; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Glue = reader.ReadBytes(9); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Glue); - } - } - - /// - /// This indicates the end of a group. This block has no body. - /// - public class TzxGroupEndDataBlock : TzxBodylessDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x22; - } - - /// - /// This block marks the start of a group of blocks which are - /// to be treated as one single (composite) block. - /// - public class TzxGroupStartDataBlock : TzxDataBlockBase - { - /// - /// Number of group name - /// - public byte Length { get; set; } - - /// - /// Group name bytes - /// - public byte[] Chars { get; set; } - - /// - /// Gets the group name - /// - public string GroupName => ToAsciiString(Chars); - - /// - /// The ID of the block - /// - public override int BlockId => 0x21; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Length = reader.ReadByte(); - Chars = reader.ReadBytes(Length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Length); - writer.Write(Chars); - } - } - - /// - /// - /// - public class TzxHardwareInfoDataBlock : TzxDataBlockBase - { - /// - /// Number of machines and hardware types for which info is supplied - /// - public byte HwCount { get; set; } - - /// - /// List of machines and hardware - /// - public TzxHwInfo[] HwInfo { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x33; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - HwCount = reader.ReadByte(); - HwInfo = new TzxHwInfo[HwCount]; - for (var i = 0; i < HwCount; i++) - { - var hw = new TzxHwInfo(); - hw.ReadFrom(reader); - HwInfo[i] = hw; - } - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(HwCount); - foreach (var hw in HwInfo) - { - hw.WriteTo(writer); - } - } - } - - /// - /// This block will enable you to jump from one block to another within the file. - /// - /// - /// Jump 0 = 'Loop Forever' - this should never happen - /// Jump 1 = 'Go to the next block' - it is like NOP in assembler - /// Jump 2 = 'Skip one block' - /// Jump -1 = 'Go to the previous block' - /// - public class TzxJumpDataBlock : TzxDataBlockBase - { - /// - /// Relative jump value - /// - /// - /// - public short Jump { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x23; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Jump = reader.ReadInt16(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Jump); - } - } - - /// - /// It means that the utility should jump back to the start - /// of the loop if it hasn't been run for the specified number - /// of times. - /// - public class TzxLoopEndDataBlock : TzxBodylessDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x25; - } - - /// - /// If you have a sequence of identical blocks, or of identical - /// groups of blocks, you can use this block to tell how many - /// times they should be repeated. - /// - public class TzxLoopStartDataBlock : TzxDataBlockBase - { - /// - /// Number of repetitions (greater than 1) - /// - public ushort Loops { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x24; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Loops = reader.ReadUInt16(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Loops); - } - } - - /// - /// This will enable the emulators to display a message for a given time. - /// - /// - /// This should not stop the tape and it should not make silence. If the - /// time is 0 then the emulator should wait for the user to press a key. - /// - public class TzxMessageDataBlock : TzxDataBlockBase - { - /// - /// Time (in seconds) for which the message should be displayed - /// - public byte Time { get; set; } - - /// - /// Length of the description - /// - public byte MessageLength { get; set; } - - /// - /// The description bytes - /// - public byte[] Message; - - /// - /// The string form of description - /// - public string MessageText => ToAsciiString(Message); - - /// - /// The ID of the block - /// - public override int BlockId => 0x31; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Time = reader.ReadByte(); - MessageLength = reader.ReadByte(); - Message = reader.ReadBytes(MessageLength); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Time); - writer.Write(MessageLength); - writer.Write(Message); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxPulseSequenceDataBlock : TzxDataBlockBase - { - /// - /// Pause after this block - /// - public byte PulseCount { get; set; } - - /// - /// Lenght of block data - /// - public ushort[] PulseLengths { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x13; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - PulseCount = reader.ReadByte(); - PulseLengths = ReadWords(reader, PulseCount); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(PulseCount); - WriteWords(writer, PulseLengths); - } - - /// - /// Override this method to check the content of the block - /// - public override bool IsValid => PulseCount == PulseLengths.Length; - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxPureDataBlock : Tzx3ByteDataBlockBase - { - /// - /// Length of the zero bit - /// - public ushort ZeroBitPulseLength { get; set; } - - /// - /// Length of the one bit - /// - public ushort OneBitPulseLength { get; set; } - - /// - /// Pause after this block - /// - public ushort PauseAfter { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x14; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - ZeroBitPulseLength = reader.ReadUInt16(); - OneBitPulseLength = reader.ReadUInt16(); - LastByteUsedBits = reader.ReadByte(); - PauseAfter = reader.ReadUInt16(); - DataLength = reader.ReadBytes(3); - Data = reader.ReadBytes(GetLength()); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(ZeroBitPulseLength); - writer.Write(OneBitPulseLength); - writer.Write(LastByteUsedBits); - writer.Write(PauseAfter); - writer.Write(DataLength); - writer.Write(Data); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxPureToneDataBlock : TzxDataBlockBase - { - /// - /// Pause after this block - /// - public ushort PulseLength { get; private set; } - - /// - /// Lenght of block data - /// - public ushort PulseCount { get; private set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x12; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - PulseLength = reader.ReadUInt16(); - PulseCount = reader.ReadUInt16(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(PulseLength); - writer.Write(PulseCount); - } - } - - /// - /// This block indicates the end of the Called Sequence. - /// The next block played will be the block after the last - /// CALL block - /// - public class TzxReturnFromSequenceDataBlock : TzxBodylessDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x27; - } - - /// - /// Pause (silence) or 'Stop the Tape' block - /// - public class TzxSelectDataBlock : TzxDataBlockBase - { - /// - /// Length of the whole block (without these two bytes) - /// - public ushort Length { get; set; } - - /// - /// Number of selections - /// - public byte SelectionCount { get; set; } - - /// - /// List of selections - /// - public TzxSelect[] Selections { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x28; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Length = reader.ReadUInt16(); - SelectionCount = reader.ReadByte(); - Selections = new TzxSelect[SelectionCount]; - foreach (var selection in Selections) - { - selection.ReadFrom(reader); - } - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Length); - writer.Write(SelectionCount); - foreach (var selection in Selections) - { - selection.WriteTo(writer); - } - } - } - - /// - /// This block sets the current signal level to the specified value (high or low). - /// - public class TzxSetSignalLevelDataBlock : TzxDataBlockBase - { - /// - /// Length of the block without these four bytes - /// - public uint Lenght { get; } = 1; - - /// - /// Signal level (0=low, 1=high) - /// - public byte SignalLevel { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x2B; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - reader.ReadUInt32(); - SignalLevel = reader.ReadByte(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Lenght); - writer.Write(SignalLevel); - } - } - - /// - /// Pause (silence) or 'Stop the Tape' block - /// - public class TzxSilenceDataBlock : TzxDataBlockBase - { - /// - /// Duration of silence - /// - /// - /// This will make a silence (low amplitude level (0)) for a given time - /// in milliseconds. If the value is 0 then the emulator or utility should - /// (in effect) STOP THE TAPE, i.e. should not continue loading until - /// the user or emulator requests it. - /// - public ushort Duration { get; set; } - - /// - /// The ID of the block - /// - public override int BlockId => 0x20; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Duration = reader.ReadUInt16(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Duration); - } - } - - /// - /// This block was created to support the Commodore 64 standard - /// ROM and similar tape blocks. - /// - public class TzxSnapshotBlock : TzxDeprecatedDataBlockBase - { - /// - /// The ID of the block - /// - public override int BlockId => 0x40; - - /// - /// Reads through the block infromation, and does not store it - /// - /// Stream to read the block from - public override void ReadThrough(BinaryReader reader) - { - var length = reader.ReadInt32(); - length = length & 0x00FFFFFF; - reader.ReadBytes(length); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxStandardSpeedDataBlock : TzxDataBlockBase, ISupportsTapeBlockPlayback, ITapeData - { - private TapeDataBlockPlayer _player; - - /// - /// Pause after this block (default: 1000ms) - /// - public ushort PauseAfter { get; set; } = 1000; - - /// - /// Lenght of block data - /// - private ushort dataLength; - public ushort DataLength - { - get { return dataLength; } - set { dataLength = value; } - } - - /// - /// Block Data - /// - private byte[] data; - public byte[] Data - { - get { return data; } - set { data = value; } - } - - - /// - /// The ID of the block - /// - public override int BlockId => 0x10; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - PauseAfter = reader.ReadUInt16(); - DataLength = reader.ReadUInt16(); - Data = reader.ReadBytes(DataLength); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write((byte)BlockId); - writer.Write(PauseAfter); - writer.Write(DataLength); - writer.Write(Data, 0, DataLength); - } - - /// - /// The index of the currently playing byte - /// - /// This proprty is made public for test purposes - public int ByteIndex => _player.ByteIndex; - - /// - /// The mask of the currently playing bit in the current byte - /// - public byte BitMask => _player.BitMask; - - /// - /// The current playing phase - /// - public PlayPhase PlayPhase => _player.PlayPhase; - - /// - /// The tact count of the CPU when playing starts - /// - public long StartCycle=> _player.StartCycle; - - /// - /// Last tact queried - /// - public long LastTact => _player.LastCycle; - - /// - /// Initializes the player - /// - public void InitPlay(long startCycle) - { - _player = new TapeDataBlockPlayer(Data, PauseAfter); - _player.InitPlay(startCycle); - } - - /// - /// Gets the EAR bit value for the specified tact - /// - /// Tacts to retrieve the EAR bit - /// - /// The EAR bit value to play back - /// - public bool GetEarBit(long currentCycle) => _player.GetEarBit(currentCycle); - - public void SyncState(Serializer ser) - { - ser.BeginSection("TzxStandardSpeedDataBlock"); - - ser.Sync("dataLength", ref dataLength); - ser.Sync("data", ref data, false); - - ser.EndSection(); - } - } - - /// - /// When this block is encountered, the tape will stop ONLY if - /// the machine is an 48K Spectrum. - /// - /// - /// This block is to be used for multiloading games that load one - /// level at a time in 48K mode, but load the entire tape at once - /// if in 128K mode. This block has no body of its own, but follows - /// the extension rule. - /// - public class TzxStopTheTape48DataBlock : TzxDataBlockBase - { - /// - /// Length of the block without these four bytes (0) - /// - public uint Lenght { get; } = 0; - - /// - /// The ID of the block - /// - public override int BlockId => 0x2A; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - reader.ReadUInt32(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Lenght); - } - } - - /// - /// This is meant to identify parts of the tape, so you know where level 1 starts, - /// where to rewind to when the game ends, etc. - /// - /// - /// This description is not guaranteed to be shown while the tape is playing, - /// but can be read while browsing the tape or changing the tape pointer. - /// - public class TzxTextDescriptionDataBlock : TzxDataBlockBase - { - /// - /// Length of the description - /// - public byte DescriptionLength { get; set; } - - /// - /// The description bytes - /// - public byte[] Description; - - /// - /// The string form of description - /// - public string DescriptionText => ToAsciiString(Description); - - /// - /// The ID of the block - /// - public override int BlockId => 0x30; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - DescriptionLength = reader.ReadByte(); - Description = reader.ReadBytes(DescriptionLength); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(DescriptionLength); - writer.Write(Description); - } - } - - /// - /// Represents the standard speed data block in a TZX file - /// - public class TzxTurboSpeedDataBlock : Tzx3ByteDataBlockBase - { - /// - /// Length of pilot pulse - /// - public ushort PilotPulseLength { get; set; } - - /// - /// Length of the first sync pulse - /// - public ushort Sync1PulseLength { get; set; } - - /// - /// Length of the second sync pulse - /// - public ushort Sync2PulseLength { get; set; } - - /// - /// Length of the zero bit - /// - public ushort ZeroBitPulseLength { get; set; } - - /// - /// Length of the one bit - /// - public ushort OneBitPulseLength { get; set; } - - /// - /// Length of the pilot tone - /// - public ushort PilotToneLength { get; set; } - - /// - /// Pause after this block - /// - public ushort PauseAfter { get; set; } - - public TzxTurboSpeedDataBlock() - { - PilotPulseLength = 2168; - Sync1PulseLength = 667; - Sync2PulseLength = 735; - ZeroBitPulseLength = 855; - OneBitPulseLength = 1710; - PilotToneLength = 8063; - LastByteUsedBits = 8; - } - - /// - /// The ID of the block - /// - public override int BlockId => 0x11; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - PilotPulseLength = reader.ReadUInt16(); - Sync1PulseLength = reader.ReadUInt16(); - Sync2PulseLength = reader.ReadUInt16(); - ZeroBitPulseLength = reader.ReadUInt16(); - OneBitPulseLength = reader.ReadUInt16(); - PilotToneLength = reader.ReadUInt16(); - LastByteUsedBits = reader.ReadByte(); - PauseAfter = reader.ReadUInt16(); - DataLength = reader.ReadBytes(3); - Data = reader.ReadBytes(GetLength()); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(PilotPulseLength); - writer.Write(Sync1PulseLength); - writer.Write(Sync2PulseLength); - writer.Write(ZeroBitPulseLength); - writer.Write(OneBitPulseLength); - writer.Write(PilotToneLength); - writer.Write(LastByteUsedBits); - writer.Write(PauseAfter); - writer.Write(DataLength); - writer.Write(Data); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs deleted file mode 100644 index 0a83aa3bfc..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Info.cs +++ /dev/null @@ -1,250 +0,0 @@ - -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This blocks contains information about the hardware that the programs on this tape use. - /// - public class TzxHwInfo : ITapeDataSerialization - { - /// - /// Hardware type - /// - public byte HwType { get; set; } - - /// - /// Hardwer Id - /// - public byte HwId { get; set; } - - /// - /// Information about the tape - /// - /// - /// 00 - The tape RUNS on this machine or with this hardware, - /// but may or may not use the hardware or special features of the machine. - /// 01 - The tape USES the hardware or special features of the machine, - /// such as extra memory or a sound chip. - /// 02 - The tape RUNS but it DOESN'T use the hardware - /// or special features of the machine. - /// 03 - The tape DOESN'T RUN on this machine or with this hardware. - /// - public byte TapeInfo; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public void ReadFrom(BinaryReader reader) - { - HwType = reader.ReadByte(); - HwId = reader.ReadByte(); - TapeInfo = reader.ReadByte(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public void WriteTo(BinaryWriter writer) - { - writer.Write(HwType); - writer.Write(HwId); - writer.Write(TapeInfo); - } - } - - /// - /// Symbol repetitions - /// - public struct TzxPrle - { - /// - /// Symbol represented - /// - public byte Symbol; - - /// - /// Number of repetitions - /// - public ushort Repetitions; - } - - /// - /// This block represents an extremely wide range of data encoding techniques. - /// - /// - /// The basic idea is that each loading component (pilot tone, sync pulses, data) - /// is associated to a specific sequence of pulses, where each sequence (wave) can - /// contain a different number of pulses from the others. In this way we can have - /// a situation where bit 0 is represented with 4 pulses and bit 1 with 8 pulses. - /// - public class TzxSymDef : ITapeDataSerialization - { - /// - /// Bit 0 - Bit 1: Starting symbol polarity - /// - /// - /// 00: opposite to the current level (make an edge, as usual) - default - /// 01: same as the current level(no edge - prolongs the previous pulse) - /// 10: force low level - /// 11: force high level - /// - public byte SymbolFlags; - - /// - /// The array of pulse lengths - /// - public ushort[] PulseLengths; - - public TzxSymDef(byte maxPulses) - { - PulseLengths = new ushort[maxPulses]; - } - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public void ReadFrom(BinaryReader reader) - { - SymbolFlags = reader.ReadByte(); - PulseLengths = TzxDataBlockBase.ReadWords(reader, PulseLengths.Length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public void WriteTo(BinaryWriter writer) - { - writer.Write(SymbolFlags); - TzxDataBlockBase.WriteWords(writer, PulseLengths); - } - } - - /// - /// This is meant to identify parts of the tape, so you know where level 1 starts, - /// where to rewind to when the game ends, etc. - /// - /// - /// This description is not guaranteed to be shown while the tape is playing, - /// but can be read while browsing the tape or changing the tape pointer. - /// - public class TzxText : ITapeDataSerialization - { - /// - /// Text identification byte. - /// - /// - /// 00 - Full title - /// 01 - Software house/publisher - /// 02 - Author(s) - /// 03 - Year of publication - /// 04 - Language - /// 05 - Game/utility type - /// 06 - Price - /// 07 - Protection scheme/loader - /// 08 - Origin - /// FF - Comment(s) - /// - public byte Type { get; set; } - - /// - /// Length of the description - /// - public byte Length { get; set; } - - /// - /// The description bytes - /// - public byte[] TextBytes; - - /// - /// The string form of description - /// - public string Text => TzxDataBlockBase.ToAsciiString(TextBytes); - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public void ReadFrom(BinaryReader reader) - { - Type = reader.ReadByte(); - Length = reader.ReadByte(); - TextBytes = reader.ReadBytes(Length); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public void WriteTo(BinaryWriter writer) - { - writer.Write(Type); - writer.Write(Length); - writer.Write(TextBytes); - } - } - - /// - /// This block represents select structure - /// - public class TzxSelect : ITapeDataSerialization - { - /// - /// Bit 0 - Bit 1: Starting symbol polarity - /// - /// - /// 00: opposite to the current level (make an edge, as usual) - default - /// 01: same as the current level(no edge - prolongs the previous pulse) - /// 10: force low level - /// 11: force high level - /// - public ushort BlockOffset; - - /// - /// Length of the description - /// - public byte DescriptionLength { get; set; } - - /// - /// The description bytes - /// - public byte[] Description; - - /// - /// The string form of description - /// - public string DescriptionText => TzxDataBlockBase.ToAsciiString(Description); - - public TzxSelect(byte length) - { - DescriptionLength = length; - } - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public void ReadFrom(BinaryReader reader) - { - BlockOffset = reader.ReadUInt16(); - DescriptionLength = reader.ReadByte(); - Description = reader.ReadBytes(DescriptionLength); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public void WriteTo(BinaryWriter writer) - { - writer.Write(BlockOffset); - writer.Write(DescriptionLength); - writer.Write(Description); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs deleted file mode 100644 index 8742a35679..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/Types.cs +++ /dev/null @@ -1,282 +0,0 @@ - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Identified AD or DA converter types - /// - public enum TzxAdOrDaConverterType : byte - { - HarleySystemsAdc8P2 = 0x00, - BlackboardElectronics = 0x01 - } - - /// - /// Identified computer types - /// - public enum TzxComputerType : byte - { - ZxSpectrum16 = 0x00, - ZxSpectrum48OrPlus = 0x01, - ZxSpectrum48Issue1 = 0x02, - ZxSpectrum128 = 0x03, - ZxSpectrum128P2 = 0x04, - ZxSpectrum128P2AOr3 = 0x05, - Tc2048 = 0x06, - Ts2068 = 0x07, - Pentagon128 = 0x08, - SamCoupe = 0x09, - DidaktikM = 0x0A, - DidaktikGama = 0x0B, - Zx80 = 0x0C, - Zx81 = 0x0D, - ZxSpectrum128Spanish = 0x0E, - ZxSpectrumArabic = 0x0F, - Tk90X = 0x10, - Tk95 = 0x11, - Byte = 0x12, - Elwro800D3 = 0x13, - ZsScorpion256 = 0x14, - AmstradCpc464 = 0x15, - AmstradCpc664 = 0x16, - AmstradCpc6128 = 0x17, - AmstradCpc464P = 0x18, - AmstradCpc6128P = 0x19, - JupiterAce = 0x1A, - Enterprise = 0x1B, - Commodore64 = 0x1C, - Commodore128 = 0x1D, - InvesSpectrumP = 0x1E, - Profi = 0x1F, - GrandRomMax = 0x20, - Kay1024 = 0x21, - IceFelixHc91 = 0x22, - IceFelixHc2000 = 0x23, - AmaterskeRadioMistrum = 0x24, - Quorum128 = 0x25, - MicroArtAtm = 0x26, - MicroArtAtmTurbo2 = 0x27, - Chrome = 0x28, - ZxBadaloc = 0x29, - Ts1500 = 0x2A, - Lambda = 0x2B, - Tk65 = 0x2C, - Zx97 = 0x2D - } - - /// - /// Identified digitizer types - /// - public enum TzxDigitizerType : byte - { - RdDigitalTracer = 0x00, - DkTronicsLightPen = 0x01, - MicrographPad = 0x02, - RomnticRobotVideoface = 0x03 - } - - /// - /// Identified EPROM programmer types - /// - public enum TzxEpromProgrammerType : byte - { - OrmeElectronics = 0x00 - } - - /// - /// Identified external storage types - /// - public enum TzxExternalStorageType : byte - { - ZxMicroDrive = 0x00, - OpusDiscovery = 0x01, - MgtDisciple = 0x02, - MgtPlusD = 0x03, - RobotronicsWafaDrive = 0x04, - TrDosBetaDisk = 0x05, - ByteDrive = 0x06, - Watsford = 0x07, - Fiz = 0x08, - Radofin = 0x09, - DidaktikDiskDrive = 0x0A, - BsDos = 0x0B, - ZxSpectrumP3DiskDrive = 0x0C, - JloDiskInterface = 0x0D, - TimexFdd3000 = 0x0E, - ZebraDiskDrive = 0x0F, - RamexMillenia = 0x10, - Larken = 0x11, - KempstonDiskInterface = 0x12, - Sandy = 0x13, - ZxSpectrumP3EHardDisk = 0x14, - ZxAtaSp = 0x15, - DivIde = 0x16, - ZxCf = 0x17 - } - - /// - /// Identified graphics types - /// - public enum TzxGraphicsType : byte - { - WrxHiRes = 0x00, - G007 = 0x01, - Memotech = 0x02, - LambdaColour = 0x03 - } - - /// - /// Represents the hardware types that can be defined - /// - public enum TzxHwType : byte - { - Computer = 0x00, - ExternalStorage = 0x01, - RomOrRamTypeAddOn = 0x02, - SoundDevice = 0x03, - JoyStick = 0x04, - Mouse = 0x05, - OtherController = 0x06, - SerialPort = 0x07, - ParallelPort = 0x08, - Printer = 0x09, - Modem = 0x0A, - Digitizer = 0x0B, - NetworkAdapter = 0x0C, - Keyboard = 0x0D, - AdOrDaConverter = 0x0E, - EpromProgrammer = 0x0F, - Graphics = 0x10 - } - - /// - /// Identified joystick types - /// - public enum TzxJoystickType - { - Kempston = 0x00, - ProtekCursor = 0x01, - Sinclair2Left = 0x02, - Sinclair1Right = 0x03, - Fuller = 0x04 - } - - /// - /// Identified keyboard and keypad types - /// - public enum TzxKeyboardType : byte - { - KeypadForZxSpectrum128K = 0x00 - } - - /// - /// Identified modem types - /// - public enum TzxModemTypes : byte - { - PrismVtx5000 = 0x00, - Westridge2050 = 0x01 - } - - /// - /// Identified mouse types - /// - public enum TzxMouseType : byte - { - AmxMouse = 0x00, - KempstonMouse = 0x01 - } - - /// - /// Identified network adapter types - /// - public enum TzxNetworkAdapterType : byte - { - ZxInterface1 = 0x00 - } - - /// - /// Identified other controller types - /// - public enum TzxOtherControllerType : byte - { - Trisckstick = 0x00, - ZxLightGun = 0x01, - ZebraGraphicTablet = 0x02, - DefnederLightGun = 0x03 - } - - /// - /// Identified parallel port types - /// - public enum TzxParallelPortType : byte - { - KempstonS = 0x00, - KempstonE = 0x01, - ZxSpectrum3P = 0x02, - Tasman = 0x03, - DkTronics = 0x04, - Hilderbay = 0x05, - InesPrinterface = 0x06, - ZxLprintInterface3 = 0x07, - MultiPrint = 0x08, - OpusDiscovery = 0x09, - Standard8255 = 0x0A - } - - /// - /// Identified printer types - /// - public enum TzxPrinterType : byte - { - ZxPrinter = 0x00, - GenericPrinter = 0x01, - EpsonCompatible = 0x02 - } - - /// - /// Identifier ROM or RAM add-on types - /// - public enum TzxRomRamAddOnType : byte - { - SamRam = 0x00, - MultifaceOne = 0x01, - Multiface128K = 0x02, - MultifaceP3 = 0x03, - MultiPrint = 0x04, - Mb02 = 0x05, - SoftRom = 0x06, - Ram1K = 0x07, - Ram16K = 0x08, - Ram48K = 0x09, - Mem8To16KUsed = 0x0A - } - - /// - /// Identified serial port types - /// - public enum TzxSerialPortType : byte - { - ZxInterface1 = 0x00, - ZxSpectrum128 = 0x01 - } - - /// - /// Identified sound device types - /// - public enum TzxSoundDeviceType : byte - { - ClassicAy = 0x00, - FullerBox = 0x01, - CurrahMicroSpeech = 0x02, - SpectDrum = 0x03, - MelodikAyAcbStereo = 0x04, - AyAbcStereo = 0x05, - RamMusinMachine = 0x06, - Covox = 0x07, - GeneralSound = 0x08, - IntecEdiB8001 = 0x09, - ZonXAy = 0x0A, - QuickSilvaAy = 0x0B, - JupiterAce = 0x0C - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs deleted file mode 100644 index 8ebe4921e5..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxException.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class represents a TZX-related exception - /// - public class TzxException : Exception - { - /// - /// Initializes the exception with the specified message - /// - /// Exception message - public TzxException(string message) : base(message) - { - } - - /// - /// Initializes the exception with the specified message - /// and inner exception - /// - /// Exception message - /// Inner exception - public TzxException(string message, Exception innerException) : base(message, innerException) - { - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs deleted file mode 100644 index e6901b4d7b..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxHeader.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// Represents the header of the TZX file - /// - public class TzxHeader : TzxDataBlockBase - { - public static IReadOnlyList TzxSignature = - new ReadOnlyCollection(new byte[] { 0x5A, 0x58, 0x54, 0x61, 0x70, 0x65, 0x21 }); - public byte[] Signature { get; private set; } - public byte Eot { get; private set; } - public byte MajorVersion { get; private set; } - public byte MinorVersion { get; private set; } - - public TzxHeader(byte majorVersion = 1, byte minorVersion = 20) - { - Signature = TzxSignature.ToArray(); - Eot = 0x1A; - MajorVersion = majorVersion; - MinorVersion = minorVersion; - } - - /// - /// The ID of the block - /// - public override int BlockId => 0x00; - - /// - /// Reads the content of the block from the specified binary stream. - /// - /// Stream to read the block from - public override void ReadFrom(BinaryReader reader) - { - Signature = reader.ReadBytes(7); - Eot = reader.ReadByte(); - MajorVersion = reader.ReadByte(); - MinorVersion = reader.ReadByte(); - } - - /// - /// Writes the content of the block to the specified binary stream. - /// - /// Stream to write the block to - public override void WriteTo(BinaryWriter writer) - { - writer.Write(Signature); - writer.Write(Eot); - writer.Write(MajorVersion); - writer.Write(MinorVersion); - } - - #region Overrides of TzxDataBlockBase - - /// - /// Override this method to check the content of the block - /// - public override bool IsValid => Signature.SequenceEqual(TzxSignature) - && Eot == 0x1A - && MajorVersion == 1; - - #endregion - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs deleted file mode 100644 index a9ac846467..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxPlayer.cs +++ /dev/null @@ -1,94 +0,0 @@ -using BizHawk.Common; -using System.IO; -using System.Linq; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class is responsible to "play" a TZX file. - /// - public class TzxPlayer : TzxReader, ISupportsTapeBlockPlayback - { - private TapeBlockSetPlayer _player; - - /// - /// Signs that the player completed playing back the file - /// - public bool Eof => _player.Eof; - - /// - /// Initializes the player from the specified reader - /// - /// BinaryReader instance to get TZX file data from - public TzxPlayer(BinaryReader reader) : base(reader) - { - } - - /// - /// Reads in the content of the TZX file so that it can be played - /// - /// True, if read was successful; otherwise, false - public override bool ReadContent() - { - var success = base.ReadContent(); - var blocks = DataBlocks.Where(b => b is ISupportsTapeBlockPlayback) - .Cast() - .ToList(); - _player = new TapeBlockSetPlayer(blocks); - return success; - } - - /// - /// Gets the currently playing block's index - /// - public int CurrentBlockIndex => _player.CurrentBlockIndex; - - /// - /// The current playable block - /// - public ISupportsTapeBlockPlayback CurrentBlock => _player.CurrentBlock; - - /// - /// The current playing phase - /// - public PlayPhase PlayPhase => _player.PlayPhase; - - /// - /// The tact count of the CPU when playing starts - /// - public long StartCycle => _player.StartCycle; - - /// - /// Initializes the player - /// - public void InitPlay(long startTact) - { - _player.InitPlay(startTact); - } - - /// - /// Gets the EAR bit value for the specified tact - /// - /// Tacts to retrieve the EAR bit - /// - /// A tuple of the EAR bit and a flag that indicates it is time to move to the next block - /// - public bool GetEarBit(long currentTact) => _player.GetEarBit(currentTact); - - /// - /// Moves the current block index to the next playable block - /// - /// Tacts time to start the next block - public void NextBlock(long currentTact) => _player.NextBlock(currentTact); - - - public void SyncState(Serializer ser) - { - ser.BeginSection("TzxPlayer"); - - _player.SyncState(ser); - - ser.EndSection(); - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs deleted file mode 100644 index ad7ea786ab..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TZX/TzxReader.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - /// - /// This class reads a TZX file - /// - public class TzxReader - { - private readonly BinaryReader _reader; - - public static Dictionary DataBlockTypes = new Dictionary - { - {0x10, typeof(TzxStandardSpeedDataBlock)}, - {0x11, typeof(TzxTurboSpeedDataBlock)}, - {0x12, typeof(TzxPureToneDataBlock)}, - {0x13, typeof(TzxPulseSequenceDataBlock)}, - {0x14, typeof(TzxPureDataBlock)}, - {0x15, typeof(TzxDirectRecordingDataBlock)}, - {0x16, typeof(TzxC64RomTypeDataBlock)}, - {0x17, typeof(TzxC64TurboTapeDataBlock)}, - {0x18, typeof(TzxCswRecordingDataBlock)}, - {0x19, typeof(TzxGeneralizedDataBlock)}, - {0x20, typeof(TzxSilenceDataBlock)}, - {0x21, typeof(TzxGroupStartDataBlock)}, - {0x22, typeof(TzxGroupEndDataBlock)}, - {0x23, typeof(TzxJumpDataBlock)}, - {0x24, typeof(TzxLoopStartDataBlock)}, - {0x25, typeof(TzxLoopEndDataBlock)}, - {0x26, typeof(TzxCallSequenceDataBlock)}, - {0x27, typeof(TzxReturnFromSequenceDataBlock)}, - {0x28, typeof(TzxSelectDataBlock)}, - {0x2A, typeof(TzxStopTheTape48DataBlock)}, - {0x2B, typeof(TzxSetSignalLevelDataBlock)}, - {0x30, typeof(TzxTextDescriptionDataBlock)}, - {0x31, typeof(TzxMessageDataBlock)}, - {0x32, typeof(TzxArchiveInfoDataBlock)}, - {0x33, typeof(TzxHardwareInfoDataBlock)}, - {0x34, typeof(TzxEmulationInfoDataBlock)}, - {0x35, typeof(TzxCustomInfoDataBlock)}, - {0x40, typeof(TzxSnapshotBlock)}, - {0x5A, typeof(TzxGlueDataBlock)}, - }; - - /// - /// Data blocks of this TZX file - /// - public IList DataBlocks { get; } - - /// - /// Major version number of the file - /// - public byte MajorVersion { get; private set; } - - /// - /// Minor version number of the file - /// - public byte MinorVersion { get; private set; } - - /// - /// Initializes the player from the specified reader - /// - /// - public TzxReader(BinaryReader reader) - { - _reader = reader; - DataBlocks = new List(); - } - - /// - /// Reads in the content of the TZX file so that it can be played - /// - /// True, if read was successful; otherwise, false - public virtual bool ReadContent() - { - var header = new TzxHeader(); - try - { - header.ReadFrom(_reader); - if (!header.IsValid) - { - throw new TzxException("Invalid TZX header"); - } - MajorVersion = header.MajorVersion; - MinorVersion = header.MinorVersion; - - while (_reader.BaseStream.Position != _reader.BaseStream.Length) - { - var blockType = _reader.ReadByte(); - Type type; - if (!DataBlockTypes.TryGetValue(blockType, out type)) - { - throw new TzxException($"Unkonwn TZX block type: {blockType}"); - } - - try - { - var block = Activator.CreateInstance(type) as TzxDataBlockBase; - if (block.GetType() == typeof(TzxDeprecatedDataBlockBase)) - { - ((TzxDeprecatedDataBlockBase)block as TzxDeprecatedDataBlockBase).ReadThrough(_reader); - } - else - { - block?.ReadFrom(_reader); - } - DataBlocks.Add(block); - } - catch (Exception ex) - { - throw new TzxException($"Cannot read TZX data block {type}.", ex); - } - } - return true; - } - catch - { - // --- This exception is intentionally ignored - return false; - } - } - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index afcf6b122f..f4f78dd4fc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -1,6 +1,6 @@ ## ZXHawk -At this moment this is still *very* experimental and needs a lot more work. +At the moment this is very experimental and is still actively being worked on. ### Implemented and sorta working * IEmulator @@ -13,21 +13,23 @@ At this moment this is still *very* experimental and needs a lot more work. * Keyboard input (implementing IInputPollable) * Kempston joystick (mapped to J1 currently) * Tape device that will load spectrum games in realtime (*.tzx and *.tap) +* Most tape protection/loading schemes that I've tested are currently working (see caveat below) * IStatable * ISettable core settings -* IMemoryDomains (I think) ### Work in progress * Exact emulator timings * Floating memory bus emulation * Tape auto-loading routines (currently you have to manually start and stop the virtual tape device) +* TASStudio (need to verify that this works as it should) ### Not working -* IDebuggable +* IDebuggable (probably IMemoryDomains is setup incorrectly) * ZX Spectrum Plus3 emulation * Default keyboard keymappings (you have to configure yourself in the core controller settings) ### Known bugs -* Audible 'popping' from the emulated buzzer after a load state operation +* Audible 'popping' from the emulated buzzer after a load state operation (maybe this is a normal thing) +* Speedlock tape protection scheme doesn't appear to load correctly -Asnivor From 90c1e293bfc630c27dcd46ea8ea3951047ebd32c Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 5 Mar 2018 11:17:22 +0000 Subject: [PATCH 049/105] Implemented multi bundler functionlity and multiple tape controls --- BizHawk.Client.Common/RomLoader.cs | 5 +- .../MultiDiskBundler.Designer.cs | 3 +- .../VirtualPads/schema/ZXSpectrumSchema.cs | 19 +- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/Datacorder/DatacorderDevice.cs | 5 +- .../Machine/SpectrumBase.Input.cs | 10 + .../Machine/SpectrumBase.Media.cs | 191 ++++++++++++++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 6 + .../Machine/ZXSpectrum128K/ZX128.cs | 7 +- .../Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs | 4 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 7 +- .../Machine/ZXSpectrum16K/ZX16.cs | 4 +- .../Machine/ZXSpectrum48K/ZX48.cs | 9 +- .../ZXSpectrum.Controllers.cs | 2 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 30 +-- 15 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index cd1ce95f22..3bc0b7059a 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -661,7 +661,8 @@ namespace BizHawk.Client.Common case "ZXSpectrum": nextEmulator = new ZXSpectrum( nextComm, - xmlGame.Assets.Select(a => a.Value).First(), + xmlGame.Assets.Select(a => a.Value), //.First(), + GameInfo.NullInstance, (ZXSpectrum.ZXSpectrumSettings)GetCoreSettings(), (ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings()); break; @@ -999,7 +1000,7 @@ namespace BizHawk.Client.Common nextEmulator = c64; break; case "ZXSpectrum": - var zx = new ZXSpectrum(nextComm, rom.FileData, GetCoreSettings(), GetCoreSyncSettings()); + var zx = new ZXSpectrum(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); nextEmulator = zx; break; case "GBA": diff --git a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs index 3c1e2f2e9e..5616d71e02 100644 --- a/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs +++ b/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.Designer.cs @@ -143,7 +143,8 @@ "GB", "PCFX", "PSX", - "SAT"}); + "SAT", + "ZXSpectrum"}); this.SystemDropDown.Location = new System.Drawing.Point(425, 75); this.SystemDropDown.Name = "SystemDropDown"; this.SystemDropDown.Size = new System.Drawing.Size(69, 21); diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs index fbcb9b9e57..1995d75757 100644 --- a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs @@ -233,7 +233,24 @@ namespace BizHawk.Client.EmuHawk Icon = Properties.Resources.BackMore, Location = new Point(83, 22), Type = PadSchema.PadInputType.Boolean - } + }, + new PadSchema.ButtonSchema + { + Name = "Insert Next Tape", + DisplayName = "NEXT TAPE", + //Icon = Properties.Resources.MoveRight, + Location = new Point(23, 52), + Type = PadSchema.PadInputType.Boolean + }, + new PadSchema.ButtonSchema + { + Name = "Insert Previous Tape", + DisplayName = "PREV TAPE", + //Icon = Properties.Resources.MoveLeft, + Location = new Point(100, 52), + Type = PadSchema.PadInputType.Boolean + }, + } }; } diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index f108d03e54..6149403b27 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1378,6 +1378,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 0080872421..be24dffbc9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -401,6 +401,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("currentState", ref currentState); //_dataBlocks + /* ser.BeginSection("Datablocks"); if (ser.IsWriter) @@ -417,11 +418,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { ser.Sync("_tempBlockCount", ref _tempBlockCount); } - + ser.EndSection(); - + */ ser.EndSection(); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 85bac061d8..8a4837ac9f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -10,6 +10,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string Stop = "Stop Tape"; string RTZ = "RTZ Tape"; string Record = "Record Tape"; + string NextTape = "Insert Next Tape"; + string PrevTape = "Insert Previous Tape"; public void PollInput() { @@ -69,6 +71,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { } + if (Spectrum._controller.IsPressed(NextTape)) + { + TapeMediaIndex++; + } + if (Spectrum._controller.IsPressed(PrevTape)) + { + TapeMediaIndex--; + } } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs new file mode 100644 index 0000000000..3c60ad48df --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public abstract partial class SpectrumBase + { + // until +3 disk drive is emulated, we assume that incoming files are tape images + + /// + /// The tape or disk image(s) that are passed in from the main ZXSpectrum class + /// + protected List mediaImages { get; set; } + + /// + /// Tape images + /// + protected List tapeImages { get; set; } + + /// + /// Disk images + /// + protected List diskImages { get; set; } + + /// + /// The index of the currently 'loaded' tape or disk image + /// + protected int tapeMediaIndex; + public int TapeMediaIndex + { + get { return tapeMediaIndex; } + set + { + int tmp = value; + int result = value; + + if (tapeImages == null || tapeImages.Count() == 0) + { + // no tape images found + return; + } + + if (value >= tapeImages.Count()) + { + // media at this index does not exist - loop back to 0 + result = 0; + } + else if (value < 0) + { + // negative index not allowed - move to last item in the collection + result = tapeImages.Count() - 1; + } + + // load the media into the tape device + tapeMediaIndex = result; + LoadTapeMedia(); + } + } + + /// + /// The index of the currently 'loaded' tape or disk image + /// + protected int diskMediaIndex; + public int DiskMediaIndex + { + get { return diskMediaIndex; } + set + { + int tmp = value; + int result = value; + + if (diskImages == null || diskImages.Count() == 0) + { + // no tape images found + return; + } + + if (value >= diskImages.Count()) + { + // media at this index does not exist - loop back to 0 + result = 0; + } + else if (value < 0) + { + // negative index not allowed - move to last item in the collection + result = diskImages.Count() - 1; + } + + // load the media into the disk device + diskMediaIndex = result; + LoadDiskMedia(); + } + } + + /// + /// Called on first instantiation (and subsequent core reboots) + /// + /// + protected void InitializeMedia(List files) + { + mediaImages = files; + LoadAllMedia(); + } + + /// + /// Attempts to load all media into the relevant structures + /// + protected void LoadAllMedia() + { + tapeImages = new List(); + diskImages = new List(); + + foreach (var m in mediaImages) + { + switch (IdentifyMedia(m)) + { + case SpectrumMediaType.Tape: + tapeImages.Add(m); + break; + case SpectrumMediaType.Disk: + diskImages.Add(m); + break; + } + } + + if (tapeImages.Count > 0) + LoadTapeMedia(); + + if (diskImages.Count > 0) + LoadDiskMedia(); + } + + /// + /// Attempts to load a tape into the tape device based on tapeMediaIndex + /// + protected void LoadTapeMedia() + { + TapeDevice.LoadTape(tapeImages[tapeMediaIndex]); + } + + /// + /// Attempts to load a disk into the disk device based on diskMediaIndex + /// + protected void LoadDiskMedia() + { + throw new NotImplementedException("+3 disk drive device not yet implemented"); + } + + /// + /// Identifies and sorts the various media types + /// + /// + private SpectrumMediaType IdentifyMedia(byte[] data) + { + // get first 16 bytes as a string + string hdr = Encoding.ASCII.GetString(data.Take(16).ToArray()); + + // disk checking first + if (hdr.ToUpper().Contains("EXTENDED CPC DSK")) + { + // spectrum .dsk disk file + return SpectrumMediaType.Disk; + } + if (hdr.ToUpper().StartsWith("FDI")) + { + // spectrum .fdi disk file + return SpectrumMediaType.Disk; + } + + // tape checking + if (hdr.ToUpper().StartsWith("ZXTAPE!")) + { + // spectrum .tzx tape file + return SpectrumMediaType.Tape; + } + + // if we get this far, assume a .tap file + return SpectrumMediaType.Tape; + } + } + + public enum SpectrumMediaType + { + None, + Tape, + Disk + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 11ec6ae96b..38594c7670 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -208,6 +208,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (AYDevice != null) AYDevice.SyncState(ser); + ser.Sync("tapeMediaIndex", ref tapeMediaIndex); + TapeMediaIndex = tapeMediaIndex; + + ser.Sync("diskMediaIndex", ref diskMediaIndex); + DiskMediaIndex = diskMediaIndex; + TapeDevice.SyncState(ser); ser.EndSection(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 5188a658a5..1844d7ebe4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) + public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) { Spectrum = spectrum; CPU = cpu; @@ -40,11 +40,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); - TapeDevice.LoadTape(file); + + InitializeMedia(files); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs index b88bcb3e58..a1f3c8d019 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs @@ -20,8 +20,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) - : base(spectrum, cpu, borderType, file) + public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) + : base(spectrum, cpu, borderType, files) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 36380c17b3..403c8495a5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) { Spectrum = spectrum; CPU = cpu; @@ -40,11 +40,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); - TapeDevice.LoadTape(file); + + InitializeMedia(files); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index b828eb9b48..b89e07957c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -16,8 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) - : base(spectrum, cpu, borderType, file) + public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) + : base(spectrum, cpu, borderType, files) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index deddf9bda0..a9e2bc5959 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, byte[] file) + public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) { Spectrum = spectrum; CPU = cpu; @@ -31,11 +31,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new Keyboard48(this); KempstonDevice = new KempstonJoystick(this); - //TapeProvider = new DefaultTapeProvider(file); - TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); - TapeDevice.LoadTape(file); + + InitializeMedia(files); + + //TapeDevice.LoadTape(file); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index 018c9b67b1..fdbc8c5e02 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Keyboard - row 5 "Key Symbol Shift", "Key Semi-Colon", "Key Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", // Tape functions - "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape" + "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape" } }; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 6694a7e97d..4c1a1be8d4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public partial class ZXSpectrum : IDebuggable, IInputPollable, IStatable, IRegionable { [CoreConstructor("ZXSpectrum")] - public ZXSpectrum(CoreComm comm, byte[] file, object settings, object syncSettings) + public ZXSpectrum(CoreComm comm, IEnumerable files, GameInfo game, object settings, object syncSettings) { PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings()); PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings()); @@ -34,29 +34,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _tracer = new TraceBuffer { Header = _cpu.TraceHeader }; - _file = file; + //_file = file; + _files = files?.ToList() ?? new List(); switch (SyncSettings.MachineType) { case MachineType.ZXSpectrum16: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); break; case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); break; case MachineType.ZXSpectrum128: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); break; case MachineType.ZXSpectrum128Plus2: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); break; case MachineType.ZXSpectrum128Plus3: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _file); + Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); break; default: throw new InvalidOperationException("Machine not yet emulated"); @@ -104,7 +105,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private DCFilter dcf; - private byte[] _file; + //private byte[] _file; + private readonly List _files; public bool DiagRom = false; @@ -152,37 +154,37 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } - private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, byte[] file) + private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, List files) { // setup the emulated model based on the MachineType switch (machineType) { case MachineType.ZXSpectrum16: - _machine = new ZX16(this, _cpu, borderType, file); + _machine = new ZX16(this, _cpu, borderType, files); var _systemRom16 = GetFirmware(0x4000, "48ROM"); var romData16 = RomData.InitROM(machineType, _systemRom16); _machine.InitROM(romData16); break; case MachineType.ZXSpectrum48: - _machine = new ZX48(this, _cpu, borderType, file); + _machine = new ZX48(this, _cpu, borderType, files); var _systemRom = GetFirmware(0x4000, "48ROM"); var romData = RomData.InitROM(machineType, _systemRom); _machine.InitROM(romData); break; case MachineType.ZXSpectrum128: - _machine = new ZX128(this, _cpu, borderType, file); + _machine = new ZX128(this, _cpu, borderType, files); var _systemRom128 = GetFirmware(0x8000, "128ROM"); var romData128 = RomData.InitROM(machineType, _systemRom128); _machine.InitROM(romData128); break; case MachineType.ZXSpectrum128Plus2: - _machine = new ZX128Plus2(this, _cpu, borderType, file); + _machine = new ZX128Plus2(this, _cpu, borderType, files); var _systemRomP2 = GetFirmware(0x8000, "PLUS2ROM"); var romDataP2 = RomData.InitROM(machineType, _systemRomP2); _machine.InitROM(romDataP2); break; case MachineType.ZXSpectrum128Plus3: - _machine = new ZX128Plus3(this, _cpu, borderType, file); + _machine = new ZX128Plus3(this, _cpu, borderType, files); var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); var romDataP3 = RomData.InitROM(machineType, _systemRomP3); _machine.InitROM(romDataP3); From 23c07cdb678fc3b782140b32a1ee54e3b05e798a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 5 Mar 2018 13:29:34 +0000 Subject: [PATCH 050/105] OSD message handling implementation --- BizHawk.Client.Common/RomLoader.cs | 11 +- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/Datacorder/DatacorderDevice.cs | 5 + .../Machine/SpectrumBase.Input.cs | 57 ++++- .../Machine/SpectrumBase.Media.cs | 3 + .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 21 ++ .../SinclairSpectrum/ZXSpectrum.Messaging.cs | 197 ++++++++++++++++++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 6 +- 8 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index 3bc0b7059a..39ae27262e 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -659,10 +659,17 @@ namespace BizHawk.Client.Common (C64.C64SyncSettings)GetCoreSyncSettings()); break; case "ZXSpectrum": + + List zxGI = new List(); + foreach (var a in xmlGame.Assets) + { + zxGI.Add(new GameInfo { Name = Path.GetFileNameWithoutExtension(a.Key) }); + } + nextEmulator = new ZXSpectrum( nextComm, xmlGame.Assets.Select(a => a.Value), //.First(), - GameInfo.NullInstance, + zxGI, // GameInfo.NullInstance, (ZXSpectrum.ZXSpectrumSettings)GetCoreSettings(), (ZXSpectrum.ZXSpectrumSyncSettings)GetCoreSyncSettings()); break; @@ -1000,7 +1007,7 @@ namespace BizHawk.Client.Common nextEmulator = c64; break; case "ZXSpectrum": - var zx = new ZXSpectrum(nextComm, Enumerable.Repeat(rom.RomData, 1), rom.GameInfo, GetCoreSettings(), GetCoreSyncSettings()); + var zx = new ZXSpectrum(nextComm, Enumerable.Repeat(rom.RomData, 1), Enumerable.Repeat(rom.GameInfo, 1).ToList(), GetCoreSettings(), GetCoreSyncSettings()); nextEmulator = zx; break; case "GBA": diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 6149403b27..d85d488b0a 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -296,6 +296,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index be24dffbc9..eb37c02cf4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -133,6 +133,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_tapeIsPlaying) return; + _machine.Spectrum.OSD_TapePlaying(); + // update the lastCycle _lastCycle = _cpu.TotalExecutedCycles; @@ -183,6 +185,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (!_tapeIsPlaying) return; + _machine.Spectrum.OSD_TapeStopped(); + // sign that the tape is no longer playing _tapeIsPlaying = false; @@ -224,6 +228,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void RTZ() { Stop(); + _machine.Spectrum.OSD_TapeRTZ(); _currentDataBlockIndex = 0; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 8a4837ac9f..97bd1c0e5f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -13,6 +13,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string NextTape = "Insert Next Tape"; string PrevTape = "Insert Previous Tape"; + bool pressed_Play = false; + bool pressed_Stop = false; + bool pressed_RTZ = false; + bool pressed_NextTape = false; + bool pressed_PrevTape = false; + public void PollInput() { Spectrum.InputCallbacks.Call(); @@ -22,8 +28,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // parse single keyboard matrix keys 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); @@ -57,28 +61,67 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Tape control if (Spectrum._controller.IsPressed(Play)) { - TapeDevice.Play(); + if (!pressed_Play) + { + Spectrum.OSD_FireInputMessage(Play); + TapeDevice.Play(); + pressed_Play = true; + } } + else + pressed_Play = false; + if (Spectrum._controller.IsPressed(Stop)) { - TapeDevice.Stop(); + if (!pressed_Stop) + { + Spectrum.OSD_FireInputMessage(Stop); + TapeDevice.Stop(); + pressed_Stop = true; + } } + else + pressed_Stop = false; + if (Spectrum._controller.IsPressed(RTZ)) { - TapeDevice.RTZ(); + if (!pressed_RTZ) + { + Spectrum.OSD_FireInputMessage(RTZ); + TapeDevice.RTZ(); + pressed_RTZ = true; + } } + else + pressed_RTZ = false; + if (Spectrum._controller.IsPressed(Record)) { } if (Spectrum._controller.IsPressed(NextTape)) { - TapeMediaIndex++; + if (!pressed_NextTape) + { + Spectrum.OSD_FireInputMessage(NextTape); + TapeMediaIndex++; + pressed_NextTape = true; + } } + else + pressed_NextTape = false; + if (Spectrum._controller.IsPressed(PrevTape)) { - TapeMediaIndex--; + if (!pressed_PrevTape) + { + Spectrum.OSD_FireInputMessage(PrevTape); + TapeMediaIndex--; + pressed_PrevTape = true; + } } + else + pressed_PrevTape = false; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs index 3c60ad48df..aa1b943823 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs @@ -56,6 +56,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // load the media into the tape device tapeMediaIndex = result; + // fire osd message + Spectrum.OSD_TapeInserted(); LoadTapeMedia(); } } @@ -103,6 +105,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { mediaImages = files; LoadAllMedia(); + Spectrum.OSD_TapeInit(); } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index df56bd1b5b..f964d6f43d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -48,6 +48,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DefaultValue(true)] public bool StereoSound { get; set; } + [DisplayName("Core OSD Message Verbosity")] + [Description("Full: Display all GUI messages\nMedium: Display only emulator/device generated messages\nNone: Show no messages")] + [DefaultValue(OSDVerbosity.Medium)] + public OSDVerbosity OSDMessageVerbosity { get; set; } + public ZXSpectrumSettings Clone() { @@ -98,6 +103,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + public enum OSDVerbosity + { + /// + /// Show all OSD messages + /// + Full, + /// + /// Only show machine/device generated messages + /// + Medium, + /// + /// No core-driven OSD messages + /// + None + } + /// /// The size of the Spectrum border /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs new file mode 100644 index 0000000000..2d83819f95 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Handles all messaging (OSD) operations + /// + public partial class ZXSpectrum + { + /// + /// Writes a message to the OSD + /// + /// + /// + public void SendMessage(string message, MessageCategory category) + { + if (!CheckMessageSettings(category)) + return; + + StringBuilder sb = new StringBuilder(); + + switch (category) + { + case MessageCategory.Tape: + sb.Append("DATACORDER: "); + sb.Append(message); + break; + case MessageCategory.Input: + sb.Append("INPUT DETECTED: "); + sb.Append(message); + break; + case MessageCategory.Disk: + sb.Append("DISK DRIVE: "); + sb.Append(message); + break; + case MessageCategory.Emulator: + case MessageCategory.Misc: + sb.Append("ZXHAWK: "); + sb.Append(message); + break; + } + + CoreComm.Notify(sb.ToString()); + } + + #region Input Message Methods + + /// + /// Called when certain input presses are detected + /// + /// + public void OSD_FireInputMessage(string input) + { + StringBuilder sb = new StringBuilder(); + sb.Append(input); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Input); + } + + #endregion + + #region TapeDevice Message Methods + + /// + /// Tape message that is fired on core init + /// + public void OSD_TapeInit() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Tape Media Imported (count: " + _gameInfo.Count() + ")"); + sb.Append("\n"); + for (int i = 0; i < _gameInfo.Count(); i++) + sb.Append(i.ToString() + ": " + _gameInfo[i].Name + "\n"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Emulator); + } + + /// + /// Tape message that is fired when tape is playing + /// + public void OSD_TapePlaying() + { + StringBuilder sb = new StringBuilder(); + sb.Append("PLAYING (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when tape is stopped + /// + public void OSD_TapeStopped() + { + StringBuilder sb = new StringBuilder(); + sb.Append("STOPPED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when tape is rewound + /// + public void OSD_TapeRTZ() + { + StringBuilder sb = new StringBuilder(); + sb.Append("REWOUND (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a new tape is inserted into the datacorder + /// + public void OSD_TapeInserted() + { + StringBuilder sb = new StringBuilder(); + sb.Append("TAPE INSERTED (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + + /// + /// Tape message that is fired when a tape is stopped automatically + /// + public void OSD_TapeStoppedAuto() + { + StringBuilder sb = new StringBuilder(); + sb.Append("STOPPED (Auto Tape Trap) (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + #endregion + + + + /// + /// Checks whether message category is allowed to be sent + /// + /// + /// + public bool CheckMessageSettings(MessageCategory category) + { + switch (Settings.OSDMessageVerbosity) + { + case OSDVerbosity.Full: + return true; + case OSDVerbosity.None: + return false; + case OSDVerbosity.Medium: + switch (category) + { + case MessageCategory.Disk: + case MessageCategory.Emulator: + case MessageCategory.Tape: + case MessageCategory.Misc: + return true; + default: + return false; + } + default: + return true; + } + } + + /// + /// Defines the different message categories + /// + public enum MessageCategory + { + /// + /// No defined category as such + /// + Misc, + /// + /// User generated input messages (at the moment only tape/disk controls) + /// + Input, + /// + /// Tape device generated messages + /// + Tape, + /// + /// Disk device generated messages + /// + Disk, + /// + /// Emulator generated messages + /// + Emulator + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 4c1a1be8d4..df56791f0c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public partial class ZXSpectrum : IDebuggable, IInputPollable, IStatable, IRegionable { [CoreConstructor("ZXSpectrum")] - public ZXSpectrum(CoreComm comm, IEnumerable files, GameInfo game, object settings, object syncSettings) + public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) { PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings()); PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings()); @@ -30,6 +30,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CoreComm = comm; + _gameInfo = game; + _cpu = new Z80A(); _tracer = new TraceBuffer { Header = _cpu.TraceHeader }; @@ -101,6 +103,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public IController _controller; private SpectrumBase _machine; + private List _gameInfo; + private SoundProviderMixer SoundMixer; private DCFilter dcf; From e2a212a0b8ec64832802a0be1b25c67f9a542c85 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 5 Mar 2018 16:12:19 +0000 Subject: [PATCH 051/105] Added tape trap auto-load option --- .../Hardware/Datacorder/DatacorderDevice.cs | 161 ++++++++++++++++-- .../Machine/SpectrumBase.Memory.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 15 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 12 +- .../SinclairSpectrum/ZXSpectrum.Messaging.cs | 48 +++++- 5 files changed, 216 insertions(+), 22 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index eb37c02cf4..4bdc9aca30 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -2,6 +2,7 @@ using BizHawk.Emulation.Cores.Components.Z80A; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -123,6 +124,88 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + #region Emulator + + /// + /// This is the address the that ROM will jump to when the spectrum has quit tape playing + /// + public const ushort ERROR_ROM_ADDRESS = 0x0008; + + Stopwatch sw = new Stopwatch(); + + /// + /// Should be fired at the end of every frame + /// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented) + /// + public void EndFrame() + { + if (TapeIsPlaying) + { + + // check whether we need to auto-stop the tape + if (IsMachineAtErrorAddress()) + { + _machine.Spectrum.OSD_TapeStoppedAuto(); + Stop(); + } + + } + else + { + // the tape is not playing - check to see if we need to autostart the tape + if (IsMachineInLoadMode()) + { + _machine.Spectrum.OSD_TapePlayingAuto(); + Play(); + //sw.Start(); + } + } + /* + if (TapeIsPlaying && sw.IsRunning) + { + if (!IsMachineInLoadMode() && sw.ElapsedMilliseconds == 2000) + { + sw.Stop(); + sw.Reset(); + _machine.Spectrum.OSD_TapeStoppedAuto(); + Stop(); + } + } + */ + } + + /// + /// Checks whether the machine is in a state that is waiting to load tape content + /// + /// + public bool IsMachineInLoadMode() + { + if (!_machine.Spectrum.Settings.AutoLoadTape) + return false; + + if (_cpu.RegPC == 1523) + return true; + + return false; + } + + /// + /// Checks whether the machine has reached the error rom address (and the tape needs to be stopped) + /// + /// + private bool IsMachineAtErrorAddress() + { + //if (!_machine.Spectrum.Settings.AutoLoadTape) + //return false; + + if (_cpu.RegPC == 64464) // 40620) // ERROR_ROM_ADDRESS) + return true; + else + return false; + } + + #endregion + #region Tape Controls /// @@ -277,6 +360,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region Tape Device Methods + private bool initialBlockPlayed = false; + /// /// Simulates the spectrum 'EAR' input reading data from the tape /// @@ -284,6 +369,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public bool GetEarBit(long cpuCycle) { + + // decide how many cycles worth of data we are capturing long cycles = cpuCycle - _lastCycle; @@ -312,6 +399,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // flip the current state currentState = !currentState; + if (_position == 0) + { + // start of block + // notify about the current block + + var bl = _dataBlocks[_currentDataBlockIndex]; + + StringBuilder sbd = new StringBuilder(); + sbd.Append("("); + sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count()); + sbd.Append(") : "); + //sbd.Append("ID" + bl.BlockID.ToString("X2") + " - "); + sbd.Append(bl.BlockDescription); + if (bl.MetaData.Count > 0) + { + sbd.Append(" - "); + sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value); + } + _machine.Spectrum.OSD_TapePlayingBlockInfo(sbd.ToString()); + } + + // increment the current period position _position++; @@ -327,6 +436,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Stop the tape command found - if this is the end of the tape RTZ // otherwise just STOP and move to the next block case TapeCommand.STOP_THE_TAPE: + + _machine.Spectrum.OSD_TapeStoppedAuto(); + if (_currentDataBlockIndex >= _dataBlocks.Count()) RTZ(); else @@ -335,18 +447,50 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } break; case TapeCommand.STOP_THE_TAPE_48K: - if (_currentDataBlockIndex >= _dataBlocks.Count()) - RTZ(); - else + + if ((_machine.GetType() != typeof(ZX128) && + _machine.GetType() != typeof(ZX128Plus2) && + _machine.GetType() != typeof(ZX128Plus3)) || + (_machine.GetType() == typeof(ZX128) || + _machine.GetType() != typeof(ZX128Plus2) || + _machine.GetType() != typeof(ZX128Plus3)) && + _machine._ROMpaged == 1) { - Stop(); + _machine.Spectrum.OSD_TapeStoppedAuto(); + + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + } break; } + if (_dataBlocks[_currentDataBlockIndex].DataPeriods.Count() == 0) + { + // notify about the current block (we are skipping it because its empty) + var bl = _dataBlocks[_currentDataBlockIndex]; + StringBuilder sbd = new StringBuilder(); + sbd.Append("("); + sbd.Append((_currentDataBlockIndex + 1) + " of " + _dataBlocks.Count()); + sbd.Append(") : "); + //sbd.Append("ID" + bl.BlockID.ToString("X2") + " - "); + sbd.Append(bl.BlockDescription); + if (bl.MetaData.Count > 0) + { + sbd.Append(" - "); + sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value); + } + _machine.Spectrum.OSD_TapePlayingSkipBlockInfo(sbd.ToString()); + + } + // skip any empty blocks while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count()) - { + { _position = 0; _currentDataBlockIndex++; if (_currentDataBlockIndex >= _dataBlocks.Count()) @@ -378,12 +522,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return currentState; } - #endregion - - #region Media Serialization - - - #endregion #region State Serialization @@ -403,6 +541,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("_tapeIsPlaying", ref _tapeIsPlaying); ser.Sync("_lastCycle", ref _lastCycle); ser.Sync("_waitEdge", ref _waitEdge); + //ser.Sync("_initialBlockPlayed", ref initialBlockPlayed); ser.Sync("currentState", ref currentState); //_dataBlocks diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 81ee1c819f..0d750bc64c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte[] ROM1 = new byte[0x4000]; public byte[] ROM2 = new byte[0x4000]; public byte[] ROM3 = new byte[0x4000]; - + /// /// RAM Banks /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 38594c7670..9d4ec84a10 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -12,7 +12,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public abstract partial class SpectrumBase { // 128 and up only - protected int ROMPaged = 0; + //protected int ROMPaged = 0; + + protected int ROMPaged; + + public int _ROMpaged + { + get { return ROMPaged; } + set { ROMPaged = value; } + } + + protected bool SHADOWPaged; public int RAMPaged; protected bool PagingDisabled; @@ -151,6 +161,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // setup for next frame ULADevice.ResetInterrupt(); + + TapeDevice.EndFrame(); + FrameCompleted = true; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index f964d6f43d..182cedd7a4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -43,6 +43,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class ZXSpectrumSettings { + [DisplayName("Auto-load/stop tape")] + [Description("Auto or manual tape operation. Auto will attempt to detect CPU tape traps and automatically Stop/Start the tape")] + [DefaultValue(true)] + public bool AutoLoadTape { get; set; } + [DisplayName("Stereo Sound")] [Description("Turn stereo sound on or off")] [DefaultValue(true)] @@ -80,12 +85,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DisplayName("Tape Load Speed")] [Description("Select how fast the spectrum loads the game from tape")] [DefaultValue(TapeLoadSpeed.Accurate)] - public TapeLoadSpeed TapeLoadSpeed { get; set; } - - [DisplayName("Auto-load tape")] - [Description("Auto or manual tape operation")] - [DefaultValue(true)] - public bool AutoLoadTape { get; set; } + public TapeLoadSpeed TapeLoadSpeed { get; set; } public ZXSpectrumSyncSettings Clone() { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs index 2d83819f95..5b7b29b3d6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs @@ -129,15 +129,57 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void OSD_TapeStoppedAuto() { StringBuilder sb = new StringBuilder(); - sb.Append("STOPPED (Auto Tape Trap) (" + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name + ")"); + sb.Append("STOPPED (Auto Tape Trap Detected)"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape is started automatically + /// + public void OSD_TapePlayingAuto() + { + StringBuilder sb = new StringBuilder(); + sb.Append("PLAYING (Auto Tape Trap Detected)"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a new block starts playing + /// + public void OSD_TapePlayingBlockInfo(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Starting Block "+ blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape block is skipped (because it is empty) + /// + public void OSD_TapePlayingSkipBlockInfo(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Skipping Empty Block " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when a tape is started automatically + /// + public void OSD_TapeEndDetected(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("...Skipping Empty Block " + blockinfo); SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); } #endregion - - /// /// Checks whether message category is allowed to be sent /// From b409c88c50261797a86b76d0cc56cc16c8569fc9 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 5 Mar 2018 16:40:36 +0000 Subject: [PATCH 052/105] Fixed .tap system detection. was causing an exception due to shocking bit of anti-logic (on my part) when opening from zip files --- BizHawk.Emulation.Common/Database/Database.cs | 2 +- BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Common/Database/Database.cs b/BizHawk.Emulation.Common/Database/Database.cs index 9364aac02d..d106a5e91a 100644 --- a/BizHawk.Emulation.Common/Database/Database.cs +++ b/BizHawk.Emulation.Common/Database/Database.cs @@ -309,7 +309,7 @@ namespace BizHawk.Emulation.Common case ".TAP": - byte[] head = File.ReadAllBytes(fileName).Take(8).ToArray(); + byte[] head = romData.Take(8).ToArray(); if (System.Text.Encoding.Default.GetString(head).Contains("C64-TAPE")) game.System = "C64"; else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index f4f78dd4fc..7b2345b74c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -16,11 +16,11 @@ At the moment this is very experimental and is still actively being worked on. * Most tape protection/loading schemes that I've tested are currently working (see caveat below) * IStatable * ISettable core settings +* Tape auto-loading routines (as a setting) ### Work in progress * Exact emulator timings * Floating memory bus emulation -* Tape auto-loading routines (currently you have to manually start and stop the virtual tape device) * TASStudio (need to verify that this works as it should) ### Not working From 3cc4b944061ba5f5ee7d459b2d2481c214b6a6c3 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 11:17:30 +0000 Subject: [PATCH 053/105] Added default control bindings and prettified the controller configuration panels --- Assets/defctrl.json | 70 ++++++++++++++++++ .../BizHawk.Client.EmuHawk.csproj | 2 + .../Properties/Resources.Designer.cs | 12 ++- .../Properties/Resources.resx | 5 +- .../Resources/ZXSpectrumKeyboard.bmp | Bin 0 -> 3382 bytes .../config/ControllerConfig.cs | 28 +++++-- .../ControllerImages/ZXSpectrumKeyboards.png | Bin 0 -> 435119 bytes .../ZXSpectrum.Controllers.cs | 70 +++++++++++++++++- 8 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp create mode 100644 BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png diff --git a/Assets/defctrl.json b/Assets/defctrl.json index 42f8b4cee1..631e0af74b 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -461,6 +461,76 @@ "Key Cursor Up/Down": "DownArrow", "Key Cursor Left/Right": "RightArrow", "Key Space": "Space" + }, + "ZXSpectrum Controller": { + "P1 Up": "NumberPad8, J1 POV1U, X1 DpadUp, X1 LStickUp", + "P1 Down": "NumberPad2, J1 POV1D, X1 DpadDown, X1 LStickDown", + "P1 Left": "NumberPad4, J1 POV1L, X1 DpadLeft, X1 LStickLeft", + "P1 Right": "NumberPad6, J1 POV1R, X1 DpadRight, X1 LStickRight", + "P1 Button": "NumberPad1, J1 B1, X1 X", + "Key True Video": "", + "Key Inv Video": "", + "Key 1": "D1", + "Key 2": "D2", + "Key 3": "D3", + "Key 4": "D4", + "Key 5": "D5", + "Key 6": "D6", + "Key 7": "D7", + "Key 8": "D8", + "Key 9": "D9", + "Key 0": "D0", + "Key Break": "Delete", + "Key Delete": "Backspace", + "Key Graph": "", + "Key Q": "Q", + "Key W": "W", + "Key E": "E", + "Key R": "R", + "Key T": "T", + "Key Y": "Y", + "Key U": "U", + "Key I": "I", + "Key O": "O", + "Key P": "P", + "Key Extend Mode": "", + "Key Edit": "", + "Key A": "A", + "Key S": "S", + "Key D": "D", + "Key F": "F", + "Key G": "G", + "Key H": "H", + "Key J": "J", + "Key K": "K", + "Key L": "L", + "Key Return": "Return", + "Key Caps Shift": "LeftShift, RightShift", + "Key Caps Lock": "", + "Key Z": "Z", + "Key X": "X", + "Key C": "C", + "Key V": "V", + "Key B": "B", + "Key N": "N", + "Key M": "M", + "Key Period": "Period", + "Key Symbol Shift": "LeftControl, RightControl", + "Key Semi-Colon": "Semicolon", + "Key Inverted-Comma": "", + "Key Left Cursor": "LeftArrow", + "Key Right Cursor": "RightArrow", + "Key Space": "Space", + "Key Up Cursor": "UpArrow", + "Key Down Cursor": "DownArrow", + "Key Comma": "Comma", + "Play Tape": "F1", + "Stop Tape": "F2", + "RTZ Tape": "F3", + "Record Tape": "", + "Key Quote": "Shift+D2", + "Insert Next Tape": "F6", + "Insert Previous Tape": "F5" }, "Intellivision Controller": { "P1 Up": "UpArrow, J1 POV1U, X1 DpadUp, X1 LStickUp", diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index fdce3477bf..ccca5cf3eb 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -1786,6 +1786,7 @@ + @@ -2110,6 +2111,7 @@ + diff --git a/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs b/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs index 72c965441f..7504818e28 100644 --- a/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs +++ b/BizHawk.Client.EmuHawk/Properties/Resources.Designer.cs @@ -489,7 +489,7 @@ namespace BizHawk.Client.EmuHawk.Properties { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -3438,5 +3438,15 @@ namespace BizHawk.Client.EmuHawk.Properties { return ((System.Drawing.Bitmap)(obj)); } } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ZXSpectrumKeyboards { + get { + object obj = ResourceManager.GetObject("ZXSpectrumKeyboards", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } } } diff --git a/BizHawk.Client.EmuHawk/Properties/Resources.resx b/BizHawk.Client.EmuHawk/Properties/Resources.resx index 8d9fb1b714..c9a9623430 100644 --- a/BizHawk.Client.EmuHawk/Properties/Resources.resx +++ b/BizHawk.Client.EmuHawk/Properties/Resources.resx @@ -1557,4 +1557,7 @@ ..\images\ControllerImages\NGPController.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - + + ..\config\controllerimages\zxspectrumkeyboards.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp b/BizHawk.Client.EmuHawk/Resources/ZXSpectrumKeyboard.bmp new file mode 100644 index 0000000000000000000000000000000000000000..55516c7aaa3591604865d4cd18379f8f650c3104 GIT binary patch literal 3382 zcmeIwA#&YN425B3CLjUbgJ57$uqM1+gO+lZkut%6E<(#F6wORPc)-i=r?4!c_Vwp; zZ3Fx2>gm_x54#?Zw`LE_etx;JyM1xL@%<_@PLnpcGPUluKQDjn-|L&12NJySLSxLr z;9wF7q0kt!G&lm>q)=$g*LR%^B!og^%$Qm^0tune7=F^=2qc6;W6U^h9D#&TXpDvN zo8~x1LMSw5m*!3e5<;OdyXkPr%u*^;D_frLmI06Zw(3st}a59h(3XRzqWFR3F8nf*xCj$wg(3oxIax#z*3XRz|Zzlr@q3r$RV$Z*g8j2NO iuBTlt#|(_)(Q$tsX9dm*oE11La8}@~z*&L+tOB3FDm>c& literal 0 HcmV?d00001 diff --git a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs index 9800e5ec13..f17a7e1afe 100644 --- a/BizHawk.Client.EmuHawk/config/ControllerConfig.cs +++ b/BizHawk.Client.EmuHawk/config/ControllerConfig.cs @@ -172,15 +172,24 @@ namespace BizHawk.Client.EmuHawk string tabname = cat.Key; tt.TabPages.Add(tabname); tt.TabPages[pageidx].Controls.Add(createpanel(settings, cat.Value, tt.Size)); - } + + // zxhawk hack - it uses multiple categoryLabels + if (Global.Emulator.SystemId == "ZXSpectrum") + pageidx++; + + } if (buckets[0].Count > 0) { - string tabname = (Global.Emulator.SystemId == "C64" || Global.Emulator.SystemId == "ZXSpectrum") ? "Keyboard" : "Console"; // hack - tt.TabPages.Add(tabname); - tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size)); - } - } + // ZXHawk needs to skip this bit + if (Global.Emulator.SystemId == "ZXSpectrum") + return; + + string tabname = (Global.Emulator.SystemId == "C64") ? "Keyboard" : "Console"; // hack + tt.TabPages.Add(tabname); + tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size)); + } + } } public ControllerConfig(ControllerDefinition def) @@ -256,6 +265,13 @@ namespace BizHawk.Client.EmuHawk pictureBox2.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom; } + + if (controlName == "ZXSpectrum Controller") + { + pictureBox1.Image = Properties.Resources.ZXSpectrumKeyboards; + pictureBox1.Size = Properties.Resources.ZXSpectrumKeyboards.Size; + tableLayoutPanel1.ColumnStyles[1].Width = Properties.Resources.ZXSpectrumKeyboards.Width; + } } // lazy methods, but they're not called often and actually diff --git a/BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png b/BizHawk.Client.EmuHawk/config/ControllerImages/ZXSpectrumKeyboards.png new file mode 100644 index 0000000000000000000000000000000000000000..1af85ebea9d43492a61e560046e2a74126a54225 GIT binary patch literal 435119 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

ChMrh;k96x~J4C;&eVCv{U*5(cek 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 z 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

xWRU<^d;K;GOOgYoA=<~ba z;i3`w1SYE^pbYD%R6eQ0N;hfD5A)SiR>YL%B-@3XpD1J@2%4E+cwByDWg4$ree0a1 zt7(-eZ?50oGRlpnMwFi#VelyaW-#9b&%WWU&&nIV=RYeA{IPSVB}RTG+}>-CAN7@U zHPW)K*YkI6+NZfp?+~BKPZlp2SNMEjqAr*sSpXeG7Nru+!AEgqx3620XQ-yGFYR3T(A%dIQ@-t-lNBiNJa|AAPl_7gO=iG=S|nePHC_h+fdwehpEgsJkm=joF^k|d}7=(u5;$T zyhkaa=u;;O{|`)Ifp0rYF>1U?jjJgLovL_%&drrzwJ^Arw^VXgUSv+<$utLsLuH_{ z$!ay9%O~d9BfsFvrg7C%t_GnhJ)J0}P`IH1=@z&4yBHaT%;yzHjO{flCF;)YiyvC! zl^dC;7^Rhbu5wQL6?gGOXC0?f?S%j5A23-oTFPrx8! z@oaQJ3)$~l>n*?W42^W3bsN**RyZ`{xPn&+$Cyo0*5Y1gPe3$SiMo-EuWAgGl=IP{ zb&)x8pdQUnrT->8Bz)1<;bkC&7o4Ij9;1)0Kw=mRoIzWXSil2}I`X_?G|*vELVikG znbcdPma!}~jeZ5M_uTXO+~Yq=5EkXjoaDqo95j*{P#i8{l(H^2x-HNqPO3i@ip;_j zAe5dx@1huZD7RQX6mL$&s)r1swe(KHg(VffmfMi14>ID#DkZrNBIFtD?jy)C>s@nL5XCR0g4|A4= z;?62zBLo`&t7SQ+#JDW;kD_A+reF7>5m9G==J|iXU zwzu-5!0~Xu&r6!t_u&h_WgMTCPx;3?vOp={i6h2T$;H?pAURYfE|iLh8~8r8&&=KD zTFdG}ZFIgs=(zBs=(zPa<8WOB)%j&?y*cNsLiRVnIy663fp9&zPQ9e6j4w1$c|J!? zZc&$%=`v51ZSDtr`AL{GT&|q&q*dgfD0py{UAP)WuG8Z~bw!3ke?C(N#J7q%*nwyH zpU+kNmdq$SJOKqlD^KRG#J&Xyn$T!=dRN z9Y6r(2i!?N-mAnle&IjI=Eg>%YVqJ)q$=-m8dV5YCd5gt`f(Fj9|h~NjG+@o*A9d# z$fUcXr_eRf!2cM6f1MGn6YT!Tb$w2o(;iCiIMK2=G%#{-C#8|g2D0v&Uc(4H3PC&w zJJ+!`gr$T%bZ$vuf*XI-;jZ~eS7pRV)I-wa907=_Y@IPT#PKyR4ISlh@UKRq$0jhH z#2xyZ6197!tAe|!&wl8k2e`+<prdOh=Ikx2fQ1d&_b z^sqz{+v*4h3mjZ)ngTe2Aj_T<9$zk2snBGWBowUkl}Y(p0g$Mu0Dfd{1yP}siTGh$ zuam(gM$NtZT$Cz;ml`zhlrdqLgw@=ql6CRf9uM&bXoQszU4-!rLvx8OlUtOXTs7aB zFP0%Cc_^>o@p+RCC&H#OmFlQZi01+s*TGozV+9+y!bnvyyTdJ>uih7PF^`=W!>B|( zQY>heu>9AjW={`8cB>H(IAI>nL9i6681C(HYHnE!iW3!IRV)rF-b{%FW`29Jj>j|l zxkkgS?vABG%xBC+`e~@dpF$QdlISB|NuB^08A~O^Q*kg{X8_|UAL$zaGj3+ewSkv= z#b0{2I7~~WA)cYOBM*}KVlwOh7&AWBTyD+MJ+A}BoP&P1Bq>0&oii7SsPg_*z~kqZVuW#&SzES*X# zJ!+m6K4D@OG&nC`I&#v>>5yR}1H$vAJ@-5FiWg~!IQ~4tmJ>b^$7wriHl1UI#-Nclm z#>>u1nHB*z@)3dtTadG<0)ds7tv)R@VTrCuWK#eF0GgAVRypQ#KH{ZUobZ%OB(^)F zG65LYctfvGL~9V^^#i9ssc<7~Fjj42yj|78-7>s5lX3XNr}$~O2VQ~UVG7`Mv0Bf> zlz_XOe6C|k?Rag@jvG{frWaD>5iF>rtRNyOXHOUK=cD)_iXeij^5Z@QJ5^p$1Ypq} zj(HLgIX7N9C>nVg6>5`;;f+t^UIoOS6P{M)e0K7*dXe_3@aQ&;n_Kpq9XZvUw@go! z49}FGec%vLw2IgUL(+cL-FVE+2-p_4OZI@^B_C8kq&2Du<={zBD#rB0V6JQ8jlb=6 zF)_Id`0TZA_t9FnWz;^3!1>%&&MWm`7x~%fyi3!fD0_r^b%pKPv?(wulsA+s)sGI& zh)RiY>yxK z34RPD$fAAU&G@Wyj)d8Y7F}rjJQx3-x+djEUCU_TYCr{_y_A$qyDM6S>`d@C@=E2I z^WbyoW8`i7aU;>-i~EzJRmRnw@Tkq#)X&Nz7cxJLHTpz+IXW%b((lQGnta88c!==s z-JTU`&-Nu6w8)TJBKj6H_b4L}k(?z|tVGr-2?tMt$WVvL*xr=JP|RSEa=(gDS*+o1 zHee9Jv5Y*tj>{YLSI{m8GTtx~<<5F7gwV>%gc>K6NHs#8s#7$Rr*R zG!4L4!(+CUGAeNMuSC%}pak_gl5=JpZV9qv0vRseYtktyYm{Udgdm>#{mCg)pw)c1 zaG+x}7yJ!dZ&~K6r=TK}k?Po)H}LTnAJc0^#Q=e_W`xir65>8U1y+r-K*UF?!ZXAV7!t7M_F{nx;6;q7s zsZE~EsgaU`7F?_`hS7jMZZ`0&r5N)6N|tq^oTFyF`q z^2PIVmNc}KUS`>=cg(H_z?r@nAI^vCb_1! z=2++rolO37d%v;?I{~s9R^!4S@Q3&lx52M6u0rLOEix?L3P<87@jOJz$Wi&TAzu#z zEv_{1rh@>H`?x`I*J$w8o=eh@Pk;;ARF@b{Kojf+FZ7|GtJ?t1f)$6*k%-)T2`@fE zjx@4-&I2@DwmH_I>CoEKr-_j^{nZ4f;1=+86761#fYkXJ9XTV$(!eFsV7@9o*XQb! zgq81nCcTU*Kp1k`0nE)mQ%*hirgG}k9ek^<<8|walQiAAhi+b2?mXOwX@wA62w+$& z0wk15#+ODwhO3LY{C1%aey)8?n4)=im7YdHynj zl0*Y4ArQ{v9EKO~eX!G$5!Cp6CLAhgCoUu9nTc#9im4WmpKJMwmpwdoUdA8E>vFFn zyQn3zW?MG!H1sfd2`dgl@De740TFLtl=<{7`Ht!#;qhhf`gki>Q_j>jI4=jO3z z#ut9S|2k*v0>}R5`_4XlulrtW?S1yy$1Ko9gXHBB8?&*u%n~C8?b+qp3rp*=@Akqh zbHl*6%KarbIwBP*=S&dP!K~*6FAPrD9}bUVJ@)Fhllu&kTfY1L0CT$ajCwVhjb!iH z-@-X(g_K;MfC$b%QN=hc^BaL&msmA9Fs%4uC!*g9sHGE45rvwaF*k@ zr`Cv3{_TUvQRlA{M!uOs)W3kaY+Pq*PqCf>ME|4vlXh@(&5^xh1E-k#k&uquQM=i^ zBm4Lp^x;Y>r;@TL1q3vSGmT#ibRg?YgwJ1jmX{Rq*K6XCI3M+{>BFjP-^hJB^@n^4 z3fGKh)F0qS&%5|C1xTd|l#KK!vl(AWGHN;)z7#G;zf{@8P5-LncA7J^VsAts%C#Nn zjqebzk{Mvf+Y9Zz54?+Q$F~Po0?3}-`#??y4?EmSQs=wIv*beAQMivcmvxCwpE$#lVOLeLx9xDHAgITMmnkJoc3^P%-D8o{~99#B+n!9a7ES!TkY}!qZMem&YOZ9y*P4`C&r3xfK9G8;SlrC9HX6B zIl2ZjiGu(j6W2-pYeG-35eurncwt=b12 zF;-{MZlNCBIy|l*rzCM+hZ*f6_t zsR0OI*n>BOO#rkf+u8raSS#lO$W&U&_Xf62SblQwj0rF39p(v{wq465is@kBd z9>WBG{ib{WM>MFo_RiBc6 ziGC}&ryjC4VXUCeIX0hJ0(Hbc7g1bc#Oax{Fm|ZI&u`D6OTBpHVO4M>2Cy*UDpU1u zy8ppk^G~c~|AD>S9C1S-1l|vebuglU5RD|kf({z;K_EiQJ!A+q2652pT)6JWK_bRo zq@F>cBP$)HH+mge1ch-67tQ5dMsft4SU{mX#F);8<3Vsz;9*n$2-lHJ73;J$qM|v= zcB$j^Qv?rnev43r`cYAq<+mX91`3}qVY~f+S)Awcr?RCxeus9nrf&lWd1>GpKnPbB zR4N=?R{arS(VP((_|$`P7hXD%xaAkQY80z<*hm4e00*`X-{29*7l+flck~L6N7>ev(K2d{6;1ir??1O@})jzFI-5ckRPY6V4HCo zyuyzU@=0>I$Jx>2qo&;R0J3$HbxAbv)RX5nNur`2Da*Qc74daVB}Q z7qq!la{~+}fHV5gh6l-9oubO32{LS_J6uL-cW}@V8l*EiLVwg%rje2M1>JFQ$^`vK z7C2oUMZF}23moc|+9rd@upNC+4y-snY^AE%&MM$V|57KfmU#~`&9$le2~KJVPy6Ih z9wuK-eWeakuP*GLZ^q^$^ODT>F~WOfq~` z>teg>uKn$Sx4g5!@;3qGz~0;1zFW7nQzsYN=)?}9H;O=b$rdnp1p`Qc*fGoiblQa| zH<@9mloK+zCR3e5v7S|7G{Yo*S9V@Rrd=*OWCkalKl6ZQYGKA0Ylm0hTw`b-`-6n& z1w6v=6XAj;Yq`ZAaQ?@03gy<3)JwU zNPdSw9W_Q|9TXkB-A!F(sZE+tCF@59##kva_?QJcJ!FO!V}&Mf;NHxaO7x+%*fQ;O z2Du*e%15UJP{~^3L3mCbB6|!>xr<+eli%XcESBH!;?hgKfX^d99NwjLD0|}X`IR^8 zsZa9_P1*Tt1CLW#%iQpl2KA?S&2sA5AIRg}du2KJy|%oTf@Dn4CzE-Pr*z7994xx0 z6Pt~*B!w#4AcoC9xQmT?E{~axG3&QVzv{juJ%RA131yS+Q(;CeEXB5 zH*S^24kKexYSWSdul{n{Y=B+MJad{Z{Q}vja|N2dp_4I)%c%(nJZNM}T4$`oPfcu$ zaEYe5o`CA;ho+pD^H4V85w!B575Jm`PDU<|Pq6V@3gI!-z#kW+3Y@@{SIfjDk4}qW zFj;j;KNP03NKSX0%rup^cjBxgIGPgf5J8T2A)CR zWwQy01^^*Y-(-Rx;0G0OJp}~tpfhFY4m}9ZHW|5+Bwfh@5L-ZtekT3EmL}`8$48X> zIv;K!>|{y2`1Q+_Icm$YXyDj~OVRC`sYG9urL$8X6?r>@O}2vKB$mVLD%1KpuxOm2egXt5 zqB69MkgTWa74yck+IFOzoyavZ=fD+_&Nheo?LG=o+WeHU2RTIKf6A;Jn$n22Ub78t zJ^U>{{f9;=^O`WrpggPda&*$|Kjf1-It67_GO(_2&4@jEdF6QnOgW0AT>g?>+F9g@ z!+B`y%dg*|55;TH^3u~&!|S3u?xK(ugMMP|}l|^K>Lv zT5)%V*uWhd9McBi_^gC=50(@|H(d*6=)rO6%y8e*Ed99xZPFL{I)$Q>r><-J zU9r`PgsR26eEA6s>?695oE-Nf2jrdATM#0STDl%o{3%Ht$$QD8@=>dF)4 z8Q!~(kMqxF1|Bg6;UPSgQvv~;t#M*ul6nTTwfg)>$AfTMrrb!c;O*F!6l ztK!E1YQ0=XCLU{ibVec3Jh8S4S(St9*OeM2_;VK~rCl z@?BF{X7;5QVM}J#vJ;&E=(&L$RY~s z&{`dKQ0N$x;GkTB=u_-2eeJIcdyP-^3XnLoP~d6N)OYqw>rxiSrhT$Z)VY>$13gYv zI(6hz;BiLO)lMLw0EZlTmbPy#|Rws&RA?Pw zg?!o4Z|yAdvJr)f0g4kuB9accb(O70ZXQ!V{885HTKY;DiJRD;#I^Zdhst^eP!c6 zuELXT7(HP3mDtowgR+sIOmS1Q5_0LG1@Q7rln6LXE~TUN%Qru~HzO|<_$sJ|N83=r z-!=#i8*oxGF#(4!_;hsb9r=2|k`Xrk(Pw6~W9`JSD;sHNz}GhEPeUWC?Z}Z@eFe18 z2&5wRo_il^x8HGFJN_JN*EeocLj>v~z?#+RI^)+^&H6xOYmHPw>jrLToD|ttuyk3V zMs?QQJD}L3XMAnCpukYhH3gkI1c(SCEkwp?8FGYm6Pz{2&htX~!8mH1OHCKabSYXq29n_KvgEAF>N-gUUge;S zG!%FSXF3eBkFrsC77E~Sq|c6Qr8ohN9wFVB169V#rjxDLEb8Q^++y5hwc;!%Tuw^9 zdM89{0l;Sk=b}fPLgtdPgmax(=zyM>N-*2BFXo z4BF&DS?d8Obn@->ba?6jrsD@Xy%v7_S=>T7CpY?nax(CS9)5Fz-MPpCC!k<#C%>7Z z<9AijD4|c@TxG>|Gg{zSN+W)2526x?V1y$z*gi7@(V^32(C$>zS`tD%M~lkT8B4`p z@&gY1d9F+fYy}bFCpdfE6TYKk+je}1HfIc!C!Z>}w)EPs($GIRcx(t=#e-;TP6bss z9O43x{>;X)TVzswn}SR$B6EKNBtabJ)O!sG01%^6ccHreiZ8w?Na4||{0_m`2h+ul zmS=g(+8qb)YDe#XL_i!M;((-b5}U^kwAUSZJ!|u)ksJ*3q4#zKrfwV=g@9+*B$h>X zs-j4N%9O-OaIFx6cG_1b@@Ysj51(X;fzYBHK6SuVQBiyntka}}1I_tK9kT;+iHrSfD?qY8Rq8n{X9uR4vKG%xK|=@adEGanE$4ZFud7-eRJws} z77Q&KwY2-(OwE%^H-|LFdPSprEUc*$e_27A!!Kv_IZAW_wyW#;TMbKZ%82<{pSF#B zT!950I_V70F+`UOiWhebWpre-Xn+ec@~|yp)1q)C(UCTR*3zLtB-+%i%oe8uo$4(; zh4%94{T18DmLEG$_MGy@ckX#9Cmk*D!VhSB{c@greSA9=rpmS7g5*Ku` zX1n2CJSGwFAZ&me5Ni5Oos>;f(DXVqI&!k6eM>n*qhc`VTCS}p*ihH=8fiF!#@2XV z!snIxB*tI~2KzrK}SBi13#kw&#j4nwJr&+}(Zy-~4>IaTPgLJ7K zZ7{Y#z1Rr}SF(D(^TsM|LB68za^lLM zR|au;8L3tK8o_Hv%DVa%)z*&a3xZ7$GrbC32A*X9P8~`TIs|i(W=ra-I~Pdgo(Y81 z(gXQ-ic!B+zAHWneJE~qEUyaC=W?{<_%LbXwjQl^ zIYVXV4SD(^$mB!EQu>exjdTorpuU z!d&AYxs?M_p74jLh1J_YhQ}0$M`x^qQg7l2;10j=LLcyL(+7amvrOCoFGnU0ohfkn z>!{alIlOz0FDZI&qQOdKhuTfy5q^J^El8sQ-(UjI2LG;ZKM!vGIDd)p!y(!_q{mM= z7Rw?>==9ku^HpyeBLIMKjWV&Q+nTbKF6^FqIzr`@gYS$!{9Y>)Hn@34$h*U?8mv9A zbYK*5QGTUAQ>}mst`0`P@JATEeTYq3Y-3%J<$48M>y^CA9_pINq{$!^UeR01fE8Vu zIO+6o%=L(TmG^x%LDn{+%W`fUo%dT4G)PKFNE6hH;LxyUSMpfH9qJd1qFo0{AZB_= zUB#D#cB%=dss>hqPheXNf8f&vW)-H-#xOnV;jBWwyS6~$RDJa2W(s`Jy+W%;3d%N> z@0#AMp?o7=|7btJ$al$#6pHIUd?|cKSN0X*JNha6>I0p;grD>a>vJwA!6{tnvl4BQ zY1T^eUHhhS8JYV8`|1iS3WrzQ_Z@wNI^TN$K>&5!d(Ru&w(Xl)U(HjxU`qzYB@MJ! z3}>mFA(VlGNDiAZO0V^uSfS|rBsvD6aKk}42Y;`1euP>E2si}{M2${ol7oW&dSNeE zz2G&p{<`17sO+!miQkW%QiDn7fwqiFq|XkCWw_gFcmZBb3l%mywsnMI=kuw09g*+q zbcE#tQ!!GV19d#oL9ENUGO_b<%Hef?t8^Z^V;JvhAxCG*OJ`fzPPHxB;Y7&x>%p#S zFPc7}haYH(J%HzmaKPeAf3Z-PGq>*y=p=Sjjr_ zhVCL=9=LJLhWSRQzyW|tbq*viTlIUQIr?5z|0aK@T!%l$^I98W608-1~uiY0-H#>E0 zBMkE;xgK>>9~ybSL^|VKfTv9g+$lHh$$i*jzuIJo?<5vl1zb+XIv&bHn-)$_b7W6d z-_h(=?kzha@Dw_p0&WBQ(z!K}J7Ejg(N6C# z`iE3KFWl-=e856?{`KQO?O!~-k1M~U?6s2$w9=)1?M_9n`ZJj#nB|Z!?a)43K1;mC zuYJe*eza?9LMsxMvDE2+O+POu@Jvg$@!taD%>Jz?PQ)|l;1Fl=->xdjf}uCmLPXPU}W5g&4W&n7J^A;S;i?defEfJ1CENf zlhKg?};Eg7S+7ONP=oqpEN}vD$nR`v2;*PnYOUk8- zZwk|Ht!=lzk)j#ngxAiiHu8%N4UNj2WK< zxI_}0DPjy#*aheKB>>1vF(Ym}tmoB1xQ;CH09P?q*O2jbU}uXKn&!r(3}NV*q6}U- zC(l&moegGGaYnG?9%af8I`aZ_v{m!Moxb5g=Y3TJBim93-ZzlMd{mMh$~|p;Uvi!Z z2R<}yROd)iXCMsF=U|W$O*`$Rr0c#10HHa~7QVBgrH+Yy;!Dw^Zy;@OYQap`z9SU= z`-};Hk-fIhC!n|hPHo@s&|oc<%b`yB&q3t`4wVI8$_hOj3=9%|q9uoc0q{&gawe?k zPMygwa~W|=j6{{Cybt>DFQByrKiZGZ))8>+x5f?z+_#HEm2ZVAReY__N-URx6G-e! zz!NGx1lrIopYYFl+S?R5(pEM^-0^9D=nr-8dEu=-p#7-_F~pD1Rvp%6u;$?U2xvmq zxn~x{>cuC!`SzwW?|4q1!j|&lD(oerB$Gc>t^Axa41vqCuF6z2z8aMZR4#|by_G@4GevaJ@?mO}ZrpD_5(w;BBweYoL)bY(6`!!iekQ4Uj!2Jq;y`s_*Yr z)|VT^gl8RT?%`F2$r-SbSXakqEN}>BSM~~U*;J=OwbTu7fmi$sv!}W6R6T!3Pl4BW z7WGtFWp95zXCiP!Q}PX-phnkbvvy%(5RR^QAnF&al<72)CvwyY_W1pFB&q|^ofnVR zx5v|;Xe2*fn20bGc7NJ8fI1Z4eTG zP9p)8zL7STb2u{4|JTk3z^7qo!|Chj>Rui#QeC`$urq8R^$6^?sp-(1 z>ItLMndR`)k-%^DB8_!DcFuVMl8u#>yr?SLC794A569Zh>%k>{_jIG4WzvWaA}h$z zvCEJ6#3)NzpUHrA^5M!2{8c8MCWbn{I+Ud&g`(GMJr6>R-3gqAmS?4s=Uns9=jd(3 z*V}X0^pA&Mw3B}AO1&vZ^<*2*`Omqs$EFCr13Z9=z*8^Jy#d6Lca6G%0pHi$?U?qV z71;feQJ=W&(!?4GkOXaCo3GV?B}-)^+ywoAurB}b59^sW&$BmdC$o;#U*gIP7`!eo zFYzqbA_0R98m}(2@oi_@Bkz1vWdG-&oz&bozWeMq+DCruhuh}So$Plq*#?H)%#cXS zKX-0j&u~}=?rSRZHo?wFvsX;Q5iet512KpA13azk&`lIB9;%1qP*VG&$zOd96U$vA zPQB~vc9MB?9a@q?bk-dD2-Rvno+`r_k>9ALreO8DA3=7k~8QlJ4Z;AWshcP z6G!H*6apILOhdI5C*`@EShYNd>jkw}yc5iZ56kVH-d%Mv-`{#etyH>=M42R~UiB_p z7-6hq+6CSGi)_RsJ9=7_qUzPYy!qRk96q9&6x0jA&-6S>E{|&=c&v{cd@#X19>Oa0 z^}X8&7%y(u;kWQALV(K&acC~ND^p-vN2Bt$hcr_s*$H^!{h`~tn{bqWT%@9&P1y3H z-i(~K14PR8NXpLZl!GI!K%@3#&~}d7ec6FeVzOlkDp6+YxL`@E5hU3Ybxi}~B}^f) zudLNHtpuXcBs9XiF#J>mR+!p{#HlxE;;MsWsmjJ zPF9R`^Jn68CCvt&_Eh^T%RT`}1jHheDf>Vi@K)I*&FeDdo-+0Yx8GgC%V#U}h*ntC z;=v2=Ua#_^T3?YEp1AH@-ynO$ud+|g@-o9~Wd|<#8QJVme|=lJ$g5b9KSD^kx-}uP zEPAI-JR8@SFJ5jhoqCRk0MD~Q8}oupIl1EEwgV$=l3jK;tWLDKE3dS>@892k;}d_w zaFLDfzV{&E={4G4U~=!1pZt~fjc+{78$>1{_A=Xl`eu0rXGn7%%U#SC+LnoJ?Uq|_ zjnZ{!wKf&<;=B+d2H{%6JoZSVVYciX9Nd)cM9n32z~1#4WShuMBV%lDy27@y7cnTE zAEKrMjBMGAliR?~d+Y@be%v2ze7V37ZP+UHV(qz|JJL8=^uiXSZlJTSEiAI{v>p!2 za87K~&?sB74zY{gVs;Hos;OZ>+vbriKxVTfwraIMX<()hF8Cf|Yu8maRPs?!z(NB? zLZuNs>je=;4W=LMBin$t@Yi&!jm7bXWcOPY99%(=uj~Pd2~+>&M+v( zsf;^mf$>X&k0WDj&`>WgQP}nhKK#@`)JgWDrmSuVV2JJqt;2)jT4!t}`Nf#YIg*az z9Hq_n35Xb2Qex^LvUF}~6s9n2pWPi9jvB0~R-GRQV1L)y=@3k%H0X3Z@qr_{puKZ3 z{dpV+LCFIBRh+waqnUdb7@W?L^}$B?b<+rK0iQFz0Ur2aA~a=bntF$SJCUEvSL#h1 zO7qXonmJD9sN5khJ2gU|xV-L8cdNc!LlcVK>x{a6&|MQsc8hs4FN;34GidMy3LEh> z26uOW^iT#4Ir8uT4%{0aa_BsbGhsTWjck(UP*4Z2^H3H_LmSoBRA@)8LAZCj3J?|Aw(m>6l+!44>*s`&X{Ek%>!}cnL^X zZl8Apx6SOf>UPMN*sfTo>0vuJJR4<8-z9d3bW39sFm>;cYK%7!xp#*9xC>7l)nzt9mHq*~iLJ^_wpaFL=54;&6}2JtJ>w@BZ; zf^)9B!YxjA^XAd?--QKu(y5|f1KcvQ*}?Y3mr1$P7qLMd|J>{>_Peo-ZQVk@X4_-? z2YlKG=7BLbHWu5>uvbYy$ISWji0wMp#sl9WiKgibc&5)yvm+$?WYM(^E_B)mP56Np|Vmzkd%-lxNa# zej3Ed7iVC6W4rUv9^lY1P{ifSGi_#OuAMu(&?fiZ${yDf*?G-Z8@kQwsgqL(Vg)=0 zt3#qB$GI1#X3}Qw-pRIY2TG$%9^F><^lNhjpeyar!9D23`%4ZW2^^Q!w-=v(vF%0J z+qd6BC%=S|qNa7D?d6wF^0J9J@Z60Ybf$D)f{Ov*&^rVF2d8gcKY6bey1ucJjo_80cu*wfh!~5ND2;F|fG#)S1_&+P;1J$RgMiT*uGo zU=VmyudwSNv-3MAw?jYpb!a$G_nDraKHau%+nOMp3zHQqWWoCwsI(Z=l zo3YQ+QHFU5&FCmEfB=>q+4l+SaCB4?TelN9NmwXPhK=){xiFLVsL;_#G8a(ZFk;% zsNHfKPM0@C4dKw71$g$^XQ_L=-FM%8(a|clw~1`_%xh=4cB~ydcqje#wj^ziaDDO3 zi4!kIhWFfa7=GPfAG^YqFB5?N`}hmk+8`6}cOV1w#!F(%vgc>cwHKa$x$WD(yY1b# zo8ZH~`;;39jvae}etL;5HC~^xD@oecqnAtV*=LTmaRS+WZ@4G#5J*gCWN~5oQv3FI zo@sA-^Xt(4js))|g3|!=I{)$XGr(Eb9)9pBe#V;*ZUENWYxJilNvt3K{x`S1@&pa@ z3v+F3^F({{>;DN27u%zcz60Bx2OjSS7~VmDUTk0Z@;|h9zvp4zo3Vqv=C5JjGwiQF z-oEts(`|fWus!tlqa?`KFXcP%Bh%B@+E>2*h4zkj9Bt!6OH97!ShwL*9DNZs8>e~!q6`QlMD^{v zZf*Mx9$;9rAI0tEdG`Hn|A7M-`@#0`!w)C=J@wR+Fk&`wnfK$)<@X{+@7sDbj-{oQ zWCKrs^SkY~-ACF-KJudko-cEa;TA#X-1&>`8~^^D_JJS%Af4ta!>Uts28VVl?eG8Y zUoobm?LF^#PkZ8tuPKuV<=C<3+wA$P?I(Zo=i3V>juAAbap}uh%JR?u_*FX9n+Pli z+VSJZ;bnx5FwmZU>RUL4`S!7o{#<+U#p47P->yRDgRGzU;#2MY@B1Jls#|Dt3_~Bz zR-oVh*0DBwuC@2R_g(F|7oNdUc&BwFdv|{YrM>;3caj-xBgmagCS(VG?)XdX(#3)H z$fNIUubnwT8?q(bg%gWk|Mzd zB?<5*m(xKS!r7@=40RVimSD%ZS*T8Ag}uk;XBqWj_>LYn4of#%)RN)NoWGR7GP-$7 z$%Z!+W$`=d^Tl}`0DG+iW0(MA5FH#cD_zEM^B8MJWJ6?N%z=sz8dq1DI#^LA)WyO2 zU`h7Np{>DXVBKb5FfxT#<+B0U&WT=ODqwKq80Cy62?p6J)kMXYFK{Pu)9{wqt?yBF zX`L@2xxyaNE0+iw&@@Vh>vWbGyN)TZJ>(_dj#?ozfyW?ghPiX+t)VR$C3n~n3HEv! z7!%m>9dUZTQc`%iIYAWMwtYvOVqR?t-DaqcD#UyHfrF`w$s=Z@$MurXYZ#nv4 zd)@1fw&$NekxbgrldmrIMIj&l(2o;L7u(dCY3L-mAuvu)pKot`kM=qX`lG`Z?z}>?W_2r(+N3+dlO>zt_I-g~yS}3=+83&P<(6a`f3h{u92hwSOg{-bNzgh~(8* zPqz(3zxj#w_!D1gXJ@8(7r^Cq{@i@qGBVOW`=_6cJih+q*Lac6NSmgO`ug~< z{d)VMANoOL@K3y)XOJ@aJl!6A@U87rpZab3{|x@+ceK%NL9)y_ literal 0 HcmV?d00001 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index fdbc8c5e02..8712e2d425 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -1,11 +1,77 @@  using BizHawk.Emulation.Common; +using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZXSpectrum { + ///

+ /// The one ZX Hawk ControllerDefinition + /// + public static ControllerDefinition ZXSpectrumControllerDefinition + { + get + { + ControllerDefinition definition = new ControllerDefinition(); + definition.Name = "ZXSpectrum Controller"; + // joysticks + List joys = new List + { + // Kempston Joystick (P1) + "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button", + }; + + foreach (var s in joys) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Kempton Joystick"; + } + + // keyboard + List keys = new List + { + /// Controller mapping includes all keyboard keys from the following models: + /// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg + /// https://upload.wikimedia.org/wikipedia/commons/c/ca/ZX_Spectrum%2B.jpg + + // 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 Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", + }; + + foreach (var s in keys) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Keyboard"; + } + + // Datacorder (tape device) + List tape = new List + { + // Tape functions + "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape", "Next Tape Block", "Prev Tape Block" + }; + + foreach (var s in tape) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Datacorder"; + } + + return definition; + } + } + + /* /// /// Controller mapping includes all keyboard keys from the following models: /// https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/ZXSpectrum48k.jpg/1200px-ZXSpectrum48k.jpg @@ -29,8 +95,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Keyboard - row 5 "Key Symbol Shift", "Key Semi-Colon", "Key Quote", "Key Left Cursor", "Key Right Cursor", "Key Space", "Key Up Cursor", "Key Down Cursor", "Key Comma", // Tape functions - "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape" + "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape", "Next Tape Block", "Prev Tape Block" } }; + + */ } } From 93ae29c3a0a33cf3569c4bd818381ddd5b87a3ce Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 15:47:14 +0000 Subject: [PATCH 054/105] Added Cursor(Protek) and Sinclair (left and right) joystick emulation. Also user can now set J1, J2, and J3 emulated joystick type through syncsettings --- .../BizHawk.Emulation.Cores.csproj | 5 + .../Hardware/Input/CursorJoystick.cs | 116 ++++++++++++++++++ .../Hardware/Input/IJoystick.cs | 44 +++++++ .../Hardware/Input/KempstonJoystick.cs | 77 ++++++++---- .../Hardware/Input/NullJoystick.cs | 107 ++++++++++++++++ .../Hardware/Input/SinclairJoystick1.cs | 115 +++++++++++++++++ .../Hardware/Input/SinclairJoystick2.cs | 115 +++++++++++++++++ .../Machine/SpectrumBase.Input.cs | 101 +++++++++++++-- .../SinclairSpectrum/Machine/SpectrumBase.cs | 22 +++- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 3 +- .../Machine/ZXSpectrum128K/ZX128.cs | 6 +- .../Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs | 4 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 3 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 6 +- .../Machine/ZXSpectrum16K/ZX16.cs | 4 +- .../Machine/ZXSpectrum48K/ZX48.Keyboard.cs | 2 + .../Machine/ZXSpectrum48K/ZX48.Port.cs | 3 +- .../Machine/ZXSpectrum48K/ZX48.cs | 7 +- .../ZXSpectrum.Controllers.cs | 54 +++++++- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 18 ++- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 31 +++-- 21 files changed, 775 insertions(+), 68 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index d85d488b0a..e9bc4d2bac 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,6 +256,11 @@ + + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs new file mode 100644 index 0000000000..bda58ec7eb --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs @@ -0,0 +1,116 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Cursor joystick + /// Maps to a combination of 0xf7fe and 0xeffe + /// + public class CursorJoystick : IJoystick + { + private int _joyLine; + private SpectrumBase _machine; + + #region Construction + + public CursorJoystick(SpectrumBase machine, int playerNumber) + { + _machine = machine; + _joyLine = 0; + _playerNumber = playerNumber; + + ButtonCollection = new List + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "Key 5", // left + "Key 8", // right + "Key 6", // down + "Key 7", // up + "Key 0", // fire + }; + + #endregion + + #region IJoystick + + public JoystickType JoyType => JoystickType.SinclairPORT1; + + public string[] ButtonCollection { get; set; } + + private int _playerNumber; + public int PlayerNumber + { + get { return _playerNumber; } + set { _playerNumber = value; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + + if (isPressed) + { + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true); + } + else + { + if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos])) + { + // key is already pressed elswhere - leave it as is + } + else + { + // key is safe to unpress + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + var l = _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + return l; + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs new file mode 100644 index 0000000000..6421e99af4 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs @@ -0,0 +1,44 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a spectrum joystick + /// + public interface IJoystick + { + /// + /// The type of joystick + /// + JoystickType JoyType { get; } + + /// + /// Array of all the possibly button press names + /// + string[] ButtonCollection { get; set; } + + /// + /// The player number that this controller is currently assigned to + /// + int PlayerNumber { get; set; } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + void SetJoyInput(string key, bool isPressed); + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + bool GetJoyInput(string key); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs index 5da278df76..880e388f3a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/KempstonJoystick.cs @@ -1,4 +1,5 @@ -using System; +using BizHawk.Common; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,34 +7,42 @@ using System.Threading.Tasks; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { - public class KempstonJoystick + public class KempstonJoystick : IJoystick { private int _joyLine; private SpectrumBase _machine; - public readonly string[] _bitPos = new string[] - { - "P1 Right", - "P1 Left", - "P1 Down", - "P1 Up", - "P1 Button" - }; + #region Construction - /* - Active bits high - 0 0 0 F U D L R - */ - public int JoyLine - { - get { return _joyLine; } - set { _joyLine = value; } - } - - public KempstonJoystick(SpectrumBase machine) + public KempstonJoystick(SpectrumBase machine, int playerNumber) { _machine = machine; _joyLine = 0; + _playerNumber = playerNumber; + + ButtonCollection = new List + { + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + #endregion + + #region IJoystick + + public JoystickType JoyType => JoystickType.Kempston; + + public string[] ButtonCollection { get; set; } + + private int _playerNumber; + public int PlayerNumber + { + get { return _playerNumber; } + set { _playerNumber = value; } } /// @@ -60,6 +69,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var pos = GetBitPos(key); return (_joyLine & (1 << pos)) != 0; } + + #endregion + + /// + /// Active bits high + /// 0 0 0 F U D L R + /// + public int JoyLine + { + get { return _joyLine; } + set { _joyLine = value; } + } /// /// Gets the bit position of a particular joystick binding from the matrix @@ -68,8 +89,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public int GetBitPos(string key) { - int index = Array.IndexOf(_bitPos, key); + int index = Array.IndexOf(ButtonCollection, key); return index; } + + + /* + public readonly string[] _bitPos = new string[] + { + "P1 Right", + "P1 Left", + "P1 Down", + "P1 Up", + "P1 Button" + }; + */ } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs new file mode 100644 index 0000000000..a8773d0625 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs @@ -0,0 +1,107 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// A null joystick object + /// + public class NullJoystick : IJoystick + { + private int _joyLine; + private SpectrumBase _machine; + + #region Construction + + public NullJoystick(SpectrumBase machine, int playerNumber) + { + _machine = machine; + _joyLine = 0; + _playerNumber = playerNumber; + + ButtonCollection = new List + { + + }.ToArray(); + } + + #endregion + + #region IJoystick + + public JoystickType JoyType => JoystickType.None; + + public string[] ButtonCollection { get; set; } + + private int _playerNumber; + public int PlayerNumber + { + get { return _playerNumber; } + set { _playerNumber = value; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + if (isPressed) + _joyLine |= (1 << pos); + else + _joyLine &= ~(1 << pos); + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + return (_joyLine & (1 << pos)) != 0; + } + + #endregion + + /// + /// Active bits high + /// 0 0 0 F U D L R + /// + public int JoyLine + { + get { return _joyLine; } + set { _joyLine = value; } + } + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + + + /* + public readonly string[] _bitPos = new string[] + { + "P1 Right", + "P1 Left", + "P1 Down", + "P1 Up", + "P1 Button" + }; + */ + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs new file mode 100644 index 0000000000..569f25b80d --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs @@ -0,0 +1,115 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Sinclair Joystick LEFT + /// Just maps to the standard keyboard and is read the same (from port 0xf7fe) + /// + public class SinclairJoystick1 : IJoystick + { + private int _joyLine; + private SpectrumBase _machine; + + #region Construction + + public SinclairJoystick1(SpectrumBase machine, int playerNumber) + { + _machine = machine; + _joyLine = 0; + _playerNumber = playerNumber; + + ButtonCollection = new List + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "Key 1", // left + "Key 2", // right + "Key 3", // down + "Key 4", // up + "Key 5", // fire + }; + + #endregion + + #region IJoystick + + public JoystickType JoyType => JoystickType.SinclairPORT1; + + public string[] ButtonCollection { get; set; } + + private int _playerNumber; + public int PlayerNumber + { + get { return _playerNumber; } + set { _playerNumber = value; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + + if (isPressed) + { + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true); + } + else + { + if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos])) + { + // key is already pressed elswhere - leave it as is + } + else + { + // key is safe to unpress + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs new file mode 100644 index 0000000000..160ca0ea39 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs @@ -0,0 +1,115 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Sinclair Joystick RIGHT + /// Just maps to the standard keyboard and is read the same (from port 0xeffe) + /// + public class SinclairJoystick2 : IJoystick + { + private int _joyLine; + private SpectrumBase _machine; + + #region Construction + + public SinclairJoystick2(SpectrumBase machine, int playerNumber) + { + _machine = machine; + _joyLine = 0; + _playerNumber = playerNumber; + + ButtonCollection = new List + { + "P" + _playerNumber + " Left", + "P" + _playerNumber + " Right", + "P" + _playerNumber + " Down", + "P" + _playerNumber + " Up", + "P" + _playerNumber + " Button", + }.ToArray(); + } + + private List btnLookups = new List + { + "Key 6", // left + "Key 7", // right + "Key 8", // down + "Key 9", // up + "Key 0", // fire + }; + + #endregion + + #region IJoystick + + public JoystickType JoyType => JoystickType.SinclairPORT1; + + public string[] ButtonCollection { get; set; } + + private int _playerNumber; + public int PlayerNumber + { + get { return _playerNumber; } + set { _playerNumber = value; } + } + + /// + /// Sets the joystick line based on key pressed + /// + /// + /// + public void SetJoyInput(string key, bool isPressed) + { + var pos = GetBitPos(key); + + if (isPressed) + { + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], true); + } + else + { + if (_machine.KeyboardDevice.GetKeyStatus(btnLookups[pos])) + { + // key is already pressed elswhere - leave it as is + } + else + { + // key is safe to unpress + _machine.KeyboardDevice.SetKeyStatus(btnLookups[pos], false); + } + } + } + + /// + /// Gets the state of a particular joystick binding + /// + /// + /// + public bool GetJoyInput(string key) + { + var pos = GetBitPos(key); + if (_machine == null) + return false; + + return _machine.KeyboardDevice.GetKeyStatus(btnLookups[pos]); + } + + #endregion + + /// + /// Gets the bit position of a particular joystick binding from the matrix + /// + /// + /// + public int GetBitPos(string key) + { + int index = Array.IndexOf(ButtonCollection, key); + return index; + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 97bd1c0e5f..74ea1f645c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -1,4 +1,7 @@  +using System.Collections.Generic; +using System.Linq; + namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// @@ -37,6 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } // non matrix keys + /* foreach (string k in KeyboardDevice.NonMatrixKeys) { if (!k.StartsWith("Key")) @@ -46,17 +50,40 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice.SetKeyStatus(k, currState); } + */ + + // J1 + foreach (string j in JoystickCollection[0].ButtonCollection) + { + bool prevState = JoystickCollection[0].GetJoyInput(j); + bool currState = Spectrum._controller.IsPressed(j); + + if (currState != prevState) + JoystickCollection[0].SetJoyInput(j, currState); + } + + // J2 + foreach (string j in JoystickCollection[1].ButtonCollection) + { + bool prevState = JoystickCollection[1].GetJoyInput(j); + bool currState = Spectrum._controller.IsPressed(j); + + if (currState != prevState) + JoystickCollection[1].SetJoyInput(j, currState); + } + + // J3 + foreach (string j in JoystickCollection[2].ButtonCollection) + { + bool prevState = JoystickCollection[2].GetJoyInput(j); + bool currState = Spectrum._controller.IsPressed(j); + + if (currState != prevState) + JoystickCollection[2].SetJoyInput(j, currState); + } } - // J1 - foreach (string j in KempstonDevice._bitPos) - { - bool prevState = KempstonDevice.GetJoyInput(j); - bool currState = Spectrum._controller.IsPressed(j); - - if (currState != prevState) - KempstonDevice.SetJoyInput(j, currState); - } + // Tape control if (Spectrum._controller.IsPressed(Play)) @@ -123,6 +150,62 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else pressed_PrevTape = false; } + + /// + /// Instantiates the joysticks array + /// + /// + protected void InitJoysticks(List joys) + { + List jCollection = new List(); + + for (int i = 0; i < joys.Count(); i++) + { + jCollection.Add(InstantiateJoystick(joys[i], i + 1)); + } + + JoystickCollection = jCollection.ToArray(); + + for (int i = 0; i < JoystickCollection.Length; i++) + { + Spectrum.OSD_FireInputMessage("Joystick " + (i + 1) + ": " + JoystickCollection[i].JoyType.ToString()); + } + } + + /// + /// Instantiates a new IJoystick object + /// + /// + /// + /// + public IJoystick InstantiateJoystick(JoystickType type, int playerNumber) + { + switch (type) + { + case JoystickType.Kempston: + return new KempstonJoystick(this, playerNumber); + case JoystickType.Cursor: + return new CursorJoystick(this, playerNumber); + case JoystickType.SinclairPORT1: + return new SinclairJoystick1(this, playerNumber); + case JoystickType.SinclairPORT2: + return new SinclairJoystick2(this, playerNumber); + case JoystickType.None: + return new NullJoystick(this, playerNumber); + } + + return null; + } + + /// + /// Returns a IJoystick object depending on the type (or null if not found) + /// + /// + /// + protected IJoystick LocateUniqueJoystick(JoystickType type) + { + return JoystickCollection.Where(a => a.JoyType == type).FirstOrDefault(); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 9d4ec84a10..4e0080b41f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -2,6 +2,7 @@ using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.Z80A; using System; +using System.Collections.Generic; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -72,14 +73,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public virtual DatacorderDevice TapeDevice { get; set; } /// - /// The tape provider + /// Holds the currently selected joysticks /// - //public virtual ITapeProvider TapeProvider { get; set; } + public virtual IJoystick[] JoystickCollection { get; set; } + + /* + /// + /// Joystick device 1 + /// + public virtual IJoystick Joystick1 { get; set; } /// - /// Kempston joystick + /// Joystick device 2 /// - public virtual KempstonJoystick KempstonDevice { get; set; } + public virtual IJoystick Joystick2 { get; set; } + + /// + /// Joystick device 3 + /// + public virtual IJoystick Joystick3 { get; set; } + + */ /// /// Signs whether the frame has ended diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 64c2cff470..2604d8187b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -27,7 +27,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) { - return (byte)KempstonDevice.JoyLine; + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; } else if (lowBitReset) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 1844d7ebe4..eaab39cb1d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) + public ZX128(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) { Spectrum = spectrum; CPU = cpu; @@ -38,7 +38,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); - KempstonDevice = new KempstonJoystick(this); + + InitJoysticks(joysticks); + //KempstonDevice = new KempstonJoystick(this); TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs index a1f3c8d019..72c546da51 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2/ZX128Plus2.cs @@ -20,8 +20,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) - : base(spectrum, cpu, borderType, files) + public ZX128Plus2(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) + : base(spectrum, cpu, borderType, files, joysticks) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 556a5f1d23..18582daf1a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -26,7 +26,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) { - return (byte)KempstonDevice.JoyLine; + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; } else if (lowBitReset) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 403c8495a5..796f06e1db 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) + public ZX128Plus3(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) { Spectrum = spectrum; CPU = cpu; @@ -38,7 +38,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); - KempstonDevice = new KempstonJoystick(this); + + InitJoysticks(joysticks); + //KempstonDevice = new KempstonJoystick(this); TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index b89e07957c..49c6cd686b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -16,8 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) - : base(spectrum, cpu, borderType, files) + public ZX16(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) + : base(spectrum, cpu, borderType, files, joysticks) { } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs index e04eedc217..c51dbe60a3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs @@ -68,11 +68,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum }; var nonMatrix = new List(); + /* foreach (var key in ZXSpectrum.ZXSpectrumControllerDefinition.BoolButtons) { if (!KeyboardMatrix.Any(s => s == key)) nonMatrix.Add(key); } + */ NonMatrixKeys = nonMatrix.ToArray(); LineStatus = new byte[8]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 3d891fb9e7..f3f40faf8e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -26,7 +26,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) { - return (byte)KempstonDevice.JoyLine; + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; } else if (lowBitReset) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index a9e2bc5959..4aedb37344 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// /// - public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files) + public ZX48(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) { Spectrum = spectrum; CPU = cpu; @@ -29,7 +29,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new Keyboard48(this); - KempstonDevice = new KempstonJoystick(this); + + InitJoysticks(joysticks); + + //KempstonDevice = new KempstonJoystick(this); TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index 8712e2d425..de59da486e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -1,6 +1,10 @@ - -using BizHawk.Emulation.Common; +using System; using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Common.ReflectionExtensions; +using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -17,16 +21,40 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum definition.Name = "ZXSpectrum Controller"; // joysticks - List joys = new List + List joys1 = new List { - // Kempston Joystick (P1) + // P1 Joystick "P1 Up", "P1 Down", "P1 Left", "P1 Right", "P1 Button", }; - foreach (var s in joys) + foreach (var s in joys1) { definition.BoolButtons.Add(s); - definition.CategoryLabels[s] = "Kempton Joystick"; + definition.CategoryLabels[s] = "Joystick 1"; + } + + List joys2 = new List + { + // P2 Joystick + "P2 Up", "P2 Down", "P2 Left", "P2 Right", "P2 Button", + }; + + foreach (var s in joys2) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Joystick 2"; + } + + List joys3 = new List + { + // P3 Joystick + "P3 Up", "P3 Down", "P3 Left", "P3 Right", "P3 Button", + }; + + foreach (var s in joys3) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Joystick 3"; } // keyboard @@ -71,6 +99,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + + /* /// /// Controller mapping includes all keyboard keys from the following models: @@ -101,4 +131,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ } + + /// + /// The possible joystick types + /// + public enum JoystickType + { + None, + Kempston, + SinclairPORT1, + SinclairPORT2, + Cursor + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 182cedd7a4..a55807663e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -85,7 +85,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DisplayName("Tape Load Speed")] [Description("Select how fast the spectrum loads the game from tape")] [DefaultValue(TapeLoadSpeed.Accurate)] - public TapeLoadSpeed TapeLoadSpeed { get; set; } + public TapeLoadSpeed TapeLoadSpeed { get; set; } + + [DisplayName("Joystick 1")] + [Description("The emulated joystick assigned to P1 (SHOULD BE UNIQUE TYPE!)")] + [DefaultValue(JoystickType.Kempston)] + public JoystickType JoystickType1 { get; set; } + + [DisplayName("Joystick 2")] + [Description("The emulated joystick assigned to P2 (SHOULD BE UNIQUE TYPE!)")] + [DefaultValue(JoystickType.SinclairPORT1)] + public JoystickType JoystickType2 { get; set; } + + [DisplayName("Joystick 3")] + [Description("The emulated joystick assigned to P3 (SHOULD BE UNIQUE TYPE!)")] + [DefaultValue(JoystickType.SinclairPORT2)] + public JoystickType JoystickType3 { get; set; } + public ZXSpectrumSyncSettings Clone() { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index df56791f0c..840251b274 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -39,27 +39,32 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //_file = file; _files = files?.ToList() ?? new List(); + List joysticks = new List(); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType1); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2); + joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3); + switch (SyncSettings.MachineType) { case MachineType.ZXSpectrum16: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); + Init(MachineType.ZXSpectrum16, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; case MachineType.ZXSpectrum48: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); + Init(MachineType.ZXSpectrum48, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; case MachineType.ZXSpectrum128: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); + Init(MachineType.ZXSpectrum128, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; case MachineType.ZXSpectrum128Plus2: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); + Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; case MachineType.ZXSpectrum128Plus3: ControllerDefinition = ZXSpectrumControllerDefinition; - Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files); + Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; default: throw new InvalidOperationException("Machine not yet emulated"); @@ -101,7 +106,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private readonly Z80A _cpu; private readonly TraceBuffer _tracer; public IController _controller; - private SpectrumBase _machine; + public SpectrumBase _machine; private List _gameInfo; @@ -112,7 +117,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //private byte[] _file; private readonly List _files; - public bool DiagRom = false; + public bool DiagRom = true; private byte[] GetFirmware(int length, params string[] names) { @@ -158,37 +163,37 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } - private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, List files) + private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, List files, List joys) { // setup the emulated model based on the MachineType switch (machineType) { case MachineType.ZXSpectrum16: - _machine = new ZX16(this, _cpu, borderType, files); + _machine = new ZX16(this, _cpu, borderType, files, joys); var _systemRom16 = GetFirmware(0x4000, "48ROM"); var romData16 = RomData.InitROM(machineType, _systemRom16); _machine.InitROM(romData16); break; case MachineType.ZXSpectrum48: - _machine = new ZX48(this, _cpu, borderType, files); + _machine = new ZX48(this, _cpu, borderType, files, joys); var _systemRom = GetFirmware(0x4000, "48ROM"); var romData = RomData.InitROM(machineType, _systemRom); _machine.InitROM(romData); break; case MachineType.ZXSpectrum128: - _machine = new ZX128(this, _cpu, borderType, files); + _machine = new ZX128(this, _cpu, borderType, files, joys); var _systemRom128 = GetFirmware(0x8000, "128ROM"); var romData128 = RomData.InitROM(machineType, _systemRom128); _machine.InitROM(romData128); break; case MachineType.ZXSpectrum128Plus2: - _machine = new ZX128Plus2(this, _cpu, borderType, files); + _machine = new ZX128Plus2(this, _cpu, borderType, files, joys); var _systemRomP2 = GetFirmware(0x8000, "PLUS2ROM"); var romDataP2 = RomData.InitROM(machineType, _systemRomP2); _machine.InitROM(romDataP2); break; case MachineType.ZXSpectrum128Plus3: - _machine = new ZX128Plus3(this, _cpu, borderType, files); + _machine = new ZX128Plus3(this, _cpu, borderType, files, joys); var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); var romDataP3 = RomData.InitROM(machineType, _systemRomP3); _machine.InitROM(romDataP3); From f8e1174aadeb911d8f0c039039bd10b4a2c8c0b5 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 16:01:40 +0000 Subject: [PATCH 055/105] ControllerConfiguration form now shows which Joystick type is assigned to each input --- .../Hardware/Input/CursorJoystick.cs | 2 +- .../Hardware/Input/NullJoystick.cs | 2 +- .../Hardware/Input/SinclairJoystick1.cs | 2 +- .../Hardware/Input/SinclairJoystick2.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.Input.cs | 6 +++--- .../SinclairSpectrum/ZXSpectrum.Controllers.cs | 14 +++++++------- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 4 ++-- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs index bda58ec7eb..edd54dd9b1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/CursorJoystick.cs @@ -47,7 +47,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region IJoystick - public JoystickType JoyType => JoystickType.SinclairPORT1; + public JoystickType JoyType => JoystickType.Cursor; public string[] ButtonCollection { get; set; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs index a8773d0625..ba2d3ea467 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/NullJoystick.cs @@ -33,7 +33,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region IJoystick - public JoystickType JoyType => JoystickType.None; + public JoystickType JoyType => JoystickType.NULL; public string[] ButtonCollection { get; set; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs index 569f25b80d..a3789b9e5d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick1.cs @@ -47,7 +47,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region IJoystick - public JoystickType JoyType => JoystickType.SinclairPORT1; + public JoystickType JoyType => JoystickType.SinclairLEFT; public string[] ButtonCollection { get; set; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs index 160ca0ea39..82d9fd9857 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/SinclairJoystick2.cs @@ -47,7 +47,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region IJoystick - public JoystickType JoyType => JoystickType.SinclairPORT1; + public JoystickType JoyType => JoystickType.SinclairRIGHT; public string[] ButtonCollection { get; set; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 74ea1f645c..8a30b68040 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -186,11 +186,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return new KempstonJoystick(this, playerNumber); case JoystickType.Cursor: return new CursorJoystick(this, playerNumber); - case JoystickType.SinclairPORT1: + case JoystickType.SinclairLEFT: return new SinclairJoystick1(this, playerNumber); - case JoystickType.SinclairPORT2: + case JoystickType.SinclairRIGHT: return new SinclairJoystick2(this, playerNumber); - case JoystickType.None: + case JoystickType.NULL: return new NullJoystick(this, playerNumber); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index de59da486e..a59f03de9e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -13,7 +13,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The one ZX Hawk ControllerDefinition /// - public static ControllerDefinition ZXSpectrumControllerDefinition + public ControllerDefinition ZXSpectrumControllerDefinition { get { @@ -30,7 +30,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum foreach (var s in joys1) { definition.BoolButtons.Add(s); - definition.CategoryLabels[s] = "Joystick 1"; + definition.CategoryLabels[s] = "J1 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType1.ToString() + ")"; } List joys2 = new List @@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum foreach (var s in joys2) { definition.BoolButtons.Add(s); - definition.CategoryLabels[s] = "Joystick 2"; + definition.CategoryLabels[s] = "J2 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType2.ToString() + ")"; } List joys3 = new List @@ -54,7 +54,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum foreach (var s in joys3) { definition.BoolButtons.Add(s); - definition.CategoryLabels[s] = "Joystick 3"; + definition.CategoryLabels[s] = "J3 (" + ((ZXSpectrumSyncSettings)SyncSettings as ZXSpectrumSyncSettings).JoystickType3.ToString() + ")"; } // keyboard @@ -137,10 +137,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public enum JoystickType { - None, + NULL, Kempston, - SinclairPORT1, - SinclairPORT2, + SinclairLEFT, + SinclairRIGHT, Cursor } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index a55807663e..8ad6ab5b1e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -94,12 +94,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DisplayName("Joystick 2")] [Description("The emulated joystick assigned to P2 (SHOULD BE UNIQUE TYPE!)")] - [DefaultValue(JoystickType.SinclairPORT1)] + [DefaultValue(JoystickType.SinclairLEFT)] public JoystickType JoystickType2 { get; set; } [DisplayName("Joystick 3")] [Description("The emulated joystick assigned to P3 (SHOULD BE UNIQUE TYPE!)")] - [DefaultValue(JoystickType.SinclairPORT2)] + [DefaultValue(JoystickType.SinclairRIGHT)] public JoystickType JoystickType3 { get; set; } From aa1cfde69b7256be5a53bccf5a1f142c2bcc7fe3 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 16:05:50 +0000 Subject: [PATCH 056/105] Disabled replacement DiagRom bios (vblank tests now appear to be working) --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 840251b274..fe9520b245 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -117,7 +117,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //private byte[] _file; private readonly List _files; - public bool DiagRom = true; + public bool DiagRom = false; private byte[] GetFirmware(int length, params string[] names) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index 7b2345b74c..2f21a9ae56 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -11,7 +11,8 @@ At the moment this is very experimental and is still actively being worked on. * Beeper/Buzzer output (implementing ISoundProvider) * AY-3-8912 sound chip implementation * Keyboard input (implementing IInputPollable) -* Kempston joystick (mapped to J1 currently) +* Default keyboard keymappings +* Kempston, Cursor and Sinclair (Left & Right) joysticks emulated * Tape device that will load spectrum games in realtime (*.tzx and *.tap) * Most tape protection/loading schemes that I've tested are currently working (see caveat below) * IStatable @@ -26,7 +27,6 @@ At the moment this is very experimental and is still actively being worked on. ### Not working * IDebuggable (probably IMemoryDomains is setup incorrectly) * ZX Spectrum Plus3 emulation -* Default keyboard keymappings (you have to configure yourself in the core controller settings) ### Known bugs * Audible 'popping' from the emulated buzzer after a load state operation (maybe this is a normal thing) From 35bb1d0c93c34e530db288969371e513462acd05 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 16:40:25 +0000 Subject: [PATCH 057/105] Fixed default keys and also fixed a major syncsettings snafu --- Assets/defctrl.json | 4 +++- .../BizHawk.Emulation.Cores.csproj | 2 +- .../Input/StandardKeyboard.cs} | 10 +++++----- .../Machine/SpectrumBase.Input.cs | 2 -- .../Machine/ZXSpectrum128K/ZX128.cs | 2 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.cs | 2 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 15 ++++++++++----- 8 files changed, 22 insertions(+), 17 deletions(-) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/{Machine/ZXSpectrum48K/ZX48.Keyboard.cs => Hardware/Input/StandardKeyboard.cs} (98%) diff --git a/Assets/defctrl.json b/Assets/defctrl.json index 631e0af74b..c2bb94b42b 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -530,7 +530,9 @@ "Record Tape": "", "Key Quote": "Shift+D2", "Insert Next Tape": "F6", - "Insert Previous Tape": "F5" + "Insert Previous Tape": "F5", + "Next Tape Block": "F8", + "Prev Tape Block": "F7" }, "Intellivision Controller": { "P1 Up": "UpArrow, J1 POV1U, X1 DpadUp, X1 LStickUp", diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index e9bc4d2bac..b81298264b 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -1381,7 +1381,7 @@
- + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs similarity index 98% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs index c51dbe60a3..2df804efec 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Keyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs @@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The 48k keyboard device /// - public class Keyboard48 : IKeyboard + public class StandardKeyboard : IKeyboard { public SpectrumBase _machine { get; set; } private byte[] LineStatus; @@ -43,7 +43,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum set { _nonMatrixKeys = value; } } - public Keyboard48(SpectrumBase machine) + public StandardKeyboard(SpectrumBase machine) { _machine = machine; @@ -68,13 +68,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum }; var nonMatrix = new List(); - /* - foreach (var key in ZXSpectrum.ZXSpectrumControllerDefinition.BoolButtons) + + foreach (var key in _machine.Spectrum.ZXSpectrumControllerDefinition.BoolButtons) { if (!KeyboardMatrix.Any(s => s == key)) nonMatrix.Add(key); } - */ + NonMatrixKeys = nonMatrix.ToArray(); LineStatus = new byte[8]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 8a30b68040..7aee74e07a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -40,7 +40,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } // non matrix keys - /* foreach (string k in KeyboardDevice.NonMatrixKeys) { if (!k.StartsWith("Key")) @@ -50,7 +49,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice.SetKeyStatus(k, currState); } - */ // J1 foreach (string j in JoystickCollection[0].ButtonCollection) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index eaab39cb1d..1eb5b96a14 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice = new AY38912(); AYDevice.Init(44100, ULADevice.FrameLength); - KeyboardDevice = new Keyboard48(this); + KeyboardDevice = new StandardKeyboard(this); InitJoysticks(joysticks); //KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 796f06e1db..1bc487dd78 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice = new AY38912(); AYDevice.Init(44100, ULADevice.FrameLength); - KeyboardDevice = new Keyboard48(this); + KeyboardDevice = new StandardKeyboard(this); InitJoysticks(joysticks); //KempstonDevice = new KempstonJoystick(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 4aedb37344..3affcf59b1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -28,7 +28,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); - KeyboardDevice = new Keyboard48(this); + KeyboardDevice = new StandardKeyboard(this); InitJoysticks(joysticks); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index fe9520b245..3491c4a60c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -20,10 +20,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { [CoreConstructor("ZXSpectrum")] public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) - { - PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings()); - PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings()); - + { var ser = new BasicServiceProvider(this); ServiceProvider = ser; InputCallbacks = new InputCallbackSystem(); @@ -39,7 +36,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //_file = file; _files = files?.ToList() ?? new List(); - List joysticks = new List(); + if (settings == null) + settings = new ZXSpectrumSettings(); + if (syncSettings == null) + syncSettings = new ZXSpectrumSyncSettings(); + + PutSyncSettings((ZXSpectrumSyncSettings)syncSettings ?? new ZXSpectrumSyncSettings()); + PutSettings((ZXSpectrumSettings)settings ?? new ZXSpectrumSettings()); + + List joysticks = new List(); joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType1); joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2); joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3); From 7a7b84f35c3a0bcd77299ea8a21914b3fca04a9e Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 17:04:12 +0000 Subject: [PATCH 058/105] Fixed MemoryCallbacks (i think) - now debugger opens without throwing an exception --- .../Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs | 4 +--- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs index 85083f3fcc..716c29ec95 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { public partial class ZXSpectrum //: IMemoryDomains { - private MemoryDomainList memoryDomains; + internal IMemoryDomains memoryDomains; private readonly Dictionary _byteArrayDomains = new Dictionary(); private bool _memoryDomainsInit = false; @@ -42,8 +42,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum (ServiceProvider as BasicServiceProvider).Register(memoryDomains); _memoryDomainsInit = true; - - } private void SyncAllByteArrayDomains() diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 3491c4a60c..b8d3749514 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -24,6 +24,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var ser = new BasicServiceProvider(this); ServiceProvider = ser; InputCallbacks = new InputCallbackSystem(); + MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" }); CoreComm = comm; From 36485bba8ab6ba7f3a356ee63c76856ece2d529d Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 17:04:58 +0000 Subject: [PATCH 059/105] Updated readme --- BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index 2f21a9ae56..621411f812 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -17,6 +17,7 @@ At the moment this is very experimental and is still actively being worked on. * Most tape protection/loading schemes that I've tested are currently working (see caveat below) * IStatable * ISettable core settings +* IDebuggable (for what it's worth) * Tape auto-loading routines (as a setting) ### Work in progress @@ -25,7 +26,6 @@ At the moment this is very experimental and is still actively being worked on. * TASStudio (need to verify that this works as it should) ### Not working -* IDebuggable (probably IMemoryDomains is setup incorrectly) * ZX Spectrum Plus3 emulation ### Known bugs From 198008a573b14400e591b029660a742486d802ee Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 17:57:13 +0000 Subject: [PATCH 060/105] LagFrame implementation --- .../Machine/SpectrumBase.Input.cs | 5 ++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 5 ++++ .../Machine/ZXSpectrum128K/ZX128.Port.cs | 23 ++++++++++++++++++- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 20 ++++++++++++++++ .../Machine/ZXSpectrum48K/ZX48.Port.cs | 20 ++++++++++++++++ .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 7 ++++++ .../ZXSpectrum.IInputPollable.cs | 2 +- 7 files changed, 80 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 7aee74e07a..1cac046ea8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -204,6 +204,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { return JoystickCollection.Where(a => a.JoyType == type).FirstOrDefault(); } + + /// + /// Signs whether input read has been requested + /// + protected bool InputRead { get; set; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 4e0080b41f..2dc4ee6d81 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -140,6 +140,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ///
public virtual void ExecuteFrame() { + InputRead = false; + FrameCompleted = false; BuzzerDevice.StartFrame(); if (AYDevice != null) @@ -179,6 +181,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum TapeDevice.EndFrame(); FrameCompleted = true; + + // is this a lag frame? + Spectrum.IsLagFrame = !InputRead; } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 2604d8187b..4b9cb123c7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -15,6 +15,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + InputRead = true; + int result = 0xFF; // Check whether the low bit is reset @@ -29,6 +31,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (LocateUniqueJoystick(JoystickType.Kempston) != null) return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; } else if (lowBitReset) { @@ -43,28 +47,45 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ if ((port & 0x8000) == 0) + { result &= KeyboardDevice.KeyLine[7]; + } if ((port & 0x4000) == 0) + { result &= KeyboardDevice.KeyLine[6]; + } if ((port & 0x2000) == 0) + { result &= KeyboardDevice.KeyLine[5]; + } if ((port & 0x1000) == 0) + { result &= KeyboardDevice.KeyLine[4]; - + } + if ((port & 0x800) == 0) + { result &= KeyboardDevice.KeyLine[3]; + } if ((port & 0x400) == 0) + { result &= KeyboardDevice.KeyLine[2]; + } if ((port & 0x200) == 0) + { result &= KeyboardDevice.KeyLine[1]; + } if ((port & 0x100) == 0) + { result &= KeyboardDevice.KeyLine[0]; + } + result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 18582daf1a..8218ce54df 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -15,6 +15,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + InputRead = true; + int result = 0xFF; // Check whether the low bit is reset @@ -28,6 +30,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (LocateUniqueJoystick(JoystickType.Kempston) != null) return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; } else if (lowBitReset) { @@ -42,28 +46,44 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ if ((port & 0x8000) == 0) + { result &= KeyboardDevice.KeyLine[7]; + } if ((port & 0x4000) == 0) + { result &= KeyboardDevice.KeyLine[6]; + } if ((port & 0x2000) == 0) + { result &= KeyboardDevice.KeyLine[5]; + } if ((port & 0x1000) == 0) + { result &= KeyboardDevice.KeyLine[4]; + } if ((port & 0x800) == 0) + { result &= KeyboardDevice.KeyLine[3]; + } if ((port & 0x400) == 0) + { result &= KeyboardDevice.KeyLine[2]; + } if ((port & 0x200) == 0) + { result &= KeyboardDevice.KeyLine[1]; + } if ((port & 0x100) == 0) + { result &= KeyboardDevice.KeyLine[0]; + } result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index f3f40faf8e..873fa6a926 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -15,6 +15,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + InputRead = true; + int result = 0xFF; // Check whether the low bit is reset @@ -28,6 +30,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (LocateUniqueJoystick(JoystickType.Kempston) != null) return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; } else if (lowBitReset) { @@ -44,28 +48,44 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum */ if ((port & 0x8000) == 0) + { result &= KeyboardDevice.KeyLine[7]; + } if ((port & 0x4000) == 0) + { result &= KeyboardDevice.KeyLine[6]; + } if ((port & 0x2000) == 0) + { result &= KeyboardDevice.KeyLine[5]; + } if ((port & 0x1000) == 0) + { result &= KeyboardDevice.KeyLine[4]; + } if ((port & 0x800) == 0) + { result &= KeyboardDevice.KeyLine[3]; + } if ((port & 0x400) == 0) + { result &= KeyboardDevice.KeyLine[2]; + } if ((port & 0x200) == 0) + { result &= KeyboardDevice.KeyLine[1]; + } if ((port & 0x100) == 0) + { result &= KeyboardDevice.KeyLine[0]; + } result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs index 8b10763146..c318876694 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -13,6 +13,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _controller = controller; + _isLag = true; + if (_tracer.Enabled) { _cpu.TraceCallback = s => _tracer.Put(s); @@ -23,6 +25,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } _machine.ExecuteFrame(); + + if (_isLag) + { + _lagCount++; + } } public int Frame => _machine.FrameCount; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs index 70a3b15712..da34d34c66 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IInputPollable.cs @@ -21,6 +21,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public IInputCallbackSystem InputCallbacks { get; } private int _lagCount = 0; - private bool _isLag = true; + private bool _isLag = false; } } From 34663445f875af454a1fe9363e76ee5d2b5b1746 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 6 Mar 2018 18:03:55 +0000 Subject: [PATCH 061/105] LagFrame syncstate --- .../SinclairSpectrum/Machine/SpectrumBase.Input.cs | 8 +++++++- .../Computers/SinclairSpectrum/Machine/SpectrumBase.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 1cac046ea8..7b3e9a18b4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -208,7 +208,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Signs whether input read has been requested /// - protected bool InputRead { get; set; } + private bool inputRead; + public bool InputRead + { + get { return inputRead; } + set { inputRead = value; } + } + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 2dc4ee6d81..28df887d46 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -211,6 +211,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("OverFlow", ref OverFlow); ser.Sync("FrameCount", ref FrameCount); ser.Sync("_frameCycles", ref _frameCycles); + ser.Sync("inputRead", ref inputRead); ser.Sync("LastFrameStartCPUTick", ref LastFrameStartCPUTick); ser.Sync("LastULAOutByte", ref LastULAOutByte); ser.Sync("ROM0", ref ROM0, false); From fbbd75b3ab7cb4d0b0e34074fe8cd30aa80aedac Mon Sep 17 00:00:00 2001 From: Asnivor Date: Wed, 7 Mar 2018 12:21:36 +0000 Subject: [PATCH 062/105] Implemented DeterministicEmulation as a syncsetting and if this is set to false, audio and video devices respect the render and renderSound IEmulator bools --- .../Hardware/SoundOuput/AY38912.cs | 9 ++-- .../Hardware/SoundOuput/Buzzer.cs | 3 ++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 52 +++++++++---------- .../SinclairSpectrum/Machine/ULABase.cs | 11 +++- .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 19 ++++++- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 5 ++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 + .../Computers/SinclairSpectrum/readme.md | 1 + 8 files changed, 69 insertions(+), 33 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs index 8dcde1e0ee..7443c43dd3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs @@ -60,6 +60,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _tStatesPerSample = 79; _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; _AYCyclesPerFrame = _tStatesPerFrame / AY_SAMPLE_RATE; + + _samples = new short[_samplesPerFrame * 2]; + _nsamp = _samplesPerFrame; } #endregion @@ -110,11 +113,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // the stereo _samples buffer should already have been processed as a part of // ISoundProvider at the end of the last frame - _samples = new short[_samplesPerFrame * 2]; - _nsamp = _samplesPerFrame; + //_samples = new short[_samplesPerFrame * 2]; + //_nsamp = _samplesPerFrame; _sampleCounter = 0; - Init(44100, _tStatesPerFrame); + //Init(44100, _tStatesPerFrame); } public void EndFrame() diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs index d751189fae..04e5b53222 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs @@ -123,6 +123,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public void ProcessPulseValue(bool fromTape, bool earPulse) { + if (!_machine._renderSound) + return; + if (!fromTape && _tapeMode) { // tape mode is active but the pulse value came from an OUT instruction diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 28df887d46..444a22a46c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -76,25 +76,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Holds the currently selected joysticks /// public virtual IJoystick[] JoystickCollection { get; set; } - - /* - /// - /// Joystick device 1 - /// - public virtual IJoystick Joystick1 { get; set; } - - /// - /// Joystick device 2 - /// - public virtual IJoystick Joystick2 { get; set; } - - /// - /// Joystick device 3 - /// - public virtual IJoystick Joystick3 { get; set; } - - */ - + /// /// Signs whether the frame has ended /// @@ -125,6 +107,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ///
public virtual int CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick; + /// + /// Non-Deterministic bools + /// + public bool _render; + public bool _renderSound; + /// /// Mask constants /// @@ -138,14 +126,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Executes a single frame /// - public virtual void ExecuteFrame() + public virtual void ExecuteFrame(bool render, bool renderSound) { InputRead = false; + _render = render; + _renderSound = renderSound; FrameCompleted = false; - BuzzerDevice.StartFrame(); - if (AYDevice != null) - AYDevice.StartFrame(); + + if (_renderSound) + { + BuzzerDevice.StartFrame(); + if (AYDevice != null) + AYDevice.StartFrame(); + } PollInput(); @@ -158,18 +152,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CPU.ExecuteOne(); // update AY - if (AYDevice != null) - AYDevice.UpdateSound(CurrentFrameCycle); + if (_renderSound) + { + if (AYDevice != null) + AYDevice.UpdateSound(CurrentFrameCycle); + } } // we have reached the end of a frame LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow; // paint the buffer if needed - if (ULADevice.needsPaint) + if (ULADevice.needsPaint && _render) ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); - BuzzerDevice.EndFrame(); + if (_renderSound) + BuzzerDevice.EndFrame(); //TapeDevice.CPUFrameCompleted(); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 1604006525..e10bdd16c0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -315,7 +315,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine.CPU.FlagI = true; // Signal the start of ULA processing - ULAUpdateStart(); + if (_machine._render) + ULAUpdateStart(); + + CalcFlashCounter(); } #endregion @@ -383,7 +386,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum screenByteCtr = DisplayStart; lastTState = actualULAStart; needsPaint = true; + } + /// + /// Flash processing + /// + public void CalcFlashCounter() + { flashCounter++; if (flashCounter > 15) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs index c318876694..33bcc68276 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -13,6 +13,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _controller = controller; + bool ren = render; + bool renSound = renderSound; + + if (DeterministicEmulation) + { + ren = true; + renSound = true; + } + _isLag = true; if (_tracer.Enabled) @@ -24,7 +33,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.TraceCallback = null; } - _machine.ExecuteFrame(); + _machine.ExecuteFrame(ren, renSound); if (_isLag) { @@ -36,7 +45,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public string SystemId => "ZXSpectrum"; - public bool DeterministicEmulation => true; + private bool deterministicEmulation; + public bool DeterministicEmulation + { + get { return deterministicEmulation; } + } + + //public bool DeterministicEmulation => true; public void ResetCounters() { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 8ad6ab5b1e..059a2fcaa1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -72,6 +72,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public class ZXSpectrumSyncSettings { + [DisplayName("Deterministic Emulation")] + [Description("If true, the core agrees to behave in a completely deterministic manner")] + [DefaultValue(true)] + public bool DeterministicEmulation { get; set; } + [DisplayName("Spectrum model")] [Description("The model of spectrum to be emulated")] [DefaultValue(MachineType.ZXSpectrum48)] diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index b8d3749514..4656ba6fa2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -50,6 +50,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType2); joysticks.Add(((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).JoystickType3); + deterministicEmulation = ((ZXSpectrumSyncSettings)syncSettings as ZXSpectrumSyncSettings).DeterministicEmulation; + switch (SyncSettings.MachineType) { case MachineType.ZXSpectrum16: diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index 621411f812..6cc05397c2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -18,6 +18,7 @@ At the moment this is very experimental and is still actively being worked on. * IStatable * ISettable core settings * IDebuggable (for what it's worth) +* DeterministicEmulation as a SyncSetting * Tape auto-loading routines (as a setting) ### Work in progress From 74423041f3269cce569707b18824d1299130de04 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Wed, 7 Mar 2018 17:40:15 +0000 Subject: [PATCH 063/105] Removed DCFilter --- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/Datacorder/DatacorderDevice.cs | 4 +-- .../Hardware/SoundOuput/Buzzer.cs | 30 +++++++++++++++++-- .../Hardware/SoundOuput/IBeeperDevice.cs | 24 +++++++++++++++ .../Machine/SpectrumBase.Port.cs | 9 ++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 +- .../SinclairSpectrum/Machine/ULABase.cs | 2 +- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 2 +- .../Machine/ZXSpectrum128K/ZX128.cs | 1 - .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 2 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 1 - .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 11 +++++++ .../Machine/ZXSpectrum48K/ZX48.cs | 4 --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 6 ++-- 15 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index b81298264b..10d5eb3aab 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -266,6 +266,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 4bdc9aca30..0096c9af86 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -18,7 +18,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private SpectrumBase _machine { get; set; } private Z80A _cpu { get; set; } - private Buzzer _buzzer { get; set; } + private IBeeperDevice _buzzer { get; set; } /// /// Default constructor @@ -369,8 +369,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public bool GetEarBit(long cpuCycle) { - - // decide how many cycles worth of data we are capturing long cycles = cpuCycle - _lastCycle; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs index 04e5b53222..0e98c67c29 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs @@ -15,7 +15,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// 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 /// - public class Buzzer : ISoundProvider + public class Buzzer : ISoundProvider, IBeeperDevice { /// /// Supplied values are right for 48K spectrum @@ -99,6 +99,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /* + // set the tstatesperframe + _tStatesPerFrame = tStatesPerFrame; + + // calculate actual refresh rate + double refresh = (double)_machine.ULADevice.ClockSpeed / (double)_tStatesPerFrame; + + // how many samples per frame are expected by ISoundProvider (at 44.1KHz) + _samplesPerFrame = 880;// (int)((double)sampleRate / (double)refresh); + + // set the sample rate + _sampleRate = sampleRate; + + // calculate samples per frame (what ISoundProvider will be expecting at 44100) + //_samplesPerFrame = (int)((double)_tStatesPerFrame / (double)refresh); + + // calculate tstates per sameple + _tStatesPerSample = 79;// _tStatesPerFrame / _samplesPerFrame; + + /* + + + + + // get divisors var divs = from a in Enumerable.Range(2, _tStatesPerFrame / 2) where _tStatesPerFrame % a == 0 @@ -109,8 +133,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // get _samplesPerFrame _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; - */ - + + */ Pulses = new List(1000); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs new file mode 100644 index 0000000000..7367e3948f --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs @@ -0,0 +1,24 @@ +using BizHawk.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public interface IBeeperDevice + { + void Init(int sampleRate, int tStatesPerFrame); + + void ProcessPulseValue(bool fromTape, bool earPulse); + + void StartFrame(); + + void EndFrame(); + + void SetTapeMode(bool tapeMode); + + void SyncState(Serializer ser); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index a508f9fa8e..d52ac067a2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -30,6 +30,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// public abstract void WritePort(ushort port, byte value); + + /// + /// Increments the CPU totalCycles counter by the tStates value specified + /// + /// + public virtual void PortContention(int tStates) + { + CPU.TotalExecutedCycles += tStates; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 444a22a46c..67f26496c0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -55,7 +55,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The spectrum buzzer/beeper /// - public Buzzer BuzzerDevice { get; set; } + public IBeeperDevice BuzzerDevice { get; set; } /// /// Device representing the AY-3-8912 chip found in the 128k and up spectrums diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index e10bdd16c0..d4e5144b35 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -445,7 +445,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } //the additional 1 tstate is required to get correct number of bytes to output in ircontention.sna - elapsedTStates = (_tstates + 1 - lastTState); + elapsedTStates = (_tstates + 1 - lastTState) - 1; //It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state. diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index f1a63b0b24..bb3f42ac31 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -170,7 +170,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } // update ULA screen buffer if necessary - if ((addr & 49152) == 16384) + if ((addr & 49152) == 16384 && _render) ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 1eb5b96a14..eb1b25aa19 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -40,7 +40,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new StandardKeyboard(this); InitJoysticks(joysticks); - //KempstonDevice = new KempstonJoystick(this); TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index d236edc82e..a7044ca032 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -310,7 +310,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } // update ULA screen buffer if necessary - if ((addr & 49152) == 16384) + if ((addr & 49152) == 16384 && _render) ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 1bc487dd78..9fb6b4db95 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -40,7 +40,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice = new StandardKeyboard(this); InitJoysticks(joysticks); - //KempstonDevice = new KempstonJoystick(this); TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 0a1efe104e..5812c898c4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -65,7 +65,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bank[index] = value; // update ULA screen buffer if necessary - if ((addr & 49152) == 16384) + if ((addr & 49152) == 16384 && _render) ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 873fa6a926..2c7db07cbd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -17,6 +17,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { InputRead = true; + // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port + // (not including added ULA contention) + // The Bizhawk Z80A implementation appears to not consume any T-States for this operation + PortContention(4); + int result = 0xFF; // Check whether the low bit is reset @@ -171,6 +176,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port + // (not including added ULA contention) + // The Bizhawk Z80A implementation appears to not consume any T-States for this operation + PortContention(4); + + // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x01) == 0; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index 3affcf59b1..aaf4ffdebd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -32,14 +32,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum InitJoysticks(joysticks); - //KempstonDevice = new KempstonJoystick(this); - TapeDevice = new DatacorderDevice(); TapeDevice.Init(this); InitializeMedia(files); - - //TapeDevice.LoadTape(file); } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 4656ba6fa2..2af8fbe390 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -94,12 +94,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Register(_cpu); ser.Register(_machine.ULADevice); - SoundMixer = new SoundProviderMixer(_machine.BuzzerDevice); + SoundMixer = new SoundProviderMixer((ISoundProvider)_machine.BuzzerDevice); if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - dcf = new DCFilter(SoundMixer, 256); - ser.Register(dcf); + //dcf = new DCFilter(SoundMixer, 256); + ser.Register(SoundMixer); From f121aedd6a26c1b958ea4767fbcf46abb59ebee0 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 16:50:56 +0000 Subject: [PATCH 064/105] Added floating bus implementation to 128k/+2 and started looking at +3 emulation --- .../Machine/SpectrumBase.Memory.cs | 1 - .../SinclairSpectrum/Machine/SpectrumBase.cs | 46 ++++- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 30 ++- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 55 ++++- .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 29 ++- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 193 ++++++++++++++---- .../SinclairSpectrum/ZXSpectrum.Util.cs | 16 ++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 17 +- 8 files changed, 325 insertions(+), 62 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 0d750bc64c..6ddbccedbc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -87,7 +87,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Sets up the ROM /// /// - /// public abstract void InitROM(RomData romData); /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 67f26496c0..9544610a3a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -12,26 +12,53 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public abstract partial class SpectrumBase { - // 128 and up only - //protected int ROMPaged = 0; - + /// + /// Index of the currently paged ROM + /// protected int ROMPaged; - - public int _ROMpaged + public virtual int _ROMpaged { get { return ROMPaged; } set { ROMPaged = value; } } - + /// + /// Signs that the shadow screen has been paged in + /// protected bool SHADOWPaged; + + /// + /// Index of the current RAM page + /// public int RAMPaged; + + /// + /// Signs that all paging is disabled + /// protected bool PagingDisabled; // +3/+2A only + + protected bool ROMhigh = false; + protected bool ROMlow = false; + + /// + /// Signs that the +2a/+3 special paging mode is activated + /// protected bool SpecialPagingMode; + + /// + /// Index of the current special paging config + /// protected int PagingConfiguration; + /// + /// Signs whether the disk motor is on or off + /// + protected bool DiskMotorState; + + protected bool PrinterPortStrobe; + /// /// The calling ZXSpectrum class (piped in via constructor) /// @@ -156,7 +183,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (AYDevice != null) AYDevice.UpdateSound(CurrentFrameCycle); - } + } + + if (SHADOWPaged) + { + + } } // we have reached the end of a frame diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index bb3f42ac31..c86a1e6ca4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -257,6 +257,33 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Memory.Add(9, RAM7); } + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + public override byte FetchScreenMemory(ushort addr) + { + byte value = new byte(); + + if (SHADOWPaged && !PagingDisabled) + { + // shadow screen should be outputted + // this lives in RAM7 + value = RAM7[addr & 0x3FFF]; + } + else + { + // shadow screen is not set to display or paging is disabled (probably in 48k mode) + // (use screen0 at RAM5) + value = RAM5[addr & 0x3FFF]; + } + + return value; + } + /// /// Sets up the ROM /// @@ -270,7 +297,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum for (int i = 0; i < 0x4000; i++) { ROM0[i] = RomData.RomBytes[i]; - ROM1[i] = RomData.RomBytes[i + 0x4000]; + if (RomData.RomBytes.Length > 0x4000) + ROM1[i] = RomData.RomBytes[i + 0x4000]; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 4b9cb123c7..434f4c7cfa 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -17,6 +18,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { InputRead = true; + // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port + // (not including added ULA contention) + // The Bizhawk Z80A implementation appears to not consume any T-States for this operation + PortContention(4); + int result = 0xFF; // Check whether the low bit is reset @@ -24,7 +30,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bool lowBitReset = (port & 0x0001) == 0; ULADevice.Contend(port); - CPU.TotalExecutedCycles++; + //CPU.TotalExecutedCycles++; // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) @@ -143,7 +149,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Kempston Mouse - // if unused port the floating memory bus should be returned (still todo) + // if unused port the floating memory bus should be returned + + // Floating bus is read on the previous cycle + int _tStates = CurrentFrameCycle - 1; + + // if we are on the top or bottom border return 0xff + if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) + { + result = 0xff; + } + else + { + if (ULADevice.floatingBusTable[_tStates] < 0) + { + result = 0xff; + } + else + { + result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]); + } + } } return (byte)result; @@ -156,38 +182,47 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // get a BitArray of the port + BitArray portBits = new BitArray(BitConverter.GetBytes(port)); + // get a BitArray of the value byte + BitArray bits = new BitArray(new byte[] { value }); + int currT = CPU.TotalExecutedCycles; // paging if (port == 0x7ffd) { + if (PagingDisabled) + return; + // Bits 0, 1, 2 select the RAM page var rp = value & 0x07; if (rp < 8) RAMPaged = rp; + // bit 3 controls shadow screen + SHADOWPaged = bits[3]; + // ROM page - if ((value & 0x10) != 0) + if (bits[4]) { - // 48k ROM + // 48k basic rom ROMPaged = 1; } else { + // 128k editor and menu system ROMPaged = 0; } - // Bit 5 signifies that paging is disabled until next reboot - if ((value & 0x20) != 0) - PagingDisabled = true; - - + // Bit 5 set signifies that paging is disabled until next reboot + PagingDisabled = bits[5]; return; } // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x01) == 0; + bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; ULADevice.Contend(port); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index a7044ca032..15a091fccd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -133,7 +133,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - result = Memory[ROMPaged][addr % 0x4000]; + result = Memory[_ROMpaged][addr % 0x4000]; break; // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) @@ -407,6 +407,33 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Memory.Add(11, RAM7); } + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + public override byte FetchScreenMemory(ushort addr) + { + byte value = new byte(); + + if (SHADOWPaged && !PagingDisabled) + { + // shadow screen should be outputted + // this lives in RAM7 + value = RAM7[addr & 0x3FFF]; + } + else + { + // shadow screen is not set to display or paging is disabled (probably in 48k mode) + // (use screen0 at RAM5) + value = RAM5[addr & 0x3FFF]; + } + + return value; + } + /// /// Sets up the ROM /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 8218ce54df..199dda6515 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -158,12 +159,145 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // get a BitArray of the port + BitArray portBits = new BitArray(BitConverter.GetBytes(port)); + // get a BitArray of the value byte + BitArray bits = new BitArray(new byte[] { value }); + // Check whether the low bit is reset - // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x01) == 0; + bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; ULADevice.Contend(port); + // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set + if (port == 0x7ffd) + { + if (!PagingDisabled) + { + // bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // bit 3 controls shadow screen + SHADOWPaged = bits[3]; + + // Bit 5 set signifies that paging is disabled until next reboot + PagingDisabled = bits[5]; + + // portbit 4 is the LOW BIT of the ROM selection + ROMlow = bits[4]; + } + } + // port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set + else if (port == 0x1ffd) + { + if (!PagingDisabled) + { + if (bits[0]) + { + // special paging is not enabled - get the ROMpage high byte + ROMhigh = bits[2]; + + // set the special paging mode flag + SpecialPagingMode = false; + } + else + { + // special paging is enabled + // this is decided based on combinations of bits 1 & 2 + // Config 0 = Bit1-0 Bit2-0 + // Config 1 = Bit1-1 Bit2-0 + // Config 2 = Bit1-0 Bit2-1 + // Config 3 = Bit1-1 Bit2-1 + BitArray confHalfNibble = new BitArray(2); + confHalfNibble[0] = bits[1]; + confHalfNibble[1] = bits[2]; + + // set special paging configuration + PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); + + // set the special paging mode flag + SpecialPagingMode = true; + } + } + + // bit 3 controls the disk motor (1=on, 0=off) + DiskMotorState = bits[3]; + + // bit 4 is the printer port strobe + PrinterPortStrobe = bits[4]; + } + /* + // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set + if (!portBits[1] && !portBits[15] && portBits[14]) + { + // paging (skip if paging has been disabled - paging can then only happen after a machine hard reset) + if (!PagingDisabled) + { + // bit 0 specifies the paging mode + SpecialPagingMode = bits[0]; + + if (!SpecialPagingMode) + { + // we are in normal mode + // portbit 4 is the LOW BIT of the ROM selection + BitArray romHalfNibble = new BitArray(2); + romHalfNibble[0] = portBits[4]; + + // value bit 2 is the high bit of the ROM selection + romHalfNibble[1] = bits[2]; + + // value bit 1 is ignored in normal paging mode + + // set the ROMPage + ROMPaged = ZXSpectrum.GetIntFromBitArray(romHalfNibble); + + + + + // bit 3 controls shadow screen + SHADOWPaged = bits[3]; + + // Bit 5 set signifies that paging is disabled until next reboot + PagingDisabled = bits[5]; + } + } + } + + // port 0x1ffd - special paging mode + // hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set + if (!portBits[1] && portBits[12] && !portBits[13] && !portBits[14] && !portBits[15]) + { + if (!PagingDisabled && SpecialPagingMode) + { + // process special paging + // this is decided based on combinations of bits 1 & 2 + // Config 0 = Bit1-0 Bit2-0 + // Config 1 = Bit1-1 Bit2-0 + // Config 2 = Bit1-0 Bit2-1 + // Config 3 = Bit1-1 Bit2-1 + BitArray confHalfNibble = new BitArray(2); + confHalfNibble[0] = bits[1]; + confHalfNibble[1] = bits[2]; + + // set special paging configuration + PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); + + // last value should be saved at 0x5b67 (23399) - not sure if this is actually needed + WriteBus(0x5b67, value); + } + + // bit 3 controls the disk motor (1=on, 0=off) + DiskMotorState = bits[3]; + + // bit 4 is the printer port strobe + PrinterPortStrobe = bits[4]; + } + + */ + + // Only even addresses address the ULA if (lowBitReset) { @@ -206,6 +340,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice.PortWrite(value); CPU.TotalExecutedCycles += 3; } + + /* + else { if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set? @@ -272,48 +409,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } + */ } } + LastULAOutByte = value; - // paging - if (port == 0x7ffd) - { - if (PagingDisabled) - return; - - LastULAOutByte = value; - - - - - - - // Bits 0, 1, 2 select the RAM page - var rp = value & 0x07; - if (rp < 8) - RAMPaged = rp; - - // ROM page - if ((value & 0x10) != 0) - { - // 48k ROM - ROMPaged = 1; - } - else - { - ROMPaged = 0; - } - - // Bit 5 signifies that paging is disabled until next reboot - if ((value & 0x20) != 0) - PagingDisabled = true; - - - return; - } + + } + + /// + /// +3 and 2a overidden method + /// + public override int _ROMpaged + { + get + { + // calculate the ROMpage from the high and low bits + return ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh })); + } + set { ROMPaged = value; } } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs index 893edbae8a..4cb9656858 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Util.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; @@ -248,5 +249,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// R3R5 = R3 | R5 } + + /// + /// Helper method that returns a single INT32 from a BitArray + /// + /// + /// + public static int GetIntFromBitArray(BitArray bitArray) + { + if (bitArray.Length > 32) + throw new ArgumentException("Argument length shall be at most 32 bits."); + + int[] array = new int[1]; + bitArray.CopyTo(array, 0); + return array[0]; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 2af8fbe390..c97a3c48d2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -77,6 +77,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum default: throw new InvalidOperationException("Machine not yet emulated"); } + + _cpu.MemoryCallbacks = MemoryCallbacks; @@ -125,13 +127,20 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //private byte[] _file; private readonly List _files; - public bool DiagRom = false; + public bool DiagRom = true; + + private List diagRoms = new List + { + @"\DiagROM.v28", + @"\zx-diagnostics\testrom.bin" + }; + private int diagIndex = 1; private byte[] GetFirmware(int length, params string[] names) { - if (DiagRom & File.Exists(Directory.GetCurrentDirectory() + @"\DiagROM.v28")) + if (DiagRom & File.Exists(Directory.GetCurrentDirectory() + diagRoms[diagIndex])) { - var rom = File.ReadAllBytes(Directory.GetCurrentDirectory() + @"\DiagROM.v28"); + var rom = File.ReadAllBytes(Directory.GetCurrentDirectory() + diagRoms[diagIndex]); return rom; } @@ -205,7 +214,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); var romDataP3 = RomData.InitROM(machineType, _systemRomP3); _machine.InitROM(romDataP3); - System.Windows.Forms.MessageBox.Show("+3 is not working at all yet :/"); + //System.Windows.Forms.MessageBox.Show("+3 is not working at all yet :/"); break; } } From cf8b632381eb915f238a90bf7b4ff09eca56a4fd Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 16:51:25 +0000 Subject: [PATCH 065/105] Disabled new test DiagRom --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index c97a3c48d2..b07dfd6b9e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -127,7 +127,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //private byte[] _file; private readonly List _files; - public bool DiagRom = true; + public bool DiagRom = false; private List diagRoms = new List { From e6d43fa5d2b540ee155ddabb4beaeb59be2bc346 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 19:33:14 +0000 Subject: [PATCH 066/105] Implemented +2a and +3 is now working (although disk drive not yet implemented so it just shows as +2a) --- .../BizHawk.Emulation.Cores.csproj | 11 +- .../SinclairSpectrum/Machine/MachineType.cs | 5 + .../SinclairSpectrum/Machine/SpectrumBase.cs | 5 - .../Machine/ZXSpectrum128K/ZX128.ULA.cs | 2 +- .../ZX128Plus2a.Memory.cs | 459 ++++++++++++++++++ .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 448 +++++++++++++++++ .../ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs | 196 ++++++++ .../ZXSpectrum128KPlus2a/ZX128Plus2a.cs | 52 ++ .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 6 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 15 +- .../ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs | 196 ++++++++ .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.ULA.cs | 8 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 19 +- .../Properties/Resources.Designer.cs | 44 +- .../Properties/Resources.resx | 18 +- .../Resources/Spectrum3_V4-0_ROM0.bin.gz | Bin 0 -> 10000 bytes .../Resources/Spectrum3_V4-0_ROM1.bin.gz | Bin 0 -> 9963 bytes .../Resources/Spectrum3_V4-0_ROM2.bin.gz | Bin 0 -> 8472 bytes .../Resources/Spectrum3_V4-0_ROM3.bin.gz | Bin 0 -> 12477 bytes .../Resources/{plus3.rom.gz => plus2a.rom.gz} | Bin 21 files changed, 1463 insertions(+), 23 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs create mode 100644 BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz create mode 100644 BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM1.bin.gz create mode 100644 BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM2.bin.gz create mode 100644 BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM3.bin.gz rename BizHawk.Emulation.Cores/Resources/{plus3.rom.gz => plus2a.rom.gz} (100%) diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 10d5eb3aab..c0fbe511fc 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -268,6 +268,11 @@ + + + + + @@ -1397,8 +1402,12 @@ - + + + + + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs index 478b821a1a..cee9f524da 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/MachineType.cs @@ -28,6 +28,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ///
ZXSpectrum128Plus2, + /// + /// Sinclair Spectrum 128 +2a model (same as the +3 just without disk drive) + /// + ZXSpectrum128Plus2a, + /// /// Sinclair Spectrum 128 +3 model /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 9544610a3a..553872b295 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -183,11 +183,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (AYDevice != null) AYDevice.UpdateSound(CurrentFrameCycle); - } - - if (SHADOWPaged) - { - } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs index 1ac1ccf326..6da4c3738f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { contentionStartPeriod = 14361; // + LateTiming; contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); - screen = _machine.Memory[1]; + screen = _machine.Memory[7]; screenByteCtr = DisplayStart; ULAByteCtr = 0; actualULAStart = 14366 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs new file mode 100644 index 0000000000..6505a5f81f --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs @@ -0,0 +1,459 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus2a : SpectrumBase + { + /* http://www.worldofspectrum.org/faq/reference/128kreference.htm + * + * Port 0x7ffd behaves in the almost exactly the same way as on the 128K/+2, with two exceptions: + + Bit 4 is now the low bit of the ROM selection. + The partial decoding used is now slightly different: the hardware will respond only to those port addresses with bit 1 reset, bit 14 set and bit 15 reset (as opposed to just bits 1 and 15 reset on the 128K/+2). + The extra paging features of the +2A/+3 are controlled by port 0x1ffd (again, partial decoding applies here: the hardware will respond to all port addresses with bit 1 reset, bit 12 set and bits 13, 14 and 15 reset). This port is also write-only, and its last value should be saved at 0x5b67 (23399). + + Port 0x1ffd responds as follows: + + Bit 0: Paging mode. 0=normal, 1=special + Bit 1: In normal mode, ignored. + Bit 2: In normal mode, high bit of ROM selection. The four ROMs are: + ROM 0: 128k editor, menu system and self-test program + ROM 1: 128k syntax checker + ROM 2: +3DOS + ROM 3: 48 BASIC + Bit 3: Disk motor; 1=on, 0=off + Bit 4: Printer port strobe. + When special mode is selected, the memory map changes to one of four configurations specified in bits 1 and 2 of port 0x1ffd: + Bit 2 =0 Bit 2 =0 Bit 2 =1 Bit 2 =1 + Bit 1 =0 Bit 1 =1 Bit 1 =0 Bit 1 =1 + 0xffff +--------+ +--------+ +--------+ +--------+ + | Bank 3 | | Bank 7 | | Bank 3 | | Bank 3 | + | | | | | | | | + | | | | | | | | + | | | screen | | | | | + 0xc000 +--------+ +--------+ +--------+ +--------+ + | Bank 2 | | Bank 6 | | Bank 6 | | Bank 6 | + | | | | | | | | + | | | | | | | | + | | | | | | | | + 0x8000 +--------+ +--------+ +--------+ +--------+ + | Bank 1 | | Bank 5 | | Bank 5 | | Bank 7 | + | | | | | | | | + | | | | | | | | + | | | screen | | screen | | screen | + 0x4000 +--------+ +--------+ +--------+ +--------+ + | Bank 0 | | Bank 4 | | Bank 4 | | Bank 4 | + | | | | | | | | + | | | | | | | | + | | | | | | | | + 0x0000 +--------+ +--------+ +--------+ +--------+ + RAM banks 1,3,4 and 6 are used for the disc cache and RAMdisc, while Bank 7 contains editor scratchpads and +3DOS workspace. + */ + + /// + /// Simulates reading from the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override byte ReadBus(ushort addr) + { + int divisor = addr / 0x4000; + byte result = 0xff; + + // special paging + if (SpecialPagingMode) + { + switch (divisor) + { + case 0: + switch (PagingConfiguration) + { + case 0: + result = Memory[4][addr % 0x4000]; + break; + case 1: + case 2: + case 3: + result = Memory[8][addr % 0x4000]; + break; + } + break; + case 1: + switch (PagingConfiguration) + { + case 0: + result = Memory[5][addr % 0x4000]; + break; + case 1: + case 2: + result = Memory[9][addr % 0x4000]; + break; + case 3: + result = Memory[11][addr % 0x4000]; + break; + } + break; + case 2: + switch (PagingConfiguration) + { + case 0: + result = Memory[6][addr % 0x4000]; + break; + case 1: + case 2: + case 3: + result = Memory[10][addr % 0x4000]; + break; + } + break; + case 3: + switch (PagingConfiguration) + { + case 0: + case 2: + case 3: + result = Memory[7][addr % 0x4000]; + break; + case 1: + result = Memory[11][addr % 0x4000]; + break; + } + break; + } + } + else + { + switch (divisor) + { + // ROM 0x000 + case 0: + result = Memory[_ROMpaged][addr % 0x4000]; + break; + + // RAM 0x4000 (RAM5 - Bank5 always) + case 1: + result = Memory[9][addr % 0x4000]; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + result = Memory[6][addr % 0x4000]; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + result = Memory[4][addr % 0x4000]; + break; + case 1: + result = Memory[5][addr % 0x4000]; + break; + case 2: + result = Memory[6][addr % 0x4000]; + break; + case 3: + result = Memory[7][addr % 0x4000]; + break; + case 4: + result = Memory[8][addr % 0x4000]; + break; + case 5: + result = Memory[9][addr % 0x4000]; + break; + case 6: + result = Memory[10][addr % 0x4000]; + break; + case 7: + result = Memory[11][addr % 0x4000]; + break; + } + break; + default: + break; + } + } + + return result; + } + + /// + /// Simulates writing to the bus (no contention) + /// Paging should be handled here + /// + /// + /// + public override void WriteBus(ushort addr, byte value) + { + int divisor = addr / 0x4000; + + // special paging + if (SpecialPagingMode) + { + switch (divisor) + { + case 0: + switch (PagingConfiguration) + { + case 0: + Memory[4][addr % 0x4000] = value; + break; + case 1: + case 2: + case 3: + Memory[8][addr % 0x4000] = value; + break; + } + break; + case 1: + switch (PagingConfiguration) + { + case 0: + Memory[5][addr % 0x4000] = value; + break; + case 1: + case 2: + Memory[9][addr % 0x4000] = value; + break; + case 3: + Memory[11][addr % 0x4000] = value; + break; + } + break; + case 2: + switch (PagingConfiguration) + { + case 0: + Memory[6][addr % 0x4000] = value; + break; + case 1: + case 2: + case 3: + Memory[10][addr % 0x4000] = value; + break; + } + break; + case 3: + switch (PagingConfiguration) + { + case 0: + case 2: + case 3: + Memory[7][addr % 0x4000] = value; + break; + case 1: + Memory[11][addr % 0x4000] = value; + break; + } + break; + } + } + else + { + switch (divisor) + { + // ROM 0x000 + case 0: + Memory[_ROMpaged][addr % 0x4000] = value; + break; + + // RAM 0x4000 (RAM5 - Bank5 only) + case 1: + Memory[9][addr % 0x4000] = value; + break; + + // RAM 0x8000 (RAM2 - Bank2) + case 2: + Memory[6][addr % 0x4000] = value; + break; + + // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) + case 3: + switch (RAMPaged) + { + case 0: + Memory[4][addr % 0x4000] = value; + break; + case 1: + Memory[5][addr % 0x4000] = value; + break; + case 2: + Memory[6][addr % 0x4000] = value; + break; + case 3: + Memory[7][addr % 0x4000] = value; + break; + case 4: + Memory[8][addr % 0x4000] = value; + break; + case 5: + Memory[9][addr % 0x4000] = value; + break; + case 6: + Memory[10][addr % 0x4000] = value; + break; + case 7: + Memory[11][addr % 0x4000] = value; + break; + } + break; + default: + break; + } + } + + // update ULA screen buffer if necessary + if ((addr & 49152) == 16384 && _render) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + } + + /// + /// Reads a byte of data from a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override byte ReadMemory(ushort addr) + { + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + var data = ReadBus(addr); + return data; + } + + /// + /// Writes a byte of data to a specified memory address + /// (with memory contention if appropriate) + /// + /// + /// + public override void WriteMemory(ushort addr, byte value) + { + // apply contention if necessary + if (ULADevice.IsContended(addr)) + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + + WriteBus(addr, value); + } + + public override void ReInitMemory() + { + if (Memory.ContainsKey(0)) + Memory[0] = ROM0; + else + Memory.Add(0, ROM0); + + if (Memory.ContainsKey(1)) + Memory[1] = ROM1; + else + Memory.Add(1, ROM1); + + if (Memory.ContainsKey(2)) + Memory[2] = ROM2; + else + Memory.Add(2, ROM2); + + if (Memory.ContainsKey(3)) + Memory[3] = ROM3; + else + Memory.Add(3, ROM3); + + if (Memory.ContainsKey(4)) + Memory[4] = RAM0; + else + Memory.Add(4, RAM0); + + if (Memory.ContainsKey(5)) + Memory[5] = RAM1; + else + Memory.Add(5, RAM1); + + if (Memory.ContainsKey(6)) + Memory[6] = RAM2; + else + Memory.Add(6, RAM2); + + if (Memory.ContainsKey(7)) + Memory[7] = RAM3; + else + Memory.Add(7, RAM3); + + if (Memory.ContainsKey(8)) + Memory[8] = RAM4; + else + Memory.Add(8, RAM4); + + if (Memory.ContainsKey(9)) + Memory[9] = RAM5; + else + Memory.Add(9, RAM5); + + if (Memory.ContainsKey(10)) + Memory[10] = RAM6; + else + Memory.Add(10, RAM6); + + if (Memory.ContainsKey(11)) + Memory[11] = RAM7; + else + Memory.Add(11, RAM7); + } + + /// + /// ULA reads the memory at the specified address + /// (No memory contention) + /// Will read RAM5 (screen0) by default, unless RAM7 (screen1) is selected as output + /// + /// + /// + public override byte FetchScreenMemory(ushort addr) + { + byte value = new byte(); + + if (SHADOWPaged && !PagingDisabled) + { + // shadow screen should be outputted + // this lives in RAM7 + value = RAM7[addr & 0x3FFF]; + } + else + { + // shadow screen is not set to display or paging is disabled (probably in 48k mode) + // (use screen0 at RAM5) + value = RAM5[addr & 0x3FFF]; + } + + return value; + } + + /// + /// Sets up the ROM + /// + /// + /// + public override void InitROM(RomData romData) + { + RomData = romData; + // +3 uses ROM0, ROM1, ROM2 & ROM3 + /* ROM 0: 128k editor, menu system and self-test program + ROM 1: 128k syntax checker + ROM 2: +3DOS + ROM 3: 48 BASIC + */ + Stream stream = new MemoryStream(RomData.RomBytes); + stream.Read(ROM0, 0, 16384); + stream.Read(ROM1, 0, 16384); + stream.Read(ROM2, 0, 16384); + stream.Read(ROM3, 0, 16384); + stream.Dispose(); + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs new file mode 100644 index 0000000000..07e22b4203 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + public partial class ZX128Plus2a : SpectrumBase + { + /// + /// Reads a byte of data from a specified port address + /// + /// + /// + public override byte ReadPort(ushort port) + { + InputRead = true; + + int result = 0xFF; + + // Check whether the low bit is reset + // Technically the ULA should respond to every even I/O address + bool lowBitReset = (port & 0x0001) == 0; + + ULADevice.Contend(port); + + // Kempston Joystick + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; + } + else 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 + */ + + if ((port & 0x8000) == 0) + { + result &= KeyboardDevice.KeyLine[7]; + } + + if ((port & 0x4000) == 0) + { + result &= KeyboardDevice.KeyLine[6]; + } + + if ((port & 0x2000) == 0) + { + result &= KeyboardDevice.KeyLine[5]; + } + + if ((port & 0x1000) == 0) + { + result &= KeyboardDevice.KeyLine[4]; + } + + if ((port & 0x800) == 0) + { + result &= KeyboardDevice.KeyLine[3]; + } + + if ((port & 0x400) == 0) + { + result &= KeyboardDevice.KeyLine[2]; + } + + if ((port & 0x200) == 0) + { + result &= KeyboardDevice.KeyLine[1]; + } + + if ((port & 0x100) == 0) + { + result &= KeyboardDevice.KeyLine[0]; + } + + result = result & 0x1f; //mask out lower 4 bits + result = result | 0xa0; //set bit 5 & 7 to 1 + + + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) + { + if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else if ((LastULAOutByte & 0x10) == 0) + { + result &= ~(0x40); + } + else + { + result |= 0x40; + } + + } + else + { + // devices other than the ULA will respond here + // (e.g. the AY sound chip in a 128k spectrum + + // AY register activate - on +3/2a both FFFD and BFFD active AY + if ((port & 0xc002) == 0xc000) + { + result = (int)AYDevice.PortRead(); + } + else if ((port & 0xc002) == 0x8000) + { + result = (int)AYDevice.PortRead(); + } + + // Kempston Mouse + + /* + else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + //result = udpDrive.DiskStatusRead(); + + // disk drive is not yet implemented - return a max status byte for the menu to load + result = 255; + } + else if ((port & 0xF002) == 0x3000) + { + //result = udpDrive.DiskReadByte(); + result = 0; + } + + else if ((port & 0xF002) == 0x0) + { + if (PagingDisabled) + result = 0x1; + else + result = 0xff; + } + */ + + // if unused port the floating memory bus should be returned (still todo) + } + + return (byte)result; + } + + /// + /// Writes a byte of data to a specified port address + /// + /// + /// + public override void WritePort(ushort port, byte value) + { + // get a BitArray of the port + BitArray portBits = new BitArray(BitConverter.GetBytes(port)); + // get a BitArray of the value byte + BitArray bits = new BitArray(new byte[] { value }); + + // Check whether the low bit is reset + bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; + + ULADevice.Contend(port); + + // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set + if (port == 0x7ffd) + { + if (!PagingDisabled) + { + // bits 0, 1, 2 select the RAM page + var rp = value & 0x07; + if (rp < 8) + RAMPaged = rp; + + // bit 3 controls shadow screen + SHADOWPaged = bits[3]; + + // Bit 5 set signifies that paging is disabled until next reboot + PagingDisabled = bits[5]; + + // portbit 4 is the LOW BIT of the ROM selection + ROMlow = bits[4]; + } + } + // port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set + else if (port == 0x1ffd) + { + if (!PagingDisabled) + { + if (!bits[0]) + { + // special paging is not enabled - get the ROMpage high byte + ROMhigh = bits[2]; + + // set the special paging mode flag + SpecialPagingMode = false; + } + else + { + // special paging is enabled + // this is decided based on combinations of bits 1 & 2 + // Config 0 = Bit1-0 Bit2-0 + // Config 1 = Bit1-1 Bit2-0 + // Config 2 = Bit1-0 Bit2-1 + // Config 3 = Bit1-1 Bit2-1 + BitArray confHalfNibble = new BitArray(2); + confHalfNibble[0] = bits[1]; + confHalfNibble[1] = bits[2]; + + // set special paging configuration + PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); + + // set the special paging mode flag + SpecialPagingMode = true; + } + } + + // bit 3 controls the disk motor (1=on, 0=off) + DiskMotorState = bits[3]; + + // bit 4 is the printer port strobe + PrinterPortStrobe = bits[4]; + } + /* + // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set + if (!portBits[1] && !portBits[15] && portBits[14]) + { + // paging (skip if paging has been disabled - paging can then only happen after a machine hard reset) + if (!PagingDisabled) + { + // bit 0 specifies the paging mode + SpecialPagingMode = bits[0]; + + if (!SpecialPagingMode) + { + // we are in normal mode + // portbit 4 is the LOW BIT of the ROM selection + BitArray romHalfNibble = new BitArray(2); + romHalfNibble[0] = portBits[4]; + + // value bit 2 is the high bit of the ROM selection + romHalfNibble[1] = bits[2]; + + // value bit 1 is ignored in normal paging mode + + // set the ROMPage + ROMPaged = ZXSpectrum.GetIntFromBitArray(romHalfNibble); + + + + + // bit 3 controls shadow screen + SHADOWPaged = bits[3]; + + // Bit 5 set signifies that paging is disabled until next reboot + PagingDisabled = bits[5]; + } + } + } + + // port 0x1ffd - special paging mode + // hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set + if (!portBits[1] && portBits[12] && !portBits[13] && !portBits[14] && !portBits[15]) + { + if (!PagingDisabled && SpecialPagingMode) + { + // process special paging + // this is decided based on combinations of bits 1 & 2 + // Config 0 = Bit1-0 Bit2-0 + // Config 1 = Bit1-1 Bit2-0 + // Config 2 = Bit1-0 Bit2-1 + // Config 3 = Bit1-1 Bit2-1 + BitArray confHalfNibble = new BitArray(2); + confHalfNibble[0] = bits[1]; + confHalfNibble[1] = bits[2]; + + // set special paging configuration + PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); + + // last value should be saved at 0x5b67 (23399) - not sure if this is actually needed + WriteBus(0x5b67, value); + } + + // bit 3 controls the disk motor (1=on, 0=off) + DiskMotorState = bits[3]; + + // bit 4 is the printer port strobe + PrinterPortStrobe = bits[4]; + } + + */ + + + // 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 + if (ULADevice.borderColour != (value & BORDER_BIT)) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + + ULADevice.borderColour = value & BORDER_BIT; + + // Buzzer + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + + // Tape + //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + } + + else + { + // AY Register activation + if ((port & 0xc002) == 0xc000) + { + var reg = value & 0x0f; + AYDevice.SelectedRegister = reg; + CPU.TotalExecutedCycles += 3; + } + else + { + if ((port & 0xC002) == 0x8000) + { + AYDevice.PortWrite(value); + CPU.TotalExecutedCycles += 3; + } + + /* + + else + { + if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set? + { + // memory paging activate + if (PagingDisabled) + return; + + // bit 5 handles paging disable (48k mode, persistent until next reboot) + if ((value & 0x20) != 0) + { + PagingDisabled = true; + } + + // shadow screen + if ((value & 0x08) != 0) + { + SHADOWPaged = true; + } + else + { + SHADOWPaged = false; + } + } + else + { + //Extra Memory Paging feature activate + if ((port & 0xF002) == 0x1000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + if (PagingDisabled) + return; + + // set disk motor state + //todo + + if ((value & 0x08) != 0) + { + //diskDriveState |= (1 << 4); + } + else + { + //diskDriveState &= ~(1 << 4); + } + + if ((value & 0x1) != 0) + { + // activate special paging mode + SpecialPagingMode = true; + PagingConfiguration = (value & 0x6 >> 1); + } + else + { + // normal paging mode + SpecialPagingMode = false; + } + } + else + { + // disk write port + if ((port & 0xF002) == 0x3000) //Is bit 12 set and bits 13,14,15 and 1 reset? + { + //udpDrive.DiskWriteByte((byte)(val & 0xff)); + } + } + } + } + */ + } + } + + LastULAOutByte = value; + + + + + } + + /// + /// +3 and 2a overidden method + /// + public override int _ROMpaged + { + get + { + // calculate the ROMpage from the high and low bits + var rp = ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh })); + + if (rp != 0) + { + + } + + return rp; + } + set { ROMPaged = value; } + } + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs new file mode 100644 index 0000000000..35547ab931 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs @@ -0,0 +1,196 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + class ULAPlus2a : ULABase + { + #region Construction + + public ULAPlus2a(SpectrumBase machine) + : base(machine) + { + InterruptPeriod = 36; + LongestOperationCycles = 64 + 2; + FrameLength = 70908; + ClockSpeed = 3546900; + + contentionTable = new byte[70930]; + floatingBusTable = new short[70930]; + for (int f = 0; f < 70930; f++) + floatingBusTable[f] = -1; + + CharRows = 24; + CharCols = 32; + ScreenWidth = 256; + ScreenHeight = 192; + BorderTopHeight = 48; + BorderBottomHeight = 56; + BorderLeftWidth = 48; + BorderRightWidth = 48; + DisplayStart = 16384; + DisplayLength = 6144; + AttributeStart = 22528; + AttributeLength = 768; + borderColour = 7; + ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth; + + TstatesPerScanline = 228; + TstateAtTop = BorderTopHeight * TstatesPerScanline; + TstateAtBottom = BorderBottomHeight * TstatesPerScanline; + tstateToDisp = new short[FrameLength]; + + ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border + + ScanLineWidth * ScreenHeight //border + main + border of 192 lines + + ScanLineWidth * BorderBottomHeight]; //56 lines of border + + attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped + + SetupScreenSize(); + + Reset(); + } + + #endregion + + #region Misc Operations + + public override void Reset() + { + contentionStartPeriod = 14361; // + LateTiming; + contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); + screen = _machine.Memory[9]; + screenByteCtr = DisplayStart; + ULAByteCtr = 0; + actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; + lastTState = actualULAStart; + BuildAttributeMap(); + BuildContentionTable(); + } + + #endregion + + #region Contention Methods + + public override bool IsContended(int addr) + { + addr = addr & 0xc000; + + if (addr == 0x4000) + { + // low port contention + return true; + } + + if (addr == 0xc000) + { + // high port contention - check for contended bank paged in + switch (_machine.RAMPaged) + { + case 4: + case 5: + case 6: + case 7: + return true; + } + } + + return false; + } + + public override void BuildContentionTable() + { + int t = contentionStartPeriod; + while (t < contentionEndPeriod) + { + contentionTable[t++] = 1; + contentionTable[t++] = 0; + + //for 128 t-states + for (int i = 0; i < 128; i += 8) + { + contentionTable[t++] = 7; + contentionTable[t++] = 6; + contentionTable[t++] = 5; + contentionTable[t++] = 4; + contentionTable[t++] = 3; + contentionTable[t++] = 2; + contentionTable[t++] = 1; + contentionTable[t++] = 0; + } + t += (TstatesPerScanline - 128) - 2; + } + + //build top half of tstateToDisp table + //vertical retrace period + for (t = 0; t < actualULAStart; t++) + tstateToDisp[t] = 0; + + //next 48 are actual border + while (t < actualULAStart + (TstateAtTop)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + + //build middle half + int _x = 0; + int _y = 0; + int scrval = 2; + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline)) + { + for (int g = 0; g < 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24; g < 24 + 128; g++) + { + //Map screenaddr to tstate + if (g % 4 == 0) + { + scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2)); + _x += 8; + } + tstateToDisp[t++] = (short)scrval; + } + + _y++; + + for (int g = 24 + 128; g < 24 + 128 + 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++) + tstateToDisp[t++] = 0; + } + + int h = contentionStartPeriod + 3; + while (h < contentionEndPeriod + 3) + { + for (int j = 0; j < 128; j += 8) + { + floatingBusTable[h] = tstateToDisp[h + 2]; + floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; + floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; + floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; + h += 8; + } + h += TstatesPerScanline - 128; + } + + //build bottom half + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + } + + + #endregion + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs new file mode 100644 index 0000000000..5cdfb1bbbf --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs @@ -0,0 +1,52 @@ +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 partial class ZX128Plus2a : SpectrumBase + { + #region Construction + + /// + /// Main constructor + /// + /// + /// + public ZX128Plus2a(ZXSpectrum spectrum, Z80A cpu, ZXSpectrum.BorderType borderType, List files, List joysticks) + { + Spectrum = spectrum; + CPU = cpu; + + ROMPaged = 0; + SHADOWPaged = false; + RAMPaged = 0; + PagingDisabled = false; + + // init addressable memory from ROM and RAM banks + ReInitMemory(); + + ULADevice = new ULAPlus2a(this); + + BuzzerDevice = new Buzzer(this); + BuzzerDevice.Init(44100, ULADevice.FrameLength); + + AYDevice = new AY38912(); + AYDevice.Init(44100, ULADevice.FrameLength); + + KeyboardDevice = new StandardKeyboard(this); + + InitJoysticks(joysticks); + + TapeDevice = new DatacorderDevice(); + TapeDevice.Init(this); + + InitializeMedia(files); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index 15a091fccd..188581fd72 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -136,7 +136,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = Memory[_ROMpaged][addr % 0x4000]; break; - // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + // RAM 0x4000 (RAM5 - Bank5 always) case 1: result = Memory[9][addr % 0x4000]; break; @@ -261,10 +261,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - Memory[ROMPaged][addr % 0x4000] = value; + Memory[_ROMpaged][addr % 0x4000] = value; break; - // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + // RAM 0x4000 (RAM5 - Bank5 only) case 1: Memory[9][addr % 0x4000] = value; break; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 199dda6515..ea53156118 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -132,10 +132,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset? { //result = udpDrive.DiskStatusRead(); + + // disk drive is not yet implemented - return a max status byte for the menu to load + result = 255; } else if ((port & 0xF002) == 0x3000) { //result = udpDrive.DiskReadByte(); + result = 0; } else if ((port & 0xF002) == 0x0) @@ -194,7 +198,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (!PagingDisabled) { - if (bits[0]) + if (!bits[0]) { // special paging is not enabled - get the ROMpage high byte ROMhigh = bits[2]; @@ -428,7 +432,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum get { // calculate the ROMpage from the high and low bits - return ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh })); + var rp = ZXSpectrum.GetIntFromBitArray(new BitArray(new bool[] { ROMlow, ROMhigh })); + + if (rp != 0) + { + + } + + return rp; } set { ROMPaged = value; } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs new file mode 100644 index 0000000000..e6d82474dc --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs @@ -0,0 +1,196 @@ + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + class ULAPlus3 : ULABase + { + #region Construction + + public ULAPlus3(SpectrumBase machine) + : base(machine) + { + InterruptPeriod = 36; + LongestOperationCycles = 64 + 2; + FrameLength = 70908; + ClockSpeed = 3546900; + + contentionTable = new byte[70930]; + floatingBusTable = new short[70930]; + for (int f = 0; f < 70930; f++) + floatingBusTable[f] = -1; + + CharRows = 24; + CharCols = 32; + ScreenWidth = 256; + ScreenHeight = 192; + BorderTopHeight = 48; + BorderBottomHeight = 56; + BorderLeftWidth = 48; + BorderRightWidth = 48; + DisplayStart = 16384; + DisplayLength = 6144; + AttributeStart = 22528; + AttributeLength = 768; + borderColour = 7; + ScanLineWidth = BorderLeftWidth + ScreenWidth + BorderRightWidth; + + TstatesPerScanline = 228; + TstateAtTop = BorderTopHeight * TstatesPerScanline; + TstateAtBottom = BorderBottomHeight * TstatesPerScanline; + tstateToDisp = new short[FrameLength]; + + ScreenBuffer = new int[ScanLineWidth * BorderTopHeight //48 lines of border + + ScanLineWidth * ScreenHeight //border + main + border of 192 lines + + ScanLineWidth * BorderBottomHeight]; //56 lines of border + + attr = new short[DisplayLength]; //6144 bytes of display memory will be mapped + + SetupScreenSize(); + + Reset(); + } + + #endregion + + #region Misc Operations + + public override void Reset() + { + contentionStartPeriod = 14361; // + LateTiming; + contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); + screen = _machine.Memory[9]; + screenByteCtr = DisplayStart; + ULAByteCtr = 0; + actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; + lastTState = actualULAStart; + BuildAttributeMap(); + BuildContentionTable(); + } + + #endregion + + #region Contention Methods + + public override bool IsContended(int addr) + { + addr = addr & 0xc000; + + if (addr == 0x4000) + { + // low port contention + return true; + } + + if (addr == 0xc000) + { + // high port contention - check for contended bank paged in + switch (_machine.RAMPaged) + { + case 4: + case 5: + case 6: + case 7: + return true; + } + } + + return false; + } + + public override void BuildContentionTable() + { + int t = contentionStartPeriod; + while (t < contentionEndPeriod) + { + contentionTable[t++] = 1; + contentionTable[t++] = 0; + + //for 128 t-states + for (int i = 0; i < 128; i += 8) + { + contentionTable[t++] = 7; + contentionTable[t++] = 6; + contentionTable[t++] = 5; + contentionTable[t++] = 4; + contentionTable[t++] = 3; + contentionTable[t++] = 2; + contentionTable[t++] = 1; + contentionTable[t++] = 0; + } + t += (TstatesPerScanline - 128) - 2; + } + + //build top half of tstateToDisp table + //vertical retrace period + for (t = 0; t < actualULAStart; t++) + tstateToDisp[t] = 0; + + //next 48 are actual border + while (t < actualULAStart + (TstateAtTop)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + + //build middle half + int _x = 0; + int _y = 0; + int scrval = 2; + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline)) + { + for (int g = 0; g < 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24; g < 24 + 128; g++) + { + //Map screenaddr to tstate + if (g % 4 == 0) + { + scrval = (((((_y & 0xc0) >> 3) | (_y & 0x07) | (0x40)) << 8)) | (((_x >> 3) & 0x1f) | ((_y & 0x38) << 2)); + _x += 8; + } + tstateToDisp[t++] = (short)scrval; + } + + _y++; + + for (int g = 24 + 128; g < 24 + 128 + 24; g++) + tstateToDisp[t++] = 1; + + for (int g = 24 + 128 + 24; g < 24 + 128 + 24 + 52; g++) + tstateToDisp[t++] = 0; + } + + int h = contentionStartPeriod + 3; + while (h < contentionEndPeriod + 3) + { + for (int j = 0; j < 128; j += 8) + { + floatingBusTable[h] = tstateToDisp[h + 2]; + floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; + floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; + floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; + h += 8; + } + h += TstatesPerScanline - 128; + } + + //build bottom half + while (t < actualULAStart + (TstateAtTop) + (ScreenHeight * TstatesPerScanline) + (TstateAtBottom)) + { + for (int g = 0; g < 176; g++) + tstateToDisp[t++] = 1; + + for (int g = 176; g < TstatesPerScanline; g++) + tstateToDisp[t++] = 0; + } + } + + + #endregion + + + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 9fb6b4db95..4be628ba75 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -29,7 +29,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // init addressable memory from ROM and RAM banks ReInitMemory(); - ULADevice = new ULA128(this); + ULADevice = new ULAPlus3(this); BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs index 61c22499ea..4c70608d7a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -151,10 +151,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { for (int j = 0; j < 128; j += 8) { - floatingBusTable[h] = tstateToDisp[h + 2]; //screen address - floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; //attr address - floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; //screen address + 1 - floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; //attr address + 1 + floatingBusTable[h] = tstateToDisp[h + 2]; //screen address + floatingBusTable[h + 1] = attr[(tstateToDisp[h + 2] - 16384)]; //attr address + floatingBusTable[h + 2] = tstateToDisp[h + 2 + 4]; //screen address + 1 + floatingBusTable[h + 3] = attr[(tstateToDisp[h + 2 + 4] - 16384)]; //attr address + 1 h += 8; } h += TstatesPerScanline - 128; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index b07dfd6b9e..6381049957 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -70,6 +70,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum128Plus2, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); break; + case MachineType.ZXSpectrum128Plus2a: + ControllerDefinition = ZXSpectrumControllerDefinition; + Init(MachineType.ZXSpectrum128Plus2a, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); + break; case MachineType.ZXSpectrum128Plus3: ControllerDefinition = ZXSpectrumControllerDefinition; Init(MachineType.ZXSpectrum128Plus3, SyncSettings.BorderType, SyncSettings.TapeLoadSpeed, _files, joysticks); @@ -158,8 +162,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case "PLUS2ROM": embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus2_rom)); break; + case "PLUS2AROM": + embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus2a_rom)); + break; case "PLUS3ROM": - embeddedRom = Util.DecompressGzipFile(new MemoryStream(Resources.ZX_plus3_rom)); + byte[] r0 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM0_bin)); + byte[] r1 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM1_bin)); + byte[] r2 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM2_bin)); + byte[] r3 = Util.DecompressGzipFile(new MemoryStream(Resources.Spectrum3_V4_0_ROM3_bin)); + embeddedRom = r0.Concat(r1).Concat(r2).Concat(r3).ToArray(); break; default: embeddedFound = false; @@ -209,6 +220,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum var romDataP2 = RomData.InitROM(machineType, _systemRomP2); _machine.InitROM(romDataP2); break; + case MachineType.ZXSpectrum128Plus2a: + _machine = new ZX128Plus2a(this, _cpu, borderType, files, joys); + var _systemRomP4 = GetFirmware(0x10000, "PLUS2AROM"); + var romDataP4 = RomData.InitROM(machineType, _systemRomP4); + _machine.InitROM(romDataP4); + break; case MachineType.ZXSpectrum128Plus3: _machine = new ZX128Plus3(this, _cpu, borderType, files, joys); var _systemRomP3 = GetFirmware(0x10000, "PLUS3ROM"); diff --git a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs index 312ea05d92..73259a9985 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs +++ b/BizHawk.Emulation.Cores/Properties/Resources.Designer.cs @@ -90,6 +90,46 @@ namespace BizHawk.Emulation.Cores.Properties { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM0_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM0_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM1_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM1_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM2_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM2_bin", resourceCulture); + return ((byte[])(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Byte[]. + /// + internal static byte[] Spectrum3_V4_0_ROM3_bin { + get { + object obj = ResourceManager.GetObject("Spectrum3_V4_0_ROM3_bin", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// @@ -123,9 +163,9 @@ namespace BizHawk.Emulation.Cores.Properties { /// /// Looks up a localized resource of type System.Byte[]. /// - internal static byte[] ZX_plus3_rom { + internal static byte[] ZX_plus2a_rom { get { - object obj = ResourceManager.GetObject("ZX_plus3_rom", resourceCulture); + object obj = ResourceManager.GetObject("ZX_plus2a_rom", resourceCulture); return ((byte[])(obj)); } } diff --git a/BizHawk.Emulation.Cores/Properties/Resources.resx b/BizHawk.Emulation.Cores/Properties/Resources.resx index 4699ab819d..b5e12deeae 100644 --- a/BizHawk.Emulation.Cores/Properties/Resources.resx +++ b/BizHawk.Emulation.Cores/Properties/Resources.resx @@ -127,16 +127,28 @@ ..\Resources\sgb-cart-present.spc.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\resources\spectrum3_v4-0_rom0.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom1.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom2.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\spectrum3_v4-0_rom3.bin.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\128.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\48.ROM.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\resources\plus2a.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\plus2.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\plus3.rom.gz;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz b/BizHawk.Emulation.Cores/Resources/Spectrum3_V4-0_ROM0.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..d70d805d620b4bdfd61e83385bbf8a019a6bd034 GIT binary patch literal 10000 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 z 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 H06+i$iGfWn literal 0 HcmV?d00001 diff --git a/BizHawk.Emulation.Cores/Resources/plus3.rom.gz b/BizHawk.Emulation.Cores/Resources/plus2a.rom.gz similarity index 100% rename from BizHawk.Emulation.Cores/Resources/plus3.rom.gz rename to BizHawk.Emulation.Cores/Resources/plus2a.rom.gz From c7fe4c2887dd87ca3117a9dc85b4a1749986989d Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 21:25:19 +0000 Subject: [PATCH 067/105] Datacorder - implement basic manual tape block navigation (i.e. NextBlock, PrevBlock) --- .../Hardware/Datacorder/DatacorderDevice.cs | 60 +++++++++++++++++++ .../Machine/SpectrumBase.Input.cs | 28 +++++++++ .../SinclairSpectrum/ZXSpectrum.Messaging.cs | 22 +++++++ 3 files changed, 110 insertions(+) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 0096c9af86..0227fee185 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -315,6 +315,66 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _currentDataBlockIndex = 0; } + ///

+ /// Performs a block skip operation on the current tape + /// TRUE: skip forward + /// FALSE: skip backward + /// + /// + public void SkipBlock(bool skipForward) + { + int blockCount = _dataBlocks.Count; + int targetBlockId = _currentDataBlockIndex; + + if (skipForward) + { + if (_currentDataBlockIndex == blockCount - 1) + { + // last block, go back to beginning + targetBlockId = 0; + } + else + { + targetBlockId++; + } + } + else + { + if (_currentDataBlockIndex == 0) + { + // already first block, goto last block + targetBlockId = blockCount - 1; + } + else + { + targetBlockId--; + } + } + + var bl = _dataBlocks[targetBlockId]; + + StringBuilder sbd = new StringBuilder(); + sbd.Append("("); + sbd.Append((targetBlockId + 1) + " of " + _dataBlocks.Count()); + sbd.Append(") : "); + //sbd.Append("ID" + bl.BlockID.ToString("X2") + " - "); + sbd.Append(bl.BlockDescription); + if (bl.MetaData.Count > 0) + { + sbd.Append(" - "); + sbd.Append(bl.MetaData.First().Key + ": " + bl.MetaData.First().Value); + //sbd.Append("\n"); + //sbd.Append(bl.MetaData.Skip(1).First().Key + ": " + bl.MetaData.Skip(1).First().Value); + } + + if (skipForward) + _machine.Spectrum.OSD_TapeNextBlock(sbd.ToString()); + else + _machine.Spectrum.OSD_TapePrevBlock(sbd.ToString()); + + CurrentDataBlockIndex = targetBlockId; + } + /// /// Inserts a new tape and sets up the tape device accordingly /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index 7b3e9a18b4..eb2bcd63a8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -15,12 +15,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string Record = "Record Tape"; string NextTape = "Insert Next Tape"; string PrevTape = "Insert Previous Tape"; + string NextBlock = "Next Tape Block"; + string PrevBlock = "Prev Tape Block"; bool pressed_Play = false; bool pressed_Stop = false; bool pressed_RTZ = false; bool pressed_NextTape = false; bool pressed_PrevTape = false; + bool pressed_NextBlock = false; + bool pressed_PrevBlock = false; public void PollInput() { @@ -147,6 +151,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else pressed_PrevTape = false; + + if (Spectrum._controller.IsPressed(NextBlock)) + { + if (!pressed_NextBlock) + { + Spectrum.OSD_FireInputMessage(NextBlock); + TapeDevice.SkipBlock(true); + pressed_NextBlock = true; + } + } + else + pressed_NextBlock = false; + + if (Spectrum._controller.IsPressed(PrevBlock)) + { + if (!pressed_PrevBlock) + { + Spectrum.OSD_FireInputMessage(PrevBlock); + TapeDevice.SkipBlock(false); + pressed_PrevBlock = true; + } + } + else + pressed_PrevBlock = false; } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs index 5b7b29b3d6..9a583013e8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs @@ -178,6 +178,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); } + /// + /// Tape message that is fired when user has manually skipped to the next block + /// + public void OSD_TapeNextBlock(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("Manual Skip Next " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + + /// + /// Tape message that is fired when user has manually skipped to the next block + /// + public void OSD_TapePrevBlock(string blockinfo) + { + StringBuilder sb = new StringBuilder(); + sb.Append("Manual Skip Prev " + blockinfo); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + } + #endregion /// From 31328dac2b755b76cfd52432e5e6072263202d5a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 22:10:16 +0000 Subject: [PATCH 068/105] Readme progress update! --- .../Computers/SinclairSpectrum/readme.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index 6cc05397c2..e5f12cfe06 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -1,15 +1,15 @@ ## ZXHawk -At the moment this is very experimental and is still actively being worked on. +At the moment this is experimental and is still being worked on. ### Implemented and sorta working * IEmulator -* ZX Spectrum 48k, 128k & Plus2 models +* ZX Spectrum 48k, 128k, +2 & +2A models * ULA video output (implementing IVideoProvider) * ULA Mode 1 VBLANK interrupt generation * IM2 Interrupts and DataBus implementation (thanks Aloysha) * Beeper/Buzzer output (implementing ISoundProvider) -* AY-3-8912 sound chip implementation +* AY-3-8912 sound chip implementation (stereo or mono options available as a setting) * Keyboard input (implementing IInputPollable) * Default keyboard keymappings * Kempston, Cursor and Sinclair (Left & Right) joysticks emulated @@ -18,19 +18,23 @@ At the moment this is very experimental and is still actively being worked on. * IStatable * ISettable core settings * IDebuggable (for what it's worth) -* DeterministicEmulation as a SyncSetting +* DeterministicEmulation as a SyncSetting, LagFrame detection and FrameAdvance render & renderSound bools respected (when DeterministicEmulation == false) * Tape auto-loading routines (as a setting) +* Basic tape block navigation (NextBlock, PrevBlock) +* Tape-related OSD messages (verbosity level configured in settings) ### Work in progress +* ZX Spectrum +3 emulation (partially working, see below) * Exact emulator timings * Floating memory bus emulation * TASStudio (need to verify that this works as it should) ### Not working -* ZX Spectrum Plus3 emulation - -### Known bugs -* Audible 'popping' from the emulated buzzer after a load state operation (maybe this is a normal thing) +* +3 disk drive - no implementation yet +* Hard & Soft Reset menu options in the client (they are greyed out for some reason) * Speedlock tape protection scheme doesn't appear to load correctly +### Help needed +* I'm not a TASer, i've never TASed before. It would be really useful if someone (anyone) can build this branch and test this core from a TAS-workflow / TAStudio perpective. There may still be some work to do an exact timings and memory contention, but otherwise this core is able to play the majority of speccy games out there. + -Asnivor From 8708e987f7e73c9f1620887fedd60d8d2aeabda9 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 9 Mar 2018 17:52:04 +0000 Subject: [PATCH 069/105] Improved tape auto-loading functions --- .../Hardware/Datacorder/DatacorderDevice.cs | 150 ++++++++++++++---- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 1 + .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 1 + .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 2 + .../Machine/ZXSpectrum48K/ZX48.Port.cs | 1 + 5 files changed, 121 insertions(+), 34 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 0227fee185..be338e7fb2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -120,12 +120,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Signs whether the device should autodetect when the Z80 has entered into /// 'load' mode and auto-play the tape if neccesary /// - public bool AutoPlay { get; set; } + private bool _autoPlay; + public bool AutoPlay + { + get { return _machine.Spectrum.Settings.AutoLoadTape; } + set { _autoPlay = value; MonitorReset(); } + } + #endregion #region Emulator - + /// /// This is the address the that ROM will jump to when the spectrum has quit tape playing /// @@ -138,7 +144,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented) /// public void EndFrame() - { + {/* if (TapeIsPlaying) { @@ -172,6 +178,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } */ + + MonitorFrame(); } /// @@ -302,7 +310,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // update the lastCycle _lastCycle = _cpu.TotalExecutedCycles; - } /// @@ -419,9 +426,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion #region Tape Device Methods - - private bool initialBlockPlayed = false; - + /// /// Simulates the spectrum 'EAR' input reading data from the tape /// @@ -582,6 +587,105 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + #region TapeMonitor + + private long _lastINCycle = 0; + private int _monitorCount; + private int _monitorTimeOut; + private ushort _monitorLastPC; + private ushort[] _monitorLastRegs = new ushort[7]; + + /// + /// Resets the TapeMonitor + /// + private void MonitorReset() + { + _lastINCycle = 0; + _monitorCount = 0; + _monitorLastPC = 0; + _monitorLastRegs = null; + } + + /// + /// An iteration of the monitor process + /// + public void MonitorRead() + { + long cpuCycle = _cpu.TotalExecutedCycles; + int delta = (int)(cpuCycle - _lastINCycle); + _lastINCycle = cpuCycle; + + var nRegs = new ushort[] + { + _cpu.Regs[_cpu.A], + _cpu.Regs[_cpu.B], + _cpu.Regs[_cpu.C], + _cpu.Regs[_cpu.D], + _cpu.Regs[_cpu.E], + _cpu.Regs[_cpu.H], + _cpu.Regs[_cpu.L] + }; + + if (delta > 0 && + delta < 96 && + _cpu.RegPC == _monitorLastPC && + _monitorLastRegs != null) + { + int dCnt = 0; + int dVal = 0; + + for (int i = 0; i < nRegs.Length; i++) + { + if (_monitorLastRegs[i] != nRegs[i]) + { + dVal = _monitorLastRegs[i] - nRegs[i]; + dCnt++; + } + } + + if (dCnt == 1 && + (dVal == 1 || dVal == -1)) + { + _monitorCount++; + + if (_monitorCount >= 8 && _machine.Spectrum.Settings.AutoLoadTape) + { + if (!_tapeIsPlaying) + { + Play(); + _machine.Spectrum.OSD_TapePlayingAuto(); + } + + _monitorTimeOut = 50; + } + } + else + { + _monitorCount = 0; + } + } + + _monitorLastRegs = nRegs; + _monitorLastPC = _cpu.RegPC; + } + + + private void MonitorFrame() + { + if (_tapeIsPlaying && _machine.Spectrum.Settings.AutoLoadTape) + { + _monitorTimeOut--; + + if (_monitorTimeOut < 0) + { + Stop(); + _machine.Spectrum.OSD_TapeStoppedAuto(); + } + } + } + + #endregion + #region State Serialization private int _tempBlockCount; @@ -593,39 +697,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void SyncState(Serializer ser) { ser.BeginSection("DatacorderDevice"); - ser.Sync("_currentDataBlockIndex", ref _currentDataBlockIndex); ser.Sync("_position", ref _position); ser.Sync("_tapeIsPlaying", ref _tapeIsPlaying); ser.Sync("_lastCycle", ref _lastCycle); ser.Sync("_waitEdge", ref _waitEdge); - //ser.Sync("_initialBlockPlayed", ref initialBlockPlayed); ser.Sync("currentState", ref currentState); - - //_dataBlocks - /* - ser.BeginSection("Datablocks"); - - if (ser.IsWriter) - { - _tempBlockCount = _dataBlocks.Count(); - ser.Sync("_tempBlockCount", ref _tempBlockCount); - - for (int i = 0; i < _tempBlockCount; i++) - { - _dataBlocks[i].SyncState(ser, i); - } - } - else - { - ser.Sync("_tempBlockCount", ref _tempBlockCount); - } - - - - ser.EndSection(); - */ - + ser.Sync("_lastINCycle", ref _lastINCycle); + ser.Sync("_monitorCount", ref _monitorCount); + ser.Sync("_monitorTimeOut", ref _monitorTimeOut); + ser.Sync("_monitorLastPC", ref _monitorLastPC); + ser.Sync("_monitorLastRegs", ref _monitorLastRegs, false); ser.EndSection(); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 434f4c7cfa..5ef01f9504 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -96,6 +96,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 + TapeDevice.MonitorRead(); if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index 07e22b4203..c29876cc3c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -89,6 +89,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 + TapeDevice.MonitorRead(); if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index ea53156118..eb8ff18e36 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -90,6 +90,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result | 0xa0; //set bit 5 & 7 to 1 + TapeDevice.MonitorRead(); + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 2c7db07cbd..5dc5c2a514 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -95,6 +95,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 + TapeDevice.MonitorRead(); if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { From ae8b030e57968f6fdfad9920113e459eb32bd3ba Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 10:19:42 +0000 Subject: [PATCH 070/105] Started new port contention methods and increased the auto-tape monitor timeout (to eliminate false-positive stops) --- .../Hardware/Datacorder/DatacorderDevice.cs | 75 ++---------------- .../Machine/SpectrumBase.Port.cs | 76 +++++++++++++++++++ .../Machine/ZXSpectrum48K/ZX48.Port.cs | 76 +++++++++---------- .../Media/Tape/TzxSerializer.cs | 1 + 4 files changed, 121 insertions(+), 107 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index be338e7fb2..f0bba8ffff 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -144,74 +144,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented) /// public void EndFrame() - {/* - if (TapeIsPlaying) - { - - // check whether we need to auto-stop the tape - if (IsMachineAtErrorAddress()) - { - _machine.Spectrum.OSD_TapeStoppedAuto(); - Stop(); - } - - } - else - { - // the tape is not playing - check to see if we need to autostart the tape - if (IsMachineInLoadMode()) - { - _machine.Spectrum.OSD_TapePlayingAuto(); - Play(); - //sw.Start(); - } - } - /* - if (TapeIsPlaying && sw.IsRunning) - { - if (!IsMachineInLoadMode() && sw.ElapsedMilliseconds == 2000) - { - sw.Stop(); - sw.Reset(); - _machine.Spectrum.OSD_TapeStoppedAuto(); - Stop(); - } - } - */ - + { MonitorFrame(); } - /// - /// Checks whether the machine is in a state that is waiting to load tape content - /// - /// - public bool IsMachineInLoadMode() - { - if (!_machine.Spectrum.Settings.AutoLoadTape) - return false; - - if (_cpu.RegPC == 1523) - return true; - - return false; - } - - /// - /// Checks whether the machine has reached the error rom address (and the tape needs to be stopped) - /// - /// - private bool IsMachineAtErrorAddress() - { - //if (!_machine.Spectrum.Settings.AutoLoadTape) - //return false; - - if (_cpu.RegPC == 64464) // 40620) // ERROR_ROM_ADDRESS) - return true; - else - return false; - } - #endregion #region Tape Controls @@ -656,7 +592,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine.Spectrum.OSD_TapePlayingAuto(); } - _monitorTimeOut = 50; + _monitorTimeOut = 500; } } else @@ -676,6 +612,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _monitorTimeOut--; + if (_monitorTimeOut < 2) + { + + } + if (_monitorTimeOut < 0) { Stop(); @@ -688,8 +629,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region State Serialization - private int _tempBlockCount; - /// /// Bizhawk state serialization /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index d52ac067a2..12b3e34f5a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -39,6 +39,82 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { CPU.TotalExecutedCycles += tStates; } + + /// + /// Simulates IO port contention based on the supplied address + /// This method is for 48k machines and should be overridden for other models + /// + /// + public virtual void ContendPortAddress(ushort addr) + { + /* + It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port. As is the case with memory access, + this can be lengthened by the ULA. There are two effects which occur here: + + If the port address being accessed has its low bit reset, the ULA is required to supply the result, which leads to a delay if it is + currently busy handling the screen. + The address of the port being accessed is placed on the data bus. If this is in the range 0x4000 to 0x7fff, the ULA treats this as an + attempted access to contended memory and therefore introduces a delay. If the port being accessed is between 0xc000 and 0xffff, + this effect does not apply, even on a 128K machine if a contended memory bank is paged into the range 0xc000 to 0xffff. + + These two effects combine to lead to the following contention patterns: + + High byte | | + in 40 - 7F? | Low bit | Contention pattern + ------------+---------+------------------- + No | Reset | N:1, C:3 + No | Set | N:4 + Yes | Reset | C:1, C:3 + Yes | Set | C:1, C:1, C:1, C:1 + + The 'Contention pattern' column should be interpreted from left to right. An "N:n" entry means that no delay is applied at this cycle, and the Z80 continues uninterrupted for 'n' T states. A "C:n" entry means that the ULA halts the Z80; the delay is exactly the same as would occur for a contended memory access at this cycle (eg 6 T states at cycle 14335, 5 at 14336, etc on the 48K machine). After this delay, the Z80 then continues for 'n' cycles. + */ + + // is the low bit reset (i.e. is this addressing the ULA)? + bool lowBit = (addr & 0x0001) != 0; + + if ((addr & 0xc000) == 0x4000 || (addr & 0xc000) == 0xC000) + { + // high byte is in 40 - 7F + if (lowBit) + { + // lowbit is set + // C:1, C:1, C:1, C:1 + for (int i = 0; i < 4; i++) + { + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + CPU.TotalExecutedCycles++; + } + } + else + { + // low bit is reset + // C:1, C:3 + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + CPU.TotalExecutedCycles++; + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + CPU.TotalExecutedCycles += 3; + } + } + else + { + // high byte is NOT in 40 - 7F + if (lowBit) + { + // lowbit is set + // C:1, C:1, C:1, C:1 + CPU.TotalExecutedCycles += 4; + } + else + { + // lowbit is reset + // N:1, C:3 + CPU.TotalExecutedCycles++; + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + CPU.TotalExecutedCycles += 3; + } + } + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 5dc5c2a514..7e0c4edd2e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -17,10 +17,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { InputRead = true; - // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port - // (not including added ULA contention) - // The Bizhawk Z80A implementation appears to not consume any T-States for this operation - PortContention(4); + // process IO contention + ContendPortAddress(port); int result = 0xFF; @@ -28,8 +26,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ULADevice.Contend(port); - // Kempston Joystick if ((port & 0xe0) == 0 || (port & 0x20) == 0) { @@ -90,12 +86,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if ((port & 0x100) == 0) { result &= KeyboardDevice.KeyLine[0]; - } + } + + TapeDevice.MonitorRead(); + + var earBit = TapeDevice.GetEarBit(CPU.TotalExecutedCycles); + + if (!earBit) + { + result &= 0xbf; + } + + /* result = result & 0x1f; //mask out lower 4 bits result = result | 0xa0; //set bit 5 & 7 to 1 - - TapeDevice.MonitorRead(); + if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) { @@ -133,7 +139,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } - + */ } else { @@ -177,45 +183,37 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { - // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port - // (not including added ULA contention) - // The Bizhawk Z80A implementation appears to not consume any T-States for this operation - PortContention(4); - + // process IO contention + ContendPortAddress(port); // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x01) == 0; + if ((port & 0x0001) != 0) + return; - ULADevice.Contend(port); + // store the last OUT byte + LastULAOutByte = value; - // Only even addresses address the ULA - if (lowBitReset) - { - // store the last OUT byte - LastULAOutByte = value; - CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + /* + Bit 7 6 5 4 3 2 1 0 + +-------------------------------+ + | | | | E | M | Border | + +-------------------------------+ + */ - /* - Bit 7 6 5 4 3 2 1 0 - +-------------------------------+ - | | | | E | M | Border | - +-------------------------------+ - */ + // Border - LSB 3 bits hold the border colour + if (ULADevice.borderColour != (value & BORDER_BIT)) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); - // Border - LSB 3 bits hold the border colour - if (ULADevice.borderColour != (value & BORDER_BIT)) - ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + ULADevice.borderColour = value & BORDER_BIT; - ULADevice.borderColour = value & BORDER_BIT; + // Buzzer + BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); - // Buzzer - BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); - - // Tape - //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); + // Tape + //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); - } } + } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs index 76c1b1fd97..9d4188e351 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs @@ -268,6 +268,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum t.DataPeriods = new List(); int pauseLen = GetWordValue(data, _position); + int blockLen = GetWordValue(data, _position + 2); _position += 4; From 0bd433210e8a9079291f1e42012cee8a4cfe2b6b Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 10:20:56 +0000 Subject: [PATCH 071/105] Fixed tzx tape standard data block PauseAfter value. Now speedlock7 encoded games should work --- .../Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs index 9d4188e351..28a761cd3d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Media/Tape/TzxSerializer.cs @@ -268,7 +268,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum t.DataPeriods = new List(); int pauseLen = GetWordValue(data, _position); - + if (pauseLen == 0) + pauseLen = 1000; int blockLen = GetWordValue(data, _position + 2); _position += 4; @@ -276,7 +277,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum byte[] tmp = new byte[blockLen]; tmp = data.Skip(_position).Take(blockLen).ToArray(); - var t2 = DecodeDataBlock(t, tmp, DataBlockType.Standard, 1000); + var t2 = DecodeDataBlock(t, tmp, DataBlockType.Standard, pauseLen); // add the block _datacorder.DataBlocks.Add(t2); From ccb5947adeb96ca7d288bf1f7322aa706293a8f0 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 12:00:17 +0000 Subject: [PATCH 072/105] Finished port IO contention rewrites --- .../BizHawk.Emulation.Cores.csproj | 7 +- .../IBeeperDevice.cs | 0 .../{Input => Abstraction}/IJoystick.cs | 0 .../{Input => Abstraction}/IKeyboard.cs | 2 +- .../Hardware/Abstraction/IPortIODevice.cs | 30 +++ .../Hardware/Datacorder/DatacorderDevice.cs | 73 ++++- .../Hardware/Input/StandardKeyboard.cs | 90 +++++++ .../Machine/SpectrumBase.Port.cs | 8 +- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 122 ++------- .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 98 ++----- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 251 ++---------------- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 128 ++------- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 10 +- .../Computers/SinclairSpectrum/readme.md | 7 +- 14 files changed, 299 insertions(+), 527 deletions(-) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{SoundOuput => Abstraction}/IBeeperDevice.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{Input => Abstraction}/IJoystick.cs (100%) rename BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/{Input => Abstraction}/IKeyboard.cs (98%) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index c0fbe511fc..272aae7483 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -256,7 +256,8 @@ - + + @@ -264,9 +265,9 @@ - + - + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IBeeperDevice.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/IBeeperDevice.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IBeeperDevice.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IJoystick.cs similarity index 100% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IJoystick.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IJoystick.cs diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs similarity index 98% rename from BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IKeyboard.cs rename to BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs index 83b8782226..77282f1dfa 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/IKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IKeyboard.cs @@ -7,7 +7,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Represents a spectrum keyboard /// - public interface IKeyboard + public interface IKeyboard : IPortIODevice { /// /// The calling spectrumbase class diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs new file mode 100644 index 0000000000..2fbd89a0db --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPortIODevice.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a device that utilizes port IN & OUT + /// + public interface IPortIODevice + { + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + bool ReadPort(ushort port, ref int result); + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + bool WritePort(ushort port, int result); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index f0bba8ffff..29a746b880 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -12,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Represents the tape device (or build-in datacorder as it was called +2 and above) /// - public class DatacorderDevice + public class DatacorderDevice : IPortIODevice { #region Construction @@ -627,6 +627,77 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + #region IPortIODevice + + /// + /// Mask constants + /// + private const int TAPE_BIT = 0x40; + private const int EAR_BIT = 0x10; + private const int MIC_BIT = 0x08; + + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + public bool ReadPort(ushort port, ref int result) + { + if (TapeIsPlaying) + { + if (GetEarBit(_cpu.TotalExecutedCycles)) + { + result &= ~(TAPE_BIT); // reset is EAR ON + } + else + { + result |= (TAPE_BIT); // set is EAR Off + } + } + else + { + if (_machine.KeyboardDevice.IsIssue2Keyboard) + { + if ((_machine.LASTULAOutByte & (EAR_BIT + MIC_BIT)) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= (TAPE_BIT); + } + } + else + { + if ((_machine.LASTULAOutByte & EAR_BIT) == 0) + { + result &= ~(TAPE_BIT); + } + else + { + result |= TAPE_BIT; + } + } + } + + return true; + } + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + public bool WritePort(ushort port, int result) + { + // not implemented yet + return false; + } + + #endregion + #region State Serialization /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs index 2df804efec..f4289edf92 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs @@ -312,6 +312,96 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return (byte)index; } + + #region IPortIODevice + + /// + /// Device responds to an IN instruction + /// + /// + /// + /// + public bool ReadPort(ushort port, ref int result) + { + /* + The high byte indicates which half-row of keys is being polled + A zero on one of these lines selects a particular half-row of five keys: + + 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 + + A zero in one of the five lowest bits means that the corresponding key is pressed. If more than one address line + is made low, the result is the logical AND of all single inputs, so a zero in a bit means that at least one of the + appropriate keys is pressed. For example, only if each of the five lowest bits of the result from reading from Port 00FE + (for instance by XOR A/IN A,(FE)) is one, no key is pressed + */ + + if ((port & 0x8000) == 0) + { + result &= KeyLine[7]; + } + + if ((port & 0x4000) == 0) + { + result &= KeyLine[6]; + } + + if ((port & 0x2000) == 0) + { + result &= KeyLine[5]; + } + + if ((port & 0x1000) == 0) + { + result &= KeyLine[4]; + } + + if ((port & 0x800) == 0) + { + result &= KeyLine[3]; + } + + if ((port & 0x400) == 0) + { + result &= KeyLine[2]; + } + + if ((port & 0x200) == 0) + { + result &= KeyLine[1]; + } + + if ((port & 0x100) == 0) + { + result &= KeyLine[0]; + } + + // mask out lower 4 bits + result = result & 0x1f; + + // set bit 5 & 7 to 1 + result = result | 0xa0; + + return true; + } + + /// + /// Device responds to an OUT instruction + /// + /// + /// + /// + public bool WritePort(ushort port, int result) + { + // not implemented + return false; + } + + #endregion + public void SyncState(Serializer ser) { ser.BeginSection("Keyboard"); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index 12b3e34f5a..874345ba9d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -16,6 +16,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// The last OUT data that was sent to the ULA /// protected byte LastULAOutByte; + public byte LASTULAOutByte + { + get { return LastULAOutByte; } + set { LastULAOutByte = value; } + } + /// /// Reads a byte of data from a specified port address @@ -42,7 +48,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Simulates IO port contention based on the supplied address - /// This method is for 48k machines and should be overridden for other models + /// This method is for 48k and 128k/+2 machines only and should be overridden for other models /// /// public virtual void ContendPortAddress(ushort addr) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 5ef01f9504..0fb70d9bca 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -16,12 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { - InputRead = true; - - // It takes four T states for the Z80 to read a value from an I/O port, or write a value to a port - // (not including added ULA contention) - // The Bizhawk Z80A implementation appears to not consume any T-States for this operation - PortContention(4); + // process IO contention + ContendPortAddress(port); int result = 0xFF; @@ -29,10 +25,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ULADevice.Contend(port); - //CPU.TotalExecutedCycles++; - - // Kempston Joystick + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte if ((port & 0xe0) == 0 || (port & 0x20) == 0) { if (LocateUniqueJoystick(JoystickType.Kempston) != null) @@ -42,104 +36,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else 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 - */ + // Even I/O address so get input from keyboard + KeyboardDevice.ReadPort(port, ref result); - if ((port & 0x8000) == 0) - { - result &= KeyboardDevice.KeyLine[7]; - } - - if ((port & 0x4000) == 0) - { - result &= KeyboardDevice.KeyLine[6]; - } - - if ((port & 0x2000) == 0) - { - result &= KeyboardDevice.KeyLine[5]; - } - - if ((port & 0x1000) == 0) - { - result &= KeyboardDevice.KeyLine[4]; - } - - if ((port & 0x800) == 0) - { - result &= KeyboardDevice.KeyLine[3]; - } - - if ((port & 0x400) == 0) - { - result &= KeyboardDevice.KeyLine[2]; - } - - if ((port & 0x200) == 0) - { - result &= KeyboardDevice.KeyLine[1]; - } - - if ((port & 0x100) == 0) - { - result &= KeyboardDevice.KeyLine[0]; - } - - - result = result & 0x1f; //mask out lower 4 bits - result = result | 0xa0; //set bit 5 & 7 to 1 + // not a lagframe + InputRead = true; + // tape loading monitor cycle TapeDevice.MonitorRead(); - if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) - { - if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) - { - result &= ~(TAPE_BIT); // reset is EAR ON - } - else - { - result |= (TAPE_BIT); // set is EAR Off - } - } - else - { - if (KeyboardDevice.IsIssue2Keyboard) - { - if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - else - { - if ((LastULAOutByte & EAR_BIT) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - } - + // process tape INs + TapeDevice.ReadPort(port, ref result); } else { // devices other than the ULA will respond here - // (e.g. the AY sound chip in a 128k spectrum + // (e.g. the AY sound chip in a 128k spectrum) // AY register activate if ((port & 0xc002) == 0xc000) @@ -147,11 +59,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = (int)AYDevice.PortRead(); } - // Kempston Mouse + // Kempston Mouse (not implemented yet) - // if unused port the floating memory bus should be returned - + // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle int _tStates = CurrentFrameCycle - 1; @@ -183,6 +94,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // process IO contention + ContendPortAddress(port); + // get a BitArray of the port BitArray portBits = new BitArray(BitConverter.GetBytes(port)); // get a BitArray of the value byte @@ -225,8 +139,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; - ULADevice.Contend(port); - // Only even addresses address the ULA if (lowBitReset) { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index c29876cc3c..4f0a9ee47b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -16,7 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { - InputRead = true; + // process IO contention + ContendPortAddress(port); int result = 0xFF; @@ -24,9 +25,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ULADevice.Contend(port); - - // Kempston Joystick + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte if ((port & 0xe0) == 0 || (port & 0x20) == 0) { if (LocateUniqueJoystick(JoystickType.Kempston) != null) @@ -36,81 +36,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else 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 - */ - - if ((port & 0x8000) == 0) - { - result &= KeyboardDevice.KeyLine[7]; - } - - if ((port & 0x4000) == 0) - { - result &= KeyboardDevice.KeyLine[6]; - } - - if ((port & 0x2000) == 0) - { - result &= KeyboardDevice.KeyLine[5]; - } - - if ((port & 0x1000) == 0) - { - result &= KeyboardDevice.KeyLine[4]; - } - - if ((port & 0x800) == 0) - { - result &= KeyboardDevice.KeyLine[3]; - } - - if ((port & 0x400) == 0) - { - result &= KeyboardDevice.KeyLine[2]; - } - - if ((port & 0x200) == 0) - { - result &= KeyboardDevice.KeyLine[1]; - } - - if ((port & 0x100) == 0) - { - result &= KeyboardDevice.KeyLine[0]; - } - - result = result & 0x1f; //mask out lower 4 bits - result = result | 0xa0; //set bit 5 & 7 to 1 + // Even I/O address so get input from keyboard + KeyboardDevice.ReadPort(port, ref result); TapeDevice.MonitorRead(); - if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) - { - if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) - { - result &= ~(TAPE_BIT); // reset is EAR ON - } - else - { - result |= (TAPE_BIT); // set is EAR Off - } - } - else if ((LastULAOutByte & 0x10) == 0) - { - result &= ~(0x40); - } - else - { - result |= 0x40; - } + // not a lagframe + InputRead = true; + // tape loading monitor cycle + TapeDevice.MonitorRead(); + + // process tape INs + TapeDevice.ReadPort(port, ref result); } else { @@ -445,5 +383,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } set { ROMPaged = value; } } + + /// + /// Override port contention + /// +3/2a does not have the same ULA IO contention + /// + /// + public override void ContendPortAddress(ushort addr) + { + CPU.TotalExecutedCycles += 4; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index eb8ff18e36..32bd26d842 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -16,7 +16,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { - InputRead = true; + // process IO contention + ContendPortAddress(port); int result = 0xFF; @@ -24,9 +25,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - ULADevice.Contend(port); - - // Kempston Joystick + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte if ((port & 0xe0) == 0 || (port & 0x20) == 0) { if (LocateUniqueJoystick(JoystickType.Kempston) != null) @@ -36,82 +36,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else 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 - */ - - if ((port & 0x8000) == 0) - { - result &= KeyboardDevice.KeyLine[7]; - } - - if ((port & 0x4000) == 0) - { - result &= KeyboardDevice.KeyLine[6]; - } - - if ((port & 0x2000) == 0) - { - result &= KeyboardDevice.KeyLine[5]; - } - - if ((port & 0x1000) == 0) - { - result &= KeyboardDevice.KeyLine[4]; - } - - if ((port & 0x800) == 0) - { - result &= KeyboardDevice.KeyLine[3]; - } - - if ((port & 0x400) == 0) - { - result &= KeyboardDevice.KeyLine[2]; - } - - if ((port & 0x200) == 0) - { - result &= KeyboardDevice.KeyLine[1]; - } - - if ((port & 0x100) == 0) - { - result &= KeyboardDevice.KeyLine[0]; - } - - result = result & 0x1f; //mask out lower 4 bits - result = result | 0xa0; //set bit 5 & 7 to 1 - + // Even I/O address so get input from keyboard + KeyboardDevice.ReadPort(port, ref result); TapeDevice.MonitorRead(); - if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) - { - if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) - { - result &= ~(TAPE_BIT); // reset is EAR ON - } - else - { - result |= (TAPE_BIT); // set is EAR Off - } - } - else if ((LastULAOutByte & 0x10) == 0) - { - result &= ~(0x40); - } - else - { - result |= 0x40; - } + // not a lagframe + InputRead = true; + // tape loading monitor cycle + TapeDevice.MonitorRead(); + + // process tape INs + TapeDevice.ReadPort(port, ref result); } else { @@ -128,7 +65,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum result = (int)AYDevice.PortRead(); } - // Kempston Mouse + // Kempston Mouse (not implemented yet) else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset? @@ -165,6 +102,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // process IO contention + ContendPortAddress(port); + // get a BitArray of the port BitArray portBits = new BitArray(BitConverter.GetBytes(port)); // get a BitArray of the value byte @@ -173,8 +113,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Check whether the low bit is reset bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; - ULADevice.Contend(port); - // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set if (port == 0x7ffd) { @@ -234,76 +172,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // bit 4 is the printer port strobe PrinterPortStrobe = bits[4]; } - /* - // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set - if (!portBits[1] && !portBits[15] && portBits[14]) - { - // paging (skip if paging has been disabled - paging can then only happen after a machine hard reset) - if (!PagingDisabled) - { - // bit 0 specifies the paging mode - SpecialPagingMode = bits[0]; - - if (!SpecialPagingMode) - { - // we are in normal mode - // portbit 4 is the LOW BIT of the ROM selection - BitArray romHalfNibble = new BitArray(2); - romHalfNibble[0] = portBits[4]; - - // value bit 2 is the high bit of the ROM selection - romHalfNibble[1] = bits[2]; - - // value bit 1 is ignored in normal paging mode - - // set the ROMPage - ROMPaged = ZXSpectrum.GetIntFromBitArray(romHalfNibble); - - - - - // bit 3 controls shadow screen - SHADOWPaged = bits[3]; - - // Bit 5 set signifies that paging is disabled until next reboot - PagingDisabled = bits[5]; - } - } - } - - // port 0x1ffd - special paging mode - // hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set - if (!portBits[1] && portBits[12] && !portBits[13] && !portBits[14] && !portBits[15]) - { - if (!PagingDisabled && SpecialPagingMode) - { - // process special paging - // this is decided based on combinations of bits 1 & 2 - // Config 0 = Bit1-0 Bit2-0 - // Config 1 = Bit1-1 Bit2-0 - // Config 2 = Bit1-0 Bit2-1 - // Config 3 = Bit1-1 Bit2-1 - BitArray confHalfNibble = new BitArray(2); - confHalfNibble[0] = bits[1]; - confHalfNibble[1] = bits[2]; - - // set special paging configuration - PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); - - // last value should be saved at 0x5b67 (23399) - not sure if this is actually needed - WriteBus(0x5b67, value); - } - - // bit 3 controls the disk motor (1=on, 0=off) - DiskMotorState = bits[3]; - - // bit 4 is the printer port strobe - PrinterPortStrobe = bits[4]; - } - - */ - - + // Only even addresses address the ULA if (lowBitReset) { @@ -346,84 +215,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum AYDevice.PortWrite(value); CPU.TotalExecutedCycles += 3; } - - /* - - else - { - if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set? - { - // memory paging activate - if (PagingDisabled) - return; - - // bit 5 handles paging disable (48k mode, persistent until next reboot) - if ((value & 0x20) != 0) - { - PagingDisabled = true; - } - - // shadow screen - if ((value & 0x08) != 0) - { - SHADOWPaged = true; - } - else - { - SHADOWPaged = false; - } - } - else - { - //Extra Memory Paging feature activate - if ((port & 0xF002) == 0x1000) //Is bit 12 set and bits 13,14,15 and 1 reset? - { - if (PagingDisabled) - return; - - // set disk motor state - //todo - - if ((value & 0x08) != 0) - { - //diskDriveState |= (1 << 4); - } - else - { - //diskDriveState &= ~(1 << 4); - } - - if ((value & 0x1) != 0) - { - // activate special paging mode - SpecialPagingMode = true; - PagingConfiguration = (value & 0x6 >> 1); - } - else - { - // normal paging mode - SpecialPagingMode = false; - } - } - else - { - // disk write port - if ((port & 0xF002) == 0x3000) //Is bit 12 set and bits 13,14,15 and 1 reset? - { - //udpDrive.DiskWriteByte((byte)(val & 0xff)); - } - } - } - } - */ } } LastULAOutByte = value; - - - - } /// @@ -445,5 +240,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } set { ROMPaged = value; } } + + /// + /// Override port contention + /// +3/2a does not have the same ULA IO contention + /// + /// + public override void ContendPortAddress(ushort addr) + { + CPU.TotalExecutedCycles += 4; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 7e0c4edd2e..0cc99cf239 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -15,8 +15,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { - InputRead = true; - // process IO contention ContendPortAddress(port); @@ -26,132 +24,41 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; - // Kempston Joystick + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte if ((port & 0xe0) == 0 || (port & 0x20) == 0) { if (LocateUniqueJoystick(JoystickType.Kempston) != null) return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + // not a lag frame InputRead = true; } else if (lowBitReset) { - CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; + // Even I/O address so get input from keyboard + KeyboardDevice.ReadPort(port, ref result); - // 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 - */ + // not a lagframe + InputRead = true; - if ((port & 0x8000) == 0) - { - result &= KeyboardDevice.KeyLine[7]; - } + // tape loading monitor cycle + TapeDevice.MonitorRead(); - if ((port & 0x4000) == 0) - { - result &= KeyboardDevice.KeyLine[6]; - } - - if ((port & 0x2000) == 0) - { - result &= KeyboardDevice.KeyLine[5]; - } - - if ((port & 0x1000) == 0) - { - result &= KeyboardDevice.KeyLine[4]; - } - - if ((port & 0x800) == 0) - { - result &= KeyboardDevice.KeyLine[3]; - } - - if ((port & 0x400) == 0) - { - result &= KeyboardDevice.KeyLine[2]; - } - - if ((port & 0x200) == 0) - { - result &= KeyboardDevice.KeyLine[1]; - } - - if ((port & 0x100) == 0) - { - result &= KeyboardDevice.KeyLine[0]; - } - - TapeDevice.MonitorRead(); - - var earBit = TapeDevice.GetEarBit(CPU.TotalExecutedCycles); - - if (!earBit) - { - result &= 0xbf; - } - - /* - - result = result & 0x1f; //mask out lower 4 bits - result = result | 0xa0; //set bit 5 & 7 to 1 - - - if (TapeDevice.TapeIsPlaying)//.CurrentMode == TapeOperationMode.Load) - { - if (!TapeDevice.GetEarBit(CPU.TotalExecutedCycles)) - { - result &= ~(TAPE_BIT); // reset is EAR ON - } - else - { - result |= (TAPE_BIT); // set is EAR Off - } - } - else - { - if (KeyboardDevice.IsIssue2Keyboard) - { - if ((LastULAOutByte & (EAR_BIT + MIC_BIT)) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - else - { - if ((LastULAOutByte & EAR_BIT) == 0) - { - result &= ~(TAPE_BIT); - } - else - { - result |= TAPE_BIT; - } - } - } - */ + // process tape INs + TapeDevice.ReadPort(port, ref result); } else { // devices other than the ULA will respond here // (e.g. the AY sound chip in a 128k spectrum - // AY register activate - // Kemptson Mouse + // AY register activate - no AY chip in a 48k spectrum + + // Kemptson Mouse (not implemented yet) - // if unused port the floating memory bus should be returned - + // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle int _tStates = CurrentFrameCycle - 1; @@ -203,14 +110,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Border - LSB 3 bits hold the border colour if (ULADevice.borderColour != (value & BORDER_BIT)) + { + // border value has changed - update the screen buffer ULADevice.UpdateScreenBuffer(CurrentFrameCycle); + } ULADevice.borderColour = value & BORDER_BIT; // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); - // Tape + // Tape mic processing (not implemented yet) //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 6381049957..d2c7db7abe 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -86,8 +86,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.MemoryCallbacks = MemoryCallbacks; - HardReset = _machine.HardReset; - SoftReset = _machine.SoftReset; + //HardReset = _machine.HardReset; + //SoftReset = _machine.SoftReset; _cpu.FetchMemory = _machine.ReadMemory; _cpu.ReadMemory = _machine.ReadMemory; @@ -109,13 +109,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum - HardReset(); + //HardReset(); SetupMemoryDomains(); } - public Action HardReset; - public Action SoftReset; + //public Action HardReset; + //public Action SoftReset; private readonly Z80A _cpu; private readonly TraceBuffer _tracer; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index e5f12cfe06..af479f8536 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -2,7 +2,7 @@ At the moment this is experimental and is still being worked on. -### Implemented and sorta working +### Implemented and working (as far as I can tell) * IEmulator * ZX Spectrum 48k, 128k, +2 & +2A models * ULA video output (implementing IVideoProvider) @@ -14,12 +14,12 @@ At the moment this is experimental and is still being worked on. * Default keyboard keymappings * Kempston, Cursor and Sinclair (Left & Right) joysticks emulated * Tape device that will load spectrum games in realtime (*.tzx and *.tap) -* Most tape protection/loading schemes that I've tested are currently working (see caveat below) +* Most tape protection/loading schemes that I've tested are currently working * IStatable * ISettable core settings * IDebuggable (for what it's worth) * DeterministicEmulation as a SyncSetting, LagFrame detection and FrameAdvance render & renderSound bools respected (when DeterministicEmulation == false) -* Tape auto-loading routines (as a setting) +* Tape auto-loading routines (as a setting - default ON) * Basic tape block navigation (NextBlock, PrevBlock) * Tape-related OSD messages (verbosity level configured in settings) @@ -32,7 +32,6 @@ At the moment this is experimental and is still being worked on. ### Not working * +3 disk drive - no implementation yet * Hard & Soft Reset menu options in the client (they are greyed out for some reason) -* Speedlock tape protection scheme doesn't appear to load correctly ### Help needed * I'm not a TASer, i've never TASed before. It would be really useful if someone (anyone) can build this branch and test this core from a TAS-workflow / TAStudio perpective. There may still be some work to do an exact timings and memory contention, but otherwise this core is able to play the majority of speccy games out there. From deba6b18b8733e12e277f5d02a09729f8755eb1e Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 14:37:45 +0000 Subject: [PATCH 073/105] Added 'Get Tape Status' keybinding - fires an OSD message with state info about the current tape --- Assets/defctrl.json | 3 +- .../Hardware/Datacorder/DatacorderDevice.cs | 123 +++++++++++------- .../Machine/SpectrumBase.Input.cs | 16 ++- .../Machine/SpectrumBase.Memory.cs | 13 ++ .../ZXSpectrum.Controllers.cs | 3 +- .../SinclairSpectrum/ZXSpectrum.Messaging.cs | 40 ++++++ 6 files changed, 145 insertions(+), 53 deletions(-) diff --git a/Assets/defctrl.json b/Assets/defctrl.json index c2bb94b42b..dace0123e0 100644 --- a/Assets/defctrl.json +++ b/Assets/defctrl.json @@ -532,7 +532,8 @@ "Insert Next Tape": "F6", "Insert Previous Tape": "F5", "Next Tape Block": "F8", - "Prev Tape Block": "F7" + "Prev Tape Block": "F7", + "Get Tape Status": "F10" }, "Intellivision Controller": { "P1 Up": "UpArrow, J1 POV1U, X1 DpadUp, X1 LStickUp", diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 29a746b880..88d5f844f3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -137,8 +137,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public const ushort ERROR_ROM_ADDRESS = 0x0008; - Stopwatch sw = new Stopwatch(); - /// /// Should be fired at the end of every frame /// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented) @@ -373,6 +371,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // decide how many cycles worth of data we are capturing long cycles = cpuCycle - _lastCycle; + bool is48k = _machine.IsIn48kMode(); + // check whether tape is actually playing if (_tapeIsPlaying == false) { @@ -398,11 +398,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // flip the current state currentState = !currentState; - if (_position == 0) + if (_position == 0 && _tapeIsPlaying) { // start of block - // notify about the current block + // notify about the current block var bl = _dataBlocks[_currentDataBlockIndex]; StringBuilder sbd = new StringBuilder(); @@ -427,47 +427,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // we have reached the end of the current block - // check for any commands - var command = _dataBlocks[_currentDataBlockIndex].Command; - var block = _dataBlocks[_currentDataBlockIndex]; - switch (command) - { - // Stop the tape command found - if this is the end of the tape RTZ - // otherwise just STOP and move to the next block - case TapeCommand.STOP_THE_TAPE: - - _machine.Spectrum.OSD_TapeStoppedAuto(); - - if (_currentDataBlockIndex >= _dataBlocks.Count()) - RTZ(); - else - { - Stop(); - } - break; - case TapeCommand.STOP_THE_TAPE_48K: - - if ((_machine.GetType() != typeof(ZX128) && - _machine.GetType() != typeof(ZX128Plus2) && - _machine.GetType() != typeof(ZX128Plus3)) || - (_machine.GetType() == typeof(ZX128) || - _machine.GetType() != typeof(ZX128Plus2) || - _machine.GetType() != typeof(ZX128Plus3)) && - _machine._ROMpaged == 1) - { - _machine.Spectrum.OSD_TapeStoppedAuto(); - - if (_currentDataBlockIndex >= _dataBlocks.Count()) - RTZ(); - else - { - Stop(); - } - - } - break; - } - if (_dataBlocks[_currentDataBlockIndex].DataPeriods.Count() == 0) { // notify about the current block (we are skipping it because its empty) @@ -487,11 +446,77 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } - // skip any empty blocks + // skip any empty blocks (and process any command blocks) while (_position >= _dataBlocks[_currentDataBlockIndex].DataPeriods.Count()) - { + { + // check for any commands + var command = _dataBlocks[_currentDataBlockIndex].Command; + var block = _dataBlocks[_currentDataBlockIndex]; + bool shouldStop = false; + switch (command) + { + // Stop the tape command found - if this is the end of the tape RTZ + // otherwise just STOP and move to the next block + case TapeCommand.STOP_THE_TAPE: + + _machine.Spectrum.OSD_TapeStoppedAuto(); + + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + + _monitorTimeOut = 2000; + + break; + case TapeCommand.STOP_THE_TAPE_48K: + if (is48k) + { + _machine.Spectrum.OSD_TapeStoppedAuto(); + + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + + _monitorTimeOut = 2000; + } + /* + if ((_machine.GetType() != typeof(ZX128) && + _machine.GetType() != typeof(ZX128Plus2) && + _machine.GetType() != typeof(ZX128Plus3)) || + (_machine.GetType() == typeof(ZX128) || + _machine.GetType() != typeof(ZX128Plus2) || + _machine.GetType() != typeof(ZX128Plus3)) && + _machine._ROMpaged == 1) + { + _machine.Spectrum.OSD_TapeStoppedAuto(); + + if (_currentDataBlockIndex >= _dataBlocks.Count()) + RTZ(); + else + { + Stop(); + } + + _monitorTimeOut = 2000; + } + */ + break; + } + + if (shouldStop) + break; + _position = 0; _currentDataBlockIndex++; + + + if (_currentDataBlockIndex >= _dataBlocks.Count()) { break; @@ -584,7 +609,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _monitorCount++; - if (_monitorCount >= 8 && _machine.Spectrum.Settings.AutoLoadTape) + if (_monitorCount >= 16 && _cpu.RegPC == 1523 && _machine.Spectrum.Settings.AutoLoadTape) { if (!_tapeIsPlaying) { @@ -592,7 +617,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine.Spectrum.OSD_TapePlayingAuto(); } - _monitorTimeOut = 500; + _monitorTimeOut = 90; } } else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index eb2bcd63a8..bf6a679f15 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -17,6 +17,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string PrevTape = "Insert Previous Tape"; string NextBlock = "Next Tape Block"; string PrevBlock = "Prev Tape Block"; + string TapeStatus = "Get Tape Status"; bool pressed_Play = false; bool pressed_Stop = false; @@ -25,6 +26,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bool pressed_PrevTape = false; bool pressed_NextBlock = false; bool pressed_PrevBlock = false; + bool pressed_TapeStatus = false; public void PollInput() { @@ -85,8 +87,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } - - // Tape control if (Spectrum._controller.IsPressed(Play)) { @@ -175,6 +175,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else pressed_PrevBlock = false; + + if (Spectrum._controller.IsPressed(TapeStatus)) + { + if (!pressed_TapeStatus) + { + //Spectrum.OSD_FireInputMessage(TapeStatus); + Spectrum.OSD_ShowTapeStatus(); + pressed_TapeStatus = true; + } + } + else + pressed_TapeStatus = false; } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 6ddbccedbc..0179c0c5f5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -105,6 +105,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Helper function to refresh memory array (probably not the best way to do things) /// public abstract void ReInitMemory(); + + /// + /// Detects whether the 48k rom is resident (or paged in) at 0x0001 + /// + /// + public virtual bool IsIn48kMode() + { + var data = ReadBus(0x0001); + if (data == 0xaf) + return true; + + return false; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index a59f03de9e..1ba7dd5b74 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -86,7 +86,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum List tape = new List { // Tape functions - "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", "Insert Previous Tape", "Next Tape Block", "Prev Tape Block" + "Play Tape", "Stop Tape", "RTZ Tape", "Record Tape", "Insert Next Tape", + "Insert Previous Tape", "Next Tape Block", "Prev Tape Block", "Get Tape Status" }; foreach (var s in tape) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs index 9a583013e8..9ad4e3615a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Messaging.cs @@ -200,6 +200,46 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); } + /// + /// Tape message that prints the current status of the tape device + /// + public void OSD_ShowTapeStatus() + { + StringBuilder sb = new StringBuilder(); + sb.Append("Status: "); + + if (_machine.TapeDevice.TapeIsPlaying) + sb.Append("PLAYING"); + else + sb.Append("STOPPED"); + + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Tape: " + _machine.TapeMediaIndex + ": " + _gameInfo[_machine.TapeMediaIndex].Name); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Block: "); + sb.Append("(" + (_machine.TapeDevice.CurrentDataBlockIndex + 1) + + " of " + _machine.TapeDevice.DataBlocks.Count() + ") " + + _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].BlockDescription); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + + sb.Append("Block Pos: "); + + int pos = _machine.TapeDevice.Position; + int end = _machine.TapeDevice.DataBlocks[_machine.TapeDevice.CurrentDataBlockIndex].DataPeriods.Count; + double p = 0; + if (end != 0) + p = ((double)pos / (double)end) * (double)100; + + sb.Append(p.ToString("N0") + "%"); + SendMessage(sb.ToString().TrimEnd('\n'), MessageCategory.Tape); + sb.Clear(); + } + #endregion /// From 213437362db1c7a206e506c62c3eab161a329fe9 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 15:22:06 +0000 Subject: [PATCH 074/105] Memory overhaul for a nice performance benefit --- .../Machine/SpectrumBase.Memory.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 6 +- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 58 +++++---- .../Machine/ZXSpectrum128K/ZX128.ULA.cs | 2 +- .../Machine/ZXSpectrum128K/ZX128.cs | 3 - .../ZX128Plus2a.Memory.cs | 116 +++++++++++------- .../ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs | 2 +- .../ZXSpectrum128KPlus2a/ZX128Plus2a.cs | 3 - .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 114 ++++++++++------- .../ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs | 2 +- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 3 - .../Machine/ZXSpectrum16K/ZX16.cs | 3 +- .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 35 +++++- .../Machine/ZXSpectrum48K/ZX48.ULA.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.cs | 2 - .../ZXSpectrum.IMemoryDomains.cs | 6 +- .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 2 - 17 files changed, 219 insertions(+), 142 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 0179c0c5f5..49a7bbd561 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -104,7 +104,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Helper function to refresh memory array (probably not the best way to do things) /// - public abstract void ReInitMemory(); + //public abstract void ReInitMemory(); /// /// Detects whether the 48k rom is resident (or paged in) at 0x0001 diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 553872b295..9650e8cfd0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -194,9 +194,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); if (_renderSound) - BuzzerDevice.EndFrame(); - - //TapeDevice.CPUFrameCompleted(); + BuzzerDevice.EndFrame(); FrameCount++; @@ -276,7 +274,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.EndSection(); - ReInitMemory(); + //ReInitMemory(); } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index c86a1e6ca4..b9eb26cbb8 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -49,24 +49,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { int divisor = addr / 0x4000; byte result = 0xff; + switch (divisor) { // ROM 0x000 case 0: if (ROMPaged == 0) - result = Memory[0][addr % 0x4000]; + result = ROM0[addr % 0x4000]; else - result = Memory[1][addr % 0x4000]; + result = ROM1[addr % 0x4000]; break; - // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) + // RAM 0x4000 (RAM5 - Bank5) case 1: - result = Memory[7][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - result = Memory[4][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -74,28 +75,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - result = Memory[2][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: - result = Memory[3][addr % 0x4000]; + result = RAM1[addr % 0x4000]; break; case 2: - result = Memory[4][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; case 3: - result = Memory[5][addr % 0x4000]; + result = RAM3[addr % 0x4000]; break; case 4: - result = Memory[6][addr % 0x4000]; + result = RAM4[addr % 0x4000]; break; case 5: - result = Memory[7][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; case 6: - result = Memory[8][addr % 0x4000]; + result = RAM6[addr % 0x4000]; break; case 7: - result = Memory[9][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -115,24 +116,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override void WriteBus(ushort addr, byte value) { int divisor = addr / 0x4000; + switch (divisor) { // ROM 0x000 case 0: + // cannot write to ROMs + /* if (ROMPaged == 0) - Memory[0][addr % 0x4000] = value; + ROM0[addr % 0x4000] = value; else - Memory[1][addr % 0x4000] = value; + ROM1[addr % 0x4000] = value; + */ break; // RAM 0x4000 (RAM5 - Bank5 or shadow bank RAM7) case 1: - Memory[7][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - Memory[4][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -140,28 +145,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - Memory[2][addr % 0x4000] = value; + RAM0[addr % 0x4000] = value; break; case 1: - Memory[3][addr % 0x4000] = value; + RAM1[addr % 0x4000] = value; break; case 2: - Memory[4][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; case 3: - Memory[5][addr % 0x4000] = value; + RAM3[addr % 0x4000] = value; break; case 4: - Memory[6][addr % 0x4000] = value; + RAM4[addr % 0x4000] = value; break; case 5: - Memory[7][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; case 6: - Memory[8][addr % 0x4000] = value; + RAM6[addr % 0x4000] = value; break; case 7: - Memory[9][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -203,7 +208,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - + /* public override void ReInitMemory() { if (Memory.ContainsKey(0)) @@ -256,6 +261,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else Memory.Add(9, RAM7); } + */ /// /// ULA reads the memory at the specified address diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs index 6da4c3738f..59d9108290 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.ULA.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { contentionStartPeriod = 14361; // + LateTiming; contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); - screen = _machine.Memory[7]; + screen = _machine.RAM5; screenByteCtr = DisplayStart; ULAByteCtr = 0; actualULAStart = 14366 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index eb1b25aa19..5e657b0863 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -26,9 +26,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RAMPaged = 0; PagingDisabled = false; - // init addressable memory from ROM and RAM banks - ReInitMemory(); - ULADevice = new ULA128(this); BuzzerDevice = new Buzzer(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs index 6505a5f81f..3ce0adcc73 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs @@ -75,12 +75,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[4][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: case 2: case 3: - result = Memory[8][addr % 0x4000]; + result = RAM4[addr % 0x4000]; break; } break; @@ -88,14 +88,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[5][addr % 0x4000]; + result = RAM1[addr % 0x4000]; break; case 1: - case 2: - result = Memory[9][addr % 0x4000]; + case 2: + result = RAM5[addr % 0x4000]; break; case 3: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -103,12 +103,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[6][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: case 2: case 3: - result = Memory[10][addr % 0x4000]; + result = RAM6[addr % 0x4000]; break; } break; @@ -118,10 +118,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case 0: case 2: case 3: - result = Memory[7][addr % 0x4000]; + result = RAM3[addr % 0x4000]; break; case 1: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -133,17 +133,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - result = Memory[_ROMpaged][addr % 0x4000]; + switch (_ROMpaged) + { + case 0: + result = ROM0[addr % 0x4000]; + break; + case 1: + result = ROM1[addr % 0x4000]; + break; + case 2: + result = ROM2[addr % 0x4000]; + break; + case 3: + result = ROM3[addr % 0x4000]; + break; + } break; // RAM 0x4000 (RAM5 - Bank5 always) case 1: - result = Memory[9][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - result = Memory[6][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -151,28 +165,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - result = Memory[4][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: - result = Memory[5][addr % 0x4000]; + result = RAM1[addr % 0x4000]; break; case 2: - result = Memory[6][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; case 3: - result = Memory[7][addr % 0x4000]; + result = RAM3[addr % 0x4000]; break; case 4: - result = Memory[8][addr % 0x4000]; + result = RAM4[addr % 0x4000]; break; case 5: - result = Memory[9][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; case 6: - result = Memory[10][addr % 0x4000]; + result = RAM6[addr % 0x4000]; break; case 7: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -203,12 +217,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[4][addr % 0x4000] = value; + RAM0[addr % 0x4000] = value; break; case 1: case 2: case 3: - Memory[8][addr % 0x4000] = value; + RAM4[addr % 0x4000] = value; break; } break; @@ -216,14 +230,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[5][addr % 0x4000] = value; + RAM1[addr % 0x4000] = value; break; case 1: case 2: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; case 3: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -231,12 +245,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; case 1: case 2: case 3: - Memory[10][addr % 0x4000] = value; + RAM6[addr % 0x4000] = value; break; } break; @@ -246,10 +260,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case 0: case 2: case 3: - Memory[7][addr % 0x4000] = value; + RAM3[addr % 0x4000] = value; break; case 1: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -261,17 +275,34 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - Memory[_ROMpaged][addr % 0x4000] = value; + /* + switch (_ROMpaged) + { + // cannot write to ROMs + case 0: + ROM0[addr % 0x4000] = value; + break; + case 1: + ROM1[addr % 0x4000] = value; + break; + case 2: + ROM2[addr % 0x4000] = value; + break; + case 3: + ROM3[addr % 0x4000] = value; + break; + } + */ break; // RAM 0x4000 (RAM5 - Bank5 only) case 1: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -279,28 +310,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - Memory[4][addr % 0x4000] = value; + RAM0[addr % 0x4000] = value; break; case 1: - Memory[5][addr % 0x4000] = value; + RAM1[addr % 0x4000] = value; break; case 2: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; case 3: - Memory[7][addr % 0x4000] = value; + RAM3[addr % 0x4000] = value; break; case 4: - Memory[8][addr % 0x4000] = value; + RAM4[addr % 0x4000] = value; break; case 5: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; case 6: - Memory[10][addr % 0x4000] = value; + RAM6[addr % 0x4000] = value; break; case 7: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -343,7 +374,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - + /* public override void ReInitMemory() { if (Memory.ContainsKey(0)) @@ -406,6 +437,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else Memory.Add(11, RAM7); } + */ /// /// ULA reads the memory at the specified address diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs index 35547ab931..c6547833ad 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.ULA.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { contentionStartPeriod = 14361; // + LateTiming; contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); - screen = _machine.Memory[9]; + screen = _machine.RAM5; screenByteCtr = DisplayStart; ULAByteCtr = 0; actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs index 5cdfb1bbbf..88b672d174 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs @@ -26,9 +26,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RAMPaged = 0; PagingDisabled = false; - // init addressable memory from ROM and RAM banks - ReInitMemory(); - ULADevice = new ULAPlus2a(this); BuzzerDevice = new Buzzer(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index 188581fd72..7ca4147f9d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -75,12 +75,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[4][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: case 2: case 3: - result = Memory[8][addr % 0x4000]; + result = RAM4[addr % 0x4000]; break; } break; @@ -88,14 +88,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[5][addr % 0x4000]; + result = RAM1[addr % 0x4000]; break; case 1: case 2: - result = Memory[9][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; case 3: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -103,12 +103,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - result = Memory[6][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: case 2: case 3: - result = Memory[10][addr % 0x4000]; + result = RAM6[addr % 0x4000]; break; } break; @@ -118,10 +118,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case 0: case 2: case 3: - result = Memory[7][addr % 0x4000]; + result = RAM3[addr % 0x4000]; break; case 1: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -133,17 +133,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - result = Memory[_ROMpaged][addr % 0x4000]; + switch (_ROMpaged) + { + case 0: + result = ROM0[addr % 0x4000]; + break; + case 1: + result = ROM1[addr % 0x4000]; + break; + case 2: + result = ROM2[addr % 0x4000]; + break; + case 3: + result = ROM3[addr % 0x4000]; + break; + } break; // RAM 0x4000 (RAM5 - Bank5 always) case 1: - result = Memory[9][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - result = Memory[6][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -151,28 +165,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - result = Memory[4][addr % 0x4000]; + result = RAM0[addr % 0x4000]; break; case 1: - result = Memory[5][addr % 0x4000]; + result = RAM1[addr % 0x4000]; break; case 2: - result = Memory[6][addr % 0x4000]; + result = RAM2[addr % 0x4000]; break; case 3: - result = Memory[7][addr % 0x4000]; + result = RAM3[addr % 0x4000]; break; case 4: - result = Memory[8][addr % 0x4000]; + result = RAM4[addr % 0x4000]; break; case 5: - result = Memory[9][addr % 0x4000]; + result = RAM5[addr % 0x4000]; break; case 6: - result = Memory[10][addr % 0x4000]; + result = RAM6[addr % 0x4000]; break; case 7: - result = Memory[11][addr % 0x4000]; + result = RAM7[addr % 0x4000]; break; } break; @@ -203,12 +217,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[4][addr % 0x4000] = value; + RAM0[addr % 0x4000] = value; break; case 1: case 2: case 3: - Memory[8][addr % 0x4000] = value; + RAM4[addr % 0x4000] = value; break; } break; @@ -216,14 +230,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[5][addr % 0x4000] = value; + RAM1[addr % 0x4000] = value; break; case 1: case 2: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; case 3: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -231,12 +245,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (PagingConfiguration) { case 0: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; case 1: case 2: case 3: - Memory[10][addr % 0x4000] = value; + RAM6[addr % 0x4000] = value; break; } break; @@ -246,10 +260,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum case 0: case 2: case 3: - Memory[7][addr % 0x4000] = value; + RAM3[addr % 0x4000] = value; break; case 1: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -261,17 +275,34 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: - Memory[_ROMpaged][addr % 0x4000] = value; + /* + switch (_ROMpaged) + { + // cannot write to ROMs + case 0: + ROM0[addr % 0x4000] = value; + break; + case 1: + ROM1[addr % 0x4000] = value; + break; + case 2: + ROM2[addr % 0x4000] = value; + break; + case 3: + ROM3[addr % 0x4000] = value; + break; + } + */ break; // RAM 0x4000 (RAM5 - Bank5 only) case 1: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; // RAM 0x8000 (RAM2 - Bank2) case 2: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; // RAM 0xc000 (any ram bank 0 - 7 may be paged in - default bank0) @@ -279,28 +310,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (RAMPaged) { case 0: - Memory[4][addr % 0x4000] = value; + RAM0[addr % 0x4000] = value; break; case 1: - Memory[5][addr % 0x4000] = value; + RAM1[addr % 0x4000] = value; break; case 2: - Memory[6][addr % 0x4000] = value; + RAM2[addr % 0x4000] = value; break; case 3: - Memory[7][addr % 0x4000] = value; + RAM3[addr % 0x4000] = value; break; case 4: - Memory[8][addr % 0x4000] = value; + RAM4[addr % 0x4000] = value; break; case 5: - Memory[9][addr % 0x4000] = value; + RAM5[addr % 0x4000] = value; break; case 6: - Memory[10][addr % 0x4000] = value; + RAM6[addr % 0x4000] = value; break; case 7: - Memory[11][addr % 0x4000] = value; + RAM7[addr % 0x4000] = value; break; } break; @@ -343,7 +374,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - + /* public override void ReInitMemory() { if (Memory.ContainsKey(0)) @@ -406,6 +437,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else Memory.Add(11, RAM7); } + */ /// /// ULA reads the memory at the specified address diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs index e6d82474dc..3b2291a082 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.ULA.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { contentionStartPeriod = 14361; // + LateTiming; contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); - screen = _machine.Memory[9]; + screen = _machine.RAM5; screenByteCtr = DisplayStart; ULAByteCtr = 0; actualULAStart = 14365 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index 4be628ba75..b9038e3af1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -26,9 +26,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum RAMPaged = 0; PagingDisabled = false; - // init addressable memory from ROM and RAM banks - ReInitMemory(); - ULADevice = new ULAPlus3(this); BuzzerDevice = new Buzzer(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index 49c6cd686b..7d79f80544 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -119,7 +119,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - + /* public override void ReInitMemory() { if (Memory.ContainsKey(0)) @@ -132,6 +132,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else Memory.Add(1, RAM1); } + */ /// /// Sets up the ROM diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 5812c898c4..c6f319cec0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -42,11 +42,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override byte ReadBus(ushort addr) { int divisor = addr / 0x4000; + var index = addr % 0x4000; + // paging logic goes here - var bank = Memory[divisor]; - var index = addr % 0x4000; - return bank[index]; + switch (divisor) + { + case 0: return ROM0[index]; + case 1: return RAM0[index]; + case 2: return RAM1[index]; + case 3: return RAM2[index]; + default: return 0; + } } /// @@ -58,11 +65,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override void WriteBus(ushort addr, byte value) { int divisor = addr / 0x4000; + var index = addr % 0x4000; + // paging logic goes here - var bank = Memory[divisor]; - var index = addr % 0x4000; - bank[index] = value; + switch (divisor) + { + case 0: + // cannot write to ROM + break; + case 1: + RAM0[index] = value; + break; + case 2: + RAM1[index] = value; + break; + case 3: + RAM2[index] = value; + break; + } // update ULA screen buffer if necessary if ((addr & 49152) == 16384 && _render) @@ -100,6 +121,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } + /* public override void ReInitMemory() { if (Memory.ContainsKey(0)) @@ -127,6 +149,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else Memory.Add(4, RAM3); } + */ /// /// Sets up the ROM diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs index 4c70608d7a..3b4eca3dc3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.ULA.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { contentionStartPeriod = 14335; // + LateTiming; contentionEndPeriod = contentionStartPeriod + (ScreenHeight * TstatesPerScanline); - screen = _machine.Memory[1]; + screen = _machine.RAM0; screenByteCtr = DisplayStart; ULAByteCtr = 0; actualULAStart = 14340 - 24 - (TstatesPerScanline * BorderTopHeight);// + LateTiming; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs index aaf4ffdebd..d9737a40f1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.cs @@ -21,8 +21,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum Spectrum = spectrum; CPU = cpu; - ReInitMemory(); - ULADevice = new ULA48(this); BuzzerDevice = new Buzzer(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs index 716c29ec95..5245886f7c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IMemoryDomains.cs @@ -45,8 +45,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } private void SyncAllByteArrayDomains() - { - + { SyncByteArrayDomain("ROM0", _machine.ROM0); SyncByteArrayDomain("ROM1", _machine.ROM1); SyncByteArrayDomain("ROM2", _machine.ROM2); @@ -58,8 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SyncByteArrayDomain("RAM4", _machine.RAM4); SyncByteArrayDomain("RAM5", _machine.RAM5); SyncByteArrayDomain("RAM6", _machine.RAM6); - SyncByteArrayDomain("RAM7", _machine.RAM7); - + SyncByteArrayDomain("RAM7", _machine.RAM7); } private void SyncByteArrayDomain(string name, byte[] data) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index 372cb8ed54..39bd7c984f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -53,12 +53,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.SyncState(ser); ser.BeginSection("ZXSpectrum"); - //_cpu.SyncState(ser); _machine.SyncState(ser); ser.Sync("Frame", ref _machine.FrameCount); ser.Sync("LagCount", ref _lagCount); ser.Sync("IsLag", ref _isLag); - //ser.Sync("_memoryDomainsInit", ref _memoryDomainsInit); ser.EndSection(); From baa46f3c99fa9a871542e8ae8d576933b9562eb7 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 15:48:00 +0000 Subject: [PATCH 075/105] Detection method for 48k (or 128k in 48k mode) - needed for 'stop the tape 48k' tzx block command detection --- .../Hardware/Datacorder/DatacorderDevice.cs | 25 +------ .../Machine/SpectrumBase.Memory.cs | 18 +++-- .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 56 +--------------- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 12 ++-- .../ZX128Plus2a.Memory.cs | 66 +------------------ .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 66 +------------------ .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 30 --------- 7 files changed, 17 insertions(+), 256 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 88d5f844f3..e71a17d8f0 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -484,28 +484,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } _monitorTimeOut = 2000; - } - /* - if ((_machine.GetType() != typeof(ZX128) && - _machine.GetType() != typeof(ZX128Plus2) && - _machine.GetType() != typeof(ZX128Plus3)) || - (_machine.GetType() == typeof(ZX128) || - _machine.GetType() != typeof(ZX128Plus2) || - _machine.GetType() != typeof(ZX128Plus3)) && - _machine._ROMpaged == 1) - { - _machine.Spectrum.OSD_TapeStoppedAuto(); - - if (_currentDataBlockIndex >= _dataBlocks.Count()) - RTZ(); - else - { - Stop(); - } - - _monitorTimeOut = 2000; - } - */ + } break; } @@ -515,8 +494,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _position = 0; _currentDataBlockIndex++; - - if (_currentDataBlockIndex >= _dataBlocks.Count()) { break; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 49a7bbd561..e625ecae77 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -102,21 +102,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } /// - /// Helper function to refresh memory array (probably not the best way to do things) - /// - //public abstract void ReInitMemory(); - - /// - /// Detects whether the 48k rom is resident (or paged in) at 0x0001 + /// Detects whether this is a 48k machine (or a 128k in 48k mode) /// /// public virtual bool IsIn48kMode() { - var data = ReadBus(0x0001); - if (data == 0xaf) + if (this.GetType() == typeof(ZX48) || + this.GetType() == typeof(ZX16) || + PagingDisabled) + { return true; - - return false; + } + else + return false; } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index b9eb26cbb8..e76cffbaf7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -208,61 +208,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - /* - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = ROM1; - else - Memory.Add(1, ROM1); - - if (Memory.ContainsKey(2)) - Memory[2] = RAM0; - else - Memory.Add(2, RAM0); - - if (Memory.ContainsKey(3)) - Memory[3] = RAM1; - else - Memory.Add(3, RAM1); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM2; - else - Memory.Add(4, RAM2); - - if (Memory.ContainsKey(5)) - Memory[5] = RAM3; - else - Memory.Add(5, RAM3); - - if (Memory.ContainsKey(6)) - Memory[6] = RAM4; - else - Memory.Add(6, RAM4); - - if (Memory.ContainsKey(7)) - Memory[7] = RAM5; - else - Memory.Add(7, RAM5); - - if (Memory.ContainsKey(8)) - Memory[8] = RAM6; - else - Memory.Add(8, RAM6); - - if (Memory.ContainsKey(9)) - Memory[9] = RAM7; - else - Memory.Add(9, RAM7); - } - */ - + /// /// ULA reads the memory at the specified address /// (No memory contention) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 0fb70d9bca..130264a4c7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -48,16 +48,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // process tape INs TapeDevice.ReadPort(port, ref result); } + else if ((port & 0xc002) == 0xc000) + { + // AY sound chip + result = (int)AYDevice.PortRead(); + } else { // devices other than the ULA will respond here - // (e.g. the AY sound chip in a 128k spectrum) - - // AY register activate - if ((port & 0xc002) == 0xc000) - { - result = (int)AYDevice.PortRead(); - } // Kempston Mouse (not implemented yet) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs index 3ce0adcc73..33aa0c2bcf 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs @@ -374,71 +374,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - /* - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = ROM1; - else - Memory.Add(1, ROM1); - - if (Memory.ContainsKey(2)) - Memory[2] = ROM2; - else - Memory.Add(2, ROM2); - - if (Memory.ContainsKey(3)) - Memory[3] = ROM3; - else - Memory.Add(3, ROM3); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM0; - else - Memory.Add(4, RAM0); - - if (Memory.ContainsKey(5)) - Memory[5] = RAM1; - else - Memory.Add(5, RAM1); - - if (Memory.ContainsKey(6)) - Memory[6] = RAM2; - else - Memory.Add(6, RAM2); - - if (Memory.ContainsKey(7)) - Memory[7] = RAM3; - else - Memory.Add(7, RAM3); - - if (Memory.ContainsKey(8)) - Memory[8] = RAM4; - else - Memory.Add(8, RAM4); - - if (Memory.ContainsKey(9)) - Memory[9] = RAM5; - else - Memory.Add(9, RAM5); - - if (Memory.ContainsKey(10)) - Memory[10] = RAM6; - else - Memory.Add(10, RAM6); - - if (Memory.ContainsKey(11)) - Memory[11] = RAM7; - else - Memory.Add(11, RAM7); - } - */ - + /// /// ULA reads the memory at the specified address /// (No memory contention) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index 7ca4147f9d..ad3d846ffd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -374,71 +374,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - /* - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = ROM1; - else - Memory.Add(1, ROM1); - - if (Memory.ContainsKey(2)) - Memory[2] = ROM2; - else - Memory.Add(2, ROM2); - - if (Memory.ContainsKey(3)) - Memory[3] = ROM3; - else - Memory.Add(3, ROM3); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM0; - else - Memory.Add(4, RAM0); - - if (Memory.ContainsKey(5)) - Memory[5] = RAM1; - else - Memory.Add(5, RAM1); - - if (Memory.ContainsKey(6)) - Memory[6] = RAM2; - else - Memory.Add(6, RAM2); - - if (Memory.ContainsKey(7)) - Memory[7] = RAM3; - else - Memory.Add(7, RAM3); - - if (Memory.ContainsKey(8)) - Memory[8] = RAM4; - else - Memory.Add(8, RAM4); - - if (Memory.ContainsKey(9)) - Memory[9] = RAM5; - else - Memory.Add(9, RAM5); - - if (Memory.ContainsKey(10)) - Memory[10] = RAM6; - else - Memory.Add(10, RAM6); - - if (Memory.ContainsKey(11)) - Memory[11] = RAM7; - else - Memory.Add(11, RAM7); - } - */ - + /// /// ULA reads the memory at the specified address /// (No memory contention) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index c6f319cec0..8b9427d66b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -121,36 +121,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum WriteBus(addr, value); } - /* - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = RAM0; - else - Memory.Add(1, RAM0); - - if (Memory.ContainsKey(2)) - Memory[2] = RAM1; - else - Memory.Add(2, RAM1); - - if (Memory.ContainsKey(3)) - Memory[3] = RAM2; - else - Memory.Add(3, RAM2); - - if (Memory.ContainsKey(4)) - Memory[4] = RAM3; - else - Memory.Add(4, RAM3); - } - */ - /// /// Sets up the ROM /// From 33aa77d8e3547abca4d38c4c4ec644a3e86d9028 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 12 Mar 2018 18:17:06 +0000 Subject: [PATCH 076/105] Removed [CoreConstructor("ZXSpectrum")] identifier (as this was causing an exception when loading roms for other systems --- BizHawk.Emulation.Common/SystemLookup.cs | 3 ++- .../SinclairSpectrum/ZXSpectrum.IEmulator.cs | 11 ++++++++++- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 1 - BizHawk.Emulation.Cores/FileID.cs | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Common/SystemLookup.cs b/BizHawk.Emulation.Common/SystemLookup.cs index f8e1a0de3c..4c6c692e46 100644 --- a/BizHawk.Emulation.Common/SystemLookup.cs +++ b/BizHawk.Emulation.Common/SystemLookup.cs @@ -32,7 +32,8 @@ namespace BizHawk.Emulation.Common new SystemInfo { SystemId = "C64", FullName = "Commodore 64" }, new SystemInfo { SystemId = "AppleII", FullName = "Apple II" }, - new SystemInfo { SystemId = "INTV", FullName = "Intellivision" } + new SystemInfo { SystemId = "INTV", FullName = "Intellivision" }, + new SystemInfo { SystemId = "ZXSpectrum", FullName = "Sinclair ZX Spectrum" } }; public SystemInfo this[string systemId] diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs index 33bcc68276..c87c9991d7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IEmulator.cs @@ -41,7 +41,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } - public int Frame => _machine.FrameCount; + public int Frame + { + get + { + if (_machine == null) + return 0; + else + return _machine.FrameCount; + } + } public string SystemId => "ZXSpectrum"; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index d2c7db7abe..bb116f8922 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -18,7 +18,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [ServiceNotApplicable(typeof(IDriveLight))] public partial class ZXSpectrum : IDebuggable, IInputPollable, IStatable, IRegionable { - [CoreConstructor("ZXSpectrum")] public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) { var ser = new BasicServiceProvider(this); diff --git a/BizHawk.Emulation.Cores/FileID.cs b/BizHawk.Emulation.Cores/FileID.cs index edc5252484..1314be8b6f 100644 --- a/BizHawk.Emulation.Cores/FileID.cs +++ b/BizHawk.Emulation.Cores/FileID.cs @@ -42,6 +42,7 @@ namespace BizHawk.Emulation.Cores WS, WSC, NGC, C64, + ZXSpectrum, INT, A26, A52, A78, LNX, From 97c453ae9157afae3d0e8f4fb6b5b10727f97d1b Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 12:48:08 +0000 Subject: [PATCH 077/105] Fixed zx16 machine after memory changes --- .../Machine/SpectrumBase.Memory.cs | 6 --- .../Machine/ZXSpectrum16K/ZX16.cs | 53 ++++++++----------- .../Machine/ZXSpectrum48K/ZX48.Memory.cs | 3 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 23 ++++---- 4 files changed, 31 insertions(+), 54 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index e625ecae77..5a21a8db42 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -32,12 +32,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte[] RAM6 = new byte[0x4000]; // Bank 6 public byte[] RAM7 = new byte[0x4000]; // Bank 7 - /// - /// Represents the addressable memory space of the spectrum - /// All banks for the emulated system should be added during initialisation - /// - public Dictionary Memory = new Dictionary(); - /// /// Simulates reading from the bus /// Paging should be handled here diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index 7d79f80544..f310b9f7de 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -51,17 +51,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override byte ReadBus(ushort addr) { int divisor = addr / 0x4000; + var index = addr % 0x4000; + // paging logic goes here - if (divisor > 1) + switch (divisor) { - // memory does not exist - return 0xff; + case 0: return ROM0[index]; + case 1: return RAM0[index]; + default: + // memory does not exist + return 0xff; } - - var bank = Memory[divisor]; - var index = addr % 0x4000; - return bank[index]; } /// @@ -73,17 +74,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override void WriteBus(ushort addr, byte value) { int divisor = addr / 0x4000; + var index = addr % 0x4000; + // paging logic goes here - if (divisor > 1) + switch (divisor) { - // memory does not exist - return; + case 0: + // cannot write to ROM + break; + case 1: + RAM0[index] = value; + break; } - var bank = Memory[divisor]; - var index = addr % 0x4000; - bank[index] = value; + // update ULA screen buffer if necessary + if ((addr & 49152) == 16384 && _render) + ULADevice.UpdateScreenBuffer(CurrentFrameCycle); } /// @@ -97,8 +104,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (ULADevice.IsContended(addr)) CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; - CPU.TotalExecutedCycles += 3; - var data = ReadBus(addr); return data; } @@ -115,25 +120,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (ULADevice.IsContended(addr)) CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; - CPU.TotalExecutedCycles += 3; - WriteBus(addr, value); } - /* - public override void ReInitMemory() - { - if (Memory.ContainsKey(0)) - Memory[0] = ROM0; - else - Memory.Add(0, ROM0); - - if (Memory.ContainsKey(1)) - Memory[1] = RAM1; - else - Memory.Add(1, RAM1); - } - */ - + /// /// Sets up the ROM /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs index 8b9427d66b..bef6b97317 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Memory.cs @@ -99,8 +99,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public override byte ReadMemory(ushort addr) { if (ULADevice.IsContended(addr)) - CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; - + CPU.TotalExecutedCycles += ULADevice.contentionTable[CurrentFrameCycle]; var data = ReadBus(addr); return data; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index bb116f8922..cb294a8146 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -16,7 +16,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum isPorted: false, isReleased: false)] [ServiceNotApplicable(typeof(IDriveLight))] - public partial class ZXSpectrum : IDebuggable, IInputPollable, IStatable, IRegionable + public partial class ZXSpectrum : IRegionable { public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) { @@ -85,8 +85,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _cpu.MemoryCallbacks = MemoryCallbacks; - //HardReset = _machine.HardReset; - //SoftReset = _machine.SoftReset; + HardReset = _machine.HardReset; + SoftReset = _machine.SoftReset; _cpu.FetchMemory = _machine.ReadMemory; _cpu.ReadMemory = _machine.ReadMemory; @@ -102,19 +102,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SoundMixer = new SoundProviderMixer((ISoundProvider)_machine.BuzzerDevice); if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - - //dcf = new DCFilter(SoundMixer, 256); + ser.Register(SoundMixer); - - //HardReset(); + HardReset(); SetupMemoryDomains(); } - //public Action HardReset; - //public Action SoftReset; + public Action HardReset; + public Action SoftReset; private readonly Z80A _cpu; private readonly TraceBuffer _tracer; @@ -123,11 +121,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private List _gameInfo; - private SoundProviderMixer SoundMixer; - - private DCFilter dcf; - - //private byte[] _file; + private SoundProviderMixer SoundMixer; + private readonly List _files; public bool DiagRom = false; From a55cf000e7ce90f56015454df3fbd1453c79bf27 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 13:09:44 +0000 Subject: [PATCH 078/105] Some code tidy --- .../Machine/SpectrumBase.Input.cs | 5 + .../Machine/SpectrumBase.Media.cs | 4 +- .../Machine/SpectrumBase.Memory.cs | 70 +++++++++++- .../Machine/SpectrumBase.Port.cs | 1 - .../SinclairSpectrum/Machine/SpectrumBase.cs | 104 ++++++++---------- 5 files changed, 119 insertions(+), 65 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index bf6a679f15..c14a7bcc62 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -28,6 +28,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bool pressed_PrevBlock = false; bool pressed_TapeStatus = false; + /// + /// Cycles through all the input callbacks + /// This should be done once per frame + /// public void PollInput() { Spectrum.InputCallbacks.Call(); @@ -247,6 +251,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Signs whether input read has been requested + /// This forms part of the IEmulator LagFrame implementation /// private bool inputRead; public bool InputRead diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs index aa1b943823..de2f5206d7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Media.cs @@ -26,7 +26,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum protected List diskImages { get; set; } /// - /// The index of the currently 'loaded' tape or disk image + /// The index of the currently 'loaded' tape image /// protected int tapeMediaIndex; public int TapeMediaIndex @@ -63,7 +63,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } /// - /// The index of the currently 'loaded' tape or disk image + /// The index of the currently 'loaded' disk image /// protected int diskMediaIndex; public int DiskMediaIndex diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 5a21a8db42..48058ee084 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -12,6 +12,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public abstract partial class SpectrumBase { + #region Memory Fields & Properties + /// /// ROM Banks /// @@ -19,7 +21,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte[] ROM1 = new byte[0x4000]; public byte[] ROM2 = new byte[0x4000]; public byte[] ROM3 = new byte[0x4000]; - + /// /// RAM Banks /// @@ -32,6 +34,65 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public byte[] RAM6 = new byte[0x4000]; // Bank 6 public byte[] RAM7 = new byte[0x4000]; // Bank 7 + /// + /// Signs that the shadow screen is now displaying + /// Note: normal screen memory in RAM5 is not altered, the ULA just outputs Screen1 instead (RAM7) + /// + protected bool SHADOWPaged; + + /// + /// Index of the current RAM page + /// /// 128k, +2/2a and +3 only + /// + public int RAMPaged; + + /// + /// Signs that all paging is disabled + /// If this is TRUE, then 128k and above machines need a hard reset before paging is allowed again + /// + protected bool PagingDisabled; + + /// + /// Index of the currently paged ROM + /// 128k, +2/2a and +3 only + /// + protected int ROMPaged; + public virtual int _ROMpaged + { + get { return ROMPaged; } + set { ROMPaged = value; } + } + + /* + * +3/+2A only + */ + + /// + /// High bit of the ROM selection (in normal paging mode) + /// + protected bool ROMhigh = false; + + /// + /// Low bit of the ROM selection (in normal paging mode) + /// + protected bool ROMlow = false; + + /// + /// Signs that the +2a/+3 special paging mode is activated + /// + protected bool SpecialPagingMode; + + /// + /// Index of the current special paging mode (0-3) + /// + protected int PagingConfiguration; + + #endregion + + + + #region Memory Related Methods + /// /// Simulates reading from the bus /// Paging should be handled here @@ -95,6 +156,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return value; } + #endregion + + #region Helper Methods + /// /// Detects whether this is a 48k machine (or a 128k in 48k mode) /// @@ -110,6 +175,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else return false; } - + + #endregion } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs index 874345ba9d..3b53dd28e3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Port.cs @@ -22,7 +22,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum set { LastULAOutByte = value; } } - /// /// Reads a byte of data from a specified port address /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 9650e8cfd0..c4b241b898 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -12,52 +12,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public abstract partial class SpectrumBase { - /// - /// Index of the currently paged ROM - /// - protected int ROMPaged; - public virtual int _ROMpaged - { - get { return ROMPaged; } - set { ROMPaged = value; } - } - - /// - /// Signs that the shadow screen has been paged in - /// - protected bool SHADOWPaged; - - /// - /// Index of the current RAM page - /// - public int RAMPaged; - - /// - /// Signs that all paging is disabled - /// - protected bool PagingDisabled; - - // +3/+2A only - - protected bool ROMhigh = false; - protected bool ROMlow = false; - - /// - /// Signs that the +2a/+3 special paging mode is activated - /// - protected bool SpecialPagingMode; - - /// - /// Index of the current special paging config - /// - protected int PagingConfiguration; - - /// - /// Signs whether the disk motor is on or off - /// - protected bool DiskMotorState; - - protected bool PrinterPortStrobe; + #region Devices /// /// The calling ZXSpectrum class (piped in via constructor) @@ -103,7 +58,21 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Holds the currently selected joysticks /// public virtual IJoystick[] JoystickCollection { get; set; } - + + /// + /// Signs whether the disk motor is on or off + /// + protected bool DiskMotorState; + + /// + /// +3/2a printer port strobe + /// + protected bool PrinterPortStrobe; + + #endregion + + #region Emulator State + /// /// Signs whether the frame has ended /// @@ -140,16 +109,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public bool _render; public bool _renderSound; + #endregion + + #region Constants + /// - /// Mask constants + /// Mask constants & misc /// protected const int BORDER_BIT = 0x07; protected const int EAR_BIT = 0x10; protected const int MIC_BIT = 0x08; protected const int TAPE_BIT = 0x40; - protected const int AY_SAMPLE_RATE = 16; + #endregion + + #region Emulation Loop + /// /// Executes a single frame /// @@ -169,7 +145,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } PollInput(); - + while (CurrentFrameCycle < ULADevice.FrameLength) // UlaFrameCycleCount) { // check for interrupt @@ -183,18 +159,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { if (AYDevice != null) AYDevice.UpdateSound(CurrentFrameCycle); - } + } } - + // we have reached the end of a frame LastFrameStartCPUTick = CPU.TotalExecutedCycles - OverFlow; // paint the buffer if needed if (ULADevice.needsPaint && _render) ULADevice.UpdateScreenBuffer(ULADevice.FrameLength); - + if (_renderSound) - BuzzerDevice.EndFrame(); + BuzzerDevice.EndFrame(); FrameCount++; @@ -208,14 +184,18 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // is this a lag frame? Spectrum.IsLagFrame = !InputRead; } - + + #endregion + + #region Reset Functions + /// /// Hard reset of the emulated machine /// public virtual void HardReset() - { + { //ResetBorder(); - ULADevice.ResetInterrupt(); + ULADevice.ResetInterrupt(); } /// @@ -227,6 +207,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ULADevice.ResetInterrupt(); } + #endregion + + #region IStatable + public void SyncState(Serializer ser) { ser.BeginSection("ZXMachine"); @@ -236,7 +220,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("_frameCycles", ref _frameCycles); ser.Sync("inputRead", ref inputRead); ser.Sync("LastFrameStartCPUTick", ref LastFrameStartCPUTick); - ser.Sync("LastULAOutByte", ref LastULAOutByte); + ser.Sync("LastULAOutByte", ref LastULAOutByte); ser.Sync("ROM0", ref ROM0, false); ser.Sync("ROM1", ref ROM1, false); ser.Sync("ROM2", ref ROM2, false); @@ -273,8 +257,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum TapeDevice.SyncState(ser); ser.EndSection(); - - //ReInitMemory(); } + + #endregion } } From 4e088574cf71307b32f3c2724f0c1ab62cfae2ed Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 13:20:09 +0000 Subject: [PATCH 079/105] more code tidy --- .../Computers/SinclairSpectrum/Machine/ULABase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index d4e5144b35..216aa33ef9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -5,8 +5,7 @@ using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { /// - /// Another ULA implementation (maybe it will be more performant & accurate) - /// -edit: it is :) + /// ULA (Uncommitted Logic Array) implementation /// public abstract class ULABase : IVideoProvider { @@ -733,7 +732,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion - #region Attribution /* From a7ed14cfe1a76dc032596a5eca7e114586ecbc46 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 14:00:29 +0000 Subject: [PATCH 080/105] new IPSG interface ahead of trying to A/B a version of the ColecoHawk AY-3-8910 --- .../BizHawk.Emulation.Cores.csproj | 1 + .../Hardware/Abstraction/IPSG.cs | 66 +++++++++++++++++++ .../Hardware/Datacorder/DatacorderDevice.cs | 2 +- .../Hardware/SoundOuput/AY38912.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 +- 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index 272aae7483..ecdd02e979 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -258,6 +258,7 @@ + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs new file mode 100644 index 0000000000..8e8965d7b0 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs @@ -0,0 +1,66 @@ +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// Represents a PSG device (in this case an AY-3-891x) + /// + public interface IPSG : ISoundProvider + { + /// + /// Initlization routine + /// + /// + /// + void Init(int sampleRate, int tStatesPerFrame); + + /// + /// Activates a register + /// + int SelectedRegister { get; set; } + + /// + /// Writes to the PSG + /// + /// + void PortWrite(int value); + + /// + /// Reads from the PSG + /// + int PortRead(); + + /// + /// Resets the PSG + /// + void Reset(); + + /// + /// Called at the start of a frame + /// + void StartFrame(); + + /// + /// called at the end of a frame + /// + void EndFrame(); + + /// + /// Updates the sound based on number of frame cycles + /// + /// + void UpdateSound(int frameCycle); + + /// + /// IStatable serialization + /// + /// + void SyncState(Serializer ser); + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index e71a17d8f0..1bcbf2b022 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -594,7 +594,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine.Spectrum.OSD_TapePlayingAuto(); } - _monitorTimeOut = 90; + _monitorTimeOut = 50; } } else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs index 7443c43dd3..f003ddd6f2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs @@ -7,7 +7,7 @@ using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { - public class AY38912 : ISoundProvider + public class AY38912 : IPSG { private int _tStatesPerFrame; private int _sampleRate; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index c4b241b898..258af9f579 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -42,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Device representing the AY-3-8912 chip found in the 128k and up spectrums /// - public AY38912 AYDevice { get; set; } + public IPSG AYDevice { get; set; } /// /// The spectrum keyboard From f612ae043b829e92924cc6857cf4a3d6b47a1462 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 14:08:53 +0000 Subject: [PATCH 081/105] Disabled tape trap auto-stop (this is more trouble than its worth - tzx formats should include 'stopthetape' blocks anyway, and tap files are generally junk and should be discouraged) --- .../Hardware/Datacorder/DatacorderDevice.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 1bcbf2b022..a7db98253d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -614,15 +614,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { _monitorTimeOut--; - if (_monitorTimeOut < 2) - { - - } - if (_monitorTimeOut < 0) { - Stop(); - _machine.Spectrum.OSD_TapeStoppedAuto(); + //Stop(); + //_machine.Spectrum.OSD_TapeStoppedAuto(); } } } From 9778cc2644c804be731c65e5cd074c6938d098b4 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 18:52:00 +0000 Subject: [PATCH 082/105] Reduced the AY center channel volume for better balance --- .../Hardware/SoundOuput/AY38912.cs | 25 ++++++++--------- .../Hardware/SoundOuput/Buzzer.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 8 +++--- .../SinclairSpectrum/SoundProviderMixer.cs | 27 +++++++++++++++++++ .../Computers/SinclairSpectrum/ZXSpectrum.cs | 2 +- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs index f003ddd6f2..62c3d7829b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs @@ -2,8 +2,6 @@ using BizHawk.Common; using BizHawk.Emulation.Common; using System; -using System.Collections.Generic; -using System.Linq; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -69,9 +67,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void UpdateSound(int currentFrameCycle) { - //if (currentFrameCycle >= _tStatesPerFrame) - //currentFrameCycle = _tStatesPerFrame; - for (int i = 0; i < (currentFrameCycle / AY_SAMPLE_RATE) - _AYCount; i++) { Update(); @@ -113,11 +108,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // the stereo _samples buffer should already have been processed as a part of // ISoundProvider at the end of the last frame - //_samples = new short[_samplesPerFrame * 2]; - //_nsamp = _samplesPerFrame; _sampleCounter = 0; - - //Init(44100, _tStatesPerFrame); } public void EndFrame() @@ -447,15 +438,25 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private void EndSampleAY() { - averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); - averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter]) / soundSampleCounter); + //averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + + // averagedChannelSamples[ChannelCenter] + + // averagedChannelSamples[ChannelRight]) + // / soundSampleCounter); + + // averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelLeft] + + // averagedChannelSamples[ChannelCenter] + + // averagedChannelSamples[ChannelRight]) + // / soundSampleCounter); + + averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter); + averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter); soundSampleCounter = 0; } private void SampleAY() { - int ah; + int ah; ah = regs[AY_ENABLE]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs index 0e98c67c29..667fe519be 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs @@ -236,7 +236,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_tapeMode) samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 6) : (short)0; else - samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 2) : (short)0; + samples[sampleIndex++] = pulse.State ? (short)(short.MaxValue / 3) : (short)0; } currentEnd += pulse.Length; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 258af9f579..bc4f9952bf 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -146,7 +146,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum PollInput(); - while (CurrentFrameCycle < ULADevice.FrameLength) // UlaFrameCycleCount) + while (CurrentFrameCycle < ULADevice.FrameLength) { // check for interrupt ULADevice.CheckForInterrupt(CurrentFrameCycle); @@ -157,8 +157,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // update AY if (_renderSound) { - if (AYDevice != null) - AYDevice.UpdateSound(CurrentFrameCycle); + if (AYDevice != null && CPU.RegPC != 1523) + { + AYDevice.UpdateSound(CurrentFrameCycle); + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs index 1b2e37b835..303eda38b7 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/SoundProviderMixer.cs @@ -45,6 +45,22 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum EqualizeVolumes(); } + public SoundProviderMixer(short maxVolume, params ISoundProvider[] soundProviders) + { + SoundProviders = new List(); + + foreach (var s in soundProviders) + { + SoundProviders.Add(new Provider + { + SoundProvider = s, + MaxVolume = maxVolume, + }); + } + + EqualizeVolumes(); + } + public void AddSource(ISoundProvider source) { SoundProviders.Add(new Provider @@ -56,6 +72,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum EqualizeVolumes(); } + public void AddSource(ISoundProvider source, short maxVolume) + { + SoundProviders.Add(new Provider + { + SoundProvider = source, + MaxVolume = maxVolume + }); + + EqualizeVolumes(); + } + public void DisableSource(ISoundProvider source) { var sp = SoundProviders.Where(a => a.SoundProvider == source); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index cb294a8146..edab502824 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -99,7 +99,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Register(_cpu); ser.Register(_machine.ULADevice); - SoundMixer = new SoundProviderMixer((ISoundProvider)_machine.BuzzerDevice); + SoundMixer = new SoundProviderMixer((int)(32767 / 10), (ISoundProvider)_machine.BuzzerDevice); if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); From 6c01ba3c6af0966ecf6df9f7871fa79ffc0e436a Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 19:01:13 +0000 Subject: [PATCH 083/105] StereoSound core setting fixed (was not used after core initliazation --- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index edab502824..f242fc10d4 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -102,7 +102,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SoundMixer = new SoundProviderMixer((int)(32767 / 10), (ISoundProvider)_machine.BuzzerDevice); if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - + SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound; + + ser.Register(SoundMixer); From 5a2b0ae6a660c18383b5218d334ac09d3e73b046 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 19:16:12 +0000 Subject: [PATCH 084/105] Added joysticks 2 & 3 and removed TapeControls from VirtualPad schema (they are not needed there) --- .../tools/VirtualPads/schema/ZXSpectrumSchema.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs index 1995d75757..cdbf9228c0 100644 --- a/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs +++ b/BizHawk.Client.EmuHawk/tools/VirtualPads/schema/ZXSpectrumSchema.cs @@ -11,19 +11,21 @@ namespace BizHawk.Client.EmuHawk { public IEnumerable GetPadSchemas(IEmulator core) { - yield return KempstonJoystick(1); + yield return Joystick(1); + yield return Joystick(2); + yield return Joystick(3); yield return Keyboard(); - yield return TapeDevice(); + //yield return TapeDevice(); } - private static PadSchema KempstonJoystick(int controller) + private static PadSchema Joystick(int controller) { return new PadSchema { - DisplayName = "Player " + controller + " (Kempton Joystick)", + DisplayName = "Joystick " + controller, IsConsole = false, DefaultSize = new Size(174, 74), - MaxSize = new Size(174, 74), + MaxSize = new Size(174, 74), Buttons = new[] { new PadSchema.ButtonSchema From d23dc0a296b9053cbcb61e2c61347fa204024397 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 20:31:13 +0000 Subject: [PATCH 085/105] Finally fixed tape trap auto loading/stopping routines --- .../Hardware/Datacorder/DatacorderDevice.cs | 22 ++++++++++++- .../Machine/SpectrumBase.Memory.cs | 33 +++++++++++++++++++ .../Machine/ZXSpectrum128K/ZX128.Memory.cs | 2 ++ .../ZX128Plus2a.Memory.cs | 2 ++ .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 2 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs | 2 ++ .../Machine/ZXSpectrum16K/ZX16.cs | 4 ++- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 27 +++++++++++++-- 8 files changed, 89 insertions(+), 5 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index a7db98253d..90bb8af1df 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -548,7 +548,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// An iteration of the monitor process /// public void MonitorRead() - { + { long cpuCycle = _cpu.TotalExecutedCycles; int delta = (int)(cpuCycle - _lastINCycle); _lastINCycle = cpuCycle; @@ -607,11 +607,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _monitorLastPC = _cpu.RegPC; } + public void AutoStopTape() + { + if (!_tapeIsPlaying) + return; + + Stop(); + _machine.Spectrum.OSD_TapeStoppedAuto(); + } + + public void AutoStartTape() + { + if (_tapeIsPlaying) + return; + + Play(); + _machine.Spectrum.OSD_TapePlayingAuto(); + } private void MonitorFrame() { + /* if (_tapeIsPlaying && _machine.Spectrum.Settings.AutoLoadTape) { + _monitorTimeOut--; if (_monitorTimeOut < 0) @@ -620,6 +639,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //_machine.Spectrum.OSD_TapeStoppedAuto(); } } + */ } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index 48058ee084..a25bbc8186 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -176,6 +176,39 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return false; } + + public virtual void TestForTapeTraps(int addr) + { + if (!TapeDevice.TapeIsPlaying) + { + if (addr == 8) + { + TapeDevice?.AutoStopTape(); + return; + } + + if (addr == 4223) + { + TapeDevice?.AutoStopTape(); + return; + } + + if (addr == 83) + { + TapeDevice?.AutoStopTape(); + return; + } + } + else + { + if (addr == 1366) + { + TapeDevice?.AutoStartTape(); + return; + } + } + } + #endregion } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs index e76cffbaf7..e769fc46ef 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Memory.cs @@ -54,6 +54,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // ROM 0x000 case 0: + TestForTapeTraps(addr % 0x4000); + if (ROMPaged == 0) result = ROM0[addr % 0x4000]; else diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs index 33aa0c2bcf..265a413002 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Memory.cs @@ -137,9 +137,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { case 0: result = ROM0[addr % 0x4000]; + TestForTapeTraps(addr % 0x4000); break; case 1: result = ROM1[addr % 0x4000]; + TestForTapeTraps(addr % 0x4000); break; case 2: result = ROM2[addr % 0x4000]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index 4f0a9ee47b..24ac1f5f97 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -45,7 +45,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum InputRead = true; // tape loading monitor cycle - TapeDevice.MonitorRead(); + //TapeDevice.MonitorRead(); // process tape INs TapeDevice.ReadPort(port, ref result); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs index ad3d846ffd..bead71f975 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Memory.cs @@ -137,9 +137,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { case 0: result = ROM0[addr % 0x4000]; + TestForTapeTraps(addr % 0x4000); break; case 1: result = ROM1[addr % 0x4000]; + TestForTapeTraps(addr % 0x4000); break; case 2: result = ROM2[addr % 0x4000]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs index f310b9f7de..3f6499e916 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum16K/ZX16.cs @@ -57,7 +57,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum switch (divisor) { - case 0: return ROM0[index]; + case 0: + TestForTapeTraps(addr % 0x4000); + return ROM0[index]; case 1: return RAM0[index]; default: // memory does not exist diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index f242fc10d4..c09598667c 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -15,8 +15,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum "Asnivor", isPorted: false, isReleased: false)] - [ServiceNotApplicable(typeof(IDriveLight))] - public partial class ZXSpectrum : IRegionable + public partial class ZXSpectrum : IRegionable, IDriveLight { public ZXSpectrum(CoreComm comm, IEnumerable files, List game, object settings, object syncSettings) { @@ -238,6 +237,30 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #endregion + #region IDriveLight + + public bool DriveLightEnabled + { + get + { + return true; + } + } + + public bool DriveLightOn + { + get + { + if (_machine != null && + _machine.TapeDevice != null && + _machine.TapeDevice.TapeIsPlaying) + return true; + + return false; + } + } + + #endregion } } From 6d66eee45998a4f716db2128f5f9dae88eaa8c02 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 20:38:43 +0000 Subject: [PATCH 086/105] Disabled AY chip when memory paging is disabled (i.e. 48k mode) --- .../Computers/SinclairSpectrum/Machine/SpectrumBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index bc4f9952bf..b7fcee7e93 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -140,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_renderSound) { BuzzerDevice.StartFrame(); - if (AYDevice != null) + if (AYDevice != null && !PagingDisabled) AYDevice.StartFrame(); } @@ -157,7 +157,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // update AY if (_renderSound) { - if (AYDevice != null && CPU.RegPC != 1523) + if (AYDevice != null && !PagingDisabled) { AYDevice.UpdateSound(CurrentFrameCycle); } From 7739c0dee103ff5a076060fd901de251a5df166b Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 22:09:46 +0000 Subject: [PATCH 087/105] Handling loadstate issues for different machine configurations --- .../Machine/SpectrumBase.Input.cs | 27 +++++++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 1 + .../ZXSpectrum.Controllers.cs | 13 ++++++ .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 46 +++++++++++++++---- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 3 ++ 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs index c14a7bcc62..978d815c52 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Input.cs @@ -19,6 +19,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum string PrevBlock = "Prev Tape Block"; string TapeStatus = "Get Tape Status"; + string HardResetStr = "Hard Reset"; + string SoftResetStr = "Soft Reset"; + bool pressed_Play = false; bool pressed_Stop = false; bool pressed_RTZ = false; @@ -27,6 +30,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum bool pressed_NextBlock = false; bool pressed_PrevBlock = false; bool pressed_TapeStatus = false; + bool pressed_HardReset = false; + bool pressed_SoftReset = false; /// /// Cycles through all the input callbacks @@ -191,6 +196,28 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } else pressed_TapeStatus = false; + + if (Spectrum._controller.IsPressed(HardResetStr)) + { + if (!pressed_HardReset) + { + HardReset(); + pressed_HardReset = true; + } + } + else + pressed_HardReset = false; + + if (Spectrum._controller.IsPressed(SoftResetStr)) + { + if (!pressed_SoftReset) + { + SoftReset(); + pressed_SoftReset = true; + } + } + else + pressed_SoftReset = false; } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index b7fcee7e93..d5c1fff9a6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -207,6 +207,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { //ResetBorder(); ULADevice.ResetInterrupt(); + CPU.RegPC = 0; } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs index 1ba7dd5b74..38a281ecca 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.Controllers.cs @@ -82,6 +82,19 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum definition.CategoryLabels[s] = "Keyboard"; } + // Datacorder (tape device) + List power = new List + { + // Tape functions + "Soft Reset", "Hard Reset" + }; + + foreach (var s in power) + { + definition.BoolButtons.Add(s); + definition.CategoryLabels[s] = "Power"; + } + // Datacorder (tape device) List tape = new List { diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index 39bd7c984f..0cd1c37a8d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -50,19 +50,49 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ms.Close(); core = ms.ToArray(); } - _cpu.SyncState(ser); - ser.BeginSection("ZXSpectrum"); - _machine.SyncState(ser); - ser.Sync("Frame", ref _machine.FrameCount); - ser.Sync("LagCount", ref _lagCount); - ser.Sync("IsLag", ref _isLag); + - ser.EndSection(); + if (ser.IsWriter) + { + ser.SyncEnum("_machineType", ref _machineType); + _cpu.SyncState(ser); + ser.BeginSection("ZXSpectrum"); + _machine.SyncState(ser); + ser.Sync("Frame", ref _machine.FrameCount); + ser.Sync("LagCount", ref _lagCount); + ser.Sync("IsLag", ref _isLag); + ser.EndSection(); + } + if (ser.IsReader) { - SyncAllByteArrayDomains(); + var tmpM = _machineType; + ser.SyncEnum("_machineType", ref _machineType); + if (tmpM != _machineType) + { + string msg = "SAVESTATE FAILED TO LOAD!!\n\n"; + msg += "Current Configuration: " + _machineType.ToString(); + msg += "\n"; + msg += "Saved Configuration: " + tmpM.ToString(); + msg += "\n\n"; + msg += "If you with to load this SaveState ensure that you have to correct machine configuration selected, reboot the core, then try again."; + CoreComm.ShowMessage(msg); + _machineType = tmpM; + } + else + { + _cpu.SyncState(ser); + ser.BeginSection("ZXSpectrum"); + _machine.SyncState(ser); + ser.Sync("Frame", ref _machine.FrameCount); + ser.Sync("LagCount", ref _lagCount); + ser.Sync("IsLag", ref _isLag); + ser.EndSection(); + + SyncAllByteArrayDomains(); + } } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index c09598667c..27eb361a88 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -185,9 +185,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return result; } + private MachineType _machineType; private void Init(MachineType machineType, BorderType borderType, TapeLoadSpeed tapeLoadSpeed, List files, List joys) { + _machineType = machineType; + // setup the emulated model based on the MachineType switch (machineType) { From df5cf0d85ffee2d5044874eecde3c3b486a17ccc Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 22:13:26 +0000 Subject: [PATCH 088/105] Type fix for sir feos --- .../Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index 0cd1c37a8d..8192f219e1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -77,7 +77,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum msg += "\n"; msg += "Saved Configuration: " + tmpM.ToString(); msg += "\n\n"; - msg += "If you with to load this SaveState ensure that you have to correct machine configuration selected, reboot the core, then try again."; + msg += "If you with to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again."; CoreComm.ShowMessage(msg); _machineType = tmpM; } From 8234b2acfa4c94e0cc2d70fb3f94141dc7be8b44 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Tue, 13 Mar 2018 22:15:53 +0000 Subject: [PATCH 089/105] more typos. FML --- .../Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index 8192f219e1..bbd45c713d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -77,7 +77,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum msg += "\n"; msg += "Saved Configuration: " + tmpM.ToString(); msg += "\n\n"; - msg += "If you with to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again."; + msg += "If you wish to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again."; CoreComm.ShowMessage(msg); _machineType = tmpM; } From 7a36f913ece9c6cbe522b759872f4c8323b32a4c Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 15 Mar 2018 16:32:26 +0000 Subject: [PATCH 090/105] New AY-3-8912 implementation. Better sounding and more performant --- .../BizHawk.Emulation.Cores.csproj | 2 +- .../Hardware/Abstraction/IPSG.cs | 5 +- .../Hardware/Datacorder/DatacorderDevice.cs | 4 + .../Hardware/Input/StandardKeyboard.cs | 3 + .../Hardware/SoundOuput/AY38912.cs | 718 ---------------- .../Hardware/SoundOuput/AYChip.cs | 802 ++++++++++++++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 15 +- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 69 +- .../Machine/ZXSpectrum128K/ZX128.cs | 2 +- .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 229 ++--- .../ZXSpectrum128KPlus2a/ZX128Plus2a.cs | 2 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 109 +-- .../Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs | 2 +- .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 16 +- .../SinclairSpectrum/ZXSpectrum.IStatable.cs | 8 +- .../Computers/SinclairSpectrum/ZXSpectrum.cs | 7 +- 16 files changed, 1020 insertions(+), 973 deletions(-) delete mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs create mode 100644 BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj index ecdd02e979..b9790199e8 100644 --- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj +++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj @@ -263,7 +263,7 @@ - + diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs index 8e8965d7b0..a6028aa2d6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Abstraction/IPSG.cs @@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Represents a PSG device (in this case an AY-3-891x) /// - public interface IPSG : ISoundProvider + public interface IPSG : ISoundProvider, IPortIODevice { /// /// Initlization routine @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Activates a register /// int SelectedRegister { get; set; } - + /// /// Writes to the PSG /// @@ -35,6 +35,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Reads from the PSG /// int PortRead(); + /// /// Resets the PSG diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 90bb8af1df..066570f6c9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -158,6 +158,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_tapeIsPlaying) return; + _machine.BuzzerDevice.SetTapeMode(true); + _machine.Spectrum.OSD_TapePlaying(); // update the lastCycle @@ -210,6 +212,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (!_tapeIsPlaying) return; + _machine.BuzzerDevice.SetTapeMode(false); + _machine.Spectrum.OSD_TapeStopped(); // sign that the tape is no longer playing diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs index f4289edf92..0616003465 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Input/StandardKeyboard.cs @@ -339,6 +339,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum (for instance by XOR A/IN A,(FE)) is one, no key is pressed */ + if ((port & 0x0001) != 0) + return false; + if ((port & 0x8000) == 0) { result &= KeyLine[7]; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs deleted file mode 100644 index 62c3d7829b..0000000000 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AY38912.cs +++ /dev/null @@ -1,718 +0,0 @@ - -using BizHawk.Common; -using BizHawk.Emulation.Common; -using System; - -namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum -{ - public class AY38912 : IPSG - { - private int _tStatesPerFrame; - private int _sampleRate; - private int _samplesPerFrame; - private int _tStatesPerSample; - private int _sampleCounter; - const int AY_SAMPLE_RATE = 16; - private int _AYCyclesPerFrame; - private int _nsamp; - private int _AYCount; - - - /// - /// The final sample buffer - /// - private short[] _samples = new short[0]; - - /// - /// Number of samples in one frame - /// - public int SamplesPerFrame - { - get { return _samplesPerFrame; } - set { _samplesPerFrame = value; } - } - - /// - /// Number of TStates in each sample - /// - public int TStatesPerSample - { - get { return _tStatesPerSample; } - set { _tStatesPerSample = value; } - } - - #region Construction & Initialisation - - public AY38912() - { - Reset(); - } - - /// - /// Initialises the AY chip - /// - public void Init(int sampleRate, int tStatesPerFrame) - { - _sampleRate = sampleRate; - _tStatesPerFrame = tStatesPerFrame; - _tStatesPerSample = 79; - _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; - _AYCyclesPerFrame = _tStatesPerFrame / AY_SAMPLE_RATE; - - _samples = new short[_samplesPerFrame * 2]; - _nsamp = _samplesPerFrame; - } - - #endregion - - public void UpdateSound(int currentFrameCycle) - { - for (int i = 0; i < (currentFrameCycle / AY_SAMPLE_RATE) - _AYCount; i++) - { - Update(); - SampleAY(); - _AYCount++; - } - - // calculate how many samples must be processed - int samplesToGenerate = (currentFrameCycle / _tStatesPerSample) - (_sampleCounter / 2); - - // begin generation - if (samplesToGenerate > 0) - { - // ensure the required resolution - while (soundSampleCounter < 4) - { - SampleAY(); - } - EndSampleAY(); - - // generate needed samples - for (int i = 0; i < samplesToGenerate; i++) - { - _samples[_sampleCounter++] = (short)(averagedChannelSamples[0]); - _samples[_sampleCounter++] = (short)(averagedChannelSamples[1]); - - samplesToGenerate--; - } - - averagedChannelSamples[0] = 0; - averagedChannelSamples[1] = 0; - averagedChannelSamples[2] = 0; - } - } - - public void StartFrame() - { - _AYCount = 0; - - // the stereo _samples buffer should already have been processed as a part of - // ISoundProvider at the end of the last frame - _sampleCounter = 0; - } - - public void EndFrame() - { - } - - - public void Reset() - { - // reset volumes - for (int i = 0; i < 16; i++) - AY_SpecVolumes[i] = (short)(AY_Volumes[i] * 8191); - - soundSampleCounter = 0; - regs[AY_NOISEPER] = 0xFF; - noiseOut = 0x01; - envelopeVolume = 0; - noiseCount = 0; - - // reset state of all channels - for (int f = 0; f < 3; f++) - { - channel_count[f] = 0; - channel_mix[f] = 0; - channel_out[f] = 0; - averagedChannelSamples[f] = 0; - } - - envelopeCount = 0; - randomSeed = 1; - selectedRegister = 0; - } - - #region IStatable - - public void SyncState(Serializer ser) - { - ser.BeginSection("AY38912"); - ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); - ser.Sync("_sampleRate", ref _sampleRate); - ser.Sync("_samplesPerFrame", ref _samplesPerFrame); - ser.Sync("_tStatesPerSample", ref _tStatesPerSample); - ser.Sync("_sampleCounter", ref _sampleCounter); - - ser.Sync("ChannelLeft", ref ChannelLeft); - ser.Sync("ChannelRight", ref ChannelRight); - ser.Sync("ChannelCenter", ref ChannelCenter); - ser.Sync("Regs", ref regs, false); - ser.Sync("NoiseOut", ref noiseOut); - ser.Sync("envelopeVolume", ref envelopeVolume); - ser.Sync("noiseCount", ref noiseCount); - ser.Sync("envelopeCount", ref envelopeCount); - ser.Sync("randomSeed", ref randomSeed); - ser.Sync("envelopeClock", ref envelopeClock); - ser.Sync("selectedRegister", ref selectedRegister); - ser.Sync("soundSampleCounter", ref soundSampleCounter); - ser.Sync("stereoSound", ref stereoSound); - ser.Sync("sustaining", ref sustaining); - ser.Sync("sustain", ref sustain); - ser.Sync("alternate", ref alternate); - ser.Sync("attack", ref attack); - ser.Sync("envelopeStep", ref envelopeStep); - - ser.Sync("channel_out", ref channel_out, false); - ser.Sync("channel_count", ref channel_count, false); - ser.Sync("averagedChannelSamples", ref averagedChannelSamples, false); - ser.Sync("channel_mix", ref channel_mix, false); - - ser.Sync("AY_SpecVolumes", ref AY_SpecVolumes, false); - - ser.Sync("_samples", ref _samples, false); - ser.Sync("_nsamp", ref _nsamp); - ser.EndSection(); - } - - #endregion - - #region AY Sound Implementation - - /* - Based on the AYSound class from ArjunNair's Zero-Emulator - https://github.com/ArjunNair/Zero-Emulator/ - *MIT LICENSED* - - The MIT License (MIT) - Copyright (c) 2009 Arjun Nair - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE - FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - /// - /// Register constants - /// - private const byte AY_A_FINE = 0; - private const byte AY_A_COARSE = 1; - private const byte AY_B_FINE = 2; - private const byte AY_B_COARSE = 3; - private const byte AY_C_FINE = 4; - private const byte AY_C_COARSE = 5; - private const byte AY_NOISEPER = 6; - private const byte AY_ENABLE = 7; - private const byte AY_A_VOL = 8; - private const byte AY_B_VOL = 9; - private const byte AY_C_VOL = 10; - private const byte AY_E_FINE = 11; - private const byte AY_E_COARSE = 12; - private const byte AY_E_SHAPE = 13; - private const byte AY_PORT_A = 14; - private const byte AY_PORT_B = 15; - - /// - /// Channels - /// - internal enum Channel - { - A, B, C - } - - /// - /// ACB configuration - /// - private int ChannelLeft = 0; - private int ChannelRight = 1; //2 if ABC - private int ChannelCenter = 2; //1 if ABC - - /// - /// Register storage - /// - private int[] regs = new int[16]; - - /// - /// State - /// - private int noiseOut; - private int envelopeVolume; - private int noiseCount; - private int envelopeCount; - private ulong randomSeed; - private byte envelopeClock = 0; - private int selectedRegister; - public ushort soundSampleCounter; - private bool stereoSound = false; - private bool sustaining; - private bool sustain; - private bool alternate; - private int attack; - private int envelopeStep; - - /// - /// Buffer arrays - /// - private int[] channel_out = new int[3]; - private int[] channel_count = new int[3]; - private int[] averagedChannelSamples = new int[3]; - private short[] channel_mix = new short[3]; - - /// - /// Measurements from comp.sys.sinclair (2001 Matthew Westcott) - /// - private float[] AY_Volumes = - { - 0.0000f, 0.0137f, 0.0205f, 0.0291f, - 0.0423f, 0.0618f, 0.0847f, 0.1369f, - 0.1691f, 0.2647f, 0.3527f, 0.4499f, - 0.5704f, 0.6873f, 0.8482f, 1.0000f - }; - - /// - /// Volume storage (short) - /// - private short[] AY_SpecVolumes = new short[16]; - - /// - /// Sets the ACB configuration - /// - /// - public void SetSpeakerACB(bool val) - { - // ACB - if (val) - { - ChannelCenter = 2; - ChannelRight = 1; - } - // ABC - else - { - ChannelCenter = 1; - ChannelRight = 2; - } - } - - /// - /// Utility method to set all registers externally - /// - /// - public void SetRegisters(byte[] _regs) - { - for (int f = 0; f < 16; f++) - regs[f] = _regs[f]; - } - - /// - /// Utility method to get all registers externally - /// - /// - public byte[] GetRegisters() - { - byte[] newArray = new byte[16]; - for (int f = 0; f < 16; f++) - newArray[f] = (byte)(regs[f] & 0xff); - return newArray; - } - - /// - /// Selected Register property - /// - public int SelectedRegister - { - get { return selectedRegister; } - set { if (value < 16) selectedRegister = value; } - } - - /// - /// Simulates a port write to the AY chip - /// - /// - public void PortWrite(int val) - { - switch (SelectedRegister) - { - // not implemented / necessary - case AY_A_FINE: - case AY_B_FINE: - case AY_C_FINE: - case AY_E_FINE: - case AY_E_COARSE: - break; - - case AY_A_COARSE: - case AY_B_COARSE: - case AY_C_COARSE: - val &= 0x0f; - break; - - case AY_NOISEPER: - case AY_A_VOL: - case AY_B_VOL: - case AY_C_VOL: - val &= 0x1f; - break; - - case AY_ENABLE: - /* - if ((lastEnable == -1) || ((lastEnable & 0x40) != (regs[AY_ENABLE] & 0x40))) { - SelectedRegister = ((regs[AY_ENABLE] & 0x40) > 0 ? regs[AY_PORT_B] : 0xff); - } - if ((lastEnable == -1) || ((lastEnable & 0x80) != (regs[AY_ENABLE] & 0x80))) { - PortWrite((regs[AY_ENABLE] & 0x80) > 0 ? regs[AY_PORT_B] : 0xff); - } - lastEnable = regs[AY_ENABLE];*/ - break; - - case AY_E_SHAPE: - val &= 0x0f; - attack = ((val & 0x04) != 0 ? 0x0f : 0x00); - // envelopeCount = 0; - if ((val & 0x08) == 0) - { - /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */ - sustain = true; - alternate = (attack != 0); - } - else - { - sustain = (val & 0x01) != 0; - alternate = (val & 0x02) != 0; - } - envelopeStep = 0x0f; - sustaining = false; - envelopeVolume = (envelopeStep ^ attack); - break; - - case AY_PORT_A: - /* - if ((regs[AY_ENABLE] & 0x40) > 0) { - selectedRegister = regs[AY_PORT_A]; - }*/ - break; - - case AY_PORT_B: - /* - if ((regs[AY_ENABLE] & 0x80) > 0) { - PortWrite(regs[AY_PORT_A]); - }*/ - break; - } - - regs[SelectedRegister] = val; - } - - /// - /// Simulates port reads from the AY chip - /// - /// - public int PortRead() - { - if (SelectedRegister == AY_PORT_B) - { - if ((regs[AY_ENABLE] & 0x80) == 0) - return 0xff; - else - return regs[AY_PORT_B]; - } - - return regs[selectedRegister]; - } - - private void EndSampleAY() - { - //averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + - // averagedChannelSamples[ChannelCenter] + - // averagedChannelSamples[ChannelRight]) - // / soundSampleCounter); - - // averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelLeft] + - // averagedChannelSamples[ChannelCenter] + - // averagedChannelSamples[ChannelRight]) - // / soundSampleCounter); - - averagedChannelSamples[0] = (short)((averagedChannelSamples[ChannelLeft] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter); - averagedChannelSamples[1] = (short)((averagedChannelSamples[ChannelRight] + averagedChannelSamples[ChannelCenter] / 1.5) / soundSampleCounter); - - soundSampleCounter = 0; - } - - private void SampleAY() - { - int ah; - - ah = regs[AY_ENABLE]; - - channel_mix[(int)Channel.A] = MixChannel(ah, regs[AY_A_VOL], (int)Channel.A); - - ah >>= 1; - channel_mix[(int)Channel.B] = MixChannel(ah, regs[AY_B_VOL], (int)Channel.B); - - ah >>= 1; - channel_mix[(int)Channel.C] = MixChannel(ah, regs[AY_C_VOL], (int)Channel.C); - - averagedChannelSamples[0] += channel_mix[(int)Channel.A]; - averagedChannelSamples[1] += channel_mix[(int)Channel.B]; - averagedChannelSamples[2] += channel_mix[(int)Channel.C]; - soundSampleCounter++; - } - - private short MixChannel(int ah, int cl, int chan) - { - int al = channel_out[chan]; - int bl, bh; - bl = ah; - bh = ah; - bh &= 0x1; - bl >>= 3; - - al |= (bh); //Tone | AY_ENABLE - bl |= (noiseOut); //Noise | AY_ENABLE - al &= bl; - - if ((al != 0)) - { - if ((cl & 16) != 0) - cl = envelopeVolume; - - cl &= 15; - - //return (AY_Volumes[cl]); - return (AY_SpecVolumes[cl]); - } - return 0; - } - - /// - /// Gets the tone period for the specified channel - /// - /// - /// - private int TonePeriod(int channel) - { - return (regs[(channel) << 1] | ((regs[((channel) << 1) | 1] & 0x0f) << 8)); - } - - /// - /// Gets the noise period for the specified channel - /// - /// - private int NoisePeriod() - { - return (regs[AY_NOISEPER] & 0x1f); - } - - /// - /// Gets the envelope period for the specified channel - /// - /// - private int EnvelopePeriod() - { - return ((regs[AY_E_FINE] | (regs[AY_E_COARSE] << 8))); - } - - /// - /// Gets the noise enable value for the specified channel - /// - /// - /// - private int NoiseEnable(int channel) - { - return ((regs[AY_ENABLE] >> (3 + channel)) & 1); - } - - /// - /// Gets the tone enable value for the specified channel - /// - /// - /// - private int ToneEnable(int channel) - { - return ((regs[AY_ENABLE] >> (channel)) & 1); - } - - /// - /// Gets the tone envelope value for the specified channel - /// - /// - /// - private int ToneEnvelope(int channel) - { - //return ((regs[AY_A_VOL + channel] & 0x10) >> 4); - return ((regs[AY_A_VOL + channel] >> 4) & 0x1); - } - - /// - /// Updates noise - /// - private void UpdateNoise() - { - noiseCount++; - if (noiseCount >= NoisePeriod() && (noiseCount > 4)) - { - /* Is noise output going to change? */ - if (((randomSeed + 1) & 2) != 0) /* (bit0^bit1)? */ - { - noiseOut ^= 1; - } - - /* The Random Number Generator of the 8910 is a 17-bit shift */ - /* register. The input to the shift register is bit0 XOR bit3 */ - /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */ - - /* The following is a fast way to compute bit17 = bit0^bit3. */ - /* Instead of doing all the logic operations, we only check */ - /* bit0, relying on the fact that after three shifts of the */ - /* register, what now is bit3 will become bit0, and will */ - /* invert, if necessary, bit14, which previously was bit17. */ - if ((randomSeed & 1) != 0) - randomSeed ^= 0x24000; /* This version is called the "Galois configuration". */ - randomSeed >>= 1; - noiseCount = 0; - } - } - - /// - /// Updates envelope - /// - private void UpdateEnvelope() - { - /* update envelope */ - if (!sustaining) - { - envelopeCount++; - if ((envelopeCount >= EnvelopePeriod())) - { - envelopeStep--; - - /* check envelope current position */ - if (envelopeStep < 0) - { - if (sustain) - { - if (alternate) - attack ^= 0x0f; - sustaining = true; - envelopeStep = 0; - } - else - { - /* if CountEnv has looped an odd number of times (usually 1), */ - /* invert the output. */ - if (alternate && ((envelopeStep & (0x0f + 1)) != 0) && (envelopeCount > 4)) - attack ^= 0x0f; - - envelopeStep &= 0x0f; - } - } - envelopeCount = 0; - } - } - envelopeVolume = (envelopeStep ^ attack); - } - - - public void Update() - { - envelopeClock ^= 1; - - if (envelopeClock == 1) - { - envelopeCount++; - - //if ((((regs[AY_A_VOL + 0] & 0x10) >> 4) & (((regs[AY_A_VOL + 1] & 0x10) >> 4) & ((regs[AY_A_VOL + 2] & 0x10) >> 4))) != 1) - //if ((((regs[AY_A_VOL + 0] >> 4) & 0x1) & (((regs[AY_A_VOL + 1] >> 4) & 0x1) & ((regs[AY_A_VOL + 2] >> 4) & 0x1))) != 0) - if (((regs[AY_A_VOL + 0] & 0x10) & (regs[AY_A_VOL + 1] & 0x10) & (regs[AY_A_VOL + 2] & 0x10)) != 1) - { - // update envelope - if (!sustaining) - UpdateEnvelope(); - - envelopeVolume = (envelopeStep ^ attack); - } - } - - // update noise - if ((regs[AY_ENABLE] & 0x38) != 0x38) - { - UpdateNoise(); - } - - // update channels - channel_count[0]++; - int regs1 = (regs[1] & 0x0f) << 8; - if (((regs[0] | regs1) > 4) && (channel_count[0] >= (regs[0] | regs1))) - { - channel_out[0] ^= 1; - channel_count[0] = 0; - } - - int regs3 = (regs[3] & 0x0f) << 8; - channel_count[1]++; - if (((regs[2] | regs3) > 4) && (channel_count[1] >= (regs[2] | regs3))) - { - channel_out[1] ^= 1; - channel_count[1] = 0; - } - - int regs5 = (regs[5] & 0x0f) << 8; - channel_count[2]++; - if (((regs[4] | regs5) > 4) && (channel_count[2] >= (regs[4] | regs5))) - { - channel_out[2] ^= 1; - channel_count[2] = 0; - } - } - - - #endregion - - #region ISoundProvider - - 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"); - } - - public void DiscardSamples() - { - - } - - public void GetSamplesSync(out short[] samples, out int nsamp) - { - samples = _samples; - nsamp = _nsamp; - } - - #endregion - - } -} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs new file mode 100644 index 0000000000..06817afe19 --- /dev/null +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs @@ -0,0 +1,802 @@ + +using BizHawk.Common; +using BizHawk.Emulation.Common; +using System; +using System.Collections.Generic; + +namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum +{ + /// + /// AY-3-8912 Emulated Device + /// + /// Based heavily on the YM-2149F / AY-3-8910 emulator used in Unreal Speccy + /// (Originally created under Public Domain license by SMT jan.2006) + /// + /// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.cpp + /// https://github.com/mkoloberdin/unrealspeccy/blob/master/sndrender/sndchip.h + /// + public class AYChip : IPSG + { + #region Device Fields + + /// + /// The emulated machine (passed in via constructor) + /// + private SpectrumBase _machine; + + private int _tStatesPerFrame; + private int _sampleRate; + private int _samplesPerFrame; + private int _tStatesPerSample; + private short[] _audioBuffer; + private int _audioBufferIndex; + private int _lastStateRendered; + + #endregion + + #region Construction & Initialization + + /// + /// Main constructor + /// + public AYChip(SpectrumBase machine) + { + _machine = machine; + } + + /// + /// Initialises the AY chip + /// + public void Init(int sampleRate, int tStatesPerFrame) + { + InitTiming(sampleRate, tStatesPerFrame); + UpdateVolume(); + Reset(); + } + + #endregion + + #region IPortIODevice + + public bool ReadPort(ushort port, ref int value) + { + if (port != 0xfffd) + { + // port read is not addressing this device + return false; + } + + value = PortRead(); + + return true; + } + + public bool WritePort(ushort port, int value) + { + if (port == 0xfffd) + { + // register select + SelectedRegister = value & 0x0f; + return true; + } + else if (port == 0xbffd) + { + // Update the audiobuffer based on the current CPU cycle + // (this process the previous data BEFORE writing to the currently selected register) + int d = (int)(_machine.CurrentFrameCycle); + BufferUpdate(d); + + // write to register + PortWrite(value); + return true; + } + return false; + } + + #endregion + + #region AY Implementation + + #region Public Properties + + /// + /// AY mixer panning configuration + /// + [Flags] + public enum AYPanConfig + { + MONO = 0, + ABC = 1, + ACB = 2, + BAC = 3, + BCA = 4, + CAB = 5, + CBA = 6, + } + + /// + /// The AY panning configuration + /// + public AYPanConfig PanningConfiguration + { + get + { + return _currentPanTab; + } + set + { + if (value != _currentPanTab) + { + _currentPanTab = value; + UpdateVolume(); + } + } + } + + /// + /// The AY chip output volume + /// (0 - 100) + /// + public int Volume + { + get + { + return _volume; + } + set + { + value = Math.Max(0, value); + value = Math.Max(100, value); + if (_volume == value) + { + return; + } + _volume = value; + UpdateVolume(); + } + } + + /// + /// The currently selected register + /// + public int SelectedRegister + { + get { return _activeRegister; } + set + { + _activeRegister = (byte)value; + } + } + + #endregion + + #region Public Methods + + /// + /// Resets the PSG + /// + public void Reset() + { + /* + _noiseVal = 0x0FFFF; + _outABC = 0; + _outNoiseABC = 0; + _counterNoise = 0; + _counterA = 0; + _counterB = 0; + _counterC = 0; + _EnvelopeCounterBend = 0; + + // clear all the registers + for (int i = 0; i < 14; i++) + { + SelectedRegister = i; + PortWrite(0); + } + + randomSeed = 1; + + // number of frames to update + var fr = (_audioBufferIndex * _tStatesPerFrame) / _audioBuffer.Length; + + // update the audio buffer + BufferUpdate(fr); + */ + } + + /// + /// Reads the value from the currently selected register + /// + /// + public int PortRead() + { + return _registers[_activeRegister]; + } + + /// + /// Writes to the currently selected register + /// + /// + public void PortWrite(int value) + { + if (_activeRegister >= 0x10) + return; + + byte val = (byte)value; + + if (((1 << _activeRegister) & ((1 << 1) | (1 << 3) | (1 << 5) | (1 << 13))) != 0) + val &= 0x0F; + + if (((1 << _activeRegister) & ((1 << 6) | (1 << 8) | (1 << 9) | (1 << 10))) != 0) + val &= 0x1F; + + if (_activeRegister != 13 && _registers[_activeRegister] == val) + return; + + _registers[_activeRegister] = val; + + switch (_activeRegister) + { + // Channel A (Combined Pitch) + // (not written to directly) + case 0: + case 1: + _dividerA = _registers[AY_A_FINE] | (_registers[AY_A_COARSE] << 8); + break; + // Channel B (Combined Pitch) + // (not written to directly) + case 2: + case 3: + _dividerB = _registers[AY_B_FINE] | (_registers[AY_B_COARSE] << 8); + break; + // Channel C (Combined Pitch) + // (not written to directly) + case 4: + case 5: + _dividerC = _registers[AY_C_FINE] | (_registers[AY_C_COARSE] << 8); + break; + // Noise Pitch + case 6: + _dividerN = val * 2; + break; + // Mixer + case 7: + _bit0 = 0 - ((val >> 0) & 1); + _bit1 = 0 - ((val >> 1) & 1); + _bit2 = 0 - ((val >> 2) & 1); + _bit3 = 0 - ((val >> 3) & 1); + _bit4 = 0 - ((val >> 4) & 1); + _bit5 = 0 - ((val >> 5) & 1); + break; + // Channel Volumes + case 8: + _eMaskA = (val & 0x10) != 0 ? -1 : 0; + _vA = ((val & 0x0F) * 2 + 1) & ~_eMaskA; + break; + case 9: + _eMaskB = (val & 0x10) != 0 ? -1 : 0; + _vB = ((val & 0x0F) * 2 + 1) & ~_eMaskB; + break; + case 10: + _eMaskC = (val & 0x10) != 0 ? -1 : 0; + _vC = ((val & 0x0F) * 2 + 1) & ~_eMaskC; + break; + // Envelope (Combined Duration) + // (not written to directly) + case 11: + case 12: + _dividerE = _registers[AY_E_FINE] | (_registers[AY_E_COARSE] << 8); + break; + // Envelope Shape + case 13: + // reset the envelope counter + _countE = 0; + + if ((_registers[AY_E_SHAPE] & 4) != 0) + { + // attack + _eState = 0; + _eDirection = 1; + } + else + { + // decay + _eState = 31; + _eDirection = -1; + } + break; + case 14: + // IO Port - not implemented + break; + } + } + + /// + /// Start of frame + /// + public void StartFrame() + { + _audioBufferIndex = 0; + BufferUpdate(0); + } + + /// + /// End of frame + /// + public void EndFrame() + { + BufferUpdate(_tStatesPerFrame); + } + + /// + /// Updates the audiobuffer based on the current frame t-state + /// + /// + public void UpdateSound(int frameCycle) + { + BufferUpdate(frameCycle); + } + + #endregion + + #region Private Fields + + /// + /// Register indicies + /// + private const int AY_A_FINE = 0; + private const int AY_A_COARSE = 1; + private const int AY_B_FINE = 2; + private const int AY_B_COARSE = 3; + private const int AY_C_FINE = 4; + private const int AY_C_COARSE = 5; + private const int AY_NOISEPITCH = 6; + private const int AY_MIXER = 7; + private const int AY_A_VOL = 8; + private const int AY_B_VOL = 9; + private const int AY_C_VOL = 10; + private const int AY_E_FINE = 11; + private const int AY_E_COARSE = 12; + private const int AY_E_SHAPE = 13; + private const int AY_PORT_A = 14; + private const int AY_PORT_B = 15; + + /// + /// The register array + /* + The AY-3-8910/8912 contains 16 internal registers as follows: + + Register Function Range + 0 Channel A fine pitch 8-bit (0-255) + 1 Channel A course pitch 4-bit (0-15) + 2 Channel B fine pitch 8-bit (0-255) + 3 Channel B course pitch 4-bit (0-15) + 4 Channel C fine pitch 8-bit (0-255) + 5 Channel C course pitch 4-bit (0-15) + 6 Noise pitch 5-bit (0-31) + 7 Mixer 8-bit (see below) + 8 Channel A volume 4-bit (0-15, see below) + 9 Channel B volume 4-bit (0-15, see below) + 10 Channel C volume 4-bit (0-15, see below) + 11 Envelope fine duration 8-bit (0-255) + 12 Envelope course duration 8-bit (0-255) + 13 Envelope shape 4-bit (0-15) + 14 I/O port A 8-bit (0-255) + 15 I/O port B 8-bit (0-255) (Not present on the AY-3-8912) + + * The volume registers (8, 9 and 10) contain a 4-bit setting but if bit 5 is set then that channel uses the + envelope defined by register 13 and ignores its volume setting. + * The mixer (register 7) is made up of the following bits (low=enabled): + + Bit: 7 6 5 4 3 2 1 0 + Register: I/O I/O Noise Noise Noise Tone Tone Tone + Channel: B A C B A C B A + + The AY-3-8912 ignores bit 7 of this register. + */ + /// + private int[] _registers = new int[16]; + + /// + /// The currently selected register + /// + private byte _activeRegister; + + /// + /// The frequency of the AY chip + /// + private static int _chipFrequency = 1773400; + + /// + /// The rendering resolution of the chip + /// + private double _resolution = 50D * 8D / _chipFrequency; + + /// + /// Channel generator state + /// + private int _bitA; + private int _bitB; + private int _bitC; + + /// + /// Envelope state + /// + private int _eState; + + /// + /// Envelope direction + /// + private int _eDirection; + + /// + /// Noise seed + /// + private int _noiseSeed; + + /// + /// Mixer state + /// + private int _bit0; + private int _bit1; + private int _bit2; + private int _bit3; + private int _bit4; + private int _bit5; + + /// + /// Noise generator state + /// + private int _bitN; + + /// + /// Envelope masks + /// + private int _eMaskA; + private int _eMaskB; + private int _eMaskC; + + /// + /// Amplitudes + /// + private int _vA; + private int _vB; + private int _vC; + + /// + /// Channel gen counters + /// + private int _countA; + private int _countB; + private int _countC; + + /// + /// Envelope gen counter + /// + private int _countE; + + /// + /// Noise gen counter + /// + private int _countN; + + /// + /// Channel gen dividers + /// + private int _dividerA; + private int _dividerB; + private int _dividerC; + + /// + /// Envelope gen divider + /// + private int _dividerE; + + /// + /// Noise gen divider + /// + private int _dividerN; + + /// + /// Panning table list + /// + private static List PanTabs = new List + { + // MONO + new uint[] { 100,100, 100,100, 100,100 }, + // ABC + new uint[] { 100,10, 66,66, 10,100 }, + // ACB + new uint[] { 100,10, 10,100, 66,66 }, + // BAC + new uint[] { 66,66, 100,10, 10,100 }, + // BCA + new uint[] { 10,100, 100,10, 66,66 }, + // CAB + new uint[] { 66,66, 10,100, 100,10 }, + // CBA + new uint[] { 10,100, 66,66, 100,10 } + }; + + /// + /// The currently selected panning configuration + /// + private AYPanConfig _currentPanTab = AYPanConfig.ABC; + + /// + /// The current volume + /// + private int _volume = 50; + + /// + /// Volume tables state + /// + private uint[][] _volumeTables; + + /// + /// Volume table to be used + /// + private static uint[] AYVolumes = new uint[] + { + 0x0000,0x0000,0x0340,0x0340,0x04C0,0x04C0,0x06F2,0x06F2, + 0x0A44,0x0A44,0x0F13,0x0F13,0x1510,0x1510,0x227E,0x227E, + 0x289F,0x289F,0x414E,0x414E,0x5B21,0x5B21,0x7258,0x7258, + 0x905E,0x905E,0xB550,0xB550,0xD7A0,0xD7A0,0xFFFF,0xFFFF, + }; + + #endregion + + #region Private Methods + + /// + /// Forces an update of the volume tables + /// + private void UpdateVolume() + { + var vol = (ulong)0xFFFF * (ulong)_volume / 100UL; + _volumeTables = new uint[6][]; + + // parent array + for (int j = 0; j < _volumeTables.Length; j++) + { + _volumeTables[j] = new uint[32]; + + // child array + for (int i = 0; i < _volumeTables[j].Length; i++) + { + _volumeTables[j][i] = (uint)( + (PanTabs[(int)_currentPanTab][j] * AYVolumes[i] * vol) / + (3 * 65535 * 100)); + } + } + } + + private int mult_const; + + /// + /// Initializes timing information for the frame + /// + /// + /// + private void InitTiming(int sampleRate, int frameTactCount) + { + _sampleRate = sampleRate; + _tStatesPerFrame = frameTactCount; + + _tStatesPerSample = 79; //(int)Math.Round(((double)_tStatesPerFrame * 50D) / + //(16D * (double)_sampleRate), + //MidpointRounding.AwayFromZero); + + _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + _audioBuffer = new short[_samplesPerFrame * 2]; //[_sampleRate / 50]; + _audioBufferIndex = 0; + + mult_const = ((_chipFrequency / 8) << 14) / _machine.ULADevice.ClockSpeed; + + var aytickspercputick = (double)_machine.ULADevice.ClockSpeed / (double)_chipFrequency; + int ayCyclesPerSample = (int)((double)_tStatesPerSample * (double)aytickspercputick); + } + + /// + /// Updates the audiobuffer based on the current frame t-state + /// + /// + private void BufferUpdate(int cycle) + { + if (cycle > _tStatesPerFrame) + { + // we are outside of the frame - just process the last value + cycle = _tStatesPerFrame; + } + + // get the current length of the audiobuffer + int bufferLength = _samplesPerFrame; // _audioBuffer.Length; + + int toEnd = ((bufferLength * cycle) / _tStatesPerFrame); + + // loop through the number of samples we need to render + while(_audioBufferIndex < toEnd) + { + // run the AY chip processing at the correct resolution + for (int i = 0; i < _tStatesPerSample / 14; i++) + { + if (++_countA >= _dividerA) + { + _countA = 0; + _bitA ^= -1; + } + + if (++_countB >= _dividerB) + { + _countB = 0; + _bitB ^= -1; + } + + if (++_countC >= _dividerC) + { + _countC = 0; + _bitC ^= -1; + } + + if (++_countN >= _dividerN) + { + _countN = 0; + _noiseSeed = (_noiseSeed * 2 + 1) ^ (((_noiseSeed >> 16) ^ (_noiseSeed >> 13)) & 1); + _bitN = 0 - ((_noiseSeed >> 16) & 1); + } + + if (++_countE >= _dividerE) + { + _countE = 0; + _eState += +_eDirection; + + var mask = (1 << _registers[AY_E_SHAPE]); + + if ((_eState & ~31) != 0) + { + if ((mask & ((1 << 0) | (1 << 1) | (1 << 2) | + (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | + (1 << 7) | (1 << 9) | (1 << 15))) != 0) + { + _eState = _eDirection = 0; + } + } + else if ((mask & ((1 << 8) | (1 << 12))) != 0) + { + _eState &= 31; + } + else if ((mask & ((1 << 10) | (1 << 14))) != 0) + { + _eDirection = -_eDirection; + _eState += _eDirection; + } + else + { + // 11,13 + _eState = 31; + _eDirection = 0; + } + } + } + + // mix the sample + var mixA = ((_eMaskA & _eState) | _vA) & ((_bitA | _bit0) & (_bitN | _bit3)); + var mixB = ((_eMaskB & _eState) | _vB) & ((_bitB | _bit1) & (_bitN | _bit4)); + var mixC = ((_eMaskC & _eState) | _vC) & ((_bitC | _bit2) & (_bitN | _bit5)); + + var l = _volumeTables[0][mixA]; + var r = _volumeTables[1][mixA]; + + l += _volumeTables[2][mixB]; + r += _volumeTables[3][mixB]; + l += _volumeTables[4][mixC]; + r += _volumeTables[5][mixC]; + + _audioBuffer[_audioBufferIndex * 2] = (short)l; + _audioBuffer[(_audioBufferIndex * 2) + 1] = (short)r; + + _audioBufferIndex++; + } + + _lastStateRendered = cycle; + } + + #endregion + + #endregion + + #region ISoundProvider + + 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"); + } + + public void DiscardSamples() + { + _audioBuffer = new short[_samplesPerFrame * 2]; + } + + public void GetSamplesSync(out short[] samples, out int nsamp) + { + nsamp = _samplesPerFrame; + samples = _audioBuffer; + DiscardSamples(); + } + + #endregion + + #region State Serialization + + public int nullDump = 0; + + /// + /// State serialization + /// + /// + public void SyncState(Serializer ser) + { + ser.BeginSection("PSG-AY"); + + ser.Sync("_tStatesPerFrame", ref _tStatesPerFrame); + ser.Sync("_sampleRate", ref _sampleRate); + ser.Sync("_samplesPerFrame", ref _samplesPerFrame); + ser.Sync("_tStatesPerSample", ref _tStatesPerSample); + ser.Sync("_audioBufferIndex", ref _audioBufferIndex); + ser.Sync("_audioBuffer", ref _audioBuffer, false); + + ser.Sync("_registers", ref _registers, false); + ser.Sync("_activeRegister", ref _activeRegister); + ser.Sync("_bitA", ref _bitA); + ser.Sync("_bitB", ref _bitB); + ser.Sync("_bitC", ref _bitC); + ser.Sync("_eState", ref _eState); + ser.Sync("_eDirection", ref _eDirection); + ser.Sync("_noiseSeed", ref _noiseSeed); + ser.Sync("_bit0", ref _bit0); + ser.Sync("_bit1", ref _bit1); + ser.Sync("_bit2", ref _bit2); + ser.Sync("_bit3", ref _bit3); + ser.Sync("_bit4", ref _bit4); + ser.Sync("_bit5", ref _bit5); + ser.Sync("_bitN", ref _bitN); + ser.Sync("_eMaskA", ref _eMaskA); + ser.Sync("_eMaskB", ref _eMaskB); + ser.Sync("_eMaskC", ref _eMaskC); + ser.Sync("_vA", ref _vA); + ser.Sync("_vB", ref _vB); + ser.Sync("_vC", ref _vC); + ser.Sync("_countA", ref _countA); + ser.Sync("_countB", ref _countB); + ser.Sync("_countC", ref _countC); + ser.Sync("_countE", ref _countE); + ser.Sync("_countN", ref _countN); + ser.Sync("_dividerA", ref _dividerA); + ser.Sync("_dividerB", ref _dividerB); + ser.Sync("_dividerC", ref _dividerC); + ser.Sync("_dividerE", ref _dividerE); + ser.Sync("_dividerN", ref _dividerN); + ser.SyncEnum("_currentPanTab", ref _currentPanTab); + ser.Sync("_volume", ref nullDump); + + for (int i = 0; i < 6; i++) + { + ser.Sync("volTable" + i, ref _volumeTables[i], false); + } + + ser.EndSection(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index d5c1fff9a6..3e719e72e2 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -140,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_renderSound) { BuzzerDevice.StartFrame(); - if (AYDevice != null && !PagingDisabled) + if (AYDevice != null) AYDevice.StartFrame(); } @@ -155,11 +155,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum CPU.ExecuteOne(); // update AY + int ayCnt = 0; if (_renderSound) { - if (AYDevice != null && !PagingDisabled) + if (AYDevice != null) { - AYDevice.UpdateSound(CurrentFrameCycle); + //AYDevice.UpdateSound(CurrentFrameCycle); + if (ayCnt > 10) + { + //AYDevice.UpdateSound(CurrentFrameCycle); + ayCnt = 0; + } } } } @@ -174,6 +180,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_renderSound) BuzzerDevice.EndFrame(); + if (AYDevice != null) + AYDevice.EndFrame(); + FrameCount++; // setup for next frame diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 130264a4c7..67e164dfe1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -16,11 +16,69 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + bool deviceAddressed = true; + // process IO contention ContendPortAddress(port); int result = 0xFF; + // check AY + if (AYDevice.ReadPort(port, ref result)) + return (byte)result; + + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; + } + else + { + if (KeyboardDevice.ReadPort(port, ref result)) + { + // not a lagframe + InputRead = true; + + // tape loading monitor cycle + TapeDevice.MonitorRead(); + + // process tape INs + TapeDevice.ReadPort(port, ref result); + } + else + deviceAddressed = false; + } + + if (!deviceAddressed) + { + // If this is an unused port the floating memory bus should be returned + // Floating bus is read on the previous cycle + int _tStates = CurrentFrameCycle - 1; + + // if we are on the top or bottom border return 0xff + if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) + { + result = 0xff; + } + else + { + if (ULADevice.floatingBusTable[_tStates] < 0) + { + result = 0xff; + } + else + { + result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]); + } + } + } + + /* + // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; @@ -81,7 +139,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } } - + + */ + return (byte)result; } @@ -102,6 +162,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum int currT = CPU.TotalExecutedCycles; + AYDevice.WritePort(port, value); + // paging if (port == 0x7ffd) { @@ -164,7 +226,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } - + /* // Active AY Register if ((port & 0xc002) == 0xc000) { @@ -178,7 +240,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { AYDevice.PortWrite(value); CPU.TotalExecutedCycles += 3; - } + } + */ } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs index 5e657b0863..3ea955ddca 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.cs @@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); - AYDevice = new AY38912(); + AYDevice = new AYChip(this); AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new StandardKeyboard(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index 24ac1f5f97..3e8d97f36f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -16,11 +16,67 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + bool deviceAddressed = true; + // process IO contention ContendPortAddress(port); int result = 0xFF; + // check AY + if (AYDevice.ReadPort(port, ref result)) + return (byte)result; + + // Kempston joystick input takes priority over all other input + // if this is detected just return the kempston byte + if ((port & 0xe0) == 0 || (port & 0x20) == 0) + { + if (LocateUniqueJoystick(JoystickType.Kempston) != null) + return (byte)((KempstonJoystick)LocateUniqueJoystick(JoystickType.Kempston) as KempstonJoystick).JoyLine; + + InputRead = true; + } + else + { + if (KeyboardDevice.ReadPort(port, ref result)) + { + // not a lagframe + InputRead = true; + + // tape loading monitor cycle + TapeDevice.MonitorRead(); + + // process tape INs + TapeDevice.ReadPort(port, ref result); + } + else + deviceAddressed = false; + } + + if (!deviceAddressed) + { + // If this is an unused port the floating memory bus should be returned + // Floating bus is read on the previous cycle + int _tStates = CurrentFrameCycle - 1; + + // if we are on the top or bottom border return 0xff + if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) + { + result = 0xff; + } + else + { + if (ULADevice.floatingBusTable[_tStates] < 0) + { + result = 0xff; + } + else + { + result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]); + } + } + } + /* // Check whether the low bit is reset // Technically the ULA should respond to every even I/O address bool lowBitReset = (port & 0x0001) == 0; @@ -88,10 +144,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum else result = 0xff; } - */ + *//* // if unused port the floating memory bus should be returned (still todo) } + */ return (byte)result; } @@ -103,6 +160,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override void WritePort(ushort port, byte value) { + // process IO contention + ContendPortAddress(port); + // get a BitArray of the port BitArray portBits = new BitArray(BitConverter.GetBytes(port)); // get a BitArray of the value byte @@ -111,7 +171,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Check whether the low bit is reset bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; - ULADevice.Contend(port); + AYDevice.WritePort(port, value); // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set if (port == 0x7ffd) @@ -131,10 +191,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // portbit 4 is the LOW BIT of the ROM selection ROMlow = bits[4]; - } + } } // port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set - else if (port == 0x1ffd) + if (port == 0x1ffd) { if (!PagingDisabled) { @@ -172,75 +232,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // bit 4 is the printer port strobe PrinterPortStrobe = bits[4]; } - /* - // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set - if (!portBits[1] && !portBits[15] && portBits[14]) - { - // paging (skip if paging has been disabled - paging can then only happen after a machine hard reset) - if (!PagingDisabled) - { - // bit 0 specifies the paging mode - SpecialPagingMode = bits[0]; - - if (!SpecialPagingMode) - { - // we are in normal mode - // portbit 4 is the LOW BIT of the ROM selection - BitArray romHalfNibble = new BitArray(2); - romHalfNibble[0] = portBits[4]; - - // value bit 2 is the high bit of the ROM selection - romHalfNibble[1] = bits[2]; - - // value bit 1 is ignored in normal paging mode - - // set the ROMPage - ROMPaged = ZXSpectrum.GetIntFromBitArray(romHalfNibble); - - - - - // bit 3 controls shadow screen - SHADOWPaged = bits[3]; - - // Bit 5 set signifies that paging is disabled until next reboot - PagingDisabled = bits[5]; - } - } - } - - // port 0x1ffd - special paging mode - // hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set - if (!portBits[1] && portBits[12] && !portBits[13] && !portBits[14] && !portBits[15]) - { - if (!PagingDisabled && SpecialPagingMode) - { - // process special paging - // this is decided based on combinations of bits 1 & 2 - // Config 0 = Bit1-0 Bit2-0 - // Config 1 = Bit1-1 Bit2-0 - // Config 2 = Bit1-0 Bit2-1 - // Config 3 = Bit1-1 Bit2-1 - BitArray confHalfNibble = new BitArray(2); - confHalfNibble[0] = bits[1]; - confHalfNibble[1] = bits[2]; - - // set special paging configuration - PagingConfiguration = ZXSpectrum.GetIntFromBitArray(confHalfNibble); - - // last value should be saved at 0x5b67 (23399) - not sure if this is actually needed - WriteBus(0x5b67, value); - } - - // bit 3 controls the disk motor (1=on, 0=off) - DiskMotorState = bits[3]; - - // bit 4 is the printer port strobe - PrinterPortStrobe = bits[4]; - } - - */ - // Only even addresses address the ULA if (lowBitReset) @@ -268,100 +259,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } - else - { - // AY Register activation - if ((port & 0xc002) == 0xc000) - { - var reg = value & 0x0f; - AYDevice.SelectedRegister = reg; - CPU.TotalExecutedCycles += 3; - } - else - { - if ((port & 0xC002) == 0x8000) - { - AYDevice.PortWrite(value); - CPU.TotalExecutedCycles += 3; - } - - /* - - else - { - if ((port & 0xC002) == 0x4000) //Are bits 1 and 15 reset and bit 14 set? - { - // memory paging activate - if (PagingDisabled) - return; - - // bit 5 handles paging disable (48k mode, persistent until next reboot) - if ((value & 0x20) != 0) - { - PagingDisabled = true; - } - - // shadow screen - if ((value & 0x08) != 0) - { - SHADOWPaged = true; - } - else - { - SHADOWPaged = false; - } - } - else - { - //Extra Memory Paging feature activate - if ((port & 0xF002) == 0x1000) //Is bit 12 set and bits 13,14,15 and 1 reset? - { - if (PagingDisabled) - return; - - // set disk motor state - //todo - - if ((value & 0x08) != 0) - { - //diskDriveState |= (1 << 4); - } - else - { - //diskDriveState &= ~(1 << 4); - } - - if ((value & 0x1) != 0) - { - // activate special paging mode - SpecialPagingMode = true; - PagingConfiguration = (value & 0x6 >> 1); - } - else - { - // normal paging mode - SpecialPagingMode = false; - } - } - else - { - // disk write port - if ((port & 0xF002) == 0x3000) //Is bit 12 set and bits 13,14,15 and 1 reset? - { - //udpDrive.DiskWriteByte((byte)(val & 0xff)); - } - } - } - } - */ - } - } LastULAOutByte = value; - - - - } /// diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs index 88b672d174..3cd1f695d5 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.cs @@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); - AYDevice = new AY38912(); + AYDevice = new AYChip(this); AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new StandardKeyboard(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index 32bd26d842..bcbb31f35d 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -16,14 +16,16 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public override byte ReadPort(ushort port) { + bool deviceAddressed = true; + // process IO contention ContendPortAddress(port); int result = 0xFF; - // Check whether the low bit is reset - // Technically the ULA should respond to every even I/O address - bool lowBitReset = (port & 0x0001) == 0; + // check AY + if (AYDevice.ReadPort(port, ref result)) + return (byte)result; // Kempston joystick input takes priority over all other input // if this is detected just return the kempston byte @@ -34,62 +36,45 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum InputRead = true; } - else if (lowBitReset) - { - // Even I/O address so get input from keyboard - KeyboardDevice.ReadPort(port, ref result); - - TapeDevice.MonitorRead(); - - // not a lagframe - InputRead = true; - - // tape loading monitor cycle - TapeDevice.MonitorRead(); - - // process tape INs - TapeDevice.ReadPort(port, ref result); - } else { - // devices other than the ULA will respond here - // (e.g. the AY sound chip in a 128k spectrum - - // AY register activate - on +3/2a both FFFD and BFFD active AY - if ((port & 0xc002) == 0xc000) + if (KeyboardDevice.ReadPort(port, ref result)) { - result = (int)AYDevice.PortRead(); + // not a lagframe + InputRead = true; + + // tape loading monitor cycle + TapeDevice.MonitorRead(); + + // process tape INs + TapeDevice.ReadPort(port, ref result); } - else if ((port & 0xc002) == 0x8000) + else + deviceAddressed = false; + } + + if (!deviceAddressed) + { + // If this is an unused port the floating memory bus should be returned + // Floating bus is read on the previous cycle + int _tStates = CurrentFrameCycle - 1; + + // if we are on the top or bottom border return 0xff + if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) { - result = (int)AYDevice.PortRead(); + result = 0xff; } - - // Kempston Mouse (not implemented yet) - - - else if ((port & 0xF002) == 0x2000) //Is bit 12 set and bits 13,14,15 and 1 reset? + else { - //result = udpDrive.DiskStatusRead(); - - // disk drive is not yet implemented - return a max status byte for the menu to load - result = 255; - } - else if ((port & 0xF002) == 0x3000) - { - //result = udpDrive.DiskReadByte(); - result = 0; - } - - else if ((port & 0xF002) == 0x0) - { - if (PagingDisabled) - result = 0x1; - else + if (ULADevice.floatingBusTable[_tStates] < 0) + { result = 0xff; + } + else + { + result = ReadBus((ushort)ULADevice.floatingBusTable[_tStates]); + } } - - // if unused port the floating memory bus should be returned (still todo) } return (byte)result; @@ -113,6 +98,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Check whether the low bit is reset bool lowBitReset = !portBits[0]; // (port & 0x01) == 0; + AYDevice.WritePort(port, value); + // port 0x7ffd - hardware should only respond when bits 1 & 15 are reset and bit 14 is set if (port == 0x7ffd) { @@ -134,7 +121,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } // port 0x1ffd - hardware should only respond when bits 1, 13, 14 & 15 are reset and bit 12 is set - else if (port == 0x1ffd) + if (port == 0x1ffd) { if (!PagingDisabled) { @@ -199,25 +186,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } - else - { - // AY Register activation - if ((port & 0xc002) == 0xc000) - { - var reg = value & 0x0f; - AYDevice.SelectedRegister = reg; - CPU.TotalExecutedCycles += 3; - } - else - { - if ((port & 0xC002) == 0x8000) - { - AYDevice.PortWrite(value); - CPU.TotalExecutedCycles += 3; - } - } - } - + LastULAOutByte = value; } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs index b9038e3af1..916232bcb6 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.cs @@ -31,7 +31,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum BuzzerDevice = new Buzzer(this); BuzzerDevice.Init(44100, ULADevice.FrameLength); - AYDevice = new AY38912(); + AYDevice = new AYChip(this); AYDevice.Init(44100, ULADevice.FrameLength); KeyboardDevice = new StandardKeyboard(this); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index 059a2fcaa1..c3204f1c69 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -24,8 +24,13 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public bool PutSettings(ZXSpectrumSettings o) { - if (SoundMixer != null) - SoundMixer.Stereo = o.StereoSound; + //if (SoundMixer != null) + //SoundMixer.Stereo = o.StereoSound; + + if (_machine != null && _machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip)) + { + ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = o.AYPanConfig; + } Settings = o; @@ -48,10 +53,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum [DefaultValue(true)] public bool AutoLoadTape { get; set; } + /* [DisplayName("Stereo Sound")] [Description("Turn stereo sound on or off")] [DefaultValue(true)] public bool StereoSound { get; set; } + */ + + [DisplayName("AY-3-8912 Panning Config")] + [Description("Set the PSG panning configuration.\nThe chip has 3 audio channels that can be outputed in different configurations")] + [DefaultValue(AYChip.AYPanConfig.ABC)] + public AYChip.AYPanConfig AYPanConfig { get; set; } [DisplayName("Core OSD Message Verbosity")] [Description("Full: Display all GUI messages\nMedium: Display only emulator/device generated messages\nNone: Show no messages")] diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs index bbd45c713d..01419fd093 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IStatable.cs @@ -51,8 +51,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum core = ms.ToArray(); } - - if (ser.IsWriter) { ser.SyncEnum("_machineType", ref _machineType); @@ -70,12 +68,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { var tmpM = _machineType; ser.SyncEnum("_machineType", ref _machineType); - if (tmpM != _machineType) + if (tmpM != _machineType && _machineType.ToString() != "72") { string msg = "SAVESTATE FAILED TO LOAD!!\n\n"; - msg += "Current Configuration: " + _machineType.ToString(); + msg += "Current Configuration: " + tmpM.ToString(); msg += "\n"; - msg += "Saved Configuration: " + tmpM.ToString(); + msg += "Saved Configuration: " + _machineType.ToString(); msg += "\n\n"; msg += "If you wish to load this SaveState ensure that you have the correct machine configuration selected, reboot the core, then try again."; CoreComm.ShowMessage(msg); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs index 27eb361a88..7f72bf4b4e 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.cs @@ -101,7 +101,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum SoundMixer = new SoundProviderMixer((int)(32767 / 10), (ISoundProvider)_machine.BuzzerDevice); if (_machine.AYDevice != null) SoundMixer.AddSource(_machine.AYDevice); - SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound; + //SoundMixer.Stereo = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).StereoSound; + + if (_machine.AYDevice != null && _machine.AYDevice.GetType() == typeof(AYChip)) + { + ((AYChip)_machine.AYDevice as AYChip).PanningConfiguration = ((ZXSpectrumSettings)settings as ZXSpectrumSettings).AYPanConfig; + } ser.Register(SoundMixer); From 760ae8edf0ec6b9117d0068a98f7244778de64be Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 15 Mar 2018 17:19:13 +0000 Subject: [PATCH 091/105] Fixed 128k and +2 memory paging bug --- .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 ++ .../Machine/ZXSpectrum128K/ZX128.Port.cs | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 3e719e72e2..14ca115602 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -251,6 +251,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("PagingDisabled", ref PagingDisabled); ser.Sync("SpecialPagingMode", ref SpecialPagingMode); ser.Sync("PagingConfiguration", ref PagingConfiguration); + //ser.Sync("ROMhigh", ref ROMhigh); + //ser.Sync("ROMlow", ref ROMlow); RomData.SyncState(ser); KeyboardDevice.SyncState(ser); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 67e164dfe1..a4a69e9877 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -167,8 +167,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // paging if (port == 0x7ffd) { - if (PagingDisabled) - return; + //if (PagingDisabled) + //return; // Bits 0, 1, 2 select the RAM page var rp = value & 0x07; @@ -178,6 +178,15 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // bit 3 controls shadow screen SHADOWPaged = bits[3]; + if (SHADOWPaged == false) + { + + } + else + { + + } + // ROM page if (bits[4]) { @@ -192,7 +201,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Bit 5 set signifies that paging is disabled until next reboot PagingDisabled = bits[5]; - return; } // Check whether the low bit is reset From 8ebcadbc583f412145fa716894f408e4506a1683 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Thu, 15 Mar 2018 20:47:47 -0400 Subject: [PATCH 092/105] z80: fix port addressing in some cases --- BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs index 4a1c46cc03..c53b013a86 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Tables_Indirect.cs @@ -419,7 +419,7 @@ private void IN_OP_R(ushort operation, ushort repeat_instr) { cur_instr = new ushort[] - {IN, ALU, C, + {IN, ALU, C, B, IDLE, WR, L, H, ALU, IDLE, @@ -438,7 +438,7 @@ cur_instr = new ushort[] {RD, ALU, L, H, IDLE, - OUT, C, ALU, + OUT, C, B, ALU, IDLE, IDLE, operation, L, H, From 396f875ac21077b40912a9221bf0d71502e56843 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Thu, 15 Mar 2018 20:58:08 -0400 Subject: [PATCH 093/105] z80 disassembler: handle address wrap --- BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs index a312975d78..effe7400df 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs @@ -435,6 +435,12 @@ namespace BizHawk.Emulation.Cores.Components.Z80A addr += extra_inc; size = addr - start_addr; + // handle case of addr wrapping around at 16 bit boundary + if (addr < start_addr) + { + size = addr + 1; + } + return temp; } From 4ecb247c170569b7046e2b0c686d0b63d5317203 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 08:08:35 +0000 Subject: [PATCH 094/105] Fixed AY-3-8912 overflow bug --- .../Hardware/SoundOuput/AYChip.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs index 06817afe19..34515cd5eb 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/AYChip.cs @@ -650,32 +650,32 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _countE = 0; _eState += +_eDirection; - var mask = (1 << _registers[AY_E_SHAPE]); - if ((_eState & ~31) != 0) { + var mask = (1 << _registers[AY_E_SHAPE]); + if ((mask & ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 9) | (1 << 15))) != 0) { _eState = _eDirection = 0; } - } - else if ((mask & ((1 << 8) | (1 << 12))) != 0) - { - _eState &= 31; - } - else if ((mask & ((1 << 10) | (1 << 14))) != 0) - { - _eDirection = -_eDirection; - _eState += _eDirection; - } - else - { - // 11,13 - _eState = 31; - _eDirection = 0; - } + else if ((mask & ((1 << 8) | (1 << 12))) != 0) + { + _eState &= 31; + } + else if ((mask & ((1 << 10) | (1 << 14))) != 0) + { + _eDirection = -_eDirection; + _eState += _eDirection; + } + else + { + // 11,13 + _eState = 31; + _eDirection = 0; + } + } } } From 5bb1d1f327f807c40b8c882197295c673f81d4a5 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 10:45:57 +0000 Subject: [PATCH 095/105] Fixed +2a/+3 ROM paging bytes missing from SaveState serialization --- .../Computers/SinclairSpectrum/Machine/SpectrumBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 14ca115602..3c220d3157 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -251,8 +251,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum ser.Sync("PagingDisabled", ref PagingDisabled); ser.Sync("SpecialPagingMode", ref SpecialPagingMode); ser.Sync("PagingConfiguration", ref PagingConfiguration); - //ser.Sync("ROMhigh", ref ROMhigh); - //ser.Sync("ROMlow", ref ROMlow); + ser.Sync("ROMhigh", ref ROMhigh); + ser.Sync("ROMlow", ref ROMlow); RomData.SyncState(ser); KeyboardDevice.SyncState(ser); From 7c9c39417b9d0b35338b4b1778a572992a6b5f10 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 11:58:02 +0000 Subject: [PATCH 096/105] UI - added joystick settings menu --- .../BizHawk.Client.EmuHawk.csproj | 9 + BizHawk.Client.EmuHawk/MainForm.Designer.cs | 40 +- BizHawk.Client.EmuHawk/MainForm.Events.cs | 5 + BizHawk.Client.EmuHawk/MainForm.cs | 4 +- .../ZXSpectrumJoystickSettings.Designer.cs | 184 +++++ .../ZXSpectrum/ZXSpectrumJoystickSettings.cs | 127 ++++ .../ZXSpectrumJoystickSettings.resx | 630 ++++++++++++++++++ 7 files changed, 981 insertions(+), 18 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index ccca5cf3eb..6ed196a29b 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -497,6 +497,12 @@ TI83PaletteConfig.cs + + Form + + + ZXSpectrumJoystickSettings.cs + Form @@ -1395,6 +1401,9 @@ TI83PaletteConfig.cs + + ZXSpectrumJoystickSettings.cs + CoreFeatureAnalysis.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index dcb8272627..6a3da605ba 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -380,6 +380,7 @@ this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainStatusBar = new StatusStripEx(); this.DumpStatusButton = new System.Windows.Forms.ToolStripDropDownButton(); @@ -452,7 +453,7 @@ this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator(); this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); - this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); @@ -3237,7 +3238,7 @@ this.C64DisksSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStripSeparator36}); this.C64DisksSubMenu.Name = "C64DisksSubMenu"; - this.C64DisksSubMenu.Size = new System.Drawing.Size(125, 22); + this.C64DisksSubMenu.Size = new System.Drawing.Size(152, 22); this.C64DisksSubMenu.Text = "Disks"; this.C64DisksSubMenu.DropDownOpened += new System.EventHandler(this.C64DisksSubMenu_DropDownOpened); // @@ -3249,7 +3250,7 @@ // C64SettingsMenuItem // this.C64SettingsMenuItem.Name = "C64SettingsMenuItem"; - this.C64SettingsMenuItem.Size = new System.Drawing.Size(125, 22); + this.C64SettingsMenuItem.Size = new System.Drawing.Size(152, 22); this.C64SettingsMenuItem.Text = "&Settings..."; this.C64SettingsMenuItem.Click += new System.EventHandler(this.C64SettingsMenuItem_Click); // @@ -3281,7 +3282,7 @@ // preferencesToolStripMenuItem // this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; - this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(152, 22); this.preferencesToolStripMenuItem.Text = "Preferences..."; this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); // @@ -3296,7 +3297,7 @@ // preferencesToolStripMenuItem3 // this.preferencesToolStripMenuItem3.Name = "preferencesToolStripMenuItem3"; - this.preferencesToolStripMenuItem3.Size = new System.Drawing.Size(144, 22); + this.preferencesToolStripMenuItem3.Size = new System.Drawing.Size(152, 22); this.preferencesToolStripMenuItem3.Text = "Preferences..."; this.preferencesToolStripMenuItem3.Click += new System.EventHandler(this.preferencesToolStripMenuItem3_Click); // @@ -3326,7 +3327,7 @@ // preferencesToolStripMenuItem2 // this.preferencesToolStripMenuItem2.Name = "preferencesToolStripMenuItem2"; - this.preferencesToolStripMenuItem2.Size = new System.Drawing.Size(152, 22); + this.preferencesToolStripMenuItem2.Size = new System.Drawing.Size(144, 22); this.preferencesToolStripMenuItem2.Text = "Preferences..."; this.preferencesToolStripMenuItem2.Click += new System.EventHandler(this.preferencesToolStripMenuItem2_Click); // @@ -3346,7 +3347,7 @@ // this.OnlineHelpMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.Help; this.OnlineHelpMenuItem.Name = "OnlineHelpMenuItem"; - this.OnlineHelpMenuItem.Size = new System.Drawing.Size(146, 22); + this.OnlineHelpMenuItem.Size = new System.Drawing.Size(152, 22); this.OnlineHelpMenuItem.Text = "&Online Help..."; this.OnlineHelpMenuItem.Click += new System.EventHandler(this.OnlineHelpMenuItem_Click); // @@ -3354,7 +3355,7 @@ // this.ForumsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.TAStudio; this.ForumsMenuItem.Name = "ForumsMenuItem"; - this.ForumsMenuItem.Size = new System.Drawing.Size(146, 22); + this.ForumsMenuItem.Size = new System.Drawing.Size(152, 22); this.ForumsMenuItem.Text = "Forums..."; this.ForumsMenuItem.Click += new System.EventHandler(this.ForumsMenuItem_Click); // @@ -3362,7 +3363,7 @@ // this.FeaturesMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.kitchensink; this.FeaturesMenuItem.Name = "FeaturesMenuItem"; - this.FeaturesMenuItem.Size = new System.Drawing.Size(146, 22); + this.FeaturesMenuItem.Size = new System.Drawing.Size(152, 22); this.FeaturesMenuItem.Text = "&Features"; this.FeaturesMenuItem.Click += new System.EventHandler(this.FeaturesMenuItem_Click); // @@ -3370,19 +3371,27 @@ // this.AboutMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.CorpHawkSmall; this.AboutMenuItem.Name = "AboutMenuItem"; - this.AboutMenuItem.Size = new System.Drawing.Size(146, 22); + this.AboutMenuItem.Size = new System.Drawing.Size(152, 22); this.AboutMenuItem.Text = "&About"; this.AboutMenuItem.Click += new System.EventHandler(this.AboutMenuItem_Click); // // zXSpectrumToolStripMenuItem // this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.ZXSpectrumControllerConfigurationMenuItem, this.preferencesToolStripMenuItem4}); this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem"; this.zXSpectrumToolStripMenuItem.Size = new System.Drawing.Size(87, 19); this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum"; this.zXSpectrumToolStripMenuItem.DropDownOpened += new System.EventHandler(this.zXSpectrumToolStripMenuItem_DropDownOpened); // + // preferencesToolStripMenuItem4 + // + this.preferencesToolStripMenuItem4.Name = "preferencesToolStripMenuItem4"; + this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(192, 22); + this.preferencesToolStripMenuItem4.Text = "Preferences"; + this.preferencesToolStripMenuItem4.Click += new System.EventHandler(this.preferencesToolStripMenuItem4_Click); + // // Atari7800HawkCoreMenuItem // this.Atari7800HawkCoreMenuItem.Name = "Atari7800HawkCoreMenuItem"; @@ -4009,12 +4018,12 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // - // preferencesToolStripMenuItem4 + // ZXSpectrumControllerConfigurationMenuItem // - this.preferencesToolStripMenuItem4.Name = "preferencesToolStripMenuItem4"; - this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(152, 22); - this.preferencesToolStripMenuItem4.Text = "Preferences"; - this.preferencesToolStripMenuItem4.Click += new System.EventHandler(this.preferencesToolStripMenuItem4_Click); + this.ZXSpectrumControllerConfigurationMenuItem.Name = "ZXSpectrumControllerConfigurationMenuItem"; + this.ZXSpectrumControllerConfigurationMenuItem.Size = new System.Drawing.Size(192, 22); + this.ZXSpectrumControllerConfigurationMenuItem.Text = "Joystick Configuration"; + this.ZXSpectrumControllerConfigurationMenuItem.Click += new System.EventHandler(this.ZXSpectrumControllerConfigurationMenuItem_Click); // // MainForm // @@ -4480,5 +4489,6 @@ private System.Windows.Forms.ToolStripMenuItem SMSControllerKeyboardToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem zXSpectrumToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem4; + private System.Windows.Forms.ToolStripMenuItem ZXSpectrumControllerConfigurationMenuItem; } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 7d8a4a3829..858982ec2c 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2449,6 +2449,11 @@ namespace BizHawk.Client.EmuHawk GenericCoreConfig.DoDialog(this, "ZXSpectrum Settings"); } + private void ZXSpectrumControllerConfigurationMenuItem_Click(object sender, EventArgs e) + { + new ZXSpectrumJoystickSettings().ShowDialog(); + } + #endregion #region Help diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index d87fb3522c..137e9d075a 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -4297,9 +4297,7 @@ namespace BizHawk.Client.EmuHawk private void preferencesToolStripMenuItem3_Click(object sender, EventArgs e) { GenericCoreConfig.DoDialog(this, "PC-FX Settings"); - } - - + } private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording) { diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs new file mode 100644 index 0000000000..342e833dc1 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.Designer.cs @@ -0,0 +1,184 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumJoystickSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumJoystickSettings)); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.label5 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.Port2ComboBox = new System.Windows.Forms.ComboBox(); + this.Port1ComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.Port3ComboBox = new System.Windows.Forms.ComboBox(); + this.label2 = new System.Windows.Forms.Label(); + this.lblDoubleSize = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(170, 312); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(60, 23); + this.OkBtn.TabIndex = 3; + this.OkBtn.Text = "&OK"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(236, 312); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(60, 23); + this.CancelBtn.TabIndex = 4; + this.CancelBtn.Text = "&Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(9, 207); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(57, 13); + this.label5.TabIndex = 16; + this.label5.Text = "Joystick 2:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 157); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(57, 13); + this.label4.TabIndex = 15; + this.label4.Text = "Joystick 1:"; + // + // Port2ComboBox + // + this.Port2ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Port2ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.Port2ComboBox.FormattingEnabled = true; + this.Port2ComboBox.Location = new System.Drawing.Point(12, 223); + this.Port2ComboBox.Name = "Port2ComboBox"; + this.Port2ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port2ComboBox.TabIndex = 14; + // + // Port1ComboBox + // + this.Port1ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Port1ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.Port1ComboBox.FormattingEnabled = true; + this.Port1ComboBox.Location = new System.Drawing.Point(12, 173); + this.Port1ComboBox.Name = "Port1ComboBox"; + this.Port1ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port1ComboBox.TabIndex = 13; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 14); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(151, 13); + this.label1.TabIndex = 17; + this.label1.Text = "ZX Spectrum Joystick Settings"; + // + // Port3ComboBox + // + this.Port3ComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.Port3ComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.Port3ComboBox.FormattingEnabled = true; + this.Port3ComboBox.Location = new System.Drawing.Point(12, 275); + this.Port3ComboBox.Name = "Port3ComboBox"; + this.Port3ComboBox.Size = new System.Drawing.Size(284, 21); + this.Port3ComboBox.TabIndex = 18; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 259); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(57, 13); + this.label2.TabIndex = 19; + this.label2.Text = "Joystick 3:"; + // + // lblDoubleSize + // + this.lblDoubleSize.Location = new System.Drawing.Point(26, 40); + this.lblDoubleSize.Name = "lblDoubleSize"; + this.lblDoubleSize.Size = new System.Drawing.Size(254, 117); + this.lblDoubleSize.TabIndex = 20; + this.lblDoubleSize.Text = resources.GetString("lblDoubleSize.Text"); + // + // ZXSpectrumJoystickSettings + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(308, 347); + this.Controls.Add(this.lblDoubleSize); + this.Controls.Add(this.label2); + this.Controls.Add(this.Port3ComboBox); + this.Controls.Add(this.label1); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.Port2ComboBox); + this.Controls.Add(this.Port1ComboBox); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "ZXSpectrumJoystickSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Joystick Settings"; + this.Load += new System.EventHandler(this.IntvControllerSettings_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox Port2ComboBox; + private System.Windows.Forms.ComboBox Port1ComboBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ComboBox Port3ComboBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lblDoubleSize; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs new file mode 100644 index 0000000000..fc151c843c --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.cs @@ -0,0 +1,127 @@ +using System; +using System.Linq; +using System.Windows.Forms; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; + +namespace BizHawk.Client.EmuHawk +{ + public partial class ZXSpectrumJoystickSettings : Form + { + private ZXSpectrum.ZXSpectrumSyncSettings _syncSettings; + + public ZXSpectrumJoystickSettings() + { + InitializeComponent(); + } + + private string[] possibleControllers; + + private void IntvControllerSettings_Load(object sender, EventArgs e) + { + _syncSettings = ((ZXSpectrum)Global.Emulator).GetSyncSettings().Clone(); + + possibleControllers = Enum.GetNames(typeof(JoystickType)); + + foreach (var val in possibleControllers) + { + Port1ComboBox.Items.Add(val); + Port2ComboBox.Items.Add(val); + Port3ComboBox.Items.Add(val); + } + + Port1ComboBox.SelectedItem = _syncSettings.JoystickType1.ToString(); + Port2ComboBox.SelectedItem = _syncSettings.JoystickType2.ToString(); + Port3ComboBox.SelectedItem = _syncSettings.JoystickType3.ToString(); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _syncSettings.JoystickType1.ToString() != Port1ComboBox.SelectedItem.ToString() + || _syncSettings.JoystickType2.ToString() != Port2ComboBox.SelectedItem.ToString() + || _syncSettings.JoystickType3.ToString() != Port3ComboBox.SelectedItem.ToString(); + + if (changed) + { + // enforce unique joystick selection + + bool selectionValid = true; + + var j1 = Port1ComboBox.SelectedItem.ToString(); + if (j1 != possibleControllers.First()) + { + if (j1 == Port2ComboBox.SelectedItem.ToString()) + { + Port2ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + if (j1 == Port3ComboBox.SelectedItem.ToString()) + { + Port3ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + } + + var j2 = Port2ComboBox.SelectedItem.ToString(); + if (j2 != possibleControllers.First()) + { + if (j2 == Port1ComboBox.SelectedItem.ToString()) + { + Port1ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + if (j2 == Port3ComboBox.SelectedItem.ToString()) + { + Port3ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + } + + var j3 = Port3ComboBox.SelectedItem.ToString(); + if (j3 != possibleControllers.First()) + { + if (j3 == Port1ComboBox.SelectedItem.ToString()) + { + Port1ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + if (j3 == Port2ComboBox.SelectedItem.ToString()) + { + Port2ComboBox.SelectedItem = possibleControllers.First(); + selectionValid = false; + } + } + + if (selectionValid) + { + _syncSettings.JoystickType1 = (JoystickType)Enum.Parse(typeof(JoystickType), Port1ComboBox.SelectedItem.ToString()); + _syncSettings.JoystickType2 = (JoystickType)Enum.Parse(typeof(JoystickType), Port2ComboBox.SelectedItem.ToString()); + _syncSettings.JoystickType3 = (JoystickType)Enum.Parse(typeof(JoystickType), Port3ComboBox.SelectedItem.ToString()); + + GlobalWin.MainForm.PutCoreSyncSettings(_syncSettings); + + DialogResult = DialogResult.OK; + Close(); + } + else + { + MessageBox.Show("Invalid joystick configuration. \nDuplicates have automatically been changed to NULL.\n\nPlease review the configuration"); + } + } + else + { + DialogResult = DialogResult.OK; + Close(); + } + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + GlobalWin.OSD.AddMessage("Joystick settings aborted"); + DialogResult = DialogResult.Cancel; + Close(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx new file mode 100644 index 0000000000..c45473925d --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumJoystickSettings.resx @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ZXHawk is set up to allow 3 different unique joysticks to be attached at one time. + +This is because the Kempston joystick had to be attached via a Kempton interface plugged into the single expansion port. The Sinclair and Cursor joysticks effectively mapped to different key presses on the keyboard. + + + + + + AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA + BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ + AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm + AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA + AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// + /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA + AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw + AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA + AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ + AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA + AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE + AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 + dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH + x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI + cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI + h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA + AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH + eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA + AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA + AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA + AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA + AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI + h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA + yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA + AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB + /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// + //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA + d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI + yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH + d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ + /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// + /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA + AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI + iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA + AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// + AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 + PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF + RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU + VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP + UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ + WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s + awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr + agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 + dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf + TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 + +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC + ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh + oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP + kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj + jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk + owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 + swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr + 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme + VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc + XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 + eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg + YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 + dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz + dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz + dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM + 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO + jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A + gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud + iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc + mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer + qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv + rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 + tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV + 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa + mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc + tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA + AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 + AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP + z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb + orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR + l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH + ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA + AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA + AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 + dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 + eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ + fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek + VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P + jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK + iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ + mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi + oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 + ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf + 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA + AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy + NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA + PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM + mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ + hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv + YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// + /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA + BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw + cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K + igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS + kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay + sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS + 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA + AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb + Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 + AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA + AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS + U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP + T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY + V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw + cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 + uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA + AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 + ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 + vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA + ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT + kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck + pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 + OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk + ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br + auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 + c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg + n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA + AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA + vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg + nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA + AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO + zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv + rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH + RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF + RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ + vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB + vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX + V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i + pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ + vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv + L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z + sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 + uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e + nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 + t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV + lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 + u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC + gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej + hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y + sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T + k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn + JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC + QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK + StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ + QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ + PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L + S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ + SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ + Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 + NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km + Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// + AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA + B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA + AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA + AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// + AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q + av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw + cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 + dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 + ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ + Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA + AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA + AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc + HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A + f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z + sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui + of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP + z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB + v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 + vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ + x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O + Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK + yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz + dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc + 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI + zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw + t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il + o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX + V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc + XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 + OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD + ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 + D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA + ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv + 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 + + + \ No newline at end of file From 0ac17f2d1e68d9b67db135aff35e9dab7de6b0d6 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 14:33:18 +0000 Subject: [PATCH 097/105] UI - added Core Emulation Settings menu --- .../BizHawk.Client.EmuHawk.csproj | 9 + BizHawk.Client.EmuHawk/MainForm.Designer.cs | 42 +- BizHawk.Client.EmuHawk/MainForm.Events.cs | 5 + BizHawk.Client.EmuHawk/MainForm.cs | 2 +- ...XSpectrumCoreEmulationSettings.Designer.cs | 187 ++++++ .../ZXSpectrumCoreEmulationSettings.cs | 117 ++++ .../ZXSpectrumCoreEmulationSettings.resx | 624 ++++++++++++++++++ .../SinclairSpectrum/ZXSpectrum.ISettable.cs | 152 +++++ 8 files changed, 1121 insertions(+), 17 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 6ed196a29b..3de675a0f4 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -497,6 +497,12 @@ TI83PaletteConfig.cs + + Form + + + ZXSpectrumCoreEmulationSettings.cs + Form @@ -1401,6 +1407,9 @@ TI83PaletteConfig.cs + + ZXSpectrumCoreEmulationSettings.cs + ZXSpectrumJoystickSettings.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 6a3da605ba..80c7e11780 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -380,6 +380,7 @@ this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainStatusBar = new StatusStripEx(); @@ -453,7 +454,7 @@ this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator(); this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); - this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumCoreEmulationSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); @@ -3238,7 +3239,7 @@ this.C64DisksSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.toolStripSeparator36}); this.C64DisksSubMenu.Name = "C64DisksSubMenu"; - this.C64DisksSubMenu.Size = new System.Drawing.Size(152, 22); + this.C64DisksSubMenu.Size = new System.Drawing.Size(125, 22); this.C64DisksSubMenu.Text = "Disks"; this.C64DisksSubMenu.DropDownOpened += new System.EventHandler(this.C64DisksSubMenu_DropDownOpened); // @@ -3250,7 +3251,7 @@ // C64SettingsMenuItem // this.C64SettingsMenuItem.Name = "C64SettingsMenuItem"; - this.C64SettingsMenuItem.Size = new System.Drawing.Size(152, 22); + this.C64SettingsMenuItem.Size = new System.Drawing.Size(125, 22); this.C64SettingsMenuItem.Text = "&Settings..."; this.C64SettingsMenuItem.Click += new System.EventHandler(this.C64SettingsMenuItem_Click); // @@ -3282,7 +3283,7 @@ // preferencesToolStripMenuItem // this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; - this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(152, 22); + this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(144, 22); this.preferencesToolStripMenuItem.Text = "Preferences..."; this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); // @@ -3297,7 +3298,7 @@ // preferencesToolStripMenuItem3 // this.preferencesToolStripMenuItem3.Name = "preferencesToolStripMenuItem3"; - this.preferencesToolStripMenuItem3.Size = new System.Drawing.Size(152, 22); + this.preferencesToolStripMenuItem3.Size = new System.Drawing.Size(144, 22); this.preferencesToolStripMenuItem3.Text = "Preferences..."; this.preferencesToolStripMenuItem3.Click += new System.EventHandler(this.preferencesToolStripMenuItem3_Click); // @@ -3312,7 +3313,7 @@ // preferencesToolStripMenuItem1 // this.preferencesToolStripMenuItem1.Name = "preferencesToolStripMenuItem1"; - this.preferencesToolStripMenuItem1.Size = new System.Drawing.Size(152, 22); + this.preferencesToolStripMenuItem1.Size = new System.Drawing.Size(144, 22); this.preferencesToolStripMenuItem1.Text = "Preferences..."; this.preferencesToolStripMenuItem1.Click += new System.EventHandler(this.preferencesToolStripMenuItem1_Click); // @@ -3347,7 +3348,7 @@ // this.OnlineHelpMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.Help; this.OnlineHelpMenuItem.Name = "OnlineHelpMenuItem"; - this.OnlineHelpMenuItem.Size = new System.Drawing.Size(152, 22); + this.OnlineHelpMenuItem.Size = new System.Drawing.Size(146, 22); this.OnlineHelpMenuItem.Text = "&Online Help..."; this.OnlineHelpMenuItem.Click += new System.EventHandler(this.OnlineHelpMenuItem_Click); // @@ -3355,7 +3356,7 @@ // this.ForumsMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.TAStudio; this.ForumsMenuItem.Name = "ForumsMenuItem"; - this.ForumsMenuItem.Size = new System.Drawing.Size(152, 22); + this.ForumsMenuItem.Size = new System.Drawing.Size(146, 22); this.ForumsMenuItem.Text = "Forums..."; this.ForumsMenuItem.Click += new System.EventHandler(this.ForumsMenuItem_Click); // @@ -3363,7 +3364,7 @@ // this.FeaturesMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.kitchensink; this.FeaturesMenuItem.Name = "FeaturesMenuItem"; - this.FeaturesMenuItem.Size = new System.Drawing.Size(152, 22); + this.FeaturesMenuItem.Size = new System.Drawing.Size(146, 22); this.FeaturesMenuItem.Text = "&Features"; this.FeaturesMenuItem.Click += new System.EventHandler(this.FeaturesMenuItem_Click); // @@ -3371,13 +3372,14 @@ // this.AboutMenuItem.Image = global::BizHawk.Client.EmuHawk.Properties.Resources.CorpHawkSmall; this.AboutMenuItem.Name = "AboutMenuItem"; - this.AboutMenuItem.Size = new System.Drawing.Size(152, 22); + this.AboutMenuItem.Size = new System.Drawing.Size(146, 22); this.AboutMenuItem.Text = "&About"; this.AboutMenuItem.Click += new System.EventHandler(this.AboutMenuItem_Click); // // zXSpectrumToolStripMenuItem // this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.ZXSpectrumCoreEmulationSettingsMenuItem, this.ZXSpectrumControllerConfigurationMenuItem, this.preferencesToolStripMenuItem4}); this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem"; @@ -3385,10 +3387,17 @@ this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum"; this.zXSpectrumToolStripMenuItem.DropDownOpened += new System.EventHandler(this.zXSpectrumToolStripMenuItem_DropDownOpened); // + // ZXSpectrumControllerConfigurationMenuItem + // + this.ZXSpectrumControllerConfigurationMenuItem.Name = "ZXSpectrumControllerConfigurationMenuItem"; + this.ZXSpectrumControllerConfigurationMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumControllerConfigurationMenuItem.Text = "Joystick Configuration"; + this.ZXSpectrumControllerConfigurationMenuItem.Click += new System.EventHandler(this.ZXSpectrumControllerConfigurationMenuItem_Click); + // // preferencesToolStripMenuItem4 // this.preferencesToolStripMenuItem4.Name = "preferencesToolStripMenuItem4"; - this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(192, 22); + this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(201, 22); this.preferencesToolStripMenuItem4.Text = "Preferences"; this.preferencesToolStripMenuItem4.Click += new System.EventHandler(this.preferencesToolStripMenuItem4_Click); // @@ -4018,12 +4027,12 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // - // ZXSpectrumControllerConfigurationMenuItem + // ZXSpectrumCoreEmulationSettingsMenuItem // - this.ZXSpectrumControllerConfigurationMenuItem.Name = "ZXSpectrumControllerConfigurationMenuItem"; - this.ZXSpectrumControllerConfigurationMenuItem.Size = new System.Drawing.Size(192, 22); - this.ZXSpectrumControllerConfigurationMenuItem.Text = "Joystick Configuration"; - this.ZXSpectrumControllerConfigurationMenuItem.Click += new System.EventHandler(this.ZXSpectrumControllerConfigurationMenuItem_Click); + this.ZXSpectrumCoreEmulationSettingsMenuItem.Name = "ZXSpectrumCoreEmulationSettingsMenuItem"; + this.ZXSpectrumCoreEmulationSettingsMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumCoreEmulationSettingsMenuItem.Text = "Core Emulation Settings"; + this.ZXSpectrumCoreEmulationSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumCoreEmulationSettingsMenuItem_Click); // // MainForm // @@ -4490,5 +4499,6 @@ private System.Windows.Forms.ToolStripMenuItem zXSpectrumToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem4; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumControllerConfigurationMenuItem; + private System.Windows.Forms.ToolStripMenuItem ZXSpectrumCoreEmulationSettingsMenuItem; } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 858982ec2c..d1b6674def 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2454,6 +2454,11 @@ namespace BizHawk.Client.EmuHawk new ZXSpectrumJoystickSettings().ShowDialog(); } + private void ZXSpectrumCoreEmulationSettingsMenuItem_Click(object sender, EventArgs e) + { + new ZXSpectrumCoreEmulationSettings().ShowDialog(); + } + #endregion #region Help diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 137e9d075a..673769f728 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -4297,7 +4297,7 @@ namespace BizHawk.Client.EmuHawk private void preferencesToolStripMenuItem3_Click(object sender, EventArgs e) { GenericCoreConfig.DoDialog(this, "PC-FX Settings"); - } + } private bool Rewind(ref bool runFrame, long currentTimestamp, out bool returnToRecording) { diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs new file mode 100644 index 0000000000..c74ab81fab --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs @@ -0,0 +1,187 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumCoreEmulationSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumCoreEmulationSettings)); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.label4 = new System.Windows.Forms.Label(); + this.MachineSelectionComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.lblMachineNotes = new System.Windows.Forms.Label(); + this.determEmucheckBox1 = new System.Windows.Forms.CheckBox(); + this.label2 = new System.Windows.Forms.Label(); + this.borderTypecomboBox1 = new System.Windows.Forms.ComboBox(); + this.lblBorderInfo = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(247, 485); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(60, 23); + this.OkBtn.TabIndex = 3; + this.OkBtn.Text = "&OK"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(313, 485); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(60, 23); + this.CancelBtn.TabIndex = 4; + this.CancelBtn.Text = "&Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(12, 46); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(98, 13); + this.label4.TabIndex = 15; + this.label4.Text = "Emulated Machine:"; + // + // MachineSelectionComboBox + // + this.MachineSelectionComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.MachineSelectionComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.MachineSelectionComboBox.FormattingEnabled = true; + this.MachineSelectionComboBox.Location = new System.Drawing.Point(12, 62); + this.MachineSelectionComboBox.Name = "MachineSelectionComboBox"; + this.MachineSelectionComboBox.Size = new System.Drawing.Size(361, 21); + this.MachineSelectionComboBox.TabIndex = 13; + this.MachineSelectionComboBox.SelectionChangeCommitted += new System.EventHandler(this.MachineSelectionComboBox_SelectionChangeCommitted); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 14); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(159, 13); + this.label1.TabIndex = 17; + this.label1.Text = "ZX Spectrum Emulation Settings"; + // + // lblMachineNotes + // + this.lblMachineNotes.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblMachineNotes.Location = new System.Drawing.Point(15, 95); + this.lblMachineNotes.Name = "lblMachineNotes"; + this.lblMachineNotes.Size = new System.Drawing.Size(358, 204); + this.lblMachineNotes.TabIndex = 20; + this.lblMachineNotes.Text = "null\r\n"; + // + // determEmucheckBox1 + // + this.determEmucheckBox1.AutoSize = true; + this.determEmucheckBox1.Location = new System.Drawing.Point(15, 302); + this.determEmucheckBox1.Name = "determEmucheckBox1"; + this.determEmucheckBox1.Size = new System.Drawing.Size(135, 17); + this.determEmucheckBox1.TabIndex = 21; + this.determEmucheckBox1.Text = "Deterministic Emulation"; + this.determEmucheckBox1.UseVisualStyleBackColor = true; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 335); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(118, 13); + this.label2.TabIndex = 23; + this.label2.Text = "Rendered Border Type:"; + // + // borderTypecomboBox1 + // + this.borderTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.borderTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.borderTypecomboBox1.FormattingEnabled = true; + this.borderTypecomboBox1.Location = new System.Drawing.Point(12, 351); + this.borderTypecomboBox1.Name = "borderTypecomboBox1"; + this.borderTypecomboBox1.Size = new System.Drawing.Size(157, 21); + this.borderTypecomboBox1.TabIndex = 22; + this.borderTypecomboBox1.SelectedIndexChanged += new System.EventHandler(this.borderTypecomboBox1_SelectedIndexChanged); + // + // lblBorderInfo + // + this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblBorderInfo.Location = new System.Drawing.Point(175, 351); + this.lblBorderInfo.Name = "lblBorderInfo"; + this.lblBorderInfo.Size = new System.Drawing.Size(196, 21); + this.lblBorderInfo.TabIndex = 24; + this.lblBorderInfo.Text = "null"; + this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // ZXSpectrumCoreEmulationSettings + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(385, 520); + this.Controls.Add(this.lblBorderInfo); + this.Controls.Add(this.label2); + this.Controls.Add(this.borderTypecomboBox1); + this.Controls.Add(this.determEmucheckBox1); + this.Controls.Add(this.lblMachineNotes); + this.Controls.Add(this.label1); + this.Controls.Add(this.label4); + this.Controls.Add(this.MachineSelectionComboBox); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "ZXSpectrumCoreEmulationSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Core Emulation Settings"; + this.Load += new System.EventHandler(this.IntvControllerSettings_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox MachineSelectionComboBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label lblMachineNotes; + private System.Windows.Forms.CheckBox determEmucheckBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox borderTypecomboBox1; + private System.Windows.Forms.Label lblBorderInfo; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs new file mode 100644 index 0000000000..bb8ba92d50 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.cs @@ -0,0 +1,117 @@ +using System; +using System.Linq; +using System.Windows.Forms; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; +using System.Text; + +namespace BizHawk.Client.EmuHawk +{ + public partial class ZXSpectrumCoreEmulationSettings : Form + { + private ZXSpectrum.ZXSpectrumSyncSettings _syncSettings; + + public ZXSpectrumCoreEmulationSettings() + { + InitializeComponent(); + } + + private void IntvControllerSettings_Load(object sender, EventArgs e) + { + _syncSettings = ((ZXSpectrum)Global.Emulator).GetSyncSettings().Clone(); + + // machine selection + var machineTypes = Enum.GetNames(typeof(MachineType)); + foreach (var val in machineTypes) + { + MachineSelectionComboBox.Items.Add(val); + } + MachineSelectionComboBox.SelectedItem = _syncSettings.MachineType.ToString(); + UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString())); + + // border selecton + var borderTypes = Enum.GetNames(typeof(ZXSpectrum.BorderType)); + foreach (var val in borderTypes) + { + borderTypecomboBox1.Items.Add(val); + } + borderTypecomboBox1.SelectedItem = _syncSettings.BorderType.ToString(); + UpdateBorderNotes((ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), borderTypecomboBox1.SelectedItem.ToString())); + + // deterministic emulation + determEmucheckBox1.Checked = _syncSettings.DeterministicEmulation; + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _syncSettings.MachineType.ToString() != MachineSelectionComboBox.SelectedItem.ToString() + || _syncSettings.BorderType.ToString() != borderTypecomboBox1.SelectedItem.ToString() + || _syncSettings.DeterministicEmulation != determEmucheckBox1.Checked; + + if (changed) + { + _syncSettings.MachineType = (MachineType)Enum.Parse(typeof(MachineType), MachineSelectionComboBox.SelectedItem.ToString()); + _syncSettings.BorderType = (ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), borderTypecomboBox1.SelectedItem.ToString()); + _syncSettings.DeterministicEmulation = determEmucheckBox1.Checked; + + GlobalWin.MainForm.PutCoreSyncSettings(_syncSettings); + + DialogResult = DialogResult.OK; + Close(); + } + else + { + DialogResult = DialogResult.OK; + Close(); + } + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + GlobalWin.OSD.AddMessage("Core emulator settings aborted"); + DialogResult = DialogResult.Cancel; + Close(); + } + + private void MachineSelectionComboBox_SelectionChangeCommitted(object sender, EventArgs e) + { + ComboBox cb = sender as ComboBox; + UpdateMachineNotes((MachineType)Enum.Parse(typeof(MachineType), cb.SelectedItem.ToString())); + } + + private void UpdateMachineNotes(MachineType type) + { + lblMachineNotes.Text = ZXMachineMetaData.GetMetaString(type); + } + + private void borderTypecomboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + ComboBox cb = sender as ComboBox; + UpdateBorderNotes((ZXSpectrum.BorderType)Enum.Parse(typeof(ZXSpectrum.BorderType), cb.SelectedItem.ToString())); + } + + private void UpdateBorderNotes(ZXSpectrum.BorderType type) + { + switch (type) + { + case ZXSpectrum.BorderType.Full: + lblBorderInfo.Text = "Original border sizes"; + break; + case ZXSpectrum.BorderType.Medium: + lblBorderInfo.Text = "All borders 24px"; + break; + case ZXSpectrum.BorderType.None: + lblBorderInfo.Text = "No border at all"; + break; + case ZXSpectrum.BorderType.Small: + lblBorderInfo.Text = "All borders 10px"; + break; + case ZXSpectrum.BorderType.Widescreen: + lblBorderInfo.Text = "No top and bottom border (almost 16:9)"; + break; + } + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA + BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ + AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm + AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA + AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// + /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA + AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw + AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA + AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ + AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA + AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE + AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 + dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH + x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI + cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI + h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA + AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH + eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA + AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA + AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA + AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA + AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI + h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA + yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA + AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB + /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// + //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA + d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI + yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH + d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ + /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// + /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA + AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI + iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA + AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// + AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 + PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF + RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU + VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP + UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ + WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s + awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr + agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 + dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf + TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 + +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC + ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh + oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP + kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj + jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk + owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 + swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr + 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme + VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc + XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 + eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg + YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 + dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz + dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz + dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM + 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO + jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A + gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud + iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc + mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer + qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv + rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 + tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV + 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa + mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc + tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA + AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 + AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP + z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb + orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR + l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH + ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA + AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA + AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 + dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 + eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ + fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek + VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P + jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK + iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ + mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi + oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 + ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf + 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA + AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy + NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA + PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM + mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ + hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv + YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// + /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA + BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw + cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K + igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS + kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay + sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS + 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA + AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb + Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 + AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA + AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS + U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP + T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY + V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw + cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXjU1h6NnZv/Fc3T/x3Bx/8dw + cf/HcHH/x3Bx/8dwcf/HcHH/w3Jz/6ZoZ/9ZMzPTAQAAPQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyFxccektK0b12 + dv/HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xXR0/69wb/9jOjneBwMDSQAAAAUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNKSlNlmBf9sh3d//HcHH/x3Bx/8dwcf/HcHH/x3Bx/8dwcf/HcHH/xnd3/7Z4d/9sQUDnDgcHVQAA + AAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABkOjqKsXFw/8lyc//HcXL/yHJz/8l0df/JdXb/yXV2/8l1dv/JdHX/ynt7/7+B + f/94SknvFgsLZQAAAAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAACILCxB7TUzDwXd3/8lyc//KdXb/y3h5/8x7fP/NfX7/zX5+/819 + fv/NfH3/zoOC/8iJiP+GVVX3Hg8QegAAABIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEMiIi+SXl3oynp7/8t4ef/NfX7/z4GC/9GE + hf/Sh4j/04iJ/9KIiP/Rhof/04uK/8+RkP+XY2L9KxcXlwAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABwAA + AA0AAAAPAAAACwAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUvL1enbW37zn5+/85/ + gP/Rhob/1IuM/9aPkP/XkpP/2JOU/9iTlP/XkZH/15OT/9eZl/+rdHP/QSUlvAAAADwAAAAFAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAACQAA + ABgAAAAvAgEBSwcDA2EFAgJoAAAAWAAAADYAAAARAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGU8 + O4W5eXn/0IKD/9KIif/Wj5D/2ZWW/9ubm//dnp//3qCg/92foP/cnZ3/3Jyc/9+in//CiYf/Zj8/4wYC + AnAAAAAbAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AA4AAAAnCQQEUCISEoQ+IiKzVzEx1mU6OuZiOTnmRigo0hgNDZsAAABMAAAAEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAABnVJSK/HhIP/04eI/9aQkf/amJn/3qCh/+Gmp//jq6v/5Kyt/+OsrP/iqan/4aal/+ap + p//Umpj/nmxr/C8ZGboAAABXAAAAGAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAIAAAAOAQAALRkNDWY+IiKpZDo63YZRUfigZGP/sHBv/7V0c/+xcnH/oWZm/2k+PvEfEBCcAAAAMQAA + AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 + uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA + AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 + ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 + vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA + ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT + kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck + pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 + OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk + ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br + auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 + c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg + n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA + AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA + vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg + nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA + AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO + zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv + rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH + RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF + RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ + vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB + vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX + V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i + pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ + vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv + L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z + sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 + uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e + nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 + t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV + lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 + u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC + gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej + hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y + sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T + k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn + JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC + QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK + StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ + QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ + PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L + S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ + SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ + Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 + NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km + Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// + AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA + B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA + AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA + AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// + AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q + av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw + cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 + dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 + ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ + Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA + AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA + AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc + HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A + f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z + sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui + of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP + z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB + v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 + vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ + x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O + Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK + yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz + dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc + 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI + zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw + t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il + o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX + V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc + XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 + OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD + ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 + D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA + ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv + 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 + + + \ No newline at end of file diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs index c3204f1c69..f1c5bdff80 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.ISettable.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using BizHawk.Common; using BizHawk.Emulation.Common; using System.ComponentModel; +using System.Text; namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { @@ -194,4 +195,155 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //Fastest } } + + /// + /// Provides information on each emulated machine + /// + public class ZXMachineMetaData + { + public MachineType MachineType { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Released { get; set; } + public string CPU { get; set; } + public string Memory { get; set; } + public string Video { get; set; } + public string Audio { get; set; } + public string Media { get; set; } + public string OtherMisc { get; set; } + + + public static ZXMachineMetaData GetMetaObject(MachineType type) + { + ZXMachineMetaData m = new ZXMachineMetaData(); + m.MachineType = type; + + switch (type) + { + case MachineType.ZXSpectrum16: + m.Name = "Sinclair ZX Spectrum 16K"; + m.Description = "The original ZX Spectrum 16K RAM version. Aside from available RAM this machine is technically identical to the 48K machine that was released at the same time. "; + m.Description += "Due to the small amount of RAM, very few games were actually made to run on this model."; + m.Released = "1982"; + m.CPU = "Zilog Z80A @ 3.5MHz"; + m.Memory = "16KB ROM / 16KB RAM"; + m.Video = "ULA @ 7MHz - PAL (50.08Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) - Internal Speaker"; + m.Media = "Cassette Tape (via 3rd party external tape player)"; + break; + case MachineType.ZXSpectrum48: + m.Name = "Sinclair ZX Spectrum 48K / 48K+"; + m.Description = "The original ZX Spectrum 48K RAM version. 2 years later a 'plus' version was released that had a better keyboard. "; + m.Description += "Electronically both the 48K and + are identical, so ZXHawk treats them as the same emulated machine. "; + m.Description += "These machines dominated the UK 8-bit home computer market throughout the 1980's so most non-128k only games are compatible."; + m.Released = "1982 (48K) / 1984 (48K+)"; + m.CPU = "Zilog Z80A @ 3.5MHz"; + m.Memory = "16KB ROM / 48KB RAM"; + m.Video = "ULA @ 7MHz - PAL (50.08Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) - Internal Speaker"; + m.Media = "Cassette Tape (via 3rd party external tape player)"; + break; + case MachineType.ZXSpectrum128: + m.Name = "Sinclair ZX Spectrum 128"; + m.Description = "The first Spectrum 128K machine released in Spain in 1985 and later UK in 1986. "; + m.Description += "With an updated ROM and new memory paging system to work around the Z80's 16-bit address bus. "; + m.Description += "The 128 shipped with a copy of the 48k ROM (that is paged in when required) and a new startup menu with the option of dropping into a '48k mode'. "; + m.Description += "Even so, there were some compatibility issues with older Spectrum games that were written to utilise some of the previous model's intricacies. "; + m.Description += "Many games released after 1985 supported the new AY-3-8912 PSG chip making for far superior audio. The extra memory also enabled many games to be loaded in all at once (rather than loading each level from tape when needed)."; + m.Released = "1985 / 1986"; + m.CPU = "Zilog Z80A @ 3.5469 MHz"; + m.Memory = "32KB ROM / 128KB RAM"; + m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output"; + m.Media = "Cassette Tape (via 3rd party external tape player)"; + break; + case MachineType.ZXSpectrum128Plus2: + m.Name = "Sinclair ZX Spectrum +2"; + m.Description = "The first Sinclair Spectrum 128K machine that was released after Amstrad purchased Sinclair in 1986. "; + m.Description += "Electronically it was almost identical to the 128, but with the addition of a built-in tape deck and 2 Sinclair Joystick ports."; + m.Released = "1986"; + m.CPU = "Zilog Z80A @ 3.5469 MHz"; + m.Memory = "32KB ROM / 128KB RAM"; + m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output"; + m.Media = "Cassette Tape (via built-in Datacorder)"; + break; + case MachineType.ZXSpectrum128Plus2a: + m.Name = "Sinclair ZX Spectrum +2a"; + m.Description = "The +2a looks almost identical to the +2 but is a variant of the +3 machine that was released the same year (except with the same built-in datacorder that the +2 had rather than a floppy drive). "; + m.Description += "Memory paging again changed significantly and this (along with memory contention timing changes) caused more compatibility issues with some older games. "; + m.Description += "Although functionally identical to the +3, it does not contain floppy disk controller."; + m.Released = "1987"; + m.CPU = "Zilog Z80A @ 3.5469 MHz"; + m.Memory = "64KB ROM / 128KB RAM"; + m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output"; + m.Media = "Cassette Tape (via built-in Datacorder)"; + break; + case MachineType.ZXSpectrum128Plus3: + m.Name = "Sinclair ZX Spectrum +3"; + m.Description = "Amstrad released the +3 the same year as the +2a, but it featured a built-in floppy drive rather than a datacorder. An external cassette player could still be connected though as in the older 48k models. "; + m.Description += "Memory paging again changed significantly and this (along with memory contention timing changes) caused more compatibility issues with some older games. "; + m.Description += "Currently ZXHawk does not emulate the floppy drive or floppy controller so the machine reports as a +2a on boot."; + m.Released = "1987"; + m.CPU = "Zilog Z80A @ 3.5469 MHz"; + m.Memory = "64KB ROM / 128KB RAM"; + m.Video = "ULA @ 7.0938MHz - PAL (50.01Hz Interrupt)"; + m.Audio = "Beeper (HW 1ch. / 10oct.) & General Instruments AY-3-8912 PSG (3ch) - RF Output"; + m.Media = "3\" Floppy Disk (via built-in Floppy Drive)"; + break; + } + return m; + } + + public static string GetMetaString(MachineType type) + { + var m = GetMetaObject(type); + + StringBuilder sb = new StringBuilder(); + + sb.Append(m.Name); + sb.Append("\n"); + sb.Append("-----------------------------------------------------------------\n"); + // Release + sb.Append("Released:"); + sb.Append(" "); + sb.Append(m.Released); + sb.Append("\n"); + // CPU + sb.Append("CPU:"); + sb.Append(" "); + sb.Append(m.CPU); + sb.Append("\n"); + // Memory + sb.Append("Memory:"); + sb.Append(" "); + sb.Append(m.Memory); + sb.Append("\n"); + // Video + sb.Append("Video:"); + sb.Append(" "); + sb.Append(m.Video); + sb.Append("\n"); + // Audio + sb.Append("Audio:"); + sb.Append(" "); + sb.Append(m.Audio); + sb.Append("\n"); + // Audio + sb.Append("Media:"); + sb.Append(" "); + sb.Append(m.Media); + sb.Append("\n"); + + sb.Append("-----------------------------------------------------------------\n"); + // description + sb.Append(m.Description); + if (m.OtherMisc != null) + sb.Append("\n" + m.OtherMisc); + + return sb.ToString(); + + } + } } From a0e2695811b7acfb5b5b3b18aefcd759ca4dcb87 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 15:12:55 +0000 Subject: [PATCH 098/105] UI - Added last non-sync settings menu --- .../BizHawk.Client.EmuHawk.csproj | 9 + BizHawk.Client.EmuHawk/MainForm.Designer.cs | 22 +- BizHawk.Client.EmuHawk/MainForm.Events.cs | 5 + ...XSpectrumCoreEmulationSettings.Designer.cs | 6 +- .../ZXSpectrumNonSyncSettings.Designer.cs | 162 +++++ .../ZXSpectrum/ZXSpectrumNonSyncSettings.cs | 66 ++ .../ZXSpectrum/ZXSpectrumNonSyncSettings.resx | 624 ++++++++++++++++++ 7 files changed, 885 insertions(+), 9 deletions(-) create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs create mode 100644 BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx diff --git a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj index 3de675a0f4..b620d3b6eb 100644 --- a/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj +++ b/BizHawk.Client.EmuHawk/BizHawk.Client.EmuHawk.csproj @@ -497,6 +497,12 @@ TI83PaletteConfig.cs + + Form + + + ZXSpectrumNonSyncSettings.cs + Form @@ -1407,6 +1413,9 @@ TI83PaletteConfig.cs + + ZXSpectrumNonSyncSettings.cs + ZXSpectrumCoreEmulationSettings.cs diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 80c7e11780..28e7f9368b 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -380,6 +380,7 @@ this.FeaturesMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.AboutMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumCoreEmulationSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -454,7 +455,7 @@ this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator(); this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); - this.ZXSpectrumCoreEmulationSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumNonSyncSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); @@ -3381,12 +3382,20 @@ this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ZXSpectrumCoreEmulationSettingsMenuItem, this.ZXSpectrumControllerConfigurationMenuItem, + this.ZXSpectrumNonSyncSettingsMenuItem, this.preferencesToolStripMenuItem4}); this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem"; this.zXSpectrumToolStripMenuItem.Size = new System.Drawing.Size(87, 19); this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum"; this.zXSpectrumToolStripMenuItem.DropDownOpened += new System.EventHandler(this.zXSpectrumToolStripMenuItem_DropDownOpened); // + // ZXSpectrumCoreEmulationSettingsMenuItem + // + this.ZXSpectrumCoreEmulationSettingsMenuItem.Name = "ZXSpectrumCoreEmulationSettingsMenuItem"; + this.ZXSpectrumCoreEmulationSettingsMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumCoreEmulationSettingsMenuItem.Text = "Core Emulation Settings"; + this.ZXSpectrumCoreEmulationSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumCoreEmulationSettingsMenuItem_Click); + // // ZXSpectrumControllerConfigurationMenuItem // this.ZXSpectrumControllerConfigurationMenuItem.Name = "ZXSpectrumControllerConfigurationMenuItem"; @@ -4027,12 +4036,12 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // - // ZXSpectrumCoreEmulationSettingsMenuItem + // ZXSpectrumNonSyncSettingsMenuItem // - this.ZXSpectrumCoreEmulationSettingsMenuItem.Name = "ZXSpectrumCoreEmulationSettingsMenuItem"; - this.ZXSpectrumCoreEmulationSettingsMenuItem.Size = new System.Drawing.Size(201, 22); - this.ZXSpectrumCoreEmulationSettingsMenuItem.Text = "Core Emulation Settings"; - this.ZXSpectrumCoreEmulationSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumCoreEmulationSettingsMenuItem_Click); + this.ZXSpectrumNonSyncSettingsMenuItem.Name = "ZXSpectrumNonSyncSettingsMenuItem"; + this.ZXSpectrumNonSyncSettingsMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumNonSyncSettingsMenuItem.Text = "Non-Sync Settings"; + this.ZXSpectrumNonSyncSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumNonSyncSettingsMenuItem_Click); // // MainForm // @@ -4500,5 +4509,6 @@ private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem4; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumControllerConfigurationMenuItem; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumCoreEmulationSettingsMenuItem; + private System.Windows.Forms.ToolStripMenuItem ZXSpectrumNonSyncSettingsMenuItem; } } diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index d1b6674def..6ce8d550d7 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2459,6 +2459,11 @@ namespace BizHawk.Client.EmuHawk new ZXSpectrumCoreEmulationSettings().ShowDialog(); } + private void ZXSpectrumNonSyncSettingsMenuItem_Click(object sender, EventArgs e) + { + new ZXSpectrumNonSyncSettings().ShowDialog(); + } + #endregion #region Help diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs index c74ab81fab..d7e1cf1e63 100644 --- a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumCoreEmulationSettings.Designer.cs @@ -44,7 +44,7 @@ // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(247, 485); + this.OkBtn.Location = new System.Drawing.Point(247, 393); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(60, 23); this.OkBtn.TabIndex = 3; @@ -56,7 +56,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(313, 485); + this.CancelBtn.Location = new System.Drawing.Point(313, 393); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(60, 23); this.CancelBtn.TabIndex = 4; @@ -150,7 +150,7 @@ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(385, 520); + this.ClientSize = new System.Drawing.Size(385, 428); this.Controls.Add(this.lblBorderInfo); this.Controls.Add(this.label2); this.Controls.Add(this.borderTypecomboBox1); diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs new file mode 100644 index 0000000000..511f9eeea4 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.Designer.cs @@ -0,0 +1,162 @@ +namespace BizHawk.Client.EmuHawk +{ + partial class ZXSpectrumNonSyncSettings + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ZXSpectrumNonSyncSettings)); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.autoLoadcheckBox1 = new System.Windows.Forms.CheckBox(); + this.label2 = new System.Windows.Forms.Label(); + this.panTypecomboBox1 = new System.Windows.Forms.ComboBox(); + this.lblBorderInfo = new System.Windows.Forms.Label(); + this.lblAutoLoadText = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(247, 158); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(60, 23); + this.OkBtn.TabIndex = 3; + this.OkBtn.Text = "&OK"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(313, 158); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(60, 23); + this.CancelBtn.TabIndex = 4; + this.CancelBtn.Text = "&Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 14); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(185, 13); + this.label1.TabIndex = 17; + this.label1.Text = "ZX Spectrum Misc Non-Sync Settings"; + // + // autoLoadcheckBox1 + // + this.autoLoadcheckBox1.AutoSize = true; + this.autoLoadcheckBox1.Location = new System.Drawing.Point(15, 52); + this.autoLoadcheckBox1.Name = "autoLoadcheckBox1"; + this.autoLoadcheckBox1.Size = new System.Drawing.Size(103, 17); + this.autoLoadcheckBox1.TabIndex = 21; + this.autoLoadcheckBox1.Text = "Auto-Load Tape"; + this.autoLoadcheckBox1.UseVisualStyleBackColor = true; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 97); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(135, 13); + this.label2.TabIndex = 23; + this.label2.Text = "AY-3-8912 Panning Config:"; + // + // panTypecomboBox1 + // + this.panTypecomboBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.panTypecomboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.panTypecomboBox1.FormattingEnabled = true; + this.panTypecomboBox1.Location = new System.Drawing.Point(12, 113); + this.panTypecomboBox1.Name = "panTypecomboBox1"; + this.panTypecomboBox1.Size = new System.Drawing.Size(157, 21); + this.panTypecomboBox1.TabIndex = 22; + // + // lblBorderInfo + // + this.lblBorderInfo.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblBorderInfo.Location = new System.Drawing.Point(175, 106); + this.lblBorderInfo.Name = "lblBorderInfo"; + this.lblBorderInfo.Size = new System.Drawing.Size(196, 37); + this.lblBorderInfo.TabIndex = 24; + this.lblBorderInfo.Text = "Selects a particular panning configuration for the 3ch AY-3-8912 Programmable Sou" + + "nd Generator (128K models only)"; + this.lblBorderInfo.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblAutoLoadText + // + this.lblAutoLoadText.Font = new System.Drawing.Font("Lucida Console", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblAutoLoadText.Location = new System.Drawing.Point(175, 46); + this.lblAutoLoadText.Name = "lblAutoLoadText"; + this.lblAutoLoadText.Size = new System.Drawing.Size(196, 30); + this.lblAutoLoadText.TabIndex = 25; + this.lblAutoLoadText.Text = "When enabled ZXHawk will attempt to control the tape device automatically when th" + + "e correct traps are detected"; + this.lblAutoLoadText.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // ZXSpectrumNonSyncSettings + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(385, 193); + this.Controls.Add(this.lblAutoLoadText); + this.Controls.Add(this.lblBorderInfo); + this.Controls.Add(this.label2); + this.Controls.Add(this.panTypecomboBox1); + this.Controls.Add(this.autoLoadcheckBox1); + this.Controls.Add(this.label1); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "ZXSpectrumNonSyncSettings"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Other Non-Sync Settings"; + this.Load += new System.EventHandler(this.IntvControllerSettings_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.CheckBox autoLoadcheckBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox panTypecomboBox1; + private System.Windows.Forms.Label lblBorderInfo; + private System.Windows.Forms.Label lblAutoLoadText; + } +} \ No newline at end of file diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs new file mode 100644 index 0000000000..0b8bfcbc0c --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Windows.Forms; + +using BizHawk.Client.Common; +using BizHawk.Emulation.Cores.Computers.SinclairSpectrum; +using System.Text; + +namespace BizHawk.Client.EmuHawk +{ + public partial class ZXSpectrumNonSyncSettings : Form + { + private ZXSpectrum.ZXSpectrumSettings _settings; + + public ZXSpectrumNonSyncSettings() + { + InitializeComponent(); + } + + private void IntvControllerSettings_Load(object sender, EventArgs e) + { + _settings = ((ZXSpectrum)Global.Emulator).GetSettings().Clone(); + + // autoload tape + autoLoadcheckBox1.Checked = _settings.AutoLoadTape; + + // AY panning config + var panTypes = Enum.GetNames(typeof(AYChip.AYPanConfig)); + foreach (var val in panTypes) + { + panTypecomboBox1.Items.Add(val); + } + panTypecomboBox1.SelectedItem = _settings.AYPanConfig.ToString(); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + bool changed = + _settings.AutoLoadTape != autoLoadcheckBox1.Checked + || _settings.AYPanConfig.ToString() != panTypecomboBox1.SelectedItem.ToString(); + + if (changed) + { + _settings.AutoLoadTape = autoLoadcheckBox1.Checked; + _settings.AYPanConfig = (AYChip.AYPanConfig)Enum.Parse(typeof(AYChip.AYPanConfig), panTypecomboBox1.SelectedItem.ToString()); + + GlobalWin.MainForm.PutCoreSettings(_settings); + + DialogResult = DialogResult.OK; + Close(); + } + else + { + DialogResult = DialogResult.OK; + Close(); + } + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + GlobalWin.OSD.AddMessage("Misc settings aborted"); + DialogResult = DialogResult.Cancel; + Close(); + } + } +} diff --git a/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx new file mode 100644 index 0000000000..ca821b54f8 --- /dev/null +++ b/BizHawk.Client.EmuHawk/config/ZXSpectrum/ZXSpectrumNonSyncSettings.resx @@ -0,0 +1,624 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABAAwAMDAQAAAABABoBgAAxgAAACAgEAAAAAQA6AIAAC4HAAAYGBAAAAAEAOgBAAAWCgAAEBAQAAAA + BAAoAQAA/gsAADAwAAAAAAgAqA4AACYNAAAgIAAAAAAIAKgIAADOGwAAGBgAAAAACADIBgAAdiQAABAQ + AAAAAAgAaAUAAD4rAAAwMAAAAAAgAKglAACmMAAAICAAAAAAIACoEAAATlYAABgYAAAAACAAiAkAAPZm + AAAQEAAAAAAgAGgEAAB+cAAAKAAAADAAAABgAAAAAQAEAAAAAACABAAAAAAAAAAAAAAQAAAAEAAAAAAA + AAAAAIAAAIAAAACAgACAAAAAgACAAICAAACAgIAAwMDAAAAA/wAA/wAAAP//AP8AAAD/AP8A//8AAP// + /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAHR3AAAAAAAAAAAAAAAAAAAAAAAAAAAAdHdEcAAAAAAAAAAAAAAAAA + AAAAAAAAAHd0d3QAAAAAAAAAAAAAAAAAAAAAAAAAAEd8d3UAAAAAAAAAAAAAAAAAAAAAAAAAB3yHfHZw + AAAAAAAAAAAAAAAAAAAAAAAAd3fIyHVwAAAAAAAAAAAAAAAAAAAAAAAAfHh3jIxwAAAAAAAAAAAAAAAA + AAAAAAAHd8jIyHdgAAAAAAAAAAAAAAAAAAAAAAAHd4yHfIdAAAAAAAAAAAAAAAAAAAAAAAAHyMjIyMhQ + AAAAAAAAAAAAAAAAAAAAAAB3d3eMh4dgAAAAAAAAAAAAAAAAAAAAAAB8jIyIfIdQAAAAAAAAAAAAAAAA + AAAAAAB3h4jIiMh3AAAAAAAAAAAAAAAAAAAAAAB8jIeHeIjHAAAAAAAAAAAAAAAAAAAAAAeIiHh4eMiE + AAAAAAAAAAAAB0dHcAAAAAd8h4eIiIiHcAAAAAAAAAB0d3d3RwAAAAeIeIiIiIh3RwAAAAAAAHR3d8h3 + dAAAAAfIh4iIiHiIx0cAAAAAdHh3eIeHhwAAAAeHiIiIiIiId3R3dHR0eHd4h4eHhAAAAAd4eIiIiIiH + x3d2d3eId4iIiIiIhwAAAAd4eIiI+IiIh3d3eHh3iIiIiIeHwAAAAAfIjHeIiIiIyIeHh4iIiIiIiIiI + cAAAAAeIQ0R3h3iIiMiIiIiIiIiIiIiEAAAAAAfIR3d3d0iIiIh4iIeIiIiIiHhAAAAAAAB4d3d3SHiI + h4fTiIi3iIiIeIwAAAAAAAB3h4d3eIeIiHiJiIuIiIh4jHAAAAAAAAAHyId3h3h4iIh4iIiIiIiHeAAA + AAAAAAAAB8iMiMjIiIiIh4h3aMjHAAAAAAAAAAAAAAdYyIeIiIiMjId6d4eAAAAAAAAAAAAAAAAHdsjH + eIeH6MiId3AAAAAAAAAAAAAAAIiIh4V8jIh4eIfHcAAAAAAAAAAAAACIiIh3AAAHd3h3fHcAAAAAAAAA + AAAAAAiIjHgAAAAAAHx8eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAQAAAAAAAAC + AAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAAAACAAIAAgIAAAICAgADAwMAAAAD/AAD/ + AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdwAAAAAAAAAAAAAAAA + AAd0dAAAAAAAAAAAAAAAAAB3x3cAAAAAAAAAAAAAAAAAd3fHcAAAAAAAAAAAAAAAB3yMh3AAAAAAAAAA + AAAAAAfIeMdwAAAAAAAAAAAAAAAHjIyHQAAAAAAAAAAAAAAAfId4yHAAAAAAAAAAAAAAAHjIyIdQAAAA + AAAAAAAAAAB3iId4YAAAAAAAAAdwAAAAjIiIiIUAAAAAAHd3dAAAB4iIiHh8cAAAAHd3x4dwAAd4iIiI + h3Z3d3R3yIh4cAAHh4iIiIfHd3d4iIiIh3AAB3jHiIiIiHeHiIiIiIwAAAh3dXh4iMiIiIiIiIhwAAAA + yGd0d4iIeIi4iIiMAAAAAIeHd4iIh32IiIiIcAAAAAAAd4jIyIiIiHeHyAAAAAAAAAB3h4iIh8h3dwAA + AAAAAAAIh8fIh4eIaAAAAAAAAACIiHAAB8jIyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + ////////////////////n////g////wP///8B///+Af///gH///4B///8Af///AH///wB//n8AP/A+AB + /AHgAAAB4AAAAeAAAAPgAAAH8AAAD/AAAB/8AAA//wAA//4AA//weA////////////////////////// + //8oAAAAGAAAADAAAAABAAQAAAAAACABAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAgAAAgAAAAICAAIAA + AACAAIAAgIAAAICAgADAwMAAAAD/AAD/AAAA//8A/wAAAP8A/wD//wAA////AAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHRwAAAAAAAAAAAAB3dAAAAAAAAAAAAA + d8dwAAAAAAAAAAAAfId3AAAAAAAAAAAHeMjHAAAAAAAAAAAHyHh3AAAAAAAAAAAHh3eEAAAAAAAAAAAI + yIiHAAAAAHd2cAAIiIiIQAAAd3d4UACHiIiId3d3eHiIcACHh4iIyHeHiIiIcAAIR3d4iIiIiIiMAAAH + d3eIh3iIiIhwAAAAeMh4iIiHiMAAAAAAAHfIiMh4aAAAAAAAiIgHyIfIAAAAAAAIgAAAAIAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD///8A////AP///wD8f/8A+H//APB/ + /wDwP/8A4D//AOA//wDgP/8A4D/BAOAfAQDAAAEAwAABAOAAAwDgAAcA8AAfAPwAPwDwgP8A5/f/AP// + /wD///8A////ACgAAAAQAAAAIAAAAAEABAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACA + AAAAgIAAgAAAAIAAgACAgAAAgICAAMDAwAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAd1AAAAAAAAB8cAAAAAAAB4eAAAAAAAAHyMgAAAAAAAiIhwAAAHcACI + iHcAd3hwAIz4jIeIiIAAd3eIiIiIAACHeIiIiHAAAACMeMh4AAAAiAAIgAAAAAAAAAAAAAAAAAAAAAAA + AAD//wAA//8AAP//AADj/wAA4/8AAMP/AADB/wAAwfkAAMDBAADAAQAAwAMAAMAHAADwDwAAzn8AAP// + AAD//wAAKAAAADAAAABgAAAAAQAIAAAAAAAACQAAAAAAAAAAAAAAAQAAAAEAAAAAAAA9OzsAZD8/AGg8 + PABtPj4AQkNDAEZIRwBWQkIAV0REAF5AQABbRkYAVklJAFxPTwBTU1MAXFJSAF5ZWQBkQEAAYUREAGZF + RQBqQkEAYEtLAGNPTwBwQUEAfUZGAHJKSgB2SUkAfU9PAGBRUQBgVFQAZlZWAGZYWABqWVkAclZWAHpU + VAB9W1oAbmJiAGtoaABtaWkAcWdnAHdnZwB8Y2MAe2pqAHJxcQB+dHQAd3l5AHl6egCGT08AiU9PAIFP + UACGU1MAjVFRAIlWVgCMV1cAg1xbAIxaWQCQUlIAlVJSAJFXVgCXVVUAmVVVAJZaWQCSXV0AlV9eAJpZ + WgCeW1sAml5eAKBZWgCgXFwAql9fAIRmZQCIZWQAhWtrAI5ragCTYmEAnGBhAJ9kYwCaZmYAk25uAJ1s + awCFdHQAiXd3AIt+fgCWd3cAmHR0AJV5eQCbfHwAo2JhAKZhYQChZWUApGVkAKplZACsZGQAqmhnAKZr + agCnbGsAqmloAKlubQCsbW0AtGZnALhsbACxb3AAv29wAKVxcACrc3IAr35+ALN0cwC5c3MAvXBxALR4 + dgC1fHsAunt6AMNtbgDGb3AAw3FyAMZwcQDGdXUAyHR1AMp3eADBeXkAxnt7AMB/fgDLensANLBSAEWf + TgBBtFwAPMdnADHkdgDciiIAvoF/AISrdwDln0sA35lhAN2XfADgmmEA8LdlAO61cAArWPIALWT+AEh5 + +gDOf4AAfoCAAHiA1ABZv9wAZrnUAGK+2ABxnv4Ad6P/ADPX/QBw0OcAW+D7AIKEgwCPgoIAjI2NAJuC + ggCUiIgAmYqKAJGSkgCjhIQAqoKCAKKLiwC+hIMAsoqKALaSgQCum5sAsZubALqqlQCdgr4Ar6ytALGh + oAC6pKQAwoSDAMyBggDGiIYAyYiHAMWMigDMjIoA0ISFANKHiADUjIwA2Y6NAMCUjQDIk44A0JCPANaP + kADHlZQAzpSSAMScmwDUkpIA2ZSVANWYlgDampcA2ZeYANWcnADam5sA4p2cAMChjwDeoJ4A5aCFAOaj + jQDlpJoA2p6hAMOkowDOoaEAy62tANegoADdoqEA2aGpANGsrwDdq6kAwbG4ANGysQDdtLQA2ri3AOGk + owDjqKYA66ylAOGnqADjq6oA6a2rAOOwrwDssK4A5K+wAOaztADttLIA57i2AO24tgDmurgA6rq6APC1 + swDyuLYA9Ly5APi+uwD1wL0A+cC9AKKMwACkk8QAqprMALSayACptsEAlaDkAOy/wACRxtQAgOv9AJnr + 9wDEwsoA5sbGAOzCwgDuyMcA7MzMAPPEwgDxy8oA9dPTAPja2gAAAAAAAAAAAP///wAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAAAAAAAAADLytDi9tOemQAAAAAAUy9EecLEsa1uPTUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPj11Mme + VakAAAAAAAAAAAAATS84M0akAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///////8AAP///////wAA////////AAD///////8AAP///////wAA//////// + AAD///////8AAP///////wAA//h/////AAD/4D////8AAP/AP////wAA/8A/////AAD/gB////8AAP8A + H////wAA/wAf////AAD+AB////8AAP4AH////wAA/gAf////AAD8AB////8AAPwAH////wAA/AAP//// + AAD8AA////8AAPgAD//+BwAA+AAH//ADAAD4AAP/wAMAAPgAAP8AAwAA+AAAAAADAAD4AAAAAAMAAPgA + AAAABwAA+AAAAAAHAAD4AAAAAA8AAPgAAAAAHwAA/AAAAAA/AAD8AAAAAH8AAP4AAAAA/wAA/4AAAAP/ + AAD/4AAAB/8AAP/4AAAf/wAA/8AAAH//AAD8A+AD//8AAPgP/A///wAA////////AAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAFFNTQBRUlIAU1RUAGJHRwBiT08Aa0lIAGJTUwBrVlYAYllZAGZc + XABpWloAb1xbAHNTUwB7V1YAc1hXAHFbWwBkZWUAaWFhAG5kZABpamkAcGFhAHlubgB2cHAAf3V1AH55 + eQB8fX0AgUpKAI1PTwCLWFcAhlhYAI9ZWQCKXFsAm1ZWAJJZWQCWWVgAmlpbAJtcWwCiXFwAl2BfAIBg + YACAZ2YAgG9vAI9oaACWZWQAmGBhAJ5kZACcaWoAmm9vAIV0dACNcHAAiXZ2AIB8fACac3IAm3V0AJ51 + dQCZfHwAnHx8AKNmZgCnZmYAqmJiAK5jYwCvb24AtWVmALBtbgC5bW0AvmxtAKx+fQCxcnIAtHBwALZz + dACydXQAtnd2ALlwcAC5dnYAt3p5ALh5eAC8fHsAun18ALx+fQDGb3AAxnBxAMdzdADAd3YAyHJzAMlz + dADJdXYAynd4AMd/fwDMe3wAzXx9AHunbwBhvHIAYsN4ANuLOwC2hn4A4Zt5APC3ZABte9sAX47+AHWM + 5QAl0foAY+P8AIeDgwCFhoYAioSEAJOIiACWi4sAmpKRAKGCgQCmhYUAqYGBAKuDhACniooApYyMAKiO + jQCyhYMAvoWEALeNjQCrj5AAr5eXALSVlAC9lJMAmbCEAK6RugDBgYAAwoSCAMWDhADChoQAxYeFAM6A + gQDFiIYAxoqIAMqIiQDMi4oAy4yKAMiPjQDPj44A0ISFANKJigDUi4wA04+NANWNjgDKkY8A0JCOANud + iQDWj5AAzJSTAM2XlgDGm5oA1pGSANOUkgDVl5EA1pOUANiVlgDYmJUA2ZeYANKenADbmpsA3pmYANuc + mgDbn5wA1aacAN6gngDqqZoA3Z+gAMyjowDCra0AxqysAMqpqQDboaAA3qKiAN6logDbp6UA3aWkANer + qgDWsbMA0rW0ANe0tADfs7IA4aSiAOGlpQDkp6UA46imAOWopgDsraIA6qimAOGoqADhrqwA6a2rAOqv + rADpsK4A7LGuAOGzswDlsbEA7bKxAO+1sgDotrYA5rm3AO+4twDot7sA6bq5AOu9uwDrv70A8bazAPG2 + tADxuLUA9Lm2APC9uwD2vboA9L+9APi+uwD4v7wA8sC+APXAvgD5wL0AkILJAKqXzACsu8cAqr/LALLV + 3QDawMIA48XFAOvDwQDswMAA7cTDAO/ExQDgxsgA8cbEAPTGxADwyskA9MvJAPLNzQD21dYA+NjZAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAMEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqHCEcBQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAayU9PSYbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdQlBSQiJpAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAM0pSUlJQPRcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAnUlJSUlJGFQAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAFJSUlJSUkoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUlJSWVJZfxAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC5XWYqKioqGDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASoqMkpqa + mqAsAAAAAAAAAAAAAAAAAABoNAAAAAAAAACMjJyuvLy2toYHAAAAAAAAAAAAABcOIDouBgAAAAAAc4yc + tsHKysPAriIKAAAAAAAAABYgRk1LTX+DEAAAAABukqXB4ejo4dHPQCIEChcXEwggTXV/k66unKMpAAAA + AG6Srsro6ero0dN/Rk1NRk2Dg4STrsbh4cHAt2sAAAAAbpKuOXPe6ajW15KGg4OGk528yuHo5eHPz882 + AAAAAAB4jCkDAxSoMabXt5yjt8ro3ePo5dbT09HTdAAAAAAAAABGcBFoGgFwdtfDwHxi2dpmZcrX09HP + z0MAAAAAAAAAAHh/qWwaOa6cz9PNZGPYsdzbzc3DwLk2AAAAAAAAAAAAAAAvhpKakoyg19HNyKS5wHtb + orZ/cwAAAAAAAAAAAAAAAAAANkaKWVm5zb1gYV6cXVxfNgAAAAAAAAAAAAAAAAAAALGvlTIuP1K5tqCR + l4xfLwAAAAAAAAAAAAAAAAAAsbPBenkAAAAAcCVYjE0scwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////+f///+D////A////wH + ///4B///+Af///gH///wB///8Af///AH/+fwA/8D4AH8AeAAAAHgAAAB4AAAA+AAAAfwAAAP8AAAH/wA + AD//AAD//gAD//B4D////////////////////////////ygAAAAYAAAAMAAAAAEACAAAAAAAQAIAAAAA + AAAAAAAAAAEAAAABAAAAAAAAWlJSAHBJSQB1SEgAe1dXAHdYWAB5WlkAel1dAGBiYgB1bGwAfWtrAHh2 + dgB9fn4Ag01NAIRXVwCIV1cAhV9eAItbWgCgX14ApV1dAJhgXwCNYGAAnWtqAJhtbQCCdnYAh3x8AI15 + eACeensAqGBgAKhoZwCga2oArGpqALNqagCzb28AtG1tALltbQCxb3AApnVzAKlzcwCqdHMApnp6AKd+ + fgCpensAq3x7ALZ3dgC8dHQAvH59AMZvcADGcHEAxXN0AMhycwDJdncAynh5AMx5egDNfn8Ajo1wAOek + VgDGgH8A4p53AEZ2+gB8u4AAd8PaAIuEhACOh4cAjo6OAJ+DggCejo4Ao4SEAKSIiACsi4sAqo2MAK6P + jgC+gYAAvoaGAL+KiACskJAAtJeXALWenQC5np4At6iOAKmyjgC9nroAwYSDAMaGhADOhoYAxomHAMiK + iQDJjYwA0oeIANOOjwDUjY0A2ZiPANaPkADGkZEAx5eXAMySkADGnZwA1ZOSANeTlADWl5YA2JSVANGZ + mADan50A3J6dAOCcmwDVoJ8A7K2fAMOtrQDXo6IA3aCgAN+kpADVq6oA3ay3AMu0tADPtrYA3L+/AOCi + oQDhpqUA5KelAOinpgDlq6gA46usAOOvrQDqrqwA7LGuAOayswDjtrQA5re1AOqysQDts7EA57y6AO+8 + ugDrvL0A8LOwAPC1sgDwtrQA87q3APS6twD2vboA8b69APi/vAD2wb4A+cC9AJmTzwDHqMMAu8PMAIHf + 8QDByNAA7cLCAO3FwwDvxsQA5cjIAOzOzgDwxcQA9cbEAPPP0AD10tIAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + BQMJAAAAAAAAAAAAAAAAAAAAAAAAAAAPHBMNAAAAAAAAAAAAAAAAAAAAAAAAABojLy8TAAAAAAAAAAAA + AAAAAAAAAAAAAB0wMDAiPgAAAAAAAAAAAAAAAAAAAAAAQjAwMDAtGAAAAAAAAAAAAAAAAAAAAAAAFzIy + NTU5CgAAAAAAAAAAAAAAAAAAAAAAIjZYWFxcBwAAAAAAAAAAAAAAAAAAAAAANlxtdW11JQAAAAAAAAAA + PgcRDgkAAAAAXG1/lISAZgMAAAAAABkVLC5SVhcAAABNY3WWnJuLfB8UBAcQHkhWaX91dSsAAABNY2BM + mJeCiVJSVl9laX+WloSJgEIAAAAAXAEIC0tGjnR0dJaRk5qNjIyJQwAAAAAAJkNADBtdjIaPO1GSPYuJ + hnVEAAAAAAAAAClISWRcd4xwkGp8UE90VwAAAAAAAAAAAAAAKSQ1NYZ7OjhbPDdGAAAAAAAAAAAAAHNv + YGsAKyJoXFYmRwAAAAAAAAAAAAAAcnIAAAAAAAAATgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////AP// + /wD///8A////APx//wD4f/8A8H//APA//wDgP/8A4D//AOA//wDgP8EA4B8BAMAAAQDAAAEA4AADAOAA + BwDwAB8A/AA/APCA/wDn9/8A////AP///wD///8AKAAAABAAAAAgAAAAAQAIAAAAAAAAAQAAAAAAAAAA + AAAAAQAAAAEAAAAAAABjZGQAdmRjAHtpaQB/eHgAgU9PAKBaWgCFbm0AlWtqAKptbgCwZ2cAsGhoAKxw + cACteHkAvnJyAMZvcADGcHEAy3l5AMx9fgCFmXQAwIB/ANeUfQDhoX8AlIqJAJWMjACYiIgAoIaGAK2K + igCxh4cAvoGAALKKigC4iYgAuJWVAL2cnACss50AuqKhAL+mpgDLgoIAxImHAMeNjADLkI8AxpWTANCS + kQDYlZUA1J6dANqZmgDdnp4A1J+oAMaiogDOr68AzLKyANi5uADhpaIA4qypAOWtqADrrqsA4bKwAOay + sgDtuLYA57++AOy4uADxtLIA8be0APa9ugDswL4A9sG+ALCcxwC5ncIA06zBALnH0QC2ytQA7sPDAPLS + 0gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAZBgUAAAAAAAAAAAAAAAAACw8KAAAAAAAAAAAAAAAAGhAQDgAAAAAAAAAAAAAAAAkRESUYAAAA + AAAAAAAAAAAlKy4uBwAAAAAAAAcDAAAAKzlHPCYCAAAYCB0oKgAAAC0wSDs0FB0nLDlAOiwAAAANAQQb + Pi9DRkVBPzUAAAAAJB4cKz5EQjMiNSkAAAAAAAAAHwwRNxYVEyQAAAAAAAAxMgAAACEgAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAA4/8AAOP/AADD/wAAwf8AAMH5 + AADAwQAAwAEAAMADAADABwAA8A8AAM5/AAD//wAA//8AACgAAAAwAAAAYAAAAAEAIAAAAAAAgCUAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAkAAAAJAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAUAAAAOAEBAVUAAABUAAAANQAAABAAAAABAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAkFBSUvGRl5TCkpwlYuLtxDJCTQFw0NmQAA + AEkAAAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACGAwMKE8rK6V6RET2klJR/5ZS + U/+OT0//ZDc38B0QEJoAAAAyAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYDAwYVzAwoopP + T/ygXVz/oFtb/55ZWf+bWFf/k1NT/1UvL9wGAwNcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AARNKipxhk5O+adkY/+uZWX/tWdo/7VmZ/+qYWH/nltb/3hERPcfERGCAAAAFgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADEZGS1zQ0LXqGdm/7ptbf/Fb3D/x3Bx/8hwcf/BbW7/q2Vl/4hPT/82HR2gAAAAIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAB1gxMYyYXl3/vXFx/8Zwcf/HcHH/x3Bx/8dwcf/HcHH/uG1t/5NY + V/9EJia2AAAAKQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB8fNH1MS+K4cnH/x3Fy/8dwcf/HcHH/x3Bx/8dw + cf/HcHH/wHBx/51gX/9PLCzGAAAAMwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + 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 + AAMAAAAAAAAAAAAAAAAAAAAALhAQFIZXVs/RjIz/1Y2O/9qYmP/eoaL/46qr/+aysv/ot7f/6rm5/+m4 + uf/otbX/5q+v/+uvrf/jqab/wYeF/28/P/QhEhKvAAAAXwAAACgAAAANAAAABQAAAAMAAAACAAAAAwAA + AAUAAAAKAAAAFQAAADAdDg9oSSkptHZHRu2dYmL+t3Z1/758e/+6enn/tnh3/7d5eP+8fn3/w4SD/7Z6 + ef9eODfbBgICTgAAAAgAAAAAAAAAAAAAAAAAAAAAPhwcJJVjYuPXkZH/2JOU/92fn//iqqr/57O0/+u8 + vP/uwsL/78XG/+/Exf/twMD/67i4/+60sv/wtrP/zZKQ/5taWv9xQED2MRsaxAgEBIcAAABaAAAAQQAA + ADcAAAA2AAAAOwAAAEUEAgJZHA4OfUcnJ7l5SkntqGxr/8CAfv/DgoH/vH59/7p+ff/DiIb/zZGP/9GT + kf/UlJP/1peV/9eZl/+GVlbuGQsLVwAAAAcAAAAAAAAAAAAAAAAAAAAARiIiLZ9rauvZk5P/2peY/+Ck + pP/lsLD/6ru7/+/Fxf/yzMz/9NDQ//PPz//xycr/7sDA//K5tv/1u7j/36Kg/6dmZf+mZWX/j1ZW/WM6 + OutDJSXQNBwcvDAaGrQ0HBy1PiIivUwsLMtkPDzfh1VU9a1xcP/EhIP/xIWE/7+Cgf/Ch4b/zZST/9mk + ov/grq3/4a6t/96lo//eoJ7/36Kg/+Cjof+IWVjnGwwMQwAAAAIAAAAAAAAAAAAAAAAAAAAARyQkL6Br + auzZk5P/25qb/+GnqP/ntLT/7cDA//LLy//209T/+NjY//fX1//00ND/8cbG//W9u//4vrz/46ak/7d0 + c/+vb27/s3Jy/7d2df+ucXD/pWpp/6Npaf+nbWz/sHVz/7p9fP/EhYT/yImI/8WIhv/DiIb/ypGP/9eg + n//hr63/57q5/+rCwP/rwsD/6bq4/+evrf/nq6n/6q6r/9qgnv9wRkbDBwAAHgAAAAAAAAAAAAAAAAAA + AAAAAAAASCQkLZ1nZuvYkpP/25uc/+Opqv/qtrf/7cHB//TOzv/52Nj/+tzc//na2v/xz9D/8MfH//fA + vv/6wb7/6a6r/8OBgP/DgoD/vX58/7h7ev+8fn3/woOC/8aHhv/HiYj/xoqJ/8aLif/Ijoz/zZST/9eg + nv/hrav/6Lm3/+zCwf/uyMf/78nH/+/Dwf/uvLr/7ba0/+60sf/vtLL/8ri1/7J+fflMKSltAAAABAAA + AAAAAAAAAAAAAAAAAAAAAAAAQyEhI5JcXOPWj5D/3Juc/8qVlf+BZmb/bl5e/4l4eP/AqKj/8tPT//LO + zv+5p6b/w6qq//fBv//7wr//8LWy/86Ojf/Ojoz/0ZGP/9GSkP/OkY//zpOR/9GamP/VoJ//2qel/+Gv + rf/nt7X/6727/+3Dwf/wycf/8czL//LLyf/yxsT/8cC+//G7uf/yubf/87m3//S7uP/4vrv/1J6c/3JH + RrAdCgsWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANRcXEYJNTcvPiIn/15aW/2VNTf85Ojr/Q0VF/0JF + RP9dXFz/n5GR/+S/v/+bh4f/hXp6/+25uP/7wr//9bu4/9qcmv/Zmpj/252b/96gnf/ipKH/5q+s/+u+ + vP/vycf/8srI/+3Hxv/wysj/9c7M//TNy//0ysj/9MbE//TBv//1vrz/9r26//e9u//4vrv/+L+8//vB + vv/hqqf/g1ZVzDwcHC4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW4+Ppq/env/05OT/2ZX + V/9rbm7/fX9//3l6ev99f3//cHJy/5F9ff+ff3//XFhY/9eop//8wr//+L+8/+Wppv/ipaP/5qil/96i + pP/Kmaz/1qi1//LGxP/tyMf/qb3J/23E3P9kw9//vMTN//jDwP/3wb//+MC9//i/vf/5v73/+b+8//i/ + vP/3vrv/+L68/92mo/+IWlnRRSMjOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFcv + L0mbX1/y15GS/6GAgP9XV1b/iYuL/4CBgf98fX3/cnR0/1dPT/++j4//km9w/9Sfnv/6wL3/+cC9/+6z + sP/ssK3/0Z+u/4OH1P9YffD/QGPs/7KYyv/Ct7z/Ytrz/3Ts//8s2f//cbvU//m+u//4v7z/+L67//e9 + uv/1vLn/9Lq3//O5tv/zuLX/0puZ/4RVVctGIyM4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAADIXFwdrPDySq2ts/diZmf/ApKT/sKur/4CBgP95enr/iYiI/49zdP/do6P/36Ch/96e + nv/zuLX/+sK///W7uP/1ubT/qZC//2qY+/9tnf//MGT6/56FxP/esK//nMbS/57n8/9+z+T/ybG3//a6 + t//zubb/8re0//C1s//utLH/7rKw/+qvrP++iIb9dklJtkMgISoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHIyMSazw8kZ5hYvXNjI3/2aSk/7OMjP+bd3f/sIKC/9KV + lv/cnJz/2peY/9aRkf/koqL/+sG+//nAvf/5v7z/4amw/6qZx/+aouP/qpvP/+mxtv/2urj/6rGv/+S6 + u//ptrX/466n/+Ovqf/ssK7/6q6s/+isqv/oq6n/2J2b/6JubfFoPT2NOxoaFwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOBoaCFowMFd7SEjAomZm9sWC + gv/XkZL/25SV/9iSk//Wj5D/1IyN/9KHiP/UiIj/8bOx//rCv//3vbv/9ru4//O3s//xuLX/7q6e/+ej + hf/npIn/7bCp/+Otp/+KsX3/ULdm/1WjWv+7oYz/5KWk/9uenP+4gH79glJRzVYuLlQgCAkGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAA8HBwQVy4uS3FBQaCPV1fjsG5v/cmAgf/ShYb/0YKD/85+f//LeXr/2I2M//e8uf/1vLn/7rOx/+2y + sP/lpJX/5qFY/+6xXP/djS3/35h9/86gl/9SwW7/Nd90/0WxXP+vlH//wYSE/49cW+VlOTmBQR4eHAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAGk7OhqIWFd8oG5u8J5qav+eX2D/tmts/8Z0df/KdHX/yXJz/92T + k//3vLn/7LGu/+Snpf/dm5L/4Z1q/+61dP/fmmX/15WM/9eYlv/Bm43/r6uR/6uNgP+WYWDtbkBAnUwn + JzQVAQECAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiFJSBnhC + QgpqNDQJWSUlB08dHQdfKisKfENDFJJWViinbGtRvYOCjtOcm8/pt7X157y6/7eOjfhxRUW7aTk5m4RK + StehWlr6uGdo/8Zwcf/dkpH/8bSx/+OnpP/YmZj/1ZWT/9ealP/Vl5X/0JCP/8eIhv+zdnb/lFtc6nA/ + QKRSKio/JQwNBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADTn6AB2qioDMuUlCHBhYU8voCAWcCBgXTEhoaLzZGQqdeensngrKvn47Sz/NOop/+yiIfyi2Bgs2k+ + PlZXKysPAAAAAUYlJRxcMTFYcj4+pYpMTeWmXF3+xnl5/9+Zl//dnJr/z46M/8KCgf+vc3L/ll9e831L + S8hlOTl/TigoMy0REQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABzQUIDnmprDriGhifHlpZMzp6eeNCgoZ7On5+2yJqaybuPj9WnfHzVj2RkunVJ + SYNbLy8/PRQUCgAAAAAAAAAAAAAAAAAAAAAAAAAAKRUVBU0pKSphNDRtd0BAsotNTd2ZW1vrkVlY4HtJ + Sb5lOTmCUysrQTsbGxEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWCwsA2Y4OA5xQkImdkhIRHhKSll0R0dibUBAWWI2 + NkNUKCgoOhISDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMhkZB0km + Jh5LJiYsRSEhITATFAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP///////wAA////////AAD/+H////8AAP/gH////wAA/8Af//// + AAD/gA////8AAP+AD////wAA/wAP////AAD/AA////8AAP4AB////wAA/gAH////AAD8AAf///8AAPwA + B////wAA/AAH////AAD8AAf///8AAPgAB////wAA+AAH//4HAAD4AAP/8AEAAPgAAf/AAQAA8AAA/wAA + AADwAAAAAAAAAPAAAAAAAAAA8AAAAAAAAADwAAAAAAEAAPAAAAAAAQAA8AAAAAADAADwAAAAAAcAAPAA + AAAADwAA+AAAAAAfAAD4AAAAAD8AAPwAAAAAfwAA/gAAAAD/AAD/gAAAA/8AAP/gAAAH/wAAgAAAAB// + AAAAAAAAf/8AAAAD4AP//wAAgB/8H///AAD///////8AAP///////wAA////////AAD///////8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAYAAAAZAAAAGQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAARCQkYOh8fb0ooKK80HByiCQUFTAAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAIhERFmA2Np2ITUz3lVNT/4dLS/5IKCi9AAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAANjODiBllhY+61kZP+vY2P/pV5e/3xHRvEhEhJfAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAASSgoN41VVeS6bW3/xW9w/8dwcf+9bG3/klZW/jogIIEAAAAGAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ1RkWcs2xs/8dxcv/HcHH/x3Bx/8Zwcf+iYWH/SSkpmAAA + AAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUC0tMZtgX+fGcnP/x3Bx/8dwcf/HcHH/x3Fy/61q + av9UMTGqAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxRER1tm9v/8hxcv/HcHH/x3Bx/8dw + cf/HcnP/tnRz/185OboAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAACIxXV7TEdHT/yHJz/8l1 + dv/Kd3j/ynd4/8p4eP/Bf37/bURDywAAACQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABNKysjo2Zm4Mt4 + ef/NfH3/z4GC/9GFhf/RhYb/0YWF/82Mi/9+UVHeCAICOwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAACwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAGc+ + Pkm1c3P30IGC/9OJiv/XkZL/2ZaW/9mWl//YlJX/2JmY/5hnZfMeEBBrAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAA0FAgItHhAQWzAbG4IqFxeHDQcHWwAAABkAAAAAAAAAAAAA + AAAAAAAAek1MdMN/f//VjI3/2piZ/9+io//hqKn/4qmp/+Clpf/jpqT/wImH/04xMLwAAAA6AAAABQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAABEbDg5GRygokW5CQs+MVlbxnGJh/JdfXvxnPz7hHA8PbgAA + AAwAAAAAAAAAAAAAAACMW1qbz4qK/9qXl//gpqb/5rKz/+q6u//rvLz/6La2/+qxr//epKL/j1lZ+DUc + HLACAQFPAAAAHQAAAA8AAAAPAAAAEwAAACIbDg5MVDExnYZUU+SpbWz+uXl4/7x+fP/AgoD/xoeF/72A + f/9fOzu1AAAAHAAAAAAAAAAAAAAABJhkZK/VkZH/3Z+g/+axsf/twMD/8svL//LNzf/vxcX/8Lq4/+6z + sf+1dHP/j1VU+144N9g7IiKqMhwclDcfH5RGKSmiYTw7v4tZWOiydXT+woOC/8aKiP/Ol5X/2aWj/9ui + of/cnpz/2pyb/35TUrgAAAAVAAAAAAAAAAAAAAAFmmVkstaTk//hpaX/7Lm6//TLy//419f/+NnZ//TP + z//1wb//9Lq3/8aGhP+1dHP/s3Rz/6xwb/+pb27+rnNy/7Z7ev/BhIL/yY2L/8+WlP/apqT/5be2/+vB + v//rvrz/6bKw/+uvrf/Um5n/bUVEgAAAAAMAAAAAAAAAAAAAAAOTXV2q1ZGR/9CYmP+dfX7/o4yM/9e8 + vP/z0tL/zLOz/+u8u//5v7z/1peV/8uLif/Ki4r/yoyL/86Ukv/TnJv/2qSi/+Gtq//nuLb/7cPB//DJ + x//xxsT/8b+9//G6t//zubf/77az/6d1dM89Hx8lAAAAAAAAAAAAAAAAAAAAAIJOTojNiIn/jGlp/01O + Tv9UVlb/dnNz/7uhof+Pfn7/xJ+e//zCv//lqKb/3J2b/+Chnv/hpaT/7Ly5/+vHxv/MxMn/0MjN//LK + yf/1x8X/9sLA//a/vP/3vrv/+L+8//S7uP+5hoXhYTo5RwAAAAAAAAAAAAAAAAAAAAAAAAAAaTs7RrVz + dPKmfn7/cXJx/4SGhv97fX3/b2Zm/516ev+7kJD/+sG+//C2s//lqqr/rpbA/3aB2/+ql83/tMHK/2jc + 9P9OzOz/2r3B//q/vP/3vrv/9ry6//a8uf/ss7D/tYGA32c+Pk0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAvEhIHg01Njbp9fvrCn5//nI+P/4R7ev+fgID/2Jyd/9ybnP/ytrT/+b+8/+ewtf+Mld3/ZI36/5eI + zv/Ttrn/sNLc/6/Czv/stLT/8re0/++0sf/tsq//2qCe/6Rxb8phODg+AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABCIB8MeUZGbqRpata8gYH8x4mJ/9eTk//YkpP/04qL/+Cbmv/5wL3/9726/+Sw + t//Zrrn/56qY/+2smf/lr6n/nLWJ/4Gtdf/Pppn/3qGf/7yEg/KJWViYTyoqIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQh0dGXJAQGOXXl7NtnR1/8V7fP/MfH3/znt8/+il + o//0urj/7LCu/+Whg//rq13/35VX/9Kek/9yvXz/ZbNv/6iCdfqYY2O/aj4+TCUJCgcAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcamsBjFRVB4FERAh9PT0JjU1ND6VnZx+/hINF0JqZiNOjoty0iIf2hFBQw5lX + V8+wY2P4xXR0/+aioP/oq6j/2pqT/92fif/Vlor/yYqJ/7N8efiVZmPGdERFYkEfHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAALiFhgXFkJEdx5CQSMqSknbNlZWbz5uaws2cnOXBlJPnqH18r4dc + XFFULy8OSCUlFm07O0+FSUmeoV1d3sF9fPrGhoX/snZ295xkZNiFUlKbbD4+T0UdHxIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc0JDA5FgYRKdbm46onR0Zp9ycnuWampzhFlZVmY6 + OikvDAwHAAAAAAAAAAAAAAAAAAAAAB0ODgRULCwhbjo7UXhERGVrPDxHTCYmGxAAAQMAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAAAgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//////////////////////D////gf///wH///4A///+AP///AD///wA///8AP//+AD + ///gA//D4AH+AeAA+ADgAAAAwAAAAMAAAADAAAAB4AAAA+AAAAfgAAAP8AAAH/wAAD8AAAD/AAAD/wB4 + D//H////////////////////KAAAABgAAAAwAAAAAQAgAAAAAABgCQAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAABMAAAAtAAAAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAgIO1cwMM1qOjrsHhAQmwAA + ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATCgogfUhI6ahgYP6lXV3+f0hI9wIBAT0AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsGBgFPLy6kuW1t/sZv + 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 + + + \ No newline at end of file From e198e7b8a6e7be7b3b4f67e725a2a37b03f80871 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Fri, 16 Mar 2018 15:16:33 +0000 Subject: [PATCH 099/105] Removed generic settings menu --- BizHawk.Client.EmuHawk/MainForm.Designer.cs | 24 ++++++--------------- BizHawk.Client.EmuHawk/MainForm.Events.cs | 2 ++ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/BizHawk.Client.EmuHawk/MainForm.Designer.cs b/BizHawk.Client.EmuHawk/MainForm.Designer.cs index 28e7f9368b..fbc83e208a 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Designer.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Designer.cs @@ -382,7 +382,7 @@ this.zXSpectrumToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ZXSpectrumCoreEmulationSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.ZXSpectrumControllerConfigurationMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.preferencesToolStripMenuItem4 = new System.Windows.Forms.ToolStripMenuItem(); + this.ZXSpectrumNonSyncSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.Atari7800HawkCoreMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainStatusBar = new StatusStripEx(); this.DumpStatusButton = new System.Windows.Forms.ToolStripDropDownButton(); @@ -455,7 +455,6 @@ this.ShowMenuContextMenuSeparator = new System.Windows.Forms.ToolStripSeparator(); this.ShowMenuContextMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.timerMouseIdle = new System.Windows.Forms.Timer(this.components); - this.ZXSpectrumNonSyncSettingsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.MainformMenu.SuspendLayout(); this.MainStatusBar.SuspendLayout(); this.MainFormContextMenu.SuspendLayout(); @@ -3382,8 +3381,7 @@ this.zXSpectrumToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.ZXSpectrumCoreEmulationSettingsMenuItem, this.ZXSpectrumControllerConfigurationMenuItem, - this.ZXSpectrumNonSyncSettingsMenuItem, - this.preferencesToolStripMenuItem4}); + this.ZXSpectrumNonSyncSettingsMenuItem}); this.zXSpectrumToolStripMenuItem.Name = "zXSpectrumToolStripMenuItem"; this.zXSpectrumToolStripMenuItem.Size = new System.Drawing.Size(87, 19); this.zXSpectrumToolStripMenuItem.Text = "ZX Spectrum"; @@ -3403,12 +3401,12 @@ this.ZXSpectrumControllerConfigurationMenuItem.Text = "Joystick Configuration"; this.ZXSpectrumControllerConfigurationMenuItem.Click += new System.EventHandler(this.ZXSpectrumControllerConfigurationMenuItem_Click); // - // preferencesToolStripMenuItem4 + // ZXSpectrumNonSyncSettingsMenuItem // - this.preferencesToolStripMenuItem4.Name = "preferencesToolStripMenuItem4"; - this.preferencesToolStripMenuItem4.Size = new System.Drawing.Size(201, 22); - this.preferencesToolStripMenuItem4.Text = "Preferences"; - this.preferencesToolStripMenuItem4.Click += new System.EventHandler(this.preferencesToolStripMenuItem4_Click); + this.ZXSpectrumNonSyncSettingsMenuItem.Name = "ZXSpectrumNonSyncSettingsMenuItem"; + this.ZXSpectrumNonSyncSettingsMenuItem.Size = new System.Drawing.Size(201, 22); + this.ZXSpectrumNonSyncSettingsMenuItem.Text = "Non-Sync Settings"; + this.ZXSpectrumNonSyncSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumNonSyncSettingsMenuItem_Click); // // Atari7800HawkCoreMenuItem // @@ -4036,13 +4034,6 @@ this.timerMouseIdle.Interval = 2000; this.timerMouseIdle.Tick += new System.EventHandler(this.TimerMouseIdle_Tick); // - // ZXSpectrumNonSyncSettingsMenuItem - // - this.ZXSpectrumNonSyncSettingsMenuItem.Name = "ZXSpectrumNonSyncSettingsMenuItem"; - this.ZXSpectrumNonSyncSettingsMenuItem.Size = new System.Drawing.Size(201, 22); - this.ZXSpectrumNonSyncSettingsMenuItem.Text = "Non-Sync Settings"; - this.ZXSpectrumNonSyncSettingsMenuItem.Click += new System.EventHandler(this.ZXSpectrumNonSyncSettingsMenuItem_Click); - // // MainForm // this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; @@ -4506,7 +4497,6 @@ private System.Windows.Forms.ToolStripMenuItem SMSControllerSportsPadToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem SMSControllerKeyboardToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem zXSpectrumToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem4; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumControllerConfigurationMenuItem; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumCoreEmulationSettingsMenuItem; private System.Windows.Forms.ToolStripMenuItem ZXSpectrumNonSyncSettingsMenuItem; diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 6ce8d550d7..ebef700b2b 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -2444,10 +2444,12 @@ namespace BizHawk.Client.EmuHawk } + private void preferencesToolStripMenuItem4_Click(object sender, EventArgs e) { GenericCoreConfig.DoDialog(this, "ZXSpectrum Settings"); } + private void ZXSpectrumControllerConfigurationMenuItem_Click(object sender, EventArgs e) { From dbb90a996d4967e0299a76e1bda7f1891f320641 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Fri, 16 Mar 2018 17:50:51 -0400 Subject: [PATCH 100/105] z80: clean up --- BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs | 3 +-- .../CPUs/Z80A/NewDisassembler.cs | 2 +- BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt | 3 +-- BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs | 16 +++++----------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs index 88e0a264c2..2c4f5c5204 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Interrupts.cs @@ -83,8 +83,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A } // Interrupt mode 2 uses the I vector combined with a byte on the data bus - // Again for now we assume only a 0 on the data bus and jump to (0xI00) - private void INTERRUPT_2(ushort src) + private void INTERRUPT_2() { cur_instr = new ushort[] {IDLE, diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs index effe7400df..8bc3938c7d 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/NewDisassembler.cs @@ -438,7 +438,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A // handle case of addr wrapping around at 16 bit boundary if (addr < start_addr) { - size = addr + 1; + size = (0x10000 + addr) - start_addr; } return temp; diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt b/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt index d11f79b637..10804fde94 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/ReadMe.txt @@ -1,8 +1,7 @@ TODO: -Mode 0 and 2 interrupts +Mode 0 Check T-cycle level memory access timing Check R register new tests for WZ Registers Memory refresh - IR is pushed onto the address bus at instruction start, does anything need this? -Data Bus - For mode zero and 2 interrupts, need a system that uses it to test diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs index 182056f026..25992d5b81 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs @@ -107,7 +107,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A public Func ReadHardware; public Action WriteHardware; - // Data BUs + // Data Bus // Interrupting Devices are responsible for putting a value onto the data bus // for as long as the interrupt is valid public Func FetchDB; @@ -196,9 +196,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A INTERRUPT_1(); break; case 2: - // Low byte of interrupt vector comes from data bus - // We'll assume it's zero for now - INTERRUPT_2(0); + INTERRUPT_2(); break; } IRQCallback(); @@ -321,9 +319,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A INTERRUPT_1(); break; case 2: - // Low byte of interrupt vector comes from data bus - // We'll assume it's zero for now - INTERRUPT_2(0); + INTERRUPT_2(); break; } IRQCallback(); @@ -391,9 +387,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A INTERRUPT_1(); break; case 2: - // Low byte of interrupt vector comes from data bus - // We'll assume it's zero for now - INTERRUPT_2(0); + INTERRUPT_2(); break; } IRQCallback(); @@ -663,8 +657,8 @@ namespace BizHawk.Emulation.Cores.Components.Z80A FlagI ? "E" : "e") }; } - // State Save/Load + // State Save/Load public void SyncState(Serializer ser) { ser.BeginSection("Z80A"); From 81e80acf86da7d2d8cb076e3e61c0c988f917e72 Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Sun, 18 Mar 2018 09:55:56 -0400 Subject: [PATCH 101/105] z80: make TotalExecutedCycles long and change related variables accordingly --- BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs | 3 +- BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs | 4 +- .../Calculator/TI83.IDebuggable.cs | 218 +++++++++--------- .../Hardware/SoundOuput/Buzzer.cs | 6 +- .../Hardware/SoundOuput/Pulse.cs | 2 +- .../SinclairSpectrum/Machine/SpectrumBase.cs | 6 +- .../SinclairSpectrum/Machine/ULABase.cs | 10 +- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 4 +- .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 2 +- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 2 +- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 2 +- .../ZXSpectrum.IDebuggable.cs | 2 +- .../Coleco/ColecoVision.IDebuggable.cs | 2 +- .../Consoles/Sega/SMS/SMS.IDebuggable.cs | 2 +- BizHawk.Emulation.Cores/Sound/SN76489.cs | 26 +-- 15 files changed, 145 insertions(+), 146 deletions(-) diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs index c70e4aa503..eddf74d0a4 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Execute.cs @@ -4,8 +4,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A { public partial class Z80A { - private int totalExecutedCycles; - public int TotalExecutedCycles { get { return totalExecutedCycles; } set { totalExecutedCycles = value; } } + public long TotalExecutedCycles; private int EI_pending; diff --git a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs index 25992d5b81..f26582dd41 100644 --- a/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs +++ b/BizHawk.Emulation.Cores/CPUs/Z80A/Z80A.cs @@ -602,7 +602,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A FTCH_DB_Func(); break; } - totalExecutedCycles++; + TotalExecutedCycles++; } // tracer stuff @@ -669,7 +669,7 @@ namespace BizHawk.Emulation.Cores.Components.Z80A ser.Sync("IFF1", ref iff1); ser.Sync("IFF2", ref iff2); ser.Sync("Halted", ref halted); - ser.Sync("ExecutedCycles", ref totalExecutedCycles); + ser.Sync("ExecutedCycles", ref TotalExecutedCycles); ser.Sync("EI_pending", ref EI_pending); ser.Sync("instruction_pointer", ref instr_pntr); diff --git a/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs b/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs index c5d795fcd5..d4e292ad18 100644 --- a/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Calculator/TI83.IDebuggable.cs @@ -12,35 +12,35 @@ namespace BizHawk.Emulation.Cores.Calculators { return new Dictionary { - ["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, + ["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 }; } @@ -49,85 +49,85 @@ namespace BizHawk.Emulation.Cores.Calculators { 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); + 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; } } @@ -145,6 +145,6 @@ namespace BizHawk.Emulation.Cores.Calculators return false; } - public int TotalExecutedCycles => _cpu.TotalExecutedCycles; + public int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles; } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs index 667fe519be..7fc0a71715 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Buzzer.cs @@ -60,7 +60,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// private long _frameStart; private bool _tapeMode; - private int _tStatesPerFrame; + private long _tStatesPerFrame; private int _sampleRate; private int _samplesPerFrame; private int _tStatesPerSample; @@ -78,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The last T-State (cpu cycle) that the last pulse was received /// - public int LastPulseTState { get; set; } + public long LastPulseTState { get; set; } #region Construction & Initialisation @@ -95,7 +95,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _sampleRate = sampleRate; _tStatesPerFrame = tStatesPerFrame; _tStatesPerSample = 79; - _samplesPerFrame = _tStatesPerFrame / _tStatesPerSample; + _samplesPerFrame = (int)_tStatesPerFrame / _tStatesPerSample; /* diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs index af14d5cea7..140c1d136a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/SoundOuput/Pulse.cs @@ -21,6 +21,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Pulse length in Z80 T-States (cycles) /// - public int Length { get; set; } + public long Length { get; set; } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 3c220d3157..c6b7567dc9 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -91,17 +91,17 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// The current cycle (T-State) that we are at in the frame /// - public int _frameCycles; + public long _frameCycles; /// /// Stores where we are in the frame after each CPU cycle /// - public int LastFrameStartCPUTick; + public long LastFrameStartCPUTick; /// /// Gets the current frame cycle according to the CPU tick count /// - public virtual int CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick; + public virtual long CurrentFrameCycle => CPU.TotalExecutedCycles - LastFrameStartCPUTick; /// /// Non-Deterministic bools diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs index 216aa33ef9..79479b4789 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ULABase.cs @@ -105,11 +105,11 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// /// Cycle at which the last render update took place /// - protected int lastTState; + protected long lastTState; /// /// T-States elapsed since last render update /// - protected int elapsedTStates; + protected long elapsedTStates; /// /// T-State of top left raster pixel /// @@ -279,7 +279,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Generates an interrupt in the current phase if needed /// /// - public virtual void CheckForInterrupt(int currentCycle) + public virtual void CheckForInterrupt(long currentCycle) { if (InterruptRevoked) { @@ -430,7 +430,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// Updates the screen buffer based on the number of T-States supplied /// /// - public virtual void UpdateScreenBuffer(int _tstates) + public virtual void UpdateScreenBuffer(long _tstates) { if (_tstates < actualULAStart) { @@ -448,7 +448,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum //It takes 4 tstates to write 1 byte. Or, 2 pixels per t-state. - int numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0); + long numBytes = (elapsedTStates >> 2) + ((elapsedTStates % 4) > 0 ? 1 : 0); int pixelData; int pixel2Data = 0xff; diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index a4a69e9877..353d7bdacd 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle - int _tStates = CurrentFrameCycle - 1; + long _tStates = CurrentFrameCycle - 1; // if we are on the top or bottom border return 0xff if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) @@ -160,7 +160,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // get a BitArray of the value byte BitArray bits = new BitArray(new byte[] { value }); - int currT = CPU.TotalExecutedCycles; + long currT = CPU.TotalExecutedCycles; AYDevice.WritePort(port, value); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index 3e8d97f36f..a99d2c53bc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle - int _tStates = CurrentFrameCycle - 1; + long _tStates = CurrentFrameCycle - 1; // if we are on the top or bottom border return 0xff if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index bcbb31f35d..ce92c7c93f 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -57,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum { // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle - int _tStates = CurrentFrameCycle - 1; + long _tStates = CurrentFrameCycle - 1; // if we are on the top or bottom border return 0xff if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 0cc99cf239..09b1b8d073 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -60,7 +60,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // If this is an unused port the floating memory bus should be returned // Floating bus is read on the previous cycle - int _tStates = CurrentFrameCycle - 1; + long _tStates = CurrentFrameCycle - 1; // if we are on the top or bottom border return 0xff if ((_tStates < ULADevice.contentionStartPeriod) || (_tStates > ULADevice.contentionEndPeriod)) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs index b473bac3f8..e086ad09e3 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/ZXSpectrum.IDebuggable.cs @@ -142,6 +142,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum throw new NotImplementedException(); } - public int TotalExecutedCycles => _cpu.TotalExecutedCycles; + public int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs index c48bbabf8f..8739d2d616 100644 --- a/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Coleco/ColecoVision.IDebuggable.cs @@ -142,6 +142,6 @@ namespace BizHawk.Emulation.Cores.ColecoVision throw new NotImplementedException(); } - public int TotalExecutedCycles => _cpu.TotalExecutedCycles; + public int TotalExecutedCycles => (int)_cpu.TotalExecutedCycles; } } diff --git a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs index d8b0d15402..3a9bf83be6 100644 --- a/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs +++ b/BizHawk.Emulation.Cores/Consoles/Sega/SMS/SMS.IDebuggable.cs @@ -144,7 +144,7 @@ namespace BizHawk.Emulation.Cores.Sega.MasterSystem public int TotalExecutedCycles { - get { return Cpu.TotalExecutedCycles; } + get { return (int)Cpu.TotalExecutedCycles; } } } } diff --git a/BizHawk.Emulation.Cores/Sound/SN76489.cs b/BizHawk.Emulation.Cores/Sound/SN76489.cs index 1944edf267..c5e7e113c5 100644 --- a/BizHawk.Emulation.Cores/Sound/SN76489.cs +++ b/BizHawk.Emulation.Cores/Sound/SN76489.cs @@ -21,15 +21,15 @@ namespace BizHawk.Emulation.Cores.Components const int SampleRate = 44100; private static readonly byte[] LogScale = { 0, 10, 13, 16, 20, 26, 32, 40, 51, 64, 81, 102, 128, 161, 203, 255 }; - public void Mix(short[] samples, int start, int len, int maxVolume) + public void Mix(short[] samples, long start, long len, int maxVolume) { if (Volume == 0) return; float adjustedWaveLengthInSamples = SampleRate / (Noise ? (Frequency / (float)Wave.Length) : Frequency); float moveThroughWaveRate = Wave.Length / adjustedWaveLengthInSamples; - int end = start + len; - for (int i = start; i < end; ) + long end = start + len; + for (long i = start; i < end; ) { short value = Wave[(int)WaveOffset]; @@ -46,7 +46,7 @@ namespace BizHawk.Emulation.Cores.Components public byte PsgLatch; private readonly Queue commands = new Queue(256); - int frameStartTime, frameStopTime; + long frameStartTime, frameStopTime; const int PsgBase = 111861; @@ -84,7 +84,7 @@ namespace BizHawk.Emulation.Cores.Components } } - public void BeginFrame(int cycles) + public void BeginFrame(long cycles) { while (commands.Count > 0) { @@ -94,12 +94,12 @@ namespace BizHawk.Emulation.Cores.Components frameStartTime = cycles; } - public void EndFrame(int cycles) + public void EndFrame(long cycles) { frameStopTime = cycles; } - public void WritePsgData(byte value, int cycles) + public void WritePsgData(byte value, long cycles) { commands.Enqueue(new QueuedCommand { Value = value, Time = cycles - frameStartTime }); } @@ -227,15 +227,15 @@ namespace BizHawk.Emulation.Cores.Components public void DiscardSamples() { commands.Clear(); } public void GetSamples(short[] samples) { - int elapsedCycles = frameStopTime - frameStartTime; + long elapsedCycles = frameStopTime - frameStartTime; if (elapsedCycles == 0) elapsedCycles = 1; // hey it's better than diving by zero - int start = 0; + long start = 0; while (commands.Count > 0) { var cmd = commands.Dequeue(); - int pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1; + long pos = ((cmd.Time * samples.Length) / elapsedCycles) & ~1; GetSamplesImmediate(samples, start, pos - start); start = pos; WritePsgDataImmediate(cmd.Value); @@ -243,16 +243,16 @@ namespace BizHawk.Emulation.Cores.Components GetSamplesImmediate(samples, start, samples.Length - start); } - public void GetSamplesImmediate(short[] samples, int start, int len) + public void GetSamplesImmediate(short[] samples, long start, long len) { - for (int i = 0; i < 4; i++) + for (long i = 0; i < 4; i++) Channels[i].Mix(samples, start, len, MaxVolume); } class QueuedCommand { public byte Value; - public int Time; + public long Time; } } } \ No newline at end of file From 22656fd373585f1f23b9700b0b55c7b567c36d53 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 19 Mar 2018 12:01:54 +0000 Subject: [PATCH 102/105] Some TapeDevice changes --- .../Hardware/Datacorder/DatacorderDevice.cs | 53 +++++++++++++------ .../Machine/SpectrumBase.Memory.cs | 26 ++++++++- .../Machine/ZXSpectrum128K/ZX128.Port.cs | 4 +- .../ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs | 6 +-- .../ZXSpectrum128KPlus3/ZX128Plus3.Port.cs | 6 +-- .../Machine/ZXSpectrum48K/ZX48.Port.cs | 10 ++-- 6 files changed, 74 insertions(+), 31 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 066570f6c9..48701e0f88 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -103,12 +103,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum private long _lastCycle = 0; /// - /// + /// Edge /// private int _waitEdge = 0; /// - /// + /// Current tapebit state /// private bool currentState = false; @@ -132,11 +132,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum #region Emulator - /// - /// This is the address the that ROM will jump to when the spectrum has quit tape playing - /// - public const ushort ERROR_ROM_ADDRESS = 0x0008; - /// /// Should be fired at the end of every frame /// Primary purpose is to detect tape traps and manage auto play (if/when this is ever implemented) @@ -522,7 +517,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _lastCycle = cpuCycle - (long)cycles; // play the buzzer - _buzzer.ProcessPulseValue(true, currentState); + _buzzer.ProcessPulseValue(false, currentState); return currentState; } @@ -598,7 +593,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum _machine.Spectrum.OSD_TapePlayingAuto(); } - _monitorTimeOut = 50; + _monitorTimeOut = 250; } } else @@ -613,7 +608,10 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum public void AutoStopTape() { - if (!_tapeIsPlaying) + if (_tapeIsPlaying) + return; + + if (!_machine.Spectrum.Settings.AutoLoadTape) return; Stop(); @@ -625,25 +623,27 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum if (_tapeIsPlaying) return; + if (!_machine.Spectrum.Settings.AutoLoadTape) + return; + Play(); _machine.Spectrum.OSD_TapePlayingAuto(); } private void MonitorFrame() { - /* if (_tapeIsPlaying && _machine.Spectrum.Settings.AutoLoadTape) { - _monitorTimeOut--; if (_monitorTimeOut < 0) { + // does not work properly - disabled for now (handled elsewhere) + //Stop(); //_machine.Spectrum.OSD_TapeStoppedAuto(); } } - */ } #endregion @@ -665,6 +665,23 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public bool ReadPort(ushort port, ref int result) { + if (TapeIsPlaying) + { + GetEarBit(_cpu.TotalExecutedCycles); + } + if (currentState) + { + result |= TAPE_BIT; + } + else + { + result &= ~TAPE_BIT; + } + + MonitorRead(); + + /* + if (TapeIsPlaying) { if (GetEarBit(_cpu.TotalExecutedCycles)) @@ -702,6 +719,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum } } + */ + return true; } @@ -713,8 +732,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public bool WritePort(ushort port, int result) { - // not implemented yet - return false; + if (!TapeIsPlaying) + { + currentState = ((byte)result & 0x10) != 0; + } + + return true; } #endregion diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs index a25bbc8186..a23c2796ff 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.Memory.cs @@ -176,36 +176,58 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return false; } - + /// + /// Monitors ROM access + /// Used to auto start/stop the tape device when appropriate + /// + /// public virtual void TestForTapeTraps(int addr) { - if (!TapeDevice.TapeIsPlaying) + if (TapeDevice.TapeIsPlaying) { + // THE 'ERROR' RESTART if (addr == 8) { TapeDevice?.AutoStopTape(); return; } + // THE 'ED-ERROR' SUBROUTINE if (addr == 4223) { TapeDevice?.AutoStopTape(); return; } + // THE 'ERROR-2' ROUTINE if (addr == 83) { TapeDevice?.AutoStopTape(); return; } + + // THE 'MASKABLE INTERRUPT' ROUTINE + if (addr == 56) + { + //TapeDevice?.AutoStopTape(); + return; + } } else { + // THE 'LD-BYTES' SUBROUTINE if (addr == 1366) { TapeDevice?.AutoStartTape(); return; } + + // THE 'LD-EDGE-2' AND 'LD-EDGE-1' SUBROUTINES + if (addr == 1507) + { + TapeDevice?.AutoStartTape(); + return; + } } } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs index 353d7bdacd..e01525017a 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128K/ZX128.Port.cs @@ -43,9 +43,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // not a lagframe InputRead = true; - // tape loading monitor cycle - TapeDevice.MonitorRead(); - // process tape INs TapeDevice.ReadPort(port, ref result); } @@ -229,6 +226,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + TapeDevice.WritePort(port, value); // Tape //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs index a99d2c53bc..24e7a13760 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus2a/ZX128Plus2a.Port.cs @@ -43,9 +43,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // not a lagframe InputRead = true; - // tape loading monitor cycle - TapeDevice.MonitorRead(); - // process tape INs TapeDevice.ReadPort(port, ref result); } @@ -255,6 +252,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + // Tape + TapeDevice.WritePort(port, value); + // Tape //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs index ce92c7c93f..e56c23440b 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum128KPlus3/ZX128Plus3.Port.cs @@ -43,9 +43,6 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // not a lagframe InputRead = true; - // tape loading monitor cycle - TapeDevice.MonitorRead(); - // process tape INs TapeDevice.ReadPort(port, ref result); } @@ -182,6 +179,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + // Tape + TapeDevice.WritePort(port, value); + // Tape //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); } diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs index 09b1b8d073..0cb5b9c7c1 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/ZXSpectrum48K/ZX48.Port.cs @@ -40,10 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum KeyboardDevice.ReadPort(port, ref result); // not a lagframe - InputRead = true; - - // tape loading monitor cycle - TapeDevice.MonitorRead(); + InputRead = true; // process tape INs TapeDevice.ReadPort(port, ref result); @@ -120,9 +117,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum // Buzzer BuzzerDevice.ProcessPulseValue(false, (value & EAR_BIT) != 0); + // Tape + TapeDevice.WritePort(port, value); + // Tape mic processing (not implemented yet) //TapeDevice.ProcessMicBit((value & MIC_BIT) != 0); - + } } From 5ab7ecd4b0b38f8402288cbf85e5ceaaff83ebe3 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 19 Mar 2018 15:34:25 +0000 Subject: [PATCH 103/105] A few more small changes --- .../Hardware/Datacorder/DatacorderDevice.cs | 106 ++++++++++++++++++ .../SinclairSpectrum/Machine/SpectrumBase.cs | 2 + 2 files changed, 108 insertions(+) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs index 48701e0f88..cd7d7a2cde 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Hardware/Datacorder/DatacorderDevice.cs @@ -1,6 +1,7 @@ using BizHawk.Common; using BizHawk.Emulation.Cores.Components.Z80A; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -141,6 +142,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum MonitorFrame(); } + public void StartFrame() + { + //if (TapeIsPlaying && AutoPlay) + //FlashLoad(); + } + #endregion #region Tape Controls @@ -522,6 +529,105 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum return currentState; } + /// + /// Flash loading implementation + /// (Deterministic Emulation must be FALSE) + /// + private bool FlashLoad() + { + // deterministic emulation must = false + //if (_machine.Spectrum.SyncSettings.DeterministicEmulation) + //return; + + var util = _machine.Spectrum; + + if (_currentDataBlockIndex < 0) + _currentDataBlockIndex = 0; + + if (_currentDataBlockIndex >= DataBlocks.Count) + return false; + + //var val = GetEarBit(_cpu.TotalExecutedCycles); + //_buzzer.ProcessPulseValue(true, val); + + ushort addr = _cpu.RegPC; + + if (_machine.Spectrum.SyncSettings.DeterministicEmulation) + { + + } + + var tb = DataBlocks[_currentDataBlockIndex]; + var tData = tb.BlockData; + + if (tData == null || tData.Length < 2) + { + // skip this + return false; + } + + var toRead = tData.Length - 1; + + if (toRead < _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8)) + { + + } + else + { + toRead = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8); + } + + if (toRead <= 0) + return false; + + var parity = tData[0]; + + if (parity != _cpu.Regs[_cpu.F_s] + (_cpu.Regs[_cpu.A_s] << 8) >> 8) + return false; + + util.SetCpuRegister("Shadow AF", 0x0145); + + for (var i = 0; i < toRead; i++) + { + var v = tData[i + 1]; + _cpu.Regs[_cpu.L] = v; + parity ^= v; + var d = (ushort)(_cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8) + 1); + _machine.WriteBus(d, v); + } + var pc = (ushort)0x05DF; + + if (_cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8) == toRead && + toRead + 1 < tData.Length) + { + var v = tData[toRead + 1]; + _cpu.Regs[_cpu.L] = v; + parity ^= v; + _cpu.Regs[_cpu.B] = 0xB0; + } + else + { + _cpu.Regs[_cpu.L] = 1; + _cpu.Regs[_cpu.B] = 0; + _cpu.Regs[_cpu.F] = 0x50; + _cpu.Regs[_cpu.A] = parity; + pc = 0x05EE; + } + + _cpu.Regs[_cpu.H] = parity; + var de = _cpu.Regs[_cpu.E] + (_cpu.Regs[_cpu.D] << 8); + util.SetCpuRegister("DE", de - toRead); + var ix = _cpu.Regs[_cpu.Ixl] + (_cpu.Regs[_cpu.Ixh] << 8); + util.SetCpuRegister("IX", ix + toRead); + + util.SetCpuRegister("PC", pc); + + _currentDataBlockIndex++; + + return true; + + } + #endregion #region TapeMonitor diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index c6b7567dc9..92c48065bc 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -137,6 +137,8 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum FrameCompleted = false; + TapeDevice.StartFrame(); + if (_renderSound) { BuzzerDevice.StartFrame(); From b939c47de6ecbf831762add6bf2beda6c3d5114f Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 19 Mar 2018 16:21:15 +0000 Subject: [PATCH 104/105] Added reset methods --- .../SinclairSpectrum/Machine/SpectrumBase.cs | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs index 92c48065bc..ac557b4c99 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/Machine/SpectrumBase.cs @@ -207,8 +207,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual void HardReset() { - //ResetBorder(); ULADevice.ResetInterrupt(); + ROMPaged = 0; + SpecialPagingMode = false; + RAMPaged = 0; + CPU.RegPC = 0; + + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("IY", 0xFFFF); + Spectrum.SetCpuRegister("IX", 0xFFFF); + Spectrum.SetCpuRegister("AF", 0xFFFF); + Spectrum.SetCpuRegister("BC", 0xFFFF); + Spectrum.SetCpuRegister("DE", 0xFFFF); + Spectrum.SetCpuRegister("HL", 0xFFFF); + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); + Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); + Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); + Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); + + CPU.Regs[CPU.I] = 0; + CPU.Regs[CPU.R] = 0; + + TapeDevice.Reset(); + if (AYDevice != null) + AYDevice.Reset(); } /// @@ -216,9 +239,31 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum /// public virtual void SoftReset() { - //ResetBorder(); - ULADevice.ResetInterrupt(); + ULADevice.ResetInterrupt(); + ROMPaged = 0; + SpecialPagingMode = false; + RAMPaged = 0; CPU.RegPC = 0; + + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("IY", 0xFFFF); + Spectrum.SetCpuRegister("IX", 0xFFFF); + Spectrum.SetCpuRegister("AF", 0xFFFF); + Spectrum.SetCpuRegister("BC", 0xFFFF); + Spectrum.SetCpuRegister("DE", 0xFFFF); + Spectrum.SetCpuRegister("HL", 0xFFFF); + Spectrum.SetCpuRegister("SP", 0xFFFF); + Spectrum.SetCpuRegister("Shadow AF", 0xFFFF); + Spectrum.SetCpuRegister("Shadow BC", 0xFFFF); + Spectrum.SetCpuRegister("Shadow DE", 0xFFFF); + Spectrum.SetCpuRegister("Shadow HL", 0xFFFF); + + CPU.Regs[CPU.I] = 0; + CPU.Regs[CPU.R] = 0; + + TapeDevice.Reset(); + if (AYDevice != null) + AYDevice.Reset(); } #endregion From 683166da89cf9fba855c40b7dc9a46038567c6f7 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Mon, 19 Mar 2018 16:25:20 +0000 Subject: [PATCH 105/105] Readme update --- BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md index af479f8536..36eb6a6f07 100644 --- a/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md +++ b/BizHawk.Emulation.Cores/Computers/SinclairSpectrum/readme.md @@ -9,7 +9,7 @@ At the moment this is experimental and is still being worked on. * ULA Mode 1 VBLANK interrupt generation * IM2 Interrupts and DataBus implementation (thanks Aloysha) * Beeper/Buzzer output (implementing ISoundProvider) -* AY-3-8912 sound chip implementation (stereo or mono options available as a setting) +* AY-3-8912 sound chip implementation (multiple stereo or mono panning options available as a setting) * Keyboard input (implementing IInputPollable) * Default keyboard keymappings * Kempston, Cursor and Sinclair (Left & Right) joysticks emulated