From b50f7a79da939dae70346c7fc89ca922a99d760c Mon Sep 17 00:00:00 2001 From: Marc Parsons Date: Wed, 22 Apr 2026 11:46:57 +0100 Subject: [PATCH] Snapshot and TAP quick loading working. Manic Miner fully emulated --- Core/Cpu/Z80.cs | 87 ++++++++++++++++++++++++++++++++++++++++- Desktop/DebuggerForm.cs | 29 +++++++++++++- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/Core/Cpu/Z80.cs b/Core/Cpu/Z80.cs index a54bd32..bcba1fa 100644 --- a/Core/Cpu/Z80.cs +++ b/Core/Cpu/Z80.cs @@ -1021,7 +1021,12 @@ namespace Core.Cpu PC = (ushort)(PC + offset); return 12; // Jump taken } - return 7; // Jump not taken + return 7; + case 0x31: // LD SP, nn + { + SP = FetchWord(); + return 10; + } case 0x32: // LD (nn), A { ushort destAddress = FetchWord(); @@ -1608,6 +1613,34 @@ namespace Core.Cpu case 0x56: // IM 1 InterruptMode = 1; return 8; + case 0x58: // IN E, (C) + // 1. Read from the I/O port. + // CRITICAL: We must pass the FULL BC register, not just C! + byte inVal58 = ReadPort(BC.Word); + + // 2. Store the hardware data in register E (Low byte of DE) + DE.Low = inVal58; + + // 3. Update the Flags Register (F) + // The Carry flag (C) is strictly preserved. H and N are always reset to 0. + byte flags58 = (byte)(AF.Low & 0x01); + + if ((inVal58 & 0x80) != 0) flags58 |= 0x80; // S: Sign flag + if (inVal58 == 0) flags58 |= 0x40; // Z: Zero flag + + // P/V: Parity flag. Collapse the bits to check if the number of 1s is even + byte p58 = inVal58; + p58 ^= (byte)(p58 >> 4); + p58 ^= (byte)(p58 >> 2); + p58 ^= (byte)(p58 >> 1); + if ((p58 & 1) == 0) flags58 |= 0x04; // Set if Parity is Even + + // Undocumented bits 3 and 5 are copied directly from the input byte + flags58 |= (byte)(inVal58 & 0x28); + + AF.Low = flags58; + + return 12; case 0x5A: // ADC HL, DE Adc16(DE.Word); return 15; @@ -2448,6 +2481,36 @@ namespace Core.Cpu switch (opcode) { + case 0x19: // ADD IY, DE + // 1. Perform the 16-bit addition using a 32-bit integer to catch the carry + int result19 = IY.Word + DE.Word; + + // 2. Update the Flags Register (F) + // We start by stripping out N, H, C, and bits 3/5, but strictly PRESERVING S, Z, and P/V (0xC4) + byte flags19 = (byte)(AF.Low & 0xC4); + + // H: Set if there is a carry from bit 11 + if (((IY.Word & 0x0FFF) + (DE.Word & 0x0FFF)) > 0x0FFF) + { + flags19 |= 0x10; + } + + // C: Set if the result overflows 16 bits (carry from bit 15) + if (result19 > 0xFFFF) + { + flags19 |= 0x01; + } + + // Undocumented bits 3 and 5 are copied directly from the high byte of the result + flags19 |= (byte)((result19 >> 8) & 0x28); + + // N (Subtract) is naturally left as 0 because of our initial bitmask + AF.Low = flags19; + + // 3. Store the clean 16-bit result back into IY + IY.Word = (ushort)result19; + + return 15; case 0x21: // LD IY, nn IY.Word = FetchWord(); return 14; @@ -2550,6 +2613,17 @@ namespace Core.Cpu // 3. Read the memory and store it in E (the low byte of DE) DE.Low = ReadMemory(address5E); + return 19; + case 0x66: // LD H, (IY+d) + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset66 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IY + offset) + ushort address66 = (ushort)(IY.Word + offset66); + + // 3. Read the byte from memory and drop it into the H register (High byte of HL) + HL.High = ReadMemory(address66); + return 19; case 0x6E: // LD L, (IY+d) sbyte displacementVal = (sbyte)FetchByte(); @@ -2602,6 +2676,17 @@ namespace Core.Cpu targetAddress = (ushort)(IY.Word + offset75); // Write the low byte of HL to memory WriteMemory(targetAddress, HL.Low); + return 19; + case 0x77: // LD (IY+d), A + // 1. Fetch the displacement byte and cast it to a signed sbyte + sbyte offset77 = (sbyte)FetchByte(); + + // 2. Calculate the exact memory address (IY + offset) + ushort address77 = (ushort)(IY.Word + offset77); + + // 3. Write the Accumulator (A) into memory + WriteMemory(address77, AF.High); + return 19; case 0x7E: // LD A, (IY+d) // 1. Fetch the displacement byte and cast it to a signed sbyte diff --git a/Desktop/DebuggerForm.cs b/Desktop/DebuggerForm.cs index 56d96d3..a7c256a 100644 --- a/Desktop/DebuggerForm.cs +++ b/Desktop/DebuggerForm.cs @@ -326,6 +326,10 @@ namespace Desktop mnemonic = $"JR NC, 0x{dest:X4}"; instructionLength = 2; break; + case 0x31: + mnemonic = "LD SP, nn"; + instructionLength = 3; + break; case 0x32: { ushort addr32 = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); @@ -1029,6 +1033,10 @@ namespace Desktop mnemonic = "IM 1"; instructionLength = 2; break; + case 0x58: + mnemonic = "IN E, (C)"; + instructionLength = 2; + break; case 0x5A: mnemonic = "ADC HL, DE"; instructionLength = 2; break; case 0x5B: ushort addr5B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); @@ -1105,7 +1113,12 @@ namespace Desktop { byte fdOpcode = _memoryBus.Read((ushort)(currentPc + 1)); - if (fdOpcode == 0x21) // LD IY, nn + if (fdOpcode == 0x19) // ADD IY, DE + { + mnemonic = $"ADD IY, DE"; + instructionLength = 2; + } + else if (fdOpcode == 0x21) // LD IY, nn { ushort iyVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD IY, 0x{iyVal:X4}"; @@ -1169,6 +1182,13 @@ namespace Desktop mnemonic = $"LD E, (IY{sign}{d})"; instructionLength = 3; } + else if (fdOpcode == 0x66) + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD H, (IY{sign}{d})"; + instructionLength = 3; + } else if (fdOpcode == 0x6E) { sbyte offsetL = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); @@ -1198,6 +1218,13 @@ namespace Desktop mnemonic = $"LD (IY{sign}{d}), H"; instructionLength = 3; } + else if (fdOpcode == 0x77) // LD (IY+d), A + { + sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); + string sign = d >= 0 ? "+" : ""; + mnemonic = $"LD (IY{sign}{d}), A"; + instructionLength = 3; + } else if (fdOpcode == 0x7E) // LD A, (IY+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));