Added double buffering ish.

This commit is contained in:
2026-05-21 17:25:57 +01:00
parent 9316a16ab9
commit cca1abf0be
3 changed files with 202 additions and 166 deletions

View File

@@ -1827,7 +1827,8 @@ namespace Core.Cpu
return 16; return 16;
} }
default: default:
throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented."); return 8;
//throw new NotImplementedException($"Extended ED Opcode 0x{extendedOpcode:X2} at PC 0x{(PC - 1):X4} is not implemented.");
} }
} }

View File

@@ -42,41 +42,80 @@ namespace Core.Memory
public byte Read(ushort address) public byte Read(ushort address)
{ {
if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF) if (address < 0x4000)
{ {
// SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged. if (address < 0x0400)
if (address < 0x0400) return ReadFromCartridge(0, address); {
return _cartridgeRom[address];
return ReadFromCartridge(_romBank0, address);
} }
if (address < 0x8000) // ROM Slot 1 (0x4000 - 0x7FFF) // Slot 0
{ return _cartridgeRom[(_romBank0 * 0x4000) + address];
return ReadFromCartridge(_romBank1, address & 0x3FFF);
} }
if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF) if (address < 0x8000)
{ {
// --- THE MISSING LINK: Read from Save RAM if enabled! --- // Slot 1
if ((_mapperControl & 0x08) != 0) return _cartridgeRom[(_romBank1 * 0x4000) + (address - 0x4000)];
}
if (address < 0xC000)
{ {
int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0; // Slot 2 (Or SRAM)
int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF); if (SramUsed && (_mapperControl & 0x08) != 0)
return _cartridgeRam[ramOffset]; return _cartridgeRam[address - 0x8000];
else
return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)];
} }
// Otherwise, read from the ROM as usual // THE FIX 1: Bitwise AND perfectly forces 0xE000-0xFFFF to mirror down to 0xC000!
return ReadFromCartridge(_romBank2, address & 0x3FFF);
}
// System RAM (0xC000 - 0xFFFF)
return _workRam[address & 0x1FFF]; return _workRam[address & 0x1FFF];
} }
public void Write(ushort address, byte value)
{
if (address < 0x8000) return; // Cannot write to physical ROM
if (address < 0xC000)
{
if (SramUsed && (_mapperControl & 0x08) != 0)
_cartridgeRam[address - 0x8000] = value;
return;
}
// THE FIX 1: Save to RAM using the mirror mask
_workRam[address & 0x1FFF] = value;
// THE FIX 2: Mapper Control Registers live at the very end of the mirrored RAM!
if (address >= 0xFFFC)
{
// Calculate the absolute maximum number of banks inside the currently loaded ROM
int totalBanks = _cartridgeRom.Length / 0x4000;
// Safety catch for empty slots
if (totalBanks == 0) totalBanks = 1;
if (address == 0xFFFC)
{
_mapperControl = value;
}
else if (address == 0xFFFD)
{
_romBank0 = value % totalBanks; // Force the bank to stay inside the array bounds!
}
else if (address == 0xFFFE)
{
_romBank1 = value % totalBanks;
}
else if (address == 0xFFFF)
{
_romBank2 = value % totalBanks;
}
}
}
//public byte Read(ushort address) //public byte Read(ushort address)
//{ //{
// if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF) // if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF)
// { // {
// // SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged. // // SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged.
// // It is hardwired to Bank 0 so the interrupt handlers don't crash.
// if (address < 0x0400) return ReadFromCartridge(0, address); // if (address < 0x0400) return ReadFromCartridge(0, address);
// return ReadFromCartridge(_romBank0, address); // return ReadFromCartridge(_romBank0, address);
@@ -87,102 +126,72 @@ namespace Core.Memory
// } // }
// if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF) // if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF)
// { // {
// // Check if Cartridge RAM is enabled (Bit 3 of Mapper Control) // // --- THE MISSING LINK: Read from Save RAM if enabled! ---
// if ((_mapperControl & 0x08) != 0) // if ((_mapperControl & 0x08) != 0)
// { // {
// // Bit 2 decides if we read the first 16KB half or the second 16KB half of the RAM chip
// int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0; // int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
// int ramOffset = (ramBank * 0x4000) + (address & 0x3FFF); // int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
// return _cartridgeRam[ramOffset]; // return _cartridgeRam[ramOffset];
// } // }
// // Otherwise, read from the ROM as usual
// return ReadFromCartridge(_romBank2, address & 0x3FFF); // return ReadFromCartridge(_romBank2, address & 0x3FFF);
// } // }
// // If we are here, we are in System RAM (0xC000 - 0xFFFF) // // System RAM (0xC000 - 0xFFFF)
// // The & 0x1FFF handles the 8KB mirroring automatically!
// return _workRam[address & 0x1FFF]; // return _workRam[address & 0x1FFF];
//} //}
//public void Write(ushort address, byte value) //public void Write(ushort address, byte value)
//{ //{
// // --- 1. CARTRIDGE RAM (Save Data) ---
// if (address < 0xC000) // if (address < 0xC000)
// { // {
// // Bypass the lock if they are trying to save their game!
// if (address >= 0x8000 && (_mapperControl & 0x08) != 0) // if (address >= 0x8000 && (_mapperControl & 0x08) != 0)
// { // {
// SramUsed = true;
// int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0; // int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
// int ramOffset = (ramBank * 0x4000) + (address & 0x3FFF); // int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
// _cartridgeRam[ramOffset] = value; // _cartridgeRam[ramOffset] = value;
// return;
// } // }
// // You cannot write to a ROM cartridge! // // You cannot write to a ROM cartridge!
// return; // return;
// } // }
// // --- 2. SYSTEM RAM --- // // Write to System RAM
// _workRam[address & 0x1FFF] = value; // _workRam[address & 0x1FFF] = value;
// // --- 3. SEGA MAPPER (With Hardware Mirroring!) --- // // --- THE SEGA MAPPER ---
// // By checking the masked address (address & 0x1FFF), we perfectly // if (address == 0xFFFC) // <-- ADD THIS
// // catch writes to 0xFFFC-0xFFFF *and* their 0xDFFC-0xDFFF mirrors! // {
// int mapperAddress = address & 0x1FFF; // _mapperControl = value;
// }
// if (mapperAddress == 0x1FFC) _mapperControl = value; // else if (address == 0xFFFD)
// else if (mapperAddress == 0x1FFD) _romBank0 = value; // {
// else if (mapperAddress == 0x1FFE) _romBank1 = value; // _romBank0 = value;
// else if (mapperAddress == 0x1FFF) _romBank2 = value;
// } // }
public void Write(ushort address, byte value) // // Write to System RAM
{ // _workRam[address & 0x1FFF] = value;
if (address < 0xC000)
{
// Bypass the lock if they are trying to save their game!
if (address >= 0x8000 && (_mapperControl & 0x08) != 0)
{
SramUsed = true;
int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
_cartridgeRam[ramOffset] = value;
return;
}
// You cannot write to a ROM cartridge! // // --- THE SEGA MAPPER ---
return; // // If the CPU wrote to the very top of memory, it is commanding a bank swap!
} // if (address == 0xFFFD)
// {
// Write to System RAM // _romBank0 = value;
_workRam[address & 0x1FFF] = value; // }
// else if (address == 0xFFFE)
// --- THE SEGA MAPPER --- // {
if (address == 0xFFFC) // <-- ADD THIS // _romBank1 = value;
{ // }
_mapperControl = value; // else if (address == 0xFFFF)
} // {
else if (address == 0xFFFD) // _romBank2 = value;
{ // }
_romBank0 = value; //}
}
// Write to System RAM
_workRam[address & 0x1FFF] = value;
// --- THE SEGA MAPPER ---
// If the CPU wrote to the very top of memory, it is commanding a bank swap!
if (address == 0xFFFD)
{
_romBank0 = value;
}
else if (address == 0xFFFE)
{
_romBank1 = value;
}
else if (address == 0xFFFF)
{
_romBank2 = value;
}
}
private byte ReadFromCartridge(int bankIndex, int offset) private byte ReadFromCartridge(int bankIndex, int offset)
{ {

View File

@@ -63,11 +63,17 @@ namespace Desktop
PopulateIncludedRomsMenu(); PopulateIncludedRomsMenu();
} }
private void DrawScreen() private void DrawScreen(int[] safeFrame)
{ {
// Rapidly copy our VDP FrameBuffer into the Windows Bitmap // Rapidly the Frame into the Windows Bitmap
var data = _screenBitmap.LockBits(new Rectangle(0, 0, 256, 192), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); var data = _screenBitmap.LockBits(new Rectangle(0, 0, 256, 192), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length);
//Frame buffer draw
Marshal.Copy(safeFrame, 0, data.Scan0, safeFrame.Length);
//Direct VDP draw
//Marshal.Copy(_machine.VideoProcessor.FrameBuffer, 0, data.Scan0, _machine.VideoProcessor.FrameBuffer.Length);
_screenBitmap.UnlockBits(data); _screenBitmap.UnlockBits(data);
this.Invalidate(); this.Invalidate();
TotalFrameCount++; TotalFrameCount++;
@@ -137,7 +143,8 @@ namespace Desktop
double nextFrameTargetTime = _stopwatch.Elapsed.TotalMilliseconds + TargetFrameTime; double nextFrameTargetTime = _stopwatch.Elapsed.TotalMilliseconds + TargetFrameTime;
double lastFpsUpdate = _stopwatch.Elapsed.TotalMilliseconds; double lastFpsUpdate = _stopwatch.Elapsed.TotalMilliseconds;
int framesThisSecond = 0; int framesThisSecond = 0;
try
{
while (IsRunning) while (IsRunning)
{ {
//targetFps = isTurboMode ? 1000 : 59.94; //targetFps = isTurboMode ? 1000 : 59.94;
@@ -168,8 +175,10 @@ namespace Desktop
// -------------------------------- // --------------------------------
_machine.RunFrame(); _machine.RunFrame();
int[] frozenFrame = (int[])_machine.VideoProcessor.FrameBuffer.Clone();
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish! // 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(); }); BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(frozenFrame); });
// 3. Safety catch // 3. Safety catch
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value) if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
@@ -220,6 +229,23 @@ namespace Desktop
nextFrameTargetTime = currentTime + TargetFrameTime; nextFrameTargetTime = currentTime + TargetFrameTime;
} }
} }
}
catch (Exception ex)
{
IsRunning = false;
this.Invoke((System.Windows.Forms.MethodInvoker)delegate
{
MessageBox.Show(
$"The Z80 CPU Panicked!\n\n" +
$"PC Address: 0x{_machine.Cpu.PC:X4}\n" +
$"Error: {ex.Message}\n\n" +
$"Stack Trace:\n{ex.StackTrace}",
"Fatal Emulator Crash",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
});
}
}); });
} }