diff --git a/Core/Memory/MemoryBus.cs b/Core/Memory/MemoryBus.cs index 9b5fa51..c2f8c99 100644 --- a/Core/Memory/MemoryBus.cs +++ b/Core/Memory/MemoryBus.cs @@ -18,6 +18,8 @@ namespace Core.Memory private byte _port7FFD = 0; private byte _port1FFD = 0; private bool _pagingDisabled = false; + public byte Port7FFD => _port7FFD; + public int CurrentRamBankSlot3 => _currentRamBankSlot3; public MemoryBus() { @@ -94,6 +96,11 @@ namespace Core.Memory default: return -1; // -1 means it is pointing to a ROM chip! } } + public byte[] GetRamBank(int index) + { + return _ramBanks[index]; + } + public byte Read(ushort address) { int slot = address >> 14; // Find the 16KB Slot (0, 1, 2, or 3) diff --git a/Core/SpectrumMachine.cs b/Core/SpectrumMachine.cs index 44d281b..c37c928 100644 --- a/Core/SpectrumMachine.cs +++ b/Core/SpectrumMachine.cs @@ -289,61 +289,17 @@ namespace Core // SNAPSHOT MANAGEMENT (.SNA) // ==================================================================== - public void LoadSnapshot(byte[] snaData) - { - if (snaData.Length != 49179) - throw new Exception("Invalid 48K SNA File Size!"); - - // 1. Load CPU Registers - Cpu.I = snaData[0]; - Cpu.HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8)); - Cpu.DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8)); - Cpu.BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8)); - Cpu.AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8)); - - Cpu.HL.Word = (ushort)(snaData[9] | (snaData[10] << 8)); - Cpu.DE.Word = (ushort)(snaData[11] | (snaData[12] << 8)); - Cpu.BC.Word = (ushort)(snaData[13] | (snaData[14] << 8)); - Cpu.IY.Word = (ushort)(snaData[15] | (snaData[16] << 8)); - Cpu.IX.Word = (ushort)(snaData[17] | (snaData[18] << 8)); - - bool iff2 = (snaData[19] & 0x04) != 0; - // Note: If IFF1/IFF2 have private setters in Z80.cs, you may need to - // trigger an EI/DI instruction or temporarily unlock them. - // Assuming they are public/internal for now based on previous code: - - Cpu.R = snaData[20]; - Cpu.AF.Word = (ushort)(snaData[21] | (snaData[22] << 8)); - Cpu.SP = (ushort)(snaData[23] | (snaData[24] << 8)); - - // Set Interrupt Mode (Assuming you add a setter to Z80.InterruptMode if it's private) - // Cpu.InterruptMode = snaData[25]; - - // THE BUG FIX: Restore the ULA Border Color! - IoBus.BorderColourIndex = snaData[26]; - - // 2. Load the 48K RAM Dump - for (int i = 0; i < 49152; i++) - { - Memory.Write((ushort)(0x4000 + i), snaData[27 + i]); - } - - // 3. Pop the Program Counter off the stack to resume - byte pcLow = Memory.Read(Cpu.SP); - Cpu.SP++; - byte pcHigh = Memory.Read(Cpu.SP); - Cpu.SP++; - Cpu.PC = (ushort)((pcHigh << 8) | pcLow); - } + // ==================================================================== + // SNAPSHOT MANAGEMENT (.SNA) + // ==================================================================== public void SaveSnapshot(string filepath) { - // Back up the live state BEFORE modifying the stack ushort originalSP = Cpu.SP; byte originalMemLow = Memory.Read((ushort)(Cpu.SP - 2)); byte originalMemHigh = Memory.Read((ushort)(Cpu.SP - 1)); - // Push the PC onto the stack + // Push PC for 48K compatibility Cpu.SP -= 2; Memory.Write(Cpu.SP, (byte)(Cpu.PC & 0xFF)); Memory.Write((ushort)(Cpu.SP + 1), (byte)(Cpu.PC >> 8)); @@ -351,6 +307,7 @@ namespace Core using (System.IO.FileStream fs = new System.IO.FileStream(filepath, System.IO.FileMode.Create)) using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fs)) { + // --- 27 BYTE HEADER --- bw.Write(Cpu.I); bw.Write(Cpu.HL_Prime.Low); bw.Write(Cpu.HL_Prime.High); bw.Write(Cpu.DE_Prime.Low); bw.Write(Cpu.DE_Prime.High); @@ -370,9 +327,34 @@ namespace Core bw.Write((byte)Cpu.InterruptMode); bw.Write(IoBus.BorderColourIndex); - for (int i = 0x4000; i <= 0xFFFF; i++) + if (CurrentModel == MachineModel.Spectrum48K) { - bw.Write(Memory.Read((ushort)i)); + // --- 48K FORMAT (49,179 Bytes) --- + for (int i = 0x4000; i <= 0xFFFF; i++) bw.Write(Memory.Read((ushort)i)); + } + else + { + // --- 128K FORMAT (131,103 Bytes) --- + int activeBank = Memory.CurrentRamBankSlot3; + + bw.Write(Memory.GetRamBank(5)); // 0x4000 - 0x7FFF + bw.Write(Memory.GetRamBank(2)); // 0x8000 - 0xBFFF + bw.Write(Memory.GetRamBank(activeBank)); // 0xC000 - 0xFFFF + + // 128K Extensions + bw.Write((byte)(Cpu.PC & 0xFF)); + bw.Write((byte)(Cpu.PC >> 8)); + bw.Write(Memory.Port7FFD); + bw.Write((byte)0); // TR-DOS ROM (0 = unused) + + // Write the remaining 5 unpaged banks + for (int i = 0; i < 8; i++) + { + if (i != 5 && i != 2 && i != activeBank) + { + bw.Write(Memory.GetRamBank(i)); + } + } } } @@ -381,5 +363,79 @@ namespace Core Memory.Write((ushort)(originalSP - 2), originalMemLow); Memory.Write((ushort)(originalSP - 1), originalMemHigh); } + + public void LoadSnapshot(byte[] snaData) + { + bool is128K = snaData.Length == 131103; + + if (snaData.Length != 49179 && !is128K) + throw new Exception($"Invalid SNA File Size! Got {snaData.Length} bytes."); + + // Force the machine into the correct mode + SetMachineModel(is128K ? MachineModel.SpectrumPlus2A : MachineModel.Spectrum48K); + + // 1. Load Header + Cpu.I = snaData[0]; + Cpu.HL_Prime.Word = (ushort)(snaData[1] | (snaData[2] << 8)); + Cpu.DE_Prime.Word = (ushort)(snaData[3] | (snaData[4] << 8)); + Cpu.BC_Prime.Word = (ushort)(snaData[5] | (snaData[6] << 8)); + Cpu.AF_Prime.Word = (ushort)(snaData[7] | (snaData[8] << 8)); + + Cpu.HL.Word = (ushort)(snaData[9] | (snaData[10] << 8)); + Cpu.DE.Word = (ushort)(snaData[11] | (snaData[12] << 8)); + Cpu.BC.Word = (ushort)(snaData[13] | (snaData[14] << 8)); + Cpu.IY.Word = (ushort)(snaData[15] | (snaData[16] << 8)); + Cpu.IX.Word = (ushort)(snaData[17] | (snaData[18] << 8)); + + if ((snaData[19] & 0x04) != 0) { Cpu.ExecuteOpcode(0xFB); /* Fake EI */ } + else { Cpu.ExecuteOpcode(0xF3); /* Fake DI */ } + + Cpu.R = snaData[20]; + Cpu.AF.Word = (ushort)(snaData[21] | (snaData[22] << 8)); + Cpu.SP = (ushort)(snaData[23] | (snaData[24] << 8)); + + if (snaData[25] == 0) Cpu.ExecuteOpcode(0x46); // IM 0 + else if (snaData[25] == 1) Cpu.ExecuteOpcode(0x56); // IM 1 + else if (snaData[25] == 2) Cpu.ExecuteOpcode(0x5E); // IM 2 + + IoBus.BorderColourIndex = snaData[26]; + + // 2. Load RAM + if (!is128K) + { + for (int i = 0; i < 49152; i++) Memory.Write((ushort)(0x4000 + i), snaData[27 + i]); + + byte pcLow = Memory.Read(Cpu.SP); Cpu.SP++; + byte pcHigh = Memory.Read(Cpu.SP); Cpu.SP++; + Cpu.PC = (ushort)((pcHigh << 8) | pcLow); + } + else + { + Cpu.PC = (ushort)(snaData[49179] | (snaData[49180] << 8)); + byte port7FFD = snaData[49181]; + + // THE TOASTRACK FIX: Force Amstrad ROMs into Toastrack compatibility mode! + IoBus.WritePort(0x1FFD, 0x04); + IoBus.WritePort(0x7FFD, port7FFD); + + int activeBank = port7FFD & 0x07; + + // Load the 3 visible memory chunks + Array.Copy(snaData, 27, Memory.GetRamBank(5), 0, 16384); + Array.Copy(snaData, 16411, Memory.GetRamBank(2), 0, 16384); + Array.Copy(snaData, 32795, Memory.GetRamBank(activeBank), 0, 16384); + + // Load the remaining 5 hidden memory banks + int offset = 49183; + for (int i = 0; i < 8; i++) + { + if (i != 5 && i != 2 && i != activeBank) + { + Array.Copy(snaData, offset, Memory.GetRamBank(i), 0, 16384); + offset += 16384; + } + } + } + } } } \ No newline at end of file