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 + + + } +}