From e6d43fa5d2b540ee155ddabb4beaeb59be2bc346 Mon Sep 17 00:00:00 2001 From: Asnivor Date: Thu, 8 Mar 2018 19:33:14 +0000 Subject: [PATCH] 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