Compare commits
14 Commits
SecondRele
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2300e2931e | ||
|
|
2f332ff224 | ||
|
|
4a0424a664 | ||
|
|
ce46e7ed52 | ||
|
|
b5695b5c2f | ||
|
|
cca1abf0be | ||
|
|
9316a16ab9 | ||
|
|
cd5e18a2ac | ||
|
|
66f18d0510 | ||
|
|
be43019fb0 | ||
|
|
1730687009 | ||
|
|
f1140fa115 | ||
|
|
b1ff22c9e6 | ||
|
|
aa9cc552e6 |
@@ -33,7 +33,9 @@ namespace Core.Audio
|
|||||||
0.1584f, 0.1258f, 0.1000f, 0.0794f, 0.0630f, 0.0501f, 0.0398f, 0.0f
|
0.1584f, 0.1258f, 0.1000f, 0.0794f, 0.0630f, 0.0501f, 0.0398f, 0.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
public SmsApu()
|
public SmsApu()
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
{
|
{
|
||||||
Registers[1] = 0x0F;
|
Registers[1] = 0x0F;
|
||||||
Registers[3] = 0x0F;
|
Registers[3] = 0x0F;
|
||||||
|
|||||||
121
Core/Cpu/Z80.cs
121
Core/Cpu/Z80.cs
@@ -25,10 +25,13 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
public int InterruptMode { get; private set; } = 0;
|
public int InterruptMode { get; private set; } = 0;
|
||||||
|
|
||||||
|
|
||||||
// Interrupt Flip-Flops
|
// Interrupt Flip-Flops
|
||||||
public bool IFF1 { get; private set; } = false;
|
public bool IFF1 { get; private set; } = false;
|
||||||
public bool IFF2 { get; private set; } = false;
|
public bool IFF2 { get; private set; } = false;
|
||||||
public bool InterruptRequested { get; private set; } = false;
|
public bool InterruptRequested { get; private set; } = false;
|
||||||
|
private int _eiDelay = 0;
|
||||||
|
public bool IsHalted { get; private set; } = false;
|
||||||
|
|
||||||
// Main Register Set
|
// Main Register Set
|
||||||
public RegisterPair AF;
|
public RegisterPair AF;
|
||||||
@@ -48,7 +51,7 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
// Special Purpose Registers
|
// Special Purpose Registers
|
||||||
public ushort PC; // Program Counter
|
public ushort PC; // Program Counter
|
||||||
public ushort SP; // Stack Pointer
|
public ushort SP;
|
||||||
public byte I; // Interrupt Vector
|
public byte I; // Interrupt Vector
|
||||||
public byte R; // Memory Refresh
|
public byte R; // Memory Refresh
|
||||||
|
|
||||||
@@ -96,6 +99,10 @@ namespace Core.Cpu
|
|||||||
IFF2 = false;
|
IFF2 = false;
|
||||||
InterruptMode = 0;
|
InterruptMode = 0;
|
||||||
TotalTStates = 0;
|
TotalTStates = 0;
|
||||||
|
|
||||||
|
_eiDelay = 0;
|
||||||
|
IsHalted = false;
|
||||||
|
InterruptRequested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveState(BinaryWriter bw)
|
public void SaveState(BinaryWriter bw)
|
||||||
@@ -135,6 +142,7 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
public int RequestInterrupt()
|
public int RequestInterrupt()
|
||||||
{
|
{
|
||||||
|
IsHalted = false;
|
||||||
InterruptRequested = true;
|
InterruptRequested = true;
|
||||||
// 1. If the ROM has disabled interrupts (DI), ignore the request
|
// 1. If the ROM has disabled interrupts (DI), ignore the request
|
||||||
if (!IFF1) return 0;
|
if (!IFF1) return 0;
|
||||||
@@ -143,6 +151,8 @@ namespace Core.Cpu
|
|||||||
IFF1 = false;
|
IFF1 = false;
|
||||||
IFF2 = false;
|
IFF2 = false;
|
||||||
|
|
||||||
|
_eiDelay = 0;
|
||||||
|
|
||||||
// 3. Push the current Program Counter to the stack so we can return later
|
// 3. Push the current Program Counter to the stack so we can return later
|
||||||
Push(PC);
|
Push(PC);
|
||||||
|
|
||||||
@@ -213,19 +223,41 @@ namespace Core.Cpu
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int Step()
|
public int Step()
|
||||||
{
|
{
|
||||||
|
int tStates;
|
||||||
|
|
||||||
// Fetch the next opcode and increment the Program Counter
|
R = (byte)((R & 0x80) | ((R + 1) & 0x7F));
|
||||||
byte opcode = ReadMemory(PC++);
|
|
||||||
R = (byte)((R + 1) & 0x7F);
|
if (IsHalted)
|
||||||
int tStates = ExecuteOpcode(opcode);
|
{
|
||||||
TotalTStates += tStates;
|
// The CPU is asleep! Do not fetch instructions, just pass the time.
|
||||||
|
tStates = 4;
|
||||||
|
TotalTStates += tStates;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Normal execution
|
||||||
|
byte opcode = ReadMemory(PC++);
|
||||||
|
tStates = ExecuteOpcode(opcode);
|
||||||
|
TotalTStates += tStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The interrupt enablement perfectly mimics the physical hardware delay.
|
||||||
|
// This MUST tick down even if the CPU is halted!
|
||||||
|
if (_eiDelay > 0)
|
||||||
|
{
|
||||||
|
_eiDelay--;
|
||||||
|
if (_eiDelay == 0)
|
||||||
|
{
|
||||||
|
IFF1 = true;
|
||||||
|
IFF2 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Decode and execute
|
|
||||||
return tStates;
|
return tStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public string GetFlagsString()
|
public string GetFlagsString()
|
||||||
{
|
{
|
||||||
@@ -240,9 +272,6 @@ namespace Core.Cpu
|
|||||||
$"C:{f & 1}";
|
$"C:{f & 1}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// MATH AND LOGIC HELPERS
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
private void SubA(byte value, bool isCompare)
|
private void SubA(byte value, bool isCompare)
|
||||||
{
|
{
|
||||||
@@ -924,17 +953,21 @@ namespace Core.Cpu
|
|||||||
case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
|
case 0x73: WriteMemory(HL.Word, DE.Low); return 7;
|
||||||
case 0x74: WriteMemory(HL.Word, HL.High); return 7;
|
case 0x74: WriteMemory(HL.Word, HL.High); return 7;
|
||||||
case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
|
case 0x75: WriteMemory(HL.Word, HL.Low); return 7;
|
||||||
case 0x76: //HALT
|
case 0x76: // HALT
|
||||||
if (!InterruptRequested)
|
IsHalted = true;
|
||||||
{
|
return 4;
|
||||||
PC--;
|
|
||||||
return 4;
|
//case 0x76: //HALT
|
||||||
}
|
// if (!InterruptRequested)
|
||||||
else
|
// {
|
||||||
{
|
// PC--;
|
||||||
InterruptRequested = false;
|
// return 4;
|
||||||
return 4;
|
// }
|
||||||
}
|
// else
|
||||||
|
// {
|
||||||
|
// InterruptRequested = false;
|
||||||
|
// return 4;
|
||||||
|
// }
|
||||||
case 0x77: WriteMemory(HL.Word, AF.High); return 7;
|
case 0x77: WriteMemory(HL.Word, AF.High); return 7;
|
||||||
|
|
||||||
// --- LD A, r ---
|
// --- LD A, r ---
|
||||||
@@ -1272,6 +1305,11 @@ namespace Core.Cpu
|
|||||||
case 0xF3: // DI
|
case 0xF3: // DI
|
||||||
IFF1 = false;
|
IFF1 = false;
|
||||||
IFF2 = false;
|
IFF2 = false;
|
||||||
|
_eiDelay = 0; // Hard cancel any pending EI delays
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
case 0xFB: // EI
|
||||||
|
_eiDelay = 2; // Ticks down across the current and subsequent instruction
|
||||||
return 4;
|
return 4;
|
||||||
case 0xf5: //push af
|
case 0xf5: //push af
|
||||||
Push(AF.Word);
|
Push(AF.Word);
|
||||||
@@ -1282,10 +1320,7 @@ namespace Core.Cpu
|
|||||||
case 0xF9: // LD SP, HL
|
case 0xF9: // LD SP, HL
|
||||||
SP = HL.Word;
|
SP = HL.Word;
|
||||||
return 6;
|
return 6;
|
||||||
case 0xFB: // EI
|
|
||||||
IFF1 = true;
|
|
||||||
IFF2 = true;
|
|
||||||
return 4;
|
|
||||||
case 0xFD:
|
case 0xFD:
|
||||||
return ExecuteFDPrefix();
|
return ExecuteFDPrefix();
|
||||||
case 0xFE: // CP n
|
case 0xFE: // CP n
|
||||||
@@ -1303,6 +1338,35 @@ namespace Core.Cpu
|
|||||||
|
|
||||||
switch (extendedOpcode)
|
switch (extendedOpcode)
|
||||||
{
|
{
|
||||||
|
case 0x40: // IN B, (C)
|
||||||
|
{
|
||||||
|
byte inVal40 = ReadPort(BC.Word);
|
||||||
|
BC.High = inVal40;
|
||||||
|
|
||||||
|
byte flags40 = (byte)(AF.Low & 0x01); // Preserve Carry
|
||||||
|
if ((inVal40 & 0x80) != 0) flags40 |= 0x80; // S
|
||||||
|
if (inVal40 == 0) flags40 |= 0x40; // Z
|
||||||
|
flags40 |= ParityTable[inVal40]; // P/V
|
||||||
|
flags40 |= (byte)(inVal40 & 0x28); // Undocumented bits 3 and 5
|
||||||
|
|
||||||
|
AF.Low = flags40;
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x50: // IN D, (C)
|
||||||
|
{
|
||||||
|
byte inVal50 = ReadPort(BC.Word);
|
||||||
|
DE.High = inVal50;
|
||||||
|
|
||||||
|
byte flags50 = (byte)(AF.Low & 0x01); // Preserve Carry
|
||||||
|
if ((inVal50 & 0x80) != 0) flags50 |= 0x80; // S
|
||||||
|
if (inVal50 == 0) flags50 |= 0x40; // Z
|
||||||
|
flags50 |= ParityTable[inVal50]; // P/V
|
||||||
|
flags50 |= (byte)(inVal50 & 0x28); // Undocumented bits 3 and 5
|
||||||
|
|
||||||
|
AF.Low = flags50;
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
case 0x41: // OUT (C), B
|
case 0x41: // OUT (C), B
|
||||||
_simpleIoBus.WritePort(BC.Word, BC.High);
|
_simpleIoBus.WritePort(BC.Word, BC.High);
|
||||||
return 12;
|
return 12;
|
||||||
@@ -1798,7 +1862,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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ namespace Core.Io
|
|||||||
{
|
{
|
||||||
public class SmsIoBus : IIoBus
|
public class SmsIoBus : IIoBus
|
||||||
{
|
{
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
public SmsVdp VideoProcessor { get; set; }
|
public SmsVdp VideoProcessor { get; set; }
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
public SmsApu AudioProcessor { get; set; }
|
public SmsApu AudioProcessor { get; set; }
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
|
|
||||||
// Joypad State (0xFF means no buttons pressed - the SMS uses Active-Low logic!)
|
// Joypad State (0xFF means no buttons pressed - the SMS uses Active-Low logic!)
|
||||||
public byte Joypad1Keyboard = 0xFF;
|
public byte Joypad1Keyboard = 0xFF;
|
||||||
@@ -25,7 +29,11 @@ namespace Core.Io
|
|||||||
// VDP V-Counter (Vertical Scanline Position)
|
// VDP V-Counter (Vertical Scanline Position)
|
||||||
return VideoProcessor.ReadVCounter();
|
return VideoProcessor.ReadVCounter();
|
||||||
}
|
}
|
||||||
|
if (lowerPort == 0x7F)
|
||||||
|
{
|
||||||
|
// THE FIX: VDP H-Counter (Horizontal Pixel Position)
|
||||||
|
return VideoProcessor.ReadHCounter();
|
||||||
|
}
|
||||||
if (lowerPort >= 0x80 && lowerPort <= 0xBF)
|
if (lowerPort >= 0x80 && lowerPort <= 0xBF)
|
||||||
{
|
{
|
||||||
// Even ports (like 0xBE) are Data. Odd ports (like 0xBF) are Control.
|
// Even ports (like 0xBE) are Data. Odd ports (like 0xBF) are Control.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace Core.Memory
|
|||||||
{
|
{
|
||||||
public class SmsMemoryBus : IMemory
|
public class SmsMemoryBus : IMemory
|
||||||
{
|
{
|
||||||
|
private bool _isCodemastersMapper = false; //Codemasters used their own memory mapper
|
||||||
// SMS has 8KB of internal Work RAM
|
// SMS has 8KB of internal Work RAM
|
||||||
private readonly byte[] _workRam = new byte[0x2000];
|
private readonly byte[] _workRam = new byte[0x2000];
|
||||||
|
|
||||||
@@ -42,41 +43,101 @@ namespace Core.Memory
|
|||||||
|
|
||||||
public byte Read(ushort address)
|
public byte Read(ushort address)
|
||||||
{
|
{
|
||||||
if (address < 0x4000) // ROM Slot 0 (0x0000 - 0x3FFF)
|
if (address < 0xC000 && !_isCartridgeLoaded)
|
||||||
{
|
{
|
||||||
// SMS Hardware Quirk: The first 1KB (0x0000 - 0x03FF) is NEVER paged.
|
return 0xFF;
|
||||||
if (address < 0x0400) return ReadFromCartridge(0, address);
|
}
|
||||||
|
|
||||||
return ReadFromCartridge(_romBank0, address);
|
if (address < 0x4000)
|
||||||
}
|
|
||||||
if (address < 0x8000) // ROM Slot 1 (0x4000 - 0x7FFF)
|
|
||||||
{
|
{
|
||||||
return ReadFromCartridge(_romBank1, address & 0x3FFF);
|
if (!_isCodemastersMapper && address < 0x0400)
|
||||||
}
|
|
||||||
if (address < 0xC000) // ROM Slot 2 (0x8000 - 0xBFFF)
|
|
||||||
{
|
|
||||||
// --- THE MISSING LINK: Read from Save RAM if enabled! ---
|
|
||||||
if ((_mapperControl & 0x08) != 0)
|
|
||||||
{
|
{
|
||||||
int ramBank = (_mapperControl & 0x04) != 0 ? 1 : 0;
|
return _cartridgeRom[address];
|
||||||
int ramOffset = (ramBank * 0x4000) + (address & 0x1FFF);
|
|
||||||
return _cartridgeRam[ramOffset];
|
|
||||||
}
|
}
|
||||||
|
if (address < 0x0400)
|
||||||
// Otherwise, read from the ROM as usual
|
{
|
||||||
return ReadFromCartridge(_romBank2, address & 0x3FFF);
|
return _cartridgeRom[address];
|
||||||
|
}
|
||||||
|
// Slot 0
|
||||||
|
return _cartridgeRom[(_romBank0 * 0x4000) + address];
|
||||||
|
}
|
||||||
|
if (address < 0x8000)
|
||||||
|
{
|
||||||
|
// Slot 1
|
||||||
|
return _cartridgeRom[(_romBank1 * 0x4000) + (address - 0x4000)];
|
||||||
|
}
|
||||||
|
if (address < 0xC000)
|
||||||
|
{
|
||||||
|
// Slot 2 (Or SRAM)
|
||||||
|
if ((_mapperControl & 0x08) != 0)
|
||||||
|
return _cartridgeRam[address - 0x8000];
|
||||||
|
else
|
||||||
|
return _cartridgeRom[(_romBank2 * 0x4000) + (address - 0x8000)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// System RAM (0xC000 - 0xFFFF)
|
// Bitwise AND perfectly forces 0xE000-0xFFFF to mirror down to 0xC000!
|
||||||
return _workRam[address & 0x1FFF];
|
return _workRam[address & 0x1FFF];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Write(ushort address, byte value)
|
||||||
|
{
|
||||||
|
// Codemasters games bank-switch by writing directly to ROM!
|
||||||
|
if (address == 0x0000 || address == 0x4000 || address == 0x8000)
|
||||||
|
{
|
||||||
|
_isCodemastersMapper = true;
|
||||||
|
int cTotalBanks = _cartridgeRom.Length / 0x4000;
|
||||||
|
if (cTotalBanks == 0) cTotalBanks = 1;
|
||||||
|
|
||||||
|
if (address == 0x0000) _romBank0 = value % cTotalBanks;
|
||||||
|
else if (address == 0x4000) _romBank1 = value % cTotalBanks;
|
||||||
|
else if (address == 0x8000) _romBank2 = value % cTotalBanks;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (address < 0x8000) return; // Cannot write to physical ROM
|
||||||
|
|
||||||
|
if (address < 0xC000)
|
||||||
|
{
|
||||||
|
if ((_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,103 +148,73 @@ 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;
|
||||||
|
// }
|
||||||
|
// else if (address == 0xFFFD)
|
||||||
|
// {
|
||||||
|
// _romBank0 = value;
|
||||||
|
// }
|
||||||
|
|
||||||
// if (mapperAddress == 0x1FFC) _mapperControl = value;
|
// // Write to System RAM
|
||||||
// else if (mapperAddress == 0x1FFD) _romBank0 = value;
|
// _workRam[address & 0x1FFF] = value;
|
||||||
// else if (mapperAddress == 0x1FFE) _romBank1 = value;
|
|
||||||
// else if (mapperAddress == 0x1FFF) _romBank2 = 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;
|
||||||
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
public void Write(ushort address, byte 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!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to System RAM
|
|
||||||
_workRam[address & 0x1FFF] = value;
|
|
||||||
|
|
||||||
// --- THE SEGA MAPPER ---
|
|
||||||
if (address == 0xFFFC) // <-- ADD THIS
|
|
||||||
{
|
|
||||||
_mapperControl = value;
|
|
||||||
}
|
|
||||||
else if (address == 0xFFFD)
|
|
||||||
{
|
|
||||||
_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)
|
||||||
{
|
{
|
||||||
if (!_isCartridgeLoaded) return 0xFF; // Floating bus if no game
|
if (!_isCartridgeLoaded) return 0xFF; // Floating bus if no game
|
||||||
@@ -252,6 +283,7 @@ namespace Core.Memory
|
|||||||
{
|
{
|
||||||
Array.Clear(_workRam, 0, _workRam.Length);
|
Array.Clear(_workRam, 0, _workRam.Length);
|
||||||
_mapperControl = 0;
|
_mapperControl = 0;
|
||||||
|
_isCodemastersMapper = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,13 +17,14 @@ namespace Core
|
|||||||
public SmsVdp VideoProcessor { get; private set; }
|
public SmsVdp VideoProcessor { get; private set; }
|
||||||
public SmsApu AudioProcessor { get; private set; }
|
public SmsApu AudioProcessor { get; private set; }
|
||||||
public ushort? Breakpoint { get; set; } = null;
|
public ushort? Breakpoint { get; set; } = null;
|
||||||
|
private int _tStateCarryover = 0;
|
||||||
|
|
||||||
// NTSC SMS T-States per frame
|
// NTSC SMS T-States per frame
|
||||||
public const int TStatesPerFrame = 59736; //NTSC
|
public const int TStatesPerFrame = 59736; //NTSC
|
||||||
//public const int TStatesPerFrame = 49780; //PAL
|
//public const int TStatesPerFrame = 49780; //PAL
|
||||||
|
|
||||||
public SmsMachine()
|
public SmsMachine()
|
||||||
{
|
{
|
||||||
MemoryBus = new SmsMemoryBus();
|
MemoryBus = new SmsMemoryBus();
|
||||||
VideoProcessor = new SmsVdp();
|
VideoProcessor = new SmsVdp();
|
||||||
AudioProcessor = new SmsApu();
|
AudioProcessor = new SmsApu();
|
||||||
@@ -40,13 +41,15 @@ namespace Core
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
MemoryBus.CleanRAMData();
|
MemoryBus.CleanRAMData();
|
||||||
|
VideoProcessor.Reset();
|
||||||
Cpu.Reset();
|
Cpu.Reset();
|
||||||
|
_tStateCarryover = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void RunFrame()
|
public void RunFrame()
|
||||||
{
|
{
|
||||||
int tStatesThisFrame = 0;
|
int tStatesThisFrame = _tStateCarryover;
|
||||||
while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time
|
while (tStatesThisFrame < TStatesPerFrame) // Standard NTSC frame time
|
||||||
{
|
{
|
||||||
// 1. Run one CPU instruction
|
// 1. Run one CPU instruction
|
||||||
@@ -58,12 +61,16 @@ namespace Core
|
|||||||
AudioProcessor.Update(cycles);
|
AudioProcessor.Update(cycles);
|
||||||
|
|
||||||
// 3. Check if the VDP is begging for attention!
|
// 3. Check if the VDP is begging for attention!
|
||||||
if (VideoProcessor.InterruptPending && Cpu.IFF1)
|
//if (VideoProcessor.InterruptPending && Cpu.IFF1)
|
||||||
|
if (VideoProcessor.InterruptPending)
|
||||||
{
|
{
|
||||||
int intCycles = Cpu.RequestInterrupt();
|
int intCycles = Cpu.RequestInterrupt();
|
||||||
tStatesThisFrame += intCycles;
|
if (intCycles > 0)
|
||||||
VideoProcessor.Update(intCycles);
|
{
|
||||||
AudioProcessor.Update(intCycles);
|
tStatesThisFrame += intCycles;
|
||||||
|
VideoProcessor.Update(intCycles);
|
||||||
|
AudioProcessor.Update(intCycles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. THE RESTORED BREAKPOINT TRAP
|
// 4. THE RESTORED BREAKPOINT TRAP
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Core.Video
|
namespace Core.Video
|
||||||
@@ -7,7 +8,8 @@ namespace Core.Video
|
|||||||
{
|
{
|
||||||
// The VDP's private memory! The CPU cannot touch these arrays directly.
|
// The VDP's private memory! The CPU cannot touch these arrays directly.
|
||||||
public byte[] VRAM { get; private set; } = new byte[0x4000]; // 16KB Video RAM
|
public byte[] VRAM { get; private set; } = new byte[0x4000]; // 16KB Video RAM
|
||||||
public byte[] CRAM { get; private set; } = new byte[0x20]; // 32 Bytes Color Palette
|
public byte[] smsCRAM { get; private set; } = new byte[0x20]; // Master System - 32 Bytes colour Palette
|
||||||
|
public byte[] ggCRAM { get; private set; } = new byte[0x40]; // GameGear - 64 Bytes colour palette
|
||||||
public byte[] Registers { get; private set; } = new byte[16]; // 11 Hardware Control Registers
|
public byte[] Registers { get; private set; } = new byte[16]; // 11 Hardware Control Registers
|
||||||
public int[] FrameBuffer { get; private set; } = new int[256 * 192];
|
public int[] FrameBuffer { get; private set; } = new int[256 * 192];
|
||||||
private bool[] _priorityBuffer = new bool[256 * 192]; // Tracks priority pixels!
|
private bool[] _priorityBuffer = new bool[256 * 192]; // Tracks priority pixels!
|
||||||
@@ -25,17 +27,20 @@ namespace Core.Video
|
|||||||
private int _currentScanline = 0;
|
private int _currentScanline = 0;
|
||||||
private int _lineCounter = 0;
|
private int _lineCounter = 0;
|
||||||
private byte _statusRegister = 0x00;
|
private byte _statusRegister = 0x00;
|
||||||
|
public bool IsGameGear { get; set; } = false;
|
||||||
|
|
||||||
public bool InterruptPending =>
|
public bool InterruptPending =>
|
||||||
((_statusRegister & 0x80) != 0 && (Registers[1] & 0x20) != 0) || // VBlank
|
((_statusRegister & 0x80) != 0 && (Registers[1] & 0x20) != 0) || // VBlank
|
||||||
((_statusRegister & 0x40) != 0 && (Registers[0] & 0x10) != 0); // Line Interrupt
|
((_statusRegister & 0x40) != 0 && (Registers[0] & 0x10) != 0); // Line Interrupt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public byte ReadDataPort() // Port 0xBE
|
public byte ReadDataPort() // Port 0xBE
|
||||||
{
|
{
|
||||||
_isSecondControlByte = false; // Reading data resets the control latch
|
_isSecondControlByte = false; // Reading data resets the control latch
|
||||||
byte value = _readBuffer;
|
byte value = _readBuffer;
|
||||||
_readBuffer = VRAM[_controlWord & 0x3FFF];
|
_readBuffer = VRAM[_controlWord & 0x3FFF];
|
||||||
_controlWord++;
|
IncrementVdpAddress();
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +49,8 @@ namespace Core.Video
|
|||||||
_isSecondControlByte = false;
|
_isSecondControlByte = false;
|
||||||
byte currentStatus = _statusRegister;
|
byte currentStatus = _statusRegister;
|
||||||
|
|
||||||
// CRITICAL HARDWARE QUIRK: Reading the status port physically
|
// Reading the status port physically
|
||||||
// clears the flags inside the chip! If we don't clear this,
|
// clears the flags inside the chip If we don't clear this,
|
||||||
// the interrupt line gets stuck on forever.
|
// the interrupt line gets stuck on forever.
|
||||||
_statusRegister = 0x00;
|
_statusRegister = 0x00;
|
||||||
|
|
||||||
@@ -55,20 +60,29 @@ namespace Core.Video
|
|||||||
public void WriteDataPort(byte value) // Port 0xBE
|
public void WriteDataPort(byte value) // Port 0xBE
|
||||||
{
|
{
|
||||||
_isSecondControlByte = false;
|
_isSecondControlByte = false;
|
||||||
_readBuffer = value;
|
_readBuffer = value;
|
||||||
|
|
||||||
int address = _controlWord & 0x3FFF;
|
int address = _controlWord & 0x3FFF;
|
||||||
int command = (_controlWord >> 14) & 0x03;
|
int command = (_controlWord >> 14) & 0x03;
|
||||||
|
|
||||||
if (command == 3) // Code 3: Write to Color Palette (CRAM)
|
if (command == 3) // Code 3: Write to Colour Palette (CRAM)
|
||||||
{
|
{
|
||||||
CRAM[address & 0x1F] = value;
|
if (IsGameGear)
|
||||||
|
{
|
||||||
|
ggCRAM[address & 0x3F] = value; // GG has 64 bytes of CRAM
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
smsCRAM[address & 0x1F] = value; // SMS has 32 bytes of CRAM
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else // Code 0, 1, 2: Write to VRAM
|
else // THE FIX: Code 0, 1, 2: Write graphics to VRAM!
|
||||||
{
|
{
|
||||||
VRAM[address] = value;
|
VRAM[address] = value;
|
||||||
}
|
}
|
||||||
_controlWord++; // Auto-increment so the Z80 can blast data fast
|
|
||||||
|
// THE FIX: The pointer MUST auto-increment so the CPU can blast data fast!
|
||||||
|
IncrementVdpAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteControlPort(byte value) // Port 0xBF
|
public void WriteControlPort(byte value) // Port 0xBF
|
||||||
@@ -90,7 +104,7 @@ namespace Core.Video
|
|||||||
if (command == 0) // Code 0: Prep for VRAM Read
|
if (command == 0) // Code 0: Prep for VRAM Read
|
||||||
{
|
{
|
||||||
_readBuffer = VRAM[_controlWord & 0x3FFF];
|
_readBuffer = VRAM[_controlWord & 0x3FFF];
|
||||||
_controlWord++;
|
IncrementVdpAddress();
|
||||||
}
|
}
|
||||||
else if (command == 2) // Code 2: Write to Internal VDP Register
|
else if (command == 2) // Code 2: Write to Internal VDP Register
|
||||||
{
|
{
|
||||||
@@ -102,12 +116,42 @@ namespace Core.Video
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void IncrementVdpAddress()
|
||||||
|
{
|
||||||
|
// The VDP address register is only 14 bits
|
||||||
|
// When it increments past 0x3FFF, it rolls over to 0x0000.
|
||||||
|
// It can't overflow and corrupt the 2-bit command register (bits 14 and 15).
|
||||||
|
ushort address = (ushort)(_controlWord & 0x3FFF);
|
||||||
|
ushort command = (ushort)(_controlWord & 0xC000);
|
||||||
|
|
||||||
|
address = (ushort)((address + 1) & 0x3FFF);
|
||||||
|
_controlWord = (ushort)(command | address);
|
||||||
|
}
|
||||||
|
|
||||||
public byte ReadVCounter()
|
public byte ReadVCounter()
|
||||||
{
|
{
|
||||||
// Note: On real NTSC hardware, the V-Counter jumps slightly around
|
// NTSC Math: 262 lines. Counts 0 to 218, jumps to 213 (0xD5), counts to 255.
|
||||||
// the VBlank period to keep the math 8-bit, but simply returning
|
if (_currentScanline <= 218) return (byte)_currentScanline;
|
||||||
// the raw current scanline is perfectly fine to get us booting!
|
else return (byte)(_currentScanline - 6);
|
||||||
return (byte)_currentScanline;
|
|
||||||
|
}
|
||||||
|
public byte ReadHCounter()
|
||||||
|
{
|
||||||
|
// The Master System H-Counter counts from 0x00 to 0x93, then jumps forward to 0xE9, ending at 0xFF.
|
||||||
|
|
||||||
|
// 1 T-State = 1.5 pixels. The H-Counter increments every 2 pixels.
|
||||||
|
// So H = T * 0.75
|
||||||
|
int h = (int)(_tStateCounter * 0.75);
|
||||||
|
|
||||||
|
if (h <= 0x93)
|
||||||
|
{
|
||||||
|
return (byte)h;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Emulate the hardware jump!
|
||||||
|
return (byte)(h - 0x94 + 0xE9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(int tStates)
|
public void Update(int tStates)
|
||||||
@@ -145,7 +189,9 @@ namespace Core.Video
|
|||||||
// 3. MOVE TO THE NEXT LINE
|
// 3. MOVE TO THE NEXT LINE
|
||||||
_currentScanline++;
|
_currentScanline++;
|
||||||
|
|
||||||
if (_currentScanline > 261)
|
int maxLines = 262;
|
||||||
|
|
||||||
|
if (_currentScanline > maxLines -1)
|
||||||
{
|
{
|
||||||
_currentScanline = 0;
|
_currentScanline = 0;
|
||||||
}
|
}
|
||||||
@@ -167,7 +213,6 @@ namespace Core.Video
|
|||||||
for (int x = 0; x < 256; x++) FrameBuffer[(screenY * 256) + x] = unchecked((int)0xFF000000);
|
for (int x = 0; x < 256; x++) FrameBuffer[(screenY * 256) + x] = unchecked((int)0xFF000000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 1. RENDER BACKGROUND LINE ---
|
// --- 1. RENDER BACKGROUND LINE ---
|
||||||
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
|
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
|
||||||
int scrollX = Registers[8];
|
int scrollX = Registers[8];
|
||||||
@@ -183,16 +228,10 @@ namespace Core.Video
|
|||||||
// --- LEFT COLUMN MASKING (OVERSCAN CURTAIN) ---
|
// --- LEFT COLUMN MASKING (OVERSCAN CURTAIN) ---
|
||||||
if (maskLeftCol && screenX < 8)
|
if (maskLeftCol && screenX < 8)
|
||||||
{
|
{
|
||||||
// Draw the physical backdrop color (from Sprite Palette + Reg 7 index)
|
// REPLACE THE R/G/B MATH WITH THIS SINGLE LINE:
|
||||||
byte bgSmsColor = CRAM[16 + (Registers[7] & 0x0F)];
|
|
||||||
int bgR = (bgSmsColor & 0x03) * 85;
|
|
||||||
int bgG = ((bgSmsColor >> 2) & 0x03) * 85;
|
|
||||||
int bgB = ((bgSmsColor >> 4) & 0x03) * 85;
|
|
||||||
|
|
||||||
int bgAddress = (screenY * 256) + screenX;
|
int bgAddress = (screenY * 256) + screenX;
|
||||||
FrameBuffer[bgAddress] = (255 << 24) | (bgR << 16) | (bgG << 8) | bgB;
|
FrameBuffer[bgAddress] = GetRgbColour(16, Registers[7] & 0x0F);
|
||||||
|
|
||||||
// Flag it as priority so sprites also hide behind the curtain!
|
|
||||||
_priorityBuffer[bgAddress] = true;
|
_priorityBuffer[bgAddress] = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -231,21 +270,17 @@ namespace Core.Video
|
|||||||
byte bp3 = VRAM[tileAddress + (readY * 4) + 3];
|
byte bp3 = VRAM[tileAddress + (readY * 4) + 3];
|
||||||
|
|
||||||
int readX = flipH ? tileX : (7 - tileX);
|
int readX = flipH ? tileX : (7 - tileX);
|
||||||
int colorIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) |
|
int colourIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 1) |
|
||||||
(((bp2 >> readX) & 1) << 2) | (((bp3 >> readX) & 1) << 3);
|
(((bp2 >> readX) & 1) << 2) | (((bp3 >> readX) & 1) << 3);
|
||||||
|
|
||||||
int paletteOffset = useSpritePalette ? 16 : 0;
|
int paletteOffset = useSpritePalette ? 16 : 0;
|
||||||
byte smsColor = CRAM[paletteOffset + colorIndex];
|
int finalColour = GetRgbColour(paletteOffset, colourIndex);
|
||||||
|
|
||||||
int r = (smsColor & 0x03) * 85;
|
|
||||||
int g = ((smsColor >> 2) & 0x03) * 85;
|
|
||||||
int b = ((smsColor >> 4) & 0x03) * 85;
|
|
||||||
|
|
||||||
int screenAddress = (screenY * 256) + screenX;
|
int screenAddress = (screenY * 256) + screenX;
|
||||||
|
|
||||||
// Draw background and reset priority mask for this exact pixel
|
// Draw background and reset priority mask for this exact pixel
|
||||||
FrameBuffer[screenAddress] = (255 << 24) | (r << 16) | (g << 8) | b;
|
FrameBuffer[screenAddress] = finalColour;
|
||||||
_priorityBuffer[screenAddress] = (priority && colorIndex != 0);
|
_priorityBuffer[screenAddress] = (priority && colourIndex != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 2. RENDER SPRITE LINE ---
|
// --- 2. RENDER SPRITE LINE ---
|
||||||
@@ -299,24 +334,64 @@ namespace Core.Video
|
|||||||
if (_priorityBuffer[(screenY * 256) + screenX]) continue;
|
if (_priorityBuffer[(screenY * 256) + screenX]) continue;
|
||||||
|
|
||||||
int shift = 7 - px;
|
int shift = 7 - px;
|
||||||
int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
||||||
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
||||||
|
|
||||||
if (colorIndex == 0) continue;
|
if (colourIndex == 0) continue;
|
||||||
|
|
||||||
byte smsColor = CRAM[16 + colorIndex];
|
// REPLACE THE R/G/B MATH WITH THIS SINGLE LINE:
|
||||||
int r = (smsColor & 0x03) * 85;
|
FrameBuffer[(screenY * 256) + screenX] = GetRgbColour(16, colourIndex);
|
||||||
int g = ((smsColor >> 2) & 0x03) * 85;
|
|
||||||
int b = ((smsColor >> 4) & 0x03) * 85;
|
|
||||||
|
|
||||||
FrameBuffer[(screenY * 256) + screenX] = (255 << 24) | (r << 16) | (g << 8) | b;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetRgbColour(int paletteOffset, int colourIndex)
|
||||||
|
{
|
||||||
|
//Debug.WriteLine(_isGameGear);
|
||||||
|
int r, g, b;
|
||||||
|
|
||||||
|
if (IsGameGear)
|
||||||
|
{
|
||||||
|
// Game Gear: 2 bytes per colour. Format: ----BBBBGGGGRRRR
|
||||||
|
int cramIndex = (paletteOffset + colourIndex) * 2;
|
||||||
|
ushort ggcolour = (ushort)(ggCRAM[cramIndex] | (ggCRAM[cramIndex + 1] << 8));
|
||||||
|
|
||||||
|
// Extract 4-bit values (0-15) and map them to standard 8-bit values (0-255).
|
||||||
|
// We multiply by 17 because 255 / 15 = 17!
|
||||||
|
r = (ggcolour & 0x0F) * 17;
|
||||||
|
g = ((ggcolour >> 4) & 0x0F) * 17;
|
||||||
|
b = ((ggcolour >> 8) & 0x0F) * 17;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Master System: 1 byte per colour. Format: --BBGGRR
|
||||||
|
byte smscolour = smsCRAM[paletteOffset + colourIndex];
|
||||||
|
|
||||||
|
// Extract 2-bit values (0-3) and map them to standard 8-bit values (0-255).
|
||||||
|
// We multiply by 85 because 255 / 3 = 85!
|
||||||
|
r = (smscolour & 0x03) * 85;
|
||||||
|
g = ((smscolour >> 2) & 0x03) * 85;
|
||||||
|
b = ((smscolour >> 4) & 0x03) * 85;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (255 << 24) | (r << 16) | (g << 8) | b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_tStateCounter = 0;
|
||||||
|
_currentScanline = 0;
|
||||||
|
_lineCounter = 0;
|
||||||
|
_statusRegister = 0x00;
|
||||||
|
_controlWord = 0;
|
||||||
|
_isSecondControlByte = false;
|
||||||
|
_readBuffer = 0;
|
||||||
|
}
|
||||||
public void SaveState(BinaryWriter bw)
|
public void SaveState(BinaryWriter bw)
|
||||||
{
|
{
|
||||||
bw.Write(VRAM);
|
bw.Write(VRAM);
|
||||||
bw.Write(CRAM);
|
bw.Write(smsCRAM);
|
||||||
|
bw.Write(ggCRAM);
|
||||||
bw.Write(Registers);
|
bw.Write(Registers);
|
||||||
bw.Write(_isSecondControlByte);
|
bw.Write(_isSecondControlByte);
|
||||||
bw.Write(_controlWord);
|
bw.Write(_controlWord);
|
||||||
@@ -329,12 +404,14 @@ namespace Core.Video
|
|||||||
// ADD THESE:
|
// ADD THESE:
|
||||||
bw.Write(_latchedHScroll);
|
bw.Write(_latchedHScroll);
|
||||||
bw.Write(_latchedVScroll);
|
bw.Write(_latchedVScroll);
|
||||||
|
bw.Write(IsGameGear);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadState(BinaryReader br)
|
public void LoadState(BinaryReader br)
|
||||||
{
|
{
|
||||||
Array.Copy(br.ReadBytes(VRAM.Length), VRAM, VRAM.Length);
|
Array.Copy(br.ReadBytes(VRAM.Length), VRAM, VRAM.Length);
|
||||||
Array.Copy(br.ReadBytes(CRAM.Length), CRAM, CRAM.Length);
|
Array.Copy(br.ReadBytes(smsCRAM.Length), smsCRAM, smsCRAM.Length);
|
||||||
|
Array.Copy(br.ReadBytes(ggCRAM.Length), ggCRAM, ggCRAM.Length);
|
||||||
Array.Copy(br.ReadBytes(Registers.Length), Registers, Registers.Length);
|
Array.Copy(br.ReadBytes(Registers.Length), Registers, Registers.Length);
|
||||||
_isSecondControlByte = br.ReadBoolean();
|
_isSecondControlByte = br.ReadBoolean();
|
||||||
_controlWord = br.ReadUInt16();
|
_controlWord = br.ReadUInt16();
|
||||||
@@ -347,6 +424,7 @@ namespace Core.Video
|
|||||||
// ADD THESE:
|
// ADD THESE:
|
||||||
_latchedHScroll = br.ReadInt32();
|
_latchedHScroll = br.ReadInt32();
|
||||||
_latchedVScroll = br.ReadInt32();
|
_latchedVScroll = br.ReadInt32();
|
||||||
|
IsGameGear = br.ReadBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
235
Desktop/DebuggerForm.Designer.cs
generated
235
Desktop/DebuggerForm.Designer.cs
generated
@@ -56,102 +56,104 @@
|
|||||||
lblFPS = new Label();
|
lblFPS = new Label();
|
||||||
lblFrameTime = new Label();
|
lblFrameTime = new Label();
|
||||||
richTextBox1 = new RichTextBox();
|
richTextBox1 = new RichTextBox();
|
||||||
button1 = new Button();
|
btnCpuStep = new Button();
|
||||||
CpuRun = new Button();
|
CpuRun = new Button();
|
||||||
groupBox1 = new GroupBox();
|
groupBox1 = new GroupBox();
|
||||||
lblTone0 = new Label();
|
|
||||||
lblNoise = new Label();
|
|
||||||
lblTone2 = new Label();
|
|
||||||
lblTone1 = new Label();
|
lblTone1 = new Label();
|
||||||
|
lblTone2 = new Label();
|
||||||
|
lblNoise = new Label();
|
||||||
|
lblTone0 = new Label();
|
||||||
|
lblHalt = new Label();
|
||||||
|
lblR = new Label();
|
||||||
groupBox1.SuspendLayout();
|
groupBox1.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// lblAF
|
// lblAF
|
||||||
//
|
//
|
||||||
lblAF.AutoSize = true;
|
lblAF.AutoSize = true;
|
||||||
lblAF.Location = new Point(12, 9);
|
lblAF.Location = new Point(10, 7);
|
||||||
lblAF.Margin = new Padding(2, 0, 2, 0);
|
lblAF.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblAF.Name = "lblAF";
|
lblAF.Name = "lblAF";
|
||||||
lblAF.Size = new Size(33, 25);
|
lblAF.Size = new Size(26, 20);
|
||||||
lblAF.TabIndex = 0;
|
lblAF.TabIndex = 0;
|
||||||
lblAF.Text = "AF";
|
lblAF.Text = "AF";
|
||||||
//
|
//
|
||||||
// lblBC
|
// lblBC
|
||||||
//
|
//
|
||||||
lblBC.AutoSize = true;
|
lblBC.AutoSize = true;
|
||||||
lblBC.Location = new Point(11, 60);
|
lblBC.Location = new Point(9, 48);
|
||||||
lblBC.Margin = new Padding(2, 0, 2, 0);
|
lblBC.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblBC.Name = "lblBC";
|
lblBC.Name = "lblBC";
|
||||||
lblBC.Size = new Size(33, 25);
|
lblBC.Size = new Size(27, 20);
|
||||||
lblBC.TabIndex = 1;
|
lblBC.TabIndex = 1;
|
||||||
lblBC.Text = "BC";
|
lblBC.Text = "BC";
|
||||||
//
|
//
|
||||||
// lblDE
|
// lblDE
|
||||||
//
|
//
|
||||||
lblDE.AutoSize = true;
|
lblDE.AutoSize = true;
|
||||||
lblDE.Location = new Point(12, 125);
|
lblDE.Location = new Point(10, 100);
|
||||||
lblDE.Margin = new Padding(2, 0, 2, 0);
|
lblDE.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblDE.Name = "lblDE";
|
lblDE.Name = "lblDE";
|
||||||
lblDE.Size = new Size(34, 25);
|
lblDE.Size = new Size(28, 20);
|
||||||
lblDE.TabIndex = 2;
|
lblDE.TabIndex = 2;
|
||||||
lblDE.Text = "DE";
|
lblDE.Text = "DE";
|
||||||
//
|
//
|
||||||
// lblHL
|
// lblHL
|
||||||
//
|
//
|
||||||
lblHL.AutoSize = true;
|
lblHL.AutoSize = true;
|
||||||
lblHL.Location = new Point(12, 188);
|
lblHL.Location = new Point(10, 150);
|
||||||
lblHL.Margin = new Padding(2, 0, 2, 0);
|
lblHL.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblHL.Name = "lblHL";
|
lblHL.Name = "lblHL";
|
||||||
lblHL.Size = new Size(33, 25);
|
lblHL.Size = new Size(27, 20);
|
||||||
lblHL.TabIndex = 3;
|
lblHL.TabIndex = 3;
|
||||||
lblHL.Text = "HL";
|
lblHL.Text = "HL";
|
||||||
//
|
//
|
||||||
// lblPC
|
// lblPC
|
||||||
//
|
//
|
||||||
lblPC.AutoSize = true;
|
lblPC.AutoSize = true;
|
||||||
lblPC.Location = new Point(11, 250);
|
lblPC.Location = new Point(9, 200);
|
||||||
lblPC.Margin = new Padding(2, 0, 2, 0);
|
lblPC.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblPC.Name = "lblPC";
|
lblPC.Name = "lblPC";
|
||||||
lblPC.Size = new Size(33, 25);
|
lblPC.Size = new Size(26, 20);
|
||||||
lblPC.TabIndex = 4;
|
lblPC.TabIndex = 4;
|
||||||
lblPC.Text = "PC";
|
lblPC.Text = "PC";
|
||||||
//
|
//
|
||||||
// lblSP
|
// lblSP
|
||||||
//
|
//
|
||||||
lblSP.AutoSize = true;
|
lblSP.AutoSize = true;
|
||||||
lblSP.Location = new Point(11, 315);
|
lblSP.Location = new Point(9, 252);
|
||||||
lblSP.Margin = new Padding(2, 0, 2, 0);
|
lblSP.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblSP.Name = "lblSP";
|
lblSP.Name = "lblSP";
|
||||||
lblSP.Size = new Size(32, 25);
|
lblSP.Size = new Size(25, 20);
|
||||||
lblSP.TabIndex = 6;
|
lblSP.TabIndex = 6;
|
||||||
lblSP.Text = "SP";
|
lblSP.Text = "SP";
|
||||||
//
|
//
|
||||||
// lblFlags
|
// lblFlags
|
||||||
//
|
//
|
||||||
lblFlags.AutoSize = true;
|
lblFlags.AutoSize = true;
|
||||||
lblFlags.Location = new Point(110, 65);
|
lblFlags.Location = new Point(88, 52);
|
||||||
lblFlags.Margin = new Padding(2, 0, 2, 0);
|
lblFlags.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblFlags.Name = "lblFlags";
|
lblFlags.Name = "lblFlags";
|
||||||
lblFlags.Size = new Size(53, 25);
|
lblFlags.Size = new Size(43, 20);
|
||||||
lblFlags.TabIndex = 7;
|
lblFlags.TabIndex = 7;
|
||||||
lblFlags.Text = "Flags";
|
lblFlags.Text = "Flags";
|
||||||
//
|
//
|
||||||
// lblTStates
|
// lblTStates
|
||||||
//
|
//
|
||||||
lblTStates.AutoSize = true;
|
lblTStates.AutoSize = true;
|
||||||
lblTStates.Location = new Point(110, 9);
|
lblTStates.Location = new Point(88, 7);
|
||||||
lblTStates.Margin = new Padding(2, 0, 2, 0);
|
lblTStates.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblTStates.Name = "lblTStates";
|
lblTStates.Name = "lblTStates";
|
||||||
lblTStates.Size = new Size(75, 25);
|
lblTStates.Size = new Size(63, 20);
|
||||||
lblTStates.TabIndex = 8;
|
lblTStates.TabIndex = 8;
|
||||||
lblTStates.Text = "T-States";
|
lblTStates.Text = "T-States";
|
||||||
//
|
//
|
||||||
// txtMemoryStart
|
// txtMemoryStart
|
||||||
//
|
//
|
||||||
txtMemoryStart.Location = new Point(375, 26);
|
txtMemoryStart.Location = new Point(300, 21);
|
||||||
txtMemoryStart.Margin = new Padding(2);
|
txtMemoryStart.Margin = new Padding(2);
|
||||||
txtMemoryStart.Name = "txtMemoryStart";
|
txtMemoryStart.Name = "txtMemoryStart";
|
||||||
txtMemoryStart.Size = new Size(150, 31);
|
txtMemoryStart.Size = new Size(121, 27);
|
||||||
txtMemoryStart.TabIndex = 9;
|
txtMemoryStart.TabIndex = 9;
|
||||||
txtMemoryStart.Text = "Memory Start";
|
txtMemoryStart.Text = "Memory Start";
|
||||||
txtMemoryStart.TextAlign = HorizontalAlignment.Center;
|
txtMemoryStart.TextAlign = HorizontalAlignment.Center;
|
||||||
@@ -159,10 +161,10 @@
|
|||||||
//
|
//
|
||||||
// btnRefreshMemory
|
// btnRefreshMemory
|
||||||
//
|
//
|
||||||
btnRefreshMemory.Location = new Point(531, 26);
|
btnRefreshMemory.Location = new Point(425, 21);
|
||||||
btnRefreshMemory.Margin = new Padding(2);
|
btnRefreshMemory.Margin = new Padding(2);
|
||||||
btnRefreshMemory.Name = "btnRefreshMemory";
|
btnRefreshMemory.Name = "btnRefreshMemory";
|
||||||
btnRefreshMemory.Size = new Size(112, 34);
|
btnRefreshMemory.Size = new Size(90, 27);
|
||||||
btnRefreshMemory.TabIndex = 14;
|
btnRefreshMemory.TabIndex = 14;
|
||||||
btnRefreshMemory.Text = "Refresh Memory";
|
btnRefreshMemory.Text = "Refresh Memory";
|
||||||
btnRefreshMemory.UseVisualStyleBackColor = true;
|
btnRefreshMemory.UseVisualStyleBackColor = true;
|
||||||
@@ -170,117 +172,109 @@
|
|||||||
//
|
//
|
||||||
// txtMemoryView
|
// txtMemoryView
|
||||||
//
|
//
|
||||||
txtMemoryView.Location = new Point(110, 101);
|
txtMemoryView.Location = new Point(88, 81);
|
||||||
txtMemoryView.Margin = new Padding(2);
|
txtMemoryView.Margin = new Padding(2);
|
||||||
txtMemoryView.Name = "txtMemoryView";
|
txtMemoryView.Name = "txtMemoryView";
|
||||||
txtMemoryView.Size = new Size(553, 543);
|
txtMemoryView.Size = new Size(443, 435);
|
||||||
txtMemoryView.TabIndex = 15;
|
txtMemoryView.TabIndex = 15;
|
||||||
txtMemoryView.Text = "Memory View Window";
|
txtMemoryView.Text = "Memory View Window";
|
||||||
//
|
//
|
||||||
// lstDisassembly
|
// lstDisassembly
|
||||||
//
|
//
|
||||||
lstDisassembly.FormattingEnabled = true;
|
lstDisassembly.FormattingEnabled = true;
|
||||||
lstDisassembly.ItemHeight = 25;
|
lstDisassembly.Location = new Point(567, 8);
|
||||||
lstDisassembly.Location = new Point(709, 10);
|
|
||||||
lstDisassembly.Margin = new Padding(2);
|
lstDisassembly.Margin = new Padding(2);
|
||||||
lstDisassembly.Name = "lstDisassembly";
|
lstDisassembly.Name = "lstDisassembly";
|
||||||
lstDisassembly.Size = new Size(314, 329);
|
lstDisassembly.Size = new Size(252, 264);
|
||||||
lstDisassembly.TabIndex = 16;
|
lstDisassembly.TabIndex = 16;
|
||||||
//
|
//
|
||||||
// lstStack
|
// lstStack
|
||||||
//
|
//
|
||||||
lstStack.FormattingEnabled = true;
|
lstStack.FormattingEnabled = true;
|
||||||
lstStack.ItemHeight = 25;
|
lstStack.Location = new Point(823, 8);
|
||||||
lstStack.Location = new Point(1029, 10);
|
|
||||||
lstStack.Margin = new Padding(2);
|
lstStack.Margin = new Padding(2);
|
||||||
lstStack.Name = "lstStack";
|
lstStack.Name = "lstStack";
|
||||||
lstStack.Size = new Size(162, 329);
|
lstStack.Size = new Size(130, 264);
|
||||||
lstStack.TabIndex = 17;
|
lstStack.TabIndex = 17;
|
||||||
//
|
//
|
||||||
// label1
|
// label1
|
||||||
//
|
//
|
||||||
label1.AutoSize = true;
|
label1.AutoSize = true;
|
||||||
label1.Location = new Point(710, 372);
|
label1.Location = new Point(568, 298);
|
||||||
label1.Margin = new Padding(4, 0, 4, 0);
|
|
||||||
label1.Name = "label1";
|
label1.Name = "label1";
|
||||||
label1.Size = new Size(97, 25);
|
label1.Size = new Size(81, 20);
|
||||||
label1.TabIndex = 19;
|
label1.TabIndex = 19;
|
||||||
label1.Text = "Breakpoint";
|
label1.Text = "Breakpoint";
|
||||||
//
|
//
|
||||||
// txtBreakpoint
|
// txtBreakpoint
|
||||||
//
|
//
|
||||||
txtBreakpoint.Location = new Point(819, 369);
|
txtBreakpoint.Location = new Point(655, 295);
|
||||||
txtBreakpoint.Margin = new Padding(4);
|
|
||||||
txtBreakpoint.Name = "txtBreakpoint";
|
txtBreakpoint.Name = "txtBreakpoint";
|
||||||
txtBreakpoint.Size = new Size(155, 31);
|
txtBreakpoint.Size = new Size(125, 27);
|
||||||
txtBreakpoint.TabIndex = 20;
|
txtBreakpoint.TabIndex = 20;
|
||||||
//
|
//
|
||||||
// label2
|
// label2
|
||||||
//
|
//
|
||||||
label2.AutoSize = true;
|
label2.AutoSize = true;
|
||||||
label2.Location = new Point(291, 26);
|
label2.Location = new Point(233, 21);
|
||||||
label2.Margin = new Padding(4, 0, 4, 0);
|
|
||||||
label2.Name = "label2";
|
label2.Name = "label2";
|
||||||
label2.Size = new Size(77, 25);
|
label2.Size = new Size(62, 20);
|
||||||
label2.TabIndex = 21;
|
label2.TabIndex = 21;
|
||||||
label2.Text = "Address";
|
label2.Text = "Address";
|
||||||
//
|
//
|
||||||
// lblIX
|
// lblIX
|
||||||
//
|
//
|
||||||
lblIX.AutoSize = true;
|
lblIX.AutoSize = true;
|
||||||
lblIX.Location = new Point(11, 372);
|
lblIX.Location = new Point(9, 298);
|
||||||
lblIX.Margin = new Padding(2, 0, 2, 0);
|
lblIX.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblIX.Name = "lblIX";
|
lblIX.Name = "lblIX";
|
||||||
lblIX.Size = new Size(28, 25);
|
lblIX.Size = new Size(22, 20);
|
||||||
lblIX.TabIndex = 22;
|
lblIX.TabIndex = 22;
|
||||||
lblIX.Text = "IX";
|
lblIX.Text = "IX";
|
||||||
//
|
//
|
||||||
// lblIY
|
// lblIY
|
||||||
//
|
//
|
||||||
lblIY.AutoSize = true;
|
lblIY.AutoSize = true;
|
||||||
lblIY.Location = new Point(12, 430);
|
lblIY.Location = new Point(10, 344);
|
||||||
lblIY.Margin = new Padding(2, 0, 2, 0);
|
lblIY.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblIY.Name = "lblIY";
|
lblIY.Name = "lblIY";
|
||||||
lblIY.Size = new Size(27, 25);
|
lblIY.Size = new Size(21, 20);
|
||||||
lblIY.TabIndex = 23;
|
lblIY.TabIndex = 23;
|
||||||
lblIY.Text = "IY";
|
lblIY.Text = "IY";
|
||||||
//
|
//
|
||||||
// lblIff1
|
// lblIff1
|
||||||
//
|
//
|
||||||
lblIff1.AutoSize = true;
|
lblIff1.AutoSize = true;
|
||||||
lblIff1.Location = new Point(11, 533);
|
lblIff1.Location = new Point(10, 472);
|
||||||
lblIff1.Margin = new Padding(4, 0, 4, 0);
|
|
||||||
lblIff1.Name = "lblIff1";
|
lblIff1.Name = "lblIff1";
|
||||||
lblIff1.Size = new Size(45, 25);
|
lblIff1.Size = new Size(35, 20);
|
||||||
lblIff1.TabIndex = 24;
|
lblIff1.TabIndex = 24;
|
||||||
lblIff1.Text = "IFF1";
|
lblIff1.Text = "IFF1";
|
||||||
//
|
//
|
||||||
// lblIff2
|
// lblIff2
|
||||||
//
|
//
|
||||||
lblIff2.AutoSize = true;
|
lblIff2.AutoSize = true;
|
||||||
lblIff2.Location = new Point(11, 586);
|
lblIff2.Location = new Point(10, 515);
|
||||||
lblIff2.Margin = new Padding(4, 0, 4, 0);
|
|
||||||
lblIff2.Name = "lblIff2";
|
lblIff2.Name = "lblIff2";
|
||||||
lblIff2.Size = new Size(45, 25);
|
lblIff2.Size = new Size(35, 20);
|
||||||
lblIff2.TabIndex = 25;
|
lblIff2.TabIndex = 25;
|
||||||
lblIff2.Text = "IFF2";
|
lblIff2.Text = "IFF2";
|
||||||
//
|
//
|
||||||
// lblIE
|
// lblIE
|
||||||
//
|
//
|
||||||
lblIE.AutoSize = true;
|
lblIE.AutoSize = true;
|
||||||
lblIE.Location = new Point(11, 486);
|
lblIE.Location = new Point(10, 435);
|
||||||
lblIE.Margin = new Padding(4, 0, 4, 0);
|
|
||||||
lblIE.Name = "lblIE";
|
lblIE.Name = "lblIE";
|
||||||
lblIE.Size = new Size(33, 25);
|
lblIE.Size = new Size(26, 20);
|
||||||
lblIE.TabIndex = 26;
|
lblIE.TabIndex = 26;
|
||||||
lblIE.Text = "IM";
|
lblIE.Text = "IM";
|
||||||
//
|
//
|
||||||
// btnReset
|
// btnReset
|
||||||
//
|
//
|
||||||
btnReset.Location = new Point(814, 409);
|
btnReset.Location = new Point(651, 327);
|
||||||
btnReset.Margin = new Padding(2);
|
btnReset.Margin = new Padding(2);
|
||||||
btnReset.Name = "btnReset";
|
btnReset.Name = "btnReset";
|
||||||
btnReset.Size = new Size(165, 34);
|
btnReset.Size = new Size(132, 27);
|
||||||
btnReset.TabIndex = 27;
|
btnReset.TabIndex = 27;
|
||||||
btnReset.Text = "Set Breakpoint";
|
btnReset.Text = "Set Breakpoint";
|
||||||
btnReset.UseVisualStyleBackColor = true;
|
btnReset.UseVisualStyleBackColor = true;
|
||||||
@@ -295,61 +289,58 @@
|
|||||||
// lblFrames
|
// lblFrames
|
||||||
//
|
//
|
||||||
lblFrames.AutoSize = true;
|
lblFrames.AutoSize = true;
|
||||||
lblFrames.Location = new Point(14, 660);
|
lblFrames.Location = new Point(10, 605);
|
||||||
lblFrames.Margin = new Padding(2, 0, 2, 0);
|
lblFrames.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblFrames.Name = "lblFrames";
|
lblFrames.Name = "lblFrames";
|
||||||
lblFrames.Size = new Size(149, 25);
|
lblFrames.Size = new Size(124, 20);
|
||||||
lblFrames.TabIndex = 28;
|
lblFrames.TabIndex = 28;
|
||||||
lblFrames.Text = "Frames Rendered";
|
lblFrames.Text = "Frames Rendered";
|
||||||
//
|
//
|
||||||
// lblFPS
|
// lblFPS
|
||||||
//
|
//
|
||||||
lblFPS.AutoSize = true;
|
lblFPS.AutoSize = true;
|
||||||
lblFPS.Location = new Point(129, 757);
|
lblFPS.Location = new Point(102, 683);
|
||||||
lblFPS.Margin = new Padding(2, 0, 2, 0);
|
lblFPS.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblFPS.Name = "lblFPS";
|
lblFPS.Name = "lblFPS";
|
||||||
lblFPS.Size = new Size(41, 25);
|
lblFPS.Size = new Size(32, 20);
|
||||||
lblFPS.TabIndex = 29;
|
lblFPS.TabIndex = 29;
|
||||||
lblFPS.Text = "FPS";
|
lblFPS.Text = "FPS";
|
||||||
//
|
//
|
||||||
// lblFrameTime
|
// lblFrameTime
|
||||||
//
|
//
|
||||||
lblFrameTime.AutoSize = true;
|
lblFrameTime.AutoSize = true;
|
||||||
lblFrameTime.Location = new Point(60, 706);
|
lblFrameTime.Location = new Point(47, 642);
|
||||||
lblFrameTime.Margin = new Padding(2, 0, 2, 0);
|
lblFrameTime.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblFrameTime.Name = "lblFrameTime";
|
lblFrameTime.Name = "lblFrameTime";
|
||||||
lblFrameTime.Size = new Size(104, 25);
|
lblFrameTime.Size = new Size(87, 20);
|
||||||
lblFrameTime.TabIndex = 30;
|
lblFrameTime.TabIndex = 30;
|
||||||
lblFrameTime.Text = "Frame Time";
|
lblFrameTime.Text = "Frame Time";
|
||||||
//
|
//
|
||||||
// richTextBox1
|
// richTextBox1
|
||||||
//
|
//
|
||||||
richTextBox1.Enabled = false;
|
richTextBox1.Enabled = false;
|
||||||
richTextBox1.Location = new Point(682, 474);
|
richTextBox1.Location = new Point(546, 379);
|
||||||
richTextBox1.Margin = new Padding(4);
|
|
||||||
richTextBox1.Name = "richTextBox1";
|
richTextBox1.Name = "richTextBox1";
|
||||||
richTextBox1.ReadOnly = true;
|
richTextBox1.ReadOnly = true;
|
||||||
richTextBox1.Size = new Size(379, 159);
|
richTextBox1.Size = new Size(304, 128);
|
||||||
richTextBox1.TabIndex = 32;
|
richTextBox1.TabIndex = 32;
|
||||||
richTextBox1.Text = "Sega Master System Memory Map:\n0x0000 - 0x3FFF: ROM Slot 0 (16KB)\n0x4000 - 0x7FFF: ROM Slot 1 (16KB)\n0x8000 - 0xBFFF: ROM Slot 2 (16KB)\n0xC000 - 0xDFFF: System RAM (8KB)\n0xE000 - 0xFFFF: RAM Mirror";
|
richTextBox1.Text = "Sega Master System Memory Map:\n0x0000 - 0x3FFF: ROM Slot 0 (16KB)\n0x4000 - 0x7FFF: ROM Slot 1 (16KB)\n0x8000 - 0xBFFF: ROM Slot 2 (16KB)\n0xC000 - 0xDFFF: System RAM (8KB)\n0xE000 - 0xFFFF: RAM Mirror";
|
||||||
//
|
//
|
||||||
// button1
|
// btnCpuStep
|
||||||
//
|
//
|
||||||
button1.Location = new Point(1075, 756);
|
btnCpuStep.Location = new Point(860, 605);
|
||||||
button1.Margin = new Padding(4);
|
btnCpuStep.Name = "btnCpuStep";
|
||||||
button1.Name = "button1";
|
btnCpuStep.Size = new Size(94, 29);
|
||||||
button1.Size = new Size(118, 36);
|
btnCpuStep.TabIndex = 33;
|
||||||
button1.TabIndex = 33;
|
btnCpuStep.Text = "CPU Step";
|
||||||
button1.Text = "CPU Step";
|
btnCpuStep.UseVisualStyleBackColor = true;
|
||||||
button1.UseVisualStyleBackColor = true;
|
btnCpuStep.Click += btnStep_Click;
|
||||||
button1.Click += btnStep_Click;
|
|
||||||
//
|
//
|
||||||
// CpuRun
|
// CpuRun
|
||||||
//
|
//
|
||||||
CpuRun.Location = new Point(943, 757);
|
CpuRun.Location = new Point(754, 606);
|
||||||
CpuRun.Margin = new Padding(4);
|
|
||||||
CpuRun.Name = "CpuRun";
|
CpuRun.Name = "CpuRun";
|
||||||
CpuRun.Size = new Size(118, 36);
|
CpuRun.Size = new Size(94, 29);
|
||||||
CpuRun.TabIndex = 34;
|
CpuRun.TabIndex = 34;
|
||||||
CpuRun.Text = "CPU Run";
|
CpuRun.Text = "CPU Run";
|
||||||
CpuRun.UseVisualStyleBackColor = true;
|
CpuRun.UseVisualStyleBackColor = true;
|
||||||
@@ -361,57 +352,83 @@
|
|||||||
groupBox1.Controls.Add(lblTone2);
|
groupBox1.Controls.Add(lblTone2);
|
||||||
groupBox1.Controls.Add(lblNoise);
|
groupBox1.Controls.Add(lblNoise);
|
||||||
groupBox1.Controls.Add(lblTone0);
|
groupBox1.Controls.Add(lblTone0);
|
||||||
groupBox1.Location = new Point(266, 658);
|
groupBox1.Location = new Point(213, 526);
|
||||||
|
groupBox1.Margin = new Padding(2);
|
||||||
groupBox1.Name = "groupBox1";
|
groupBox1.Name = "groupBox1";
|
||||||
groupBox1.Size = new Size(397, 222);
|
groupBox1.Padding = new Padding(2);
|
||||||
|
groupBox1.Size = new Size(318, 178);
|
||||||
groupBox1.TabIndex = 35;
|
groupBox1.TabIndex = 35;
|
||||||
groupBox1.TabStop = false;
|
groupBox1.TabStop = false;
|
||||||
groupBox1.Text = "Audio (SN76489)";
|
groupBox1.Text = "Audio (SN76489)";
|
||||||
//
|
//
|
||||||
// lblTone0
|
// lblTone1
|
||||||
//
|
//
|
||||||
lblTone0.AutoSize = true;
|
lblTone1.AutoSize = true;
|
||||||
lblTone0.Location = new Point(25, 48);
|
lblTone1.Location = new Point(20, 66);
|
||||||
lblTone0.Name = "lblTone0";
|
lblTone1.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblTone0.Size = new Size(59, 25);
|
lblTone1.Name = "lblTone1";
|
||||||
lblTone0.TabIndex = 0;
|
lblTone1.Size = new Size(49, 20);
|
||||||
lblTone0.Text = "Tone0";
|
lblTone1.TabIndex = 3;
|
||||||
//
|
lblTone1.Text = "Tone1";
|
||||||
// lblNoise
|
|
||||||
//
|
|
||||||
lblNoise.AutoSize = true;
|
|
||||||
lblNoise.Location = new Point(25, 160);
|
|
||||||
lblNoise.Name = "lblNoise";
|
|
||||||
lblNoise.Size = new Size(57, 25);
|
|
||||||
lblNoise.TabIndex = 1;
|
|
||||||
lblNoise.Text = "Noise";
|
|
||||||
//
|
//
|
||||||
// lblTone2
|
// lblTone2
|
||||||
//
|
//
|
||||||
lblTone2.AutoSize = true;
|
lblTone2.AutoSize = true;
|
||||||
lblTone2.Location = new Point(25, 122);
|
lblTone2.Location = new Point(20, 98);
|
||||||
|
lblTone2.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblTone2.Name = "lblTone2";
|
lblTone2.Name = "lblTone2";
|
||||||
lblTone2.Size = new Size(59, 25);
|
lblTone2.Size = new Size(49, 20);
|
||||||
lblTone2.TabIndex = 2;
|
lblTone2.TabIndex = 2;
|
||||||
lblTone2.Text = "Tone2";
|
lblTone2.Text = "Tone2";
|
||||||
//
|
//
|
||||||
// lblTone1
|
// lblNoise
|
||||||
//
|
//
|
||||||
lblTone1.AutoSize = true;
|
lblNoise.AutoSize = true;
|
||||||
lblTone1.Location = new Point(25, 83);
|
lblNoise.Location = new Point(20, 128);
|
||||||
lblTone1.Name = "lblTone1";
|
lblNoise.Margin = new Padding(2, 0, 2, 0);
|
||||||
lblTone1.Size = new Size(59, 25);
|
lblNoise.Name = "lblNoise";
|
||||||
lblTone1.TabIndex = 3;
|
lblNoise.Size = new Size(47, 20);
|
||||||
lblTone1.Text = "Tone1";
|
lblNoise.TabIndex = 1;
|
||||||
|
lblNoise.Text = "Noise";
|
||||||
|
//
|
||||||
|
// lblTone0
|
||||||
|
//
|
||||||
|
lblTone0.AutoSize = true;
|
||||||
|
lblTone0.Location = new Point(20, 38);
|
||||||
|
lblTone0.Margin = new Padding(2, 0, 2, 0);
|
||||||
|
lblTone0.Name = "lblTone0";
|
||||||
|
lblTone0.Size = new Size(49, 20);
|
||||||
|
lblTone0.TabIndex = 0;
|
||||||
|
lblTone0.Text = "Tone0";
|
||||||
|
//
|
||||||
|
// lblHalt
|
||||||
|
//
|
||||||
|
lblHalt.AutoSize = true;
|
||||||
|
lblHalt.Location = new Point(9, 564);
|
||||||
|
lblHalt.Name = "lblHalt";
|
||||||
|
lblHalt.Size = new Size(37, 20);
|
||||||
|
lblHalt.TabIndex = 36;
|
||||||
|
lblHalt.Text = "Halt";
|
||||||
|
//
|
||||||
|
// lblR
|
||||||
|
//
|
||||||
|
lblR.AutoSize = true;
|
||||||
|
lblR.Location = new Point(9, 392);
|
||||||
|
lblR.Name = "lblR";
|
||||||
|
lblR.Size = new Size(18, 20);
|
||||||
|
lblR.TabIndex = 37;
|
||||||
|
lblR.Text = "R";
|
||||||
//
|
//
|
||||||
// DebuggerForm
|
// DebuggerForm
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(10F, 25F);
|
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(1206, 892);
|
ClientSize = new Size(965, 714);
|
||||||
|
Controls.Add(lblR);
|
||||||
|
Controls.Add(lblHalt);
|
||||||
Controls.Add(groupBox1);
|
Controls.Add(groupBox1);
|
||||||
Controls.Add(CpuRun);
|
Controls.Add(CpuRun);
|
||||||
Controls.Add(button1);
|
Controls.Add(btnCpuStep);
|
||||||
Controls.Add(richTextBox1);
|
Controls.Add(richTextBox1);
|
||||||
Controls.Add(lblFrameTime);
|
Controls.Add(lblFrameTime);
|
||||||
Controls.Add(lblFPS);
|
Controls.Add(lblFPS);
|
||||||
@@ -475,7 +492,7 @@
|
|||||||
private Label lblFPS;
|
private Label lblFPS;
|
||||||
private Label lblFrameTime;
|
private Label lblFrameTime;
|
||||||
private RichTextBox richTextBox1;
|
private RichTextBox richTextBox1;
|
||||||
private Button button1;
|
private Button btnCpuStep;
|
||||||
private Button CpuRun;
|
private Button CpuRun;
|
||||||
public System.Windows.Forms.Timer uiUpdateTimer;
|
public System.Windows.Forms.Timer uiUpdateTimer;
|
||||||
private GroupBox groupBox1;
|
private GroupBox groupBox1;
|
||||||
@@ -483,6 +500,8 @@
|
|||||||
private Label lblTone2;
|
private Label lblTone2;
|
||||||
private Label lblNoise;
|
private Label lblNoise;
|
||||||
private Label lblTone0;
|
private Label lblTone0;
|
||||||
|
private Label lblHalt;
|
||||||
|
private Label lblR;
|
||||||
//private TextBox textBox4;
|
//private TextBox textBox4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ namespace Desktop
|
|||||||
UpdateStackView();
|
UpdateStackView();
|
||||||
UpdateDisassemblyView();
|
UpdateDisassemblyView();
|
||||||
_mainForm = mainForm;
|
_mainForm = mainForm;
|
||||||
|
CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CpuRun_Click(object sender, EventArgs e)
|
private void CpuRun_Click(object sender, EventArgs e)
|
||||||
@@ -35,7 +36,6 @@ namespace Desktop
|
|||||||
{
|
{
|
||||||
// Stop the machine
|
// Stop the machine
|
||||||
_mainForm.StopEmulator();
|
_mainForm.StopEmulator();
|
||||||
CpuRun.Text = "CPU Run";
|
|
||||||
|
|
||||||
// Stop the live UI updates and do one final manual refresh
|
// Stop the live UI updates and do one final manual refresh
|
||||||
uiUpdateTimer.Stop();
|
uiUpdateTimer.Stop();
|
||||||
@@ -45,12 +45,12 @@ namespace Desktop
|
|||||||
{
|
{
|
||||||
// Start the machine
|
// Start the machine
|
||||||
_mainForm.StartEmulator();
|
_mainForm.StartEmulator();
|
||||||
CpuRun.Text = "CPU Stop";
|
|
||||||
|
|
||||||
// Start the timer so the debugger screen updates automatically
|
// Start the timer so the debugger screen updates automatically
|
||||||
// (Make sure your uiUpdateTimer Interval in the designer is set to something like 100ms)
|
// (Make sure your uiUpdateTimer Interval in the designer is set to something like 100ms)
|
||||||
uiUpdateTimer.Start();
|
uiUpdateTimer.Start();
|
||||||
}
|
}
|
||||||
|
CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnStep_Click(object sender, EventArgs e)
|
private void btnStep_Click(object sender, EventArgs e)
|
||||||
@@ -92,6 +92,8 @@ namespace Desktop
|
|||||||
// Current Emulator State
|
// Current Emulator State
|
||||||
private void UpdateDisplay()
|
private void UpdateDisplay()
|
||||||
{
|
{
|
||||||
|
CpuRun.Text = _mainForm.IsRunning ? "CPU Stop" : "CPU Run";
|
||||||
|
btnCpuStep.Enabled = _mainForm.IsRunning ? false : true;
|
||||||
lblAF.Text = $"AF: {_cpu.AF.Word:X4}";
|
lblAF.Text = $"AF: {_cpu.AF.Word:X4}";
|
||||||
lblBC.Text = $"BC: {_cpu.BC.Word:X4}";
|
lblBC.Text = $"BC: {_cpu.BC.Word:X4}";
|
||||||
lblDE.Text = $"DE: {_cpu.DE.Word:X4}";
|
lblDE.Text = $"DE: {_cpu.DE.Word:X4}";
|
||||||
@@ -108,11 +110,12 @@ namespace Desktop
|
|||||||
lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}";
|
lblFrames.Text = $"Frames Rendered: {_mainForm.TotalFrameCount}";
|
||||||
lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms";
|
lblFrameTime.Text = $"Frame Time: {((float)_mainForm.FrameTime):F1}ms";
|
||||||
lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}";
|
lblFPS.Text = $"FPS: {_mainForm.FramesPerSecond:F2}";
|
||||||
|
lblHalt.Text = $"CPU Halted: {_cpu.IsHalted}";
|
||||||
|
lblR.Text = $"R: {_cpu.R:X2}";
|
||||||
UpdateMemoryView();
|
UpdateMemoryView();
|
||||||
UpdateStackView();
|
UpdateStackView();
|
||||||
UpdateDisassemblyView();
|
UpdateDisassemblyView();
|
||||||
// --- AUDIO REGISTERS ---
|
// --- AUDIO REGISTERS ---//
|
||||||
// Use _mainForm to access the Machine, and then grab the AudioProcessor!
|
|
||||||
if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null)
|
if (_mainForm._machine != null && _mainForm._machine.AudioProcessor != null)
|
||||||
{
|
{
|
||||||
ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers;
|
ushort[] apuRegs = _mainForm._machine.AudioProcessor.Registers;
|
||||||
@@ -666,6 +669,9 @@ namespace Desktop
|
|||||||
case 0xC9:
|
case 0xC9:
|
||||||
mnemonic = "RET";
|
mnemonic = "RET";
|
||||||
break;
|
break;
|
||||||
|
case 0x76:
|
||||||
|
mnemonic = "HALT!";
|
||||||
|
break;
|
||||||
case 0xCB:
|
case 0xCB:
|
||||||
cbOp = _memoryBus.Read((ushort)(currentPc + 1));
|
cbOp = _memoryBus.Read((ushort)(currentPc + 1));
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,20 @@
|
|||||||
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
|
||||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<StartupObject>Desktop.Program</StartupObject>
|
<StartupObject>Desktop.Program</StartupObject>
|
||||||
<AssemblyName>Parsons Master System</AssemblyName>
|
<AssemblyName>Parsons Master System 2026</AssemblyName>
|
||||||
|
<ApplicationIcon>favicon.ico</ApplicationIcon>
|
||||||
|
<Title>Sega Master System EMulator 2026</Title>
|
||||||
|
<Version>1.0</Version>
|
||||||
|
<Authors>Marc Parsons</Authors>
|
||||||
|
<Company>Parsons Limited</Company>
|
||||||
|
<Description>Parsons Master System 2026</Description>
|
||||||
|
<Copyright>Copyright 2026 Marc Parsons</Copyright>
|
||||||
|
<PackageIcon>logo1.bmp</PackageIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="ROMS\After Burner %28UE%29 [!].sms" />
|
<None Remove="ROMS\After Burner %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Aladdin %28USA, Europe, Brazil%29.gg" />
|
||||||
<None Remove="ROMS\Alex Kidd in High Tech World %28UE%29 [!].sms" />
|
<None Remove="ROMS\Alex Kidd in High Tech World %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Alex Kidd in Miracle World %28UE%29 [!].sms" />
|
<None Remove="ROMS\Alex Kidd in Miracle World %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Back to the Future 2 %28UE%29 [!].sms" />
|
<None Remove="ROMS\Back to the Future 2 %28UE%29 [!].sms" />
|
||||||
@@ -26,6 +35,7 @@
|
|||||||
<None Remove="ROMS\Bart vs. the Space Mutants %28UE%29 [!].sms" />
|
<None Remove="ROMS\Bart vs. the Space Mutants %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Bart vs. the World %28UE%29 [!].sms" />
|
<None Remove="ROMS\Bart vs. the World %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Black Belt %28UE%29 [!].sms" />
|
<None Remove="ROMS\Black Belt %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Bonanza Bros. %28Europe%29.sms" />
|
||||||
<None Remove="ROMS\California Games %28UE%29 [!].sms" />
|
<None Remove="ROMS\California Games %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\California Games 2 %28UE%29 [!].sms" />
|
<None Remove="ROMS\California Games 2 %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Casino Games %28UE%29 [!].sms" />
|
<None Remove="ROMS\Casino Games %28UE%29 [!].sms" />
|
||||||
@@ -42,9 +52,10 @@
|
|||||||
<None Remove="ROMS\Gangster Town %28UE%29 [!].sms" />
|
<None Remove="ROMS\Gangster Town %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Gauntlet %28UE%29 [!].sms" />
|
<None Remove="ROMS\Gauntlet %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Ghost House %28UE%29 [!].sms" />
|
<None Remove="ROMS\Ghost House %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Ghouls %27n Ghosts %28UE%29 [!].sms" />
|
<None Remove="ROMS\Ghouls %27n Ghosts %28USA%29.sms" />
|
||||||
<None Remove="ROMS\Global Defense %28UE%29 [!].sms" />
|
<None Remove="ROMS\Global Defense %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Golden Axe %28UE%29 [!].sms" />
|
<None Remove="ROMS\Golden Axe %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Golden Axe Warrior.sav" />
|
||||||
<None Remove="ROMS\Golden Axe Warrior.sms" />
|
<None Remove="ROMS\Golden Axe Warrior.sms" />
|
||||||
<None Remove="ROMS\Great Baseball %28UE%29 [!].sms" />
|
<None Remove="ROMS\Great Baseball %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Great Basketball %28UE%29 [!].sms" />
|
<None Remove="ROMS\Great Basketball %28UE%29 [!].sms" />
|
||||||
@@ -59,17 +70,21 @@
|
|||||||
<None Remove="ROMS\Impossible Mission %28UE%29 [!].sms" />
|
<None Remove="ROMS\Impossible Mission %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Japanese SMS BIOS v2.1 %28J%29 [!].sms" />
|
<None Remove="ROMS\Japanese SMS BIOS v2.1 %28J%29 [!].sms" />
|
||||||
<None Remove="ROMS\Jurassic Park %28UE%29 [!].sms" />
|
<None Remove="ROMS\Jurassic Park %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Lucky Dime Caper Starring Donald Duck, The %28Europe, Brazil%29 %28En%29.sms" />
|
||||||
<None Remove="ROMS\Mickey Mouse - Castle of Illusion %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mickey Mouse - Castle of Illusion %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Mickey Mouse - Land of Illusion %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mickey Mouse - Land of Illusion %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Mickey Mouse - Legend of Illusion %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mickey Mouse - Legend of Illusion %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Micro Machines %28Europe%29.sms" />
|
||||||
<None Remove="ROMS\Mortal Kombat %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mortal Kombat %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Mortal Kombat 2 %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mortal Kombat 2 %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Mortal Kombat 3 %28UE%29 [!].sms" />
|
<None Remove="ROMS\Mortal Kombat 3 %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\OutRun %28UE%29 [!].sms" />
|
<None Remove="ROMS\Olympic Gold - Barcelona %2792 %28Europe%29.sms" />
|
||||||
|
<None Remove="ROMS\OutRun %28USA%29.sms" />
|
||||||
<None Remove="ROMS\Paperboy %28UE%29 [!].sms" />
|
<None Remove="ROMS\Paperboy %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Parlour Games %28UE%29 [!].sms" />
|
<None Remove="ROMS\Parlour Games %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Phantasy Star %28UE%29 [!].sms" />
|
<None Remove="ROMS\Phantasy Star %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Populous %28UE%29 [!].sms" />
|
<None Remove="ROMS\Populous %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Prince of Persia %28Europe%29.sms" />
|
||||||
<None Remove="ROMS\Psychic World %28UE%29 [!].sms" />
|
<None Remove="ROMS\Psychic World %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Psycho Fox %28UE%29 [!].sms" />
|
<None Remove="ROMS\Psycho Fox %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\R-Type.sms" />
|
<None Remove="ROMS\R-Type.sms" />
|
||||||
@@ -77,10 +92,13 @@
|
|||||||
<None Remove="ROMS\Shadow of the Beast %28UE%29 [!].sms" />
|
<None Remove="ROMS\Shadow of the Beast %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Smash TV %28UE%29 [!].sms" />
|
<None Remove="ROMS\Smash TV %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\SMSTestSuite.sms" />
|
<None Remove="ROMS\SMSTestSuite.sms" />
|
||||||
|
<None Remove="ROMS\Sonic 1 GG.gg" />
|
||||||
<None Remove="ROMS\Sonic 2.sms" />
|
<None Remove="ROMS\Sonic 2.sms" />
|
||||||
<None Remove="ROMS\Sonic Chaos %28UE%29 [!].sms" />
|
<None Remove="ROMS\Sonic Chaos %28UE%29 [!].sms" />
|
||||||
|
<None Remove="ROMS\Sonic The Hedgehog - Triple Trouble %28USA, Europe, Brazil%29.gg" />
|
||||||
|
<None Remove="ROMS\Sonic The Hedgehog 2 %28World%29.gg" />
|
||||||
<None Remove="ROMS\Sonic.sms" />
|
<None Remove="ROMS\Sonic.sms" />
|
||||||
<None Remove="ROMS\Space Harrier %28UE%29 [!].sms" />
|
<None Remove="ROMS\Space Harrier %28USA%29.sms" />
|
||||||
<None Remove="ROMS\Speedball %28UE%29 %28Virgin%29 [!].sms" />
|
<None Remove="ROMS\Speedball %28UE%29 %28Virgin%29 [!].sms" />
|
||||||
<None Remove="ROMS\Star Wars %28UE%29 [!].sms" />
|
<None Remove="ROMS\Star Wars %28UE%29 [!].sms" />
|
||||||
<None Remove="ROMS\Street Fighter 2 %28Brazil%29 [!].sms" />
|
<None Remove="ROMS\Street Fighter 2 %28Brazil%29 [!].sms" />
|
||||||
@@ -103,10 +121,15 @@
|
|||||||
<None Remove="ROMS\zexdoc.sms" />
|
<None Remove="ROMS\zexdoc.sms" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="favicon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="ROMS\After Burner (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\After Burner (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Aladdin (USA, Europe, Brazil).gg" />
|
||||||
<EmbeddedResource Include="ROMS\Alex Kidd in High Tech World (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Alex Kidd in High Tech World (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -128,6 +151,7 @@
|
|||||||
<EmbeddedResource Include="ROMS\Black Belt (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Black Belt (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Bonanza Bros. (Europe).sms" />
|
||||||
<EmbeddedResource Include="ROMS\California Games (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\California Games (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -176,15 +200,14 @@
|
|||||||
<EmbeddedResource Include="ROMS\Ghost House (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Ghost House (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="ROMS\Ghouls 'n Ghosts (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Ghouls 'n Ghosts (USA).sms" />
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Include="ROMS\Global Defense (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Global Defense (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="ROMS\Golden Axe (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Golden Axe (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Golden Axe Warrior.sav" />
|
||||||
<EmbeddedResource Include="ROMS\Golden Axe Warrior.sms">
|
<EmbeddedResource Include="ROMS\Golden Axe Warrior.sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -227,6 +250,7 @@
|
|||||||
<EmbeddedResource Include="ROMS\Jurassic Park (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Jurassic Park (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Lucky Dime Caper Starring Donald Duck, The (Europe, Brazil) (En).sms" />
|
||||||
<EmbeddedResource Include="ROMS\Mickey Mouse - Castle of Illusion (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Mickey Mouse - Castle of Illusion (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -236,6 +260,7 @@
|
|||||||
<EmbeddedResource Include="ROMS\Mickey Mouse - Legend of Illusion (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Mickey Mouse - Legend of Illusion (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Micro Machines (Europe).sms" />
|
||||||
<EmbeddedResource Include="ROMS\Mortal Kombat (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Mortal Kombat (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -245,9 +270,8 @@
|
|||||||
<EmbeddedResource Include="ROMS\Mortal Kombat 3 (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Mortal Kombat 3 (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="ROMS\OutRun (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Olympic Gold - Barcelona '92 (Europe).sms" />
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<EmbeddedResource Include="ROMS\OutRun (USA).sms" />
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Include="ROMS\Paperboy (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Paperboy (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -260,6 +284,7 @@
|
|||||||
<EmbeddedResource Include="ROMS\Populous (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Populous (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Prince of Persia (Europe).sms" />
|
||||||
<EmbeddedResource Include="ROMS\Psychic World (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Psychic World (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -281,18 +306,19 @@
|
|||||||
<EmbeddedResource Include="ROMS\SMSTestSuite.sms">
|
<EmbeddedResource Include="ROMS\SMSTestSuite.sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Sonic 1 GG.gg" />
|
||||||
<EmbeddedResource Include="ROMS\Sonic 2.sms">
|
<EmbeddedResource Include="ROMS\Sonic 2.sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="ROMS\Sonic Chaos (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Sonic Chaos (UE) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="ROMS\Sonic The Hedgehog - Triple Trouble (USA, Europe, Brazil).gg" />
|
||||||
|
<EmbeddedResource Include="ROMS\Sonic The Hedgehog 2 (World).gg" />
|
||||||
<EmbeddedResource Include="ROMS\Sonic.sms">
|
<EmbeddedResource Include="ROMS\Sonic.sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<EmbeddedResource Include="ROMS\Space Harrier (UE) [!].sms">
|
<EmbeddedResource Include="ROMS\Space Harrier (USA).sms" />
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</EmbeddedResource>
|
|
||||||
<EmbeddedResource Include="ROMS\Speedball (UE) (Virgin) [!].sms">
|
<EmbeddedResource Include="ROMS\Speedball (UE) (Virgin) [!].sms">
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
@@ -353,6 +379,13 @@
|
|||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\logo1.bmp">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="NAudio" Version="2.3.0" />
|
<PackageReference Include="NAudio" Version="2.3.0" />
|
||||||
<PackageReference Include="Vortice.XInput" Version="3.8.3" />
|
<PackageReference Include="Vortice.XInput" Version="3.8.3" />
|
||||||
|
|||||||
92
Desktop/Form1.Designer.cs
generated
92
Desktop/Form1.Designer.cs
generated
@@ -32,6 +32,8 @@
|
|||||||
fileToolStripMenuItem = new ToolStripMenuItem();
|
fileToolStripMenuItem = new ToolStripMenuItem();
|
||||||
openToolStripMenuItem = new ToolStripMenuItem();
|
openToolStripMenuItem = new ToolStripMenuItem();
|
||||||
includedToolStripMenuItem = new ToolStripMenuItem();
|
includedToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
masterSystemToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
gameGearToolStripMenuItem = new ToolStripMenuItem();
|
||||||
selectROMToolStripMenuItem1 = new ToolStripMenuItem();
|
selectROMToolStripMenuItem1 = new ToolStripMenuItem();
|
||||||
exitToolStripMenuItem = new ToolStripMenuItem();
|
exitToolStripMenuItem = new ToolStripMenuItem();
|
||||||
viewToolStripMenuItem = new ToolStripMenuItem();
|
viewToolStripMenuItem = new ToolStripMenuItem();
|
||||||
@@ -39,10 +41,11 @@
|
|||||||
vRAMViewerToolStripMenuItem = new ToolStripMenuItem();
|
vRAMViewerToolStripMenuItem = new ToolStripMenuItem();
|
||||||
machineToolStripMenuItem = new ToolStripMenuItem();
|
machineToolStripMenuItem = new ToolStripMenuItem();
|
||||||
resetToolStripMenuItem = new ToolStripMenuItem();
|
resetToolStripMenuItem = new ToolStripMenuItem();
|
||||||
helpToolStripMenuItem = new ToolStripMenuItem();
|
|
||||||
aboutToolStripMenuItem = new ToolStripMenuItem();
|
|
||||||
saveStateToolStripMenuItem = new ToolStripMenuItem();
|
saveStateToolStripMenuItem = new ToolStripMenuItem();
|
||||||
loadStateToolStripMenuItem = new ToolStripMenuItem();
|
loadStateToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
turboModeToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
helpToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
aboutToolStripMenuItem = new ToolStripMenuItem();
|
||||||
menuStrip1.SuspendLayout();
|
menuStrip1.SuspendLayout();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
@@ -52,7 +55,8 @@
|
|||||||
menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem, machineToolStripMenuItem, helpToolStripMenuItem });
|
menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem, machineToolStripMenuItem, helpToolStripMenuItem });
|
||||||
menuStrip1.Location = new Point(0, 0);
|
menuStrip1.Location = new Point(0, 0);
|
||||||
menuStrip1.Name = "menuStrip1";
|
menuStrip1.Name = "menuStrip1";
|
||||||
menuStrip1.Size = new Size(791, 33);
|
menuStrip1.Padding = new Padding(5, 2, 0, 2);
|
||||||
|
menuStrip1.Size = new Size(633, 28);
|
||||||
menuStrip1.TabIndex = 2;
|
menuStrip1.TabIndex = 2;
|
||||||
menuStrip1.Text = "menuStrip1";
|
menuStrip1.Text = "menuStrip1";
|
||||||
//
|
//
|
||||||
@@ -60,34 +64,46 @@
|
|||||||
//
|
//
|
||||||
fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { openToolStripMenuItem, exitToolStripMenuItem });
|
fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { openToolStripMenuItem, exitToolStripMenuItem });
|
||||||
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
|
fileToolStripMenuItem.Name = "fileToolStripMenuItem";
|
||||||
fileToolStripMenuItem.Size = new Size(54, 29);
|
fileToolStripMenuItem.Size = new Size(46, 24);
|
||||||
fileToolStripMenuItem.Text = "File";
|
fileToolStripMenuItem.Text = "File";
|
||||||
//
|
//
|
||||||
// openToolStripMenuItem
|
// openToolStripMenuItem
|
||||||
//
|
//
|
||||||
openToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { includedToolStripMenuItem, selectROMToolStripMenuItem1 });
|
openToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { includedToolStripMenuItem, selectROMToolStripMenuItem1 });
|
||||||
openToolStripMenuItem.Name = "openToolStripMenuItem";
|
openToolStripMenuItem.Name = "openToolStripMenuItem";
|
||||||
openToolStripMenuItem.Size = new Size(158, 34);
|
openToolStripMenuItem.Size = new Size(128, 26);
|
||||||
openToolStripMenuItem.Text = "Open";
|
openToolStripMenuItem.Text = "Open";
|
||||||
//
|
//
|
||||||
// includedToolStripMenuItem
|
// includedToolStripMenuItem
|
||||||
//
|
//
|
||||||
|
includedToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { masterSystemToolStripMenuItem, gameGearToolStripMenuItem });
|
||||||
includedToolStripMenuItem.Name = "includedToolStripMenuItem";
|
includedToolStripMenuItem.Name = "includedToolStripMenuItem";
|
||||||
includedToolStripMenuItem.Size = new Size(218, 34);
|
includedToolStripMenuItem.Size = new Size(178, 26);
|
||||||
includedToolStripMenuItem.Text = "Included";
|
includedToolStripMenuItem.Text = "Included";
|
||||||
includedToolStripMenuItem.Click += includedToolStripMenuItem_Click;
|
//
|
||||||
|
// masterSystemToolStripMenuItem
|
||||||
|
//
|
||||||
|
masterSystemToolStripMenuItem.Name = "masterSystemToolStripMenuItem";
|
||||||
|
masterSystemToolStripMenuItem.Size = new Size(188, 26);
|
||||||
|
masterSystemToolStripMenuItem.Text = "Master System";
|
||||||
|
//
|
||||||
|
// gameGearToolStripMenuItem
|
||||||
|
//
|
||||||
|
gameGearToolStripMenuItem.Name = "gameGearToolStripMenuItem";
|
||||||
|
gameGearToolStripMenuItem.Size = new Size(188, 26);
|
||||||
|
gameGearToolStripMenuItem.Text = "Game Gear";
|
||||||
//
|
//
|
||||||
// selectROMToolStripMenuItem1
|
// selectROMToolStripMenuItem1
|
||||||
//
|
//
|
||||||
selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1";
|
selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1";
|
||||||
selectROMToolStripMenuItem1.Size = new Size(218, 34);
|
selectROMToolStripMenuItem1.Size = new Size(178, 26);
|
||||||
selectROMToolStripMenuItem1.Text = "Select ROM...";
|
selectROMToolStripMenuItem1.Text = "Select ROM...";
|
||||||
selectROMToolStripMenuItem1.Click += selectROMToolStripMenuItem_Click;
|
selectROMToolStripMenuItem1.Click += selectROMToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// exitToolStripMenuItem
|
// exitToolStripMenuItem
|
||||||
//
|
//
|
||||||
exitToolStripMenuItem.Name = "exitToolStripMenuItem";
|
exitToolStripMenuItem.Name = "exitToolStripMenuItem";
|
||||||
exitToolStripMenuItem.Size = new Size(158, 34);
|
exitToolStripMenuItem.Size = new Size(128, 26);
|
||||||
exitToolStripMenuItem.Text = "Exit";
|
exitToolStripMenuItem.Text = "Exit";
|
||||||
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
|
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
@@ -95,72 +111,79 @@
|
|||||||
//
|
//
|
||||||
viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem, vRAMViewerToolStripMenuItem });
|
viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem, vRAMViewerToolStripMenuItem });
|
||||||
viewToolStripMenuItem.Name = "viewToolStripMenuItem";
|
viewToolStripMenuItem.Name = "viewToolStripMenuItem";
|
||||||
viewToolStripMenuItem.Size = new Size(65, 29);
|
viewToolStripMenuItem.Size = new Size(55, 24);
|
||||||
viewToolStripMenuItem.Text = "View";
|
viewToolStripMenuItem.Text = "View";
|
||||||
//
|
//
|
||||||
// debuggerToolStripMenuItem
|
// debuggerToolStripMenuItem
|
||||||
//
|
//
|
||||||
debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem";
|
debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem";
|
||||||
debuggerToolStripMenuItem.Size = new Size(221, 34);
|
debuggerToolStripMenuItem.Size = new Size(182, 26);
|
||||||
debuggerToolStripMenuItem.Text = "Debugger";
|
debuggerToolStripMenuItem.Text = "Debugger";
|
||||||
debuggerToolStripMenuItem.Click += debuggerToolStripMenuItem_Click;
|
debuggerToolStripMenuItem.Click += debuggerToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// vRAMViewerToolStripMenuItem
|
// vRAMViewerToolStripMenuItem
|
||||||
//
|
//
|
||||||
vRAMViewerToolStripMenuItem.Name = "vRAMViewerToolStripMenuItem";
|
vRAMViewerToolStripMenuItem.Name = "vRAMViewerToolStripMenuItem";
|
||||||
vRAMViewerToolStripMenuItem.Size = new Size(221, 34);
|
vRAMViewerToolStripMenuItem.Size = new Size(182, 26);
|
||||||
vRAMViewerToolStripMenuItem.Text = "VRAM Viewer";
|
vRAMViewerToolStripMenuItem.Text = "VRAM Viewer";
|
||||||
vRAMViewerToolStripMenuItem.Click += vramViewerToolStripMenuItem_Click;
|
vRAMViewerToolStripMenuItem.Click += vramViewerToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// machineToolStripMenuItem
|
// machineToolStripMenuItem
|
||||||
//
|
//
|
||||||
machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem });
|
machineToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resetToolStripMenuItem, saveStateToolStripMenuItem, loadStateToolStripMenuItem, turboModeToolStripMenuItem });
|
||||||
machineToolStripMenuItem.Name = "machineToolStripMenuItem";
|
machineToolStripMenuItem.Name = "machineToolStripMenuItem";
|
||||||
machineToolStripMenuItem.Size = new Size(94, 29);
|
machineToolStripMenuItem.Size = new Size(79, 24);
|
||||||
machineToolStripMenuItem.Text = "Machine";
|
machineToolStripMenuItem.Text = "Machine";
|
||||||
//
|
//
|
||||||
// resetToolStripMenuItem
|
// resetToolStripMenuItem
|
||||||
//
|
//
|
||||||
resetToolStripMenuItem.Name = "resetToolStripMenuItem";
|
resetToolStripMenuItem.Name = "resetToolStripMenuItem";
|
||||||
resetToolStripMenuItem.Size = new Size(270, 34);
|
resetToolStripMenuItem.Size = new Size(174, 26);
|
||||||
resetToolStripMenuItem.Text = "Reset";
|
resetToolStripMenuItem.Text = "Reset";
|
||||||
resetToolStripMenuItem.Click += resetToolStripMenuItem_Click;
|
resetToolStripMenuItem.Click += resetToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// helpToolStripMenuItem
|
|
||||||
//
|
|
||||||
helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem });
|
|
||||||
helpToolStripMenuItem.Name = "helpToolStripMenuItem";
|
|
||||||
helpToolStripMenuItem.Size = new Size(65, 29);
|
|
||||||
helpToolStripMenuItem.Text = "Help";
|
|
||||||
//
|
|
||||||
// aboutToolStripMenuItem
|
|
||||||
//
|
|
||||||
aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
|
||||||
aboutToolStripMenuItem.Size = new Size(164, 34);
|
|
||||||
aboutToolStripMenuItem.Text = "About";
|
|
||||||
//
|
|
||||||
// saveStateToolStripMenuItem
|
// saveStateToolStripMenuItem
|
||||||
//
|
//
|
||||||
saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem";
|
saveStateToolStripMenuItem.Name = "saveStateToolStripMenuItem";
|
||||||
saveStateToolStripMenuItem.Size = new Size(270, 34);
|
saveStateToolStripMenuItem.Size = new Size(174, 26);
|
||||||
saveStateToolStripMenuItem.Text = "Save State";
|
saveStateToolStripMenuItem.Text = "Save State";
|
||||||
saveStateToolStripMenuItem.Click += saveStateToolStripMenuItem_Click;
|
saveStateToolStripMenuItem.Click += saveStateToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
// loadStateToolStripMenuItem
|
// loadStateToolStripMenuItem
|
||||||
//
|
//
|
||||||
loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem";
|
loadStateToolStripMenuItem.Name = "loadStateToolStripMenuItem";
|
||||||
loadStateToolStripMenuItem.Size = new Size(270, 34);
|
loadStateToolStripMenuItem.Size = new Size(174, 26);
|
||||||
loadStateToolStripMenuItem.Text = "Load State";
|
loadStateToolStripMenuItem.Text = "Load State";
|
||||||
loadStateToolStripMenuItem.Click += loadStateToolStripMenuItem_Click;
|
loadStateToolStripMenuItem.Click += loadStateToolStripMenuItem_Click;
|
||||||
//
|
//
|
||||||
|
// turboModeToolStripMenuItem
|
||||||
|
//
|
||||||
|
turboModeToolStripMenuItem.Name = "turboModeToolStripMenuItem";
|
||||||
|
turboModeToolStripMenuItem.Size = new Size(174, 26);
|
||||||
|
turboModeToolStripMenuItem.Text = "Turbo Mode";
|
||||||
|
turboModeToolStripMenuItem.Click += turboModeToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// helpToolStripMenuItem
|
||||||
|
//
|
||||||
|
helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem });
|
||||||
|
helpToolStripMenuItem.Name = "helpToolStripMenuItem";
|
||||||
|
helpToolStripMenuItem.Size = new Size(55, 24);
|
||||||
|
helpToolStripMenuItem.Text = "Help";
|
||||||
|
//
|
||||||
|
// aboutToolStripMenuItem
|
||||||
|
//
|
||||||
|
aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
|
||||||
|
aboutToolStripMenuItem.Size = new Size(224, 26);
|
||||||
|
aboutToolStripMenuItem.Text = "About";
|
||||||
|
aboutToolStripMenuItem.Click += aboutToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
// ParsonsForm1
|
// ParsonsForm1
|
||||||
//
|
//
|
||||||
AutoScaleDimensions = new SizeF(10F, 25F);
|
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
ClientSize = new Size(791, 622);
|
ClientSize = new Size(633, 498);
|
||||||
Controls.Add(menuStrip1);
|
Controls.Add(menuStrip1);
|
||||||
MainMenuStrip = menuStrip1;
|
MainMenuStrip = menuStrip1;
|
||||||
Margin = new Padding(4);
|
|
||||||
Name = "ParsonsForm1";
|
Name = "ParsonsForm1";
|
||||||
Text = "Form1";
|
Text = "Form1";
|
||||||
FormClosing += ParsonsForm1_FormClosing;
|
FormClosing += ParsonsForm1_FormClosing;
|
||||||
@@ -186,5 +209,8 @@
|
|||||||
private ToolStripMenuItem vRAMViewerToolStripMenuItem;
|
private ToolStripMenuItem vRAMViewerToolStripMenuItem;
|
||||||
private ToolStripMenuItem saveStateToolStripMenuItem;
|
private ToolStripMenuItem saveStateToolStripMenuItem;
|
||||||
private ToolStripMenuItem loadStateToolStripMenuItem;
|
private ToolStripMenuItem loadStateToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem turboModeToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem masterSystemToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem gameGearToolStripMenuItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
405
Desktop/Form1.cs
405
Desktop/Form1.cs
@@ -18,14 +18,14 @@ namespace Desktop
|
|||||||
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
|
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
|
||||||
private NAudioPlayer _audioPlayer;
|
private NAudioPlayer _audioPlayer;
|
||||||
private Task _emulatorTask;
|
private Task _emulatorTask;
|
||||||
private double TargetFrameTime = 16.667f; //NTSC
|
|
||||||
//private double TargetFrameTime = 20; //PAL
|
|
||||||
public int TotalFrameCount = 0;
|
public int TotalFrameCount = 0;
|
||||||
public double FrameTime { get; private set; } = 0;
|
public double FrameTime { get; private set; } = 0;
|
||||||
public double FramesPerSecond { get; private set; } = 0;
|
public double FramesPerSecond { get; private set; } = 0;
|
||||||
private string _currentRomName = "No ROM";
|
private string _currentRomName = "No ROM";
|
||||||
private string _currentRomPath = "";
|
private string _currentRomPath = "";
|
||||||
private Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
|
private Stopwatch _stopwatch = new Stopwatch();
|
||||||
|
private string _romType = "TBC";
|
||||||
|
private bool isTurboMode = false;
|
||||||
public bool IsRunning { get; private set; } = false;
|
public bool IsRunning { get; private set; } = false;
|
||||||
|
|
||||||
public ushort? Breakpoint
|
public ushort? Breakpoint
|
||||||
@@ -34,158 +34,218 @@ namespace Desktop
|
|||||||
set { if (_machine != null) _machine.Breakpoint = value; }
|
set { if (_machine != null) _machine.Breakpoint = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
public ParsonsForm1()
|
public ParsonsForm1()
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
// These are perfectly safe for the Visual Studio Designer!
|
|
||||||
this.KeyPreview = true;
|
this.KeyPreview = true;
|
||||||
|
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||||
this.KeyDown += Form1_KeyDown;
|
this.KeyDown += Form1_KeyDown;
|
||||||
|
#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||||
|
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||||
this.KeyUp += Form1_KeyUp;
|
this.KeyUp += Form1_KeyUp;
|
||||||
|
#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
|
||||||
this.DoubleBuffered = true;
|
this.DoubleBuffered = true;
|
||||||
this.ResizeRedraw = true;
|
this.ResizeRedraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Designer ignores this completely, but the compiled game runs it instantly!
|
|
||||||
protected override void OnLoad(EventArgs e)
|
protected override void OnLoad(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoad(e);
|
base.OnLoad(e);
|
||||||
|
|
||||||
this.Text = $"Parsons Master System - {_currentRomName}";
|
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}";
|
||||||
|
|
||||||
// Safe to initialize hardware and files here!
|
this.BackColor = Color.Black;
|
||||||
_machine = new SmsMachine();
|
_machine = new SmsMachine();
|
||||||
_audioPlayer = new NAudioPlayer();
|
_audioPlayer = new NAudioPlayer();
|
||||||
_machine.AudioProcessor.AudioDevice = _audioPlayer;
|
_machine.AudioProcessor.AudioDevice = _audioPlayer;
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPaint(PaintEventArgs e)
|
protected override void OnPaint(PaintEventArgs e)
|
||||||
{
|
{
|
||||||
// Always call the base method so Windows can draw your MenuStrip!
|
|
||||||
base.OnPaint(e);
|
base.OnPaint(e);
|
||||||
|
|
||||||
// THE FIX: We MUST ensure the designer has actually built the menu strip before asking for its height!
|
|
||||||
if (_screenBitmap != null && menuStrip1 != null)
|
if (_screenBitmap != null && menuStrip1 != null)
|
||||||
{
|
{
|
||||||
// 1. Set the rendering mode for perfect, chunky retro pixels!
|
// 1. Maintain perfect, chunky retro pixels
|
||||||
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
||||||
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
|
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
|
||||||
|
|
||||||
// 2. Calculate the drawing area.
|
|
||||||
// We start drawing BELOW the MenuStrip so it doesn't get covered up.
|
|
||||||
int topOffset = menuStrip1.Height;
|
int topOffset = menuStrip1.Height;
|
||||||
Rectangle renderArea = new Rectangle(
|
int availableWidth = this.ClientSize.Width;
|
||||||
0,
|
int availableHeight = this.ClientSize.Height - topOffset;
|
||||||
topOffset,
|
|
||||||
this.ClientSize.Width,
|
|
||||||
this.ClientSize.Height - topOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Blast the bitmap directly onto the graphics card buffer!
|
// 2. THE LENS LENS: Determine what part of the VDP buffer we actually want to show!
|
||||||
e.Graphics.DrawImage(_screenBitmap, renderArea);
|
// (Safely check if the machine/video processor exists yet)
|
||||||
|
bool isGG = _machine?.VideoProcessor?.IsGameGear ?? false;
|
||||||
|
|
||||||
|
int sourceWidth = isGG ? 160 : 256;
|
||||||
|
int sourceHeight = isGG ? 144 : 192;
|
||||||
|
int sourceX = isGG ? 48 : 0; // Skip 48 pixels in!
|
||||||
|
int sourceY = isGG ? 24 : 0; // Skip 24 pixels down!
|
||||||
|
|
||||||
|
// Create a rectangle defining the specific pixels to extract from the raw frame buffer
|
||||||
|
Rectangle sourceRect = new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight);
|
||||||
|
|
||||||
|
// 3. Calculate the maximum scale factor for the cropped image
|
||||||
|
float scaleX = (float)availableWidth / sourceWidth;
|
||||||
|
float scaleY = (float)availableHeight / sourceHeight;
|
||||||
|
|
||||||
|
// Pick the smaller scale so the image never bleeds off the edge
|
||||||
|
float scale = Math.Min(scaleX, scaleY);
|
||||||
|
|
||||||
|
// 4. Calculate the new physical pixel dimensions
|
||||||
|
int newWidth = (int)(sourceWidth * scale);
|
||||||
|
int newHeight = (int)(sourceHeight * scale);
|
||||||
|
|
||||||
|
// 5. Center the image (Letterboxing / Pillarboxing)
|
||||||
|
int offsetX = (availableWidth - newWidth) / 2;
|
||||||
|
int offsetY = topOffset + ((availableHeight - newHeight) / 2);
|
||||||
|
|
||||||
|
// Create a rectangle defining exactly where on the Windows form to draw the extracted pixels
|
||||||
|
Rectangle destRect = new Rectangle(offsetX, offsetY, newWidth, newHeight);
|
||||||
|
|
||||||
|
// 6. Blast the perfectly cropped and scaled bitmap onto the screen!
|
||||||
|
e.Graphics.DrawImage(_screenBitmap, destRect, sourceRect, GraphicsUnit.Pixel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void StartEmulator()
|
public void StartEmulator()
|
||||||
{
|
{
|
||||||
if (IsRunning) return;
|
if (IsRunning) return;
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
TotalFrameCount = 0;
|
TotalFrameCount = 0;
|
||||||
|
|
||||||
double TargetFrameTime = 1000.0 / 59.92274;
|
|
||||||
|
|
||||||
_emulatorTask = Task.Run(() =>
|
_emulatorTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
double targetFps = 59.94;
|
||||||
|
double TargetFrameTime = 1000.0 / targetFps;
|
||||||
_stopwatch.Restart();
|
_stopwatch.Restart();
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Mark exactly when the emulator starts thinking
|
while (IsRunning)
|
||||||
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
|
|
||||||
|
|
||||||
// --- POLL PHYSICAL CONTROLLER ---
|
|
||||||
if (XInput.XInputGetState(0, out XInput.XINPUT_STATE state) == 0)
|
|
||||||
{
|
{
|
||||||
ushort btns = state.Gamepad.wButtons;
|
//targetFps = isTurboMode ? 1000 : 59.94;
|
||||||
byte padState = 0xFF;
|
//TargetFrameTime = 1000.0 / targetFps;
|
||||||
|
|
||||||
if ((btns & 0x0001) != 0) padState &= 0xFE; // Up
|
|
||||||
if ((btns & 0x0002) != 0) padState &= 0xFD; // Down
|
|
||||||
if ((btns & 0x0004) != 0) padState &= 0xFB; // Left
|
|
||||||
if ((btns & 0x0008) != 0) padState &= 0xF7; // Right
|
|
||||||
if ((btns & 0x1000) != 0) padState &= 0xEF; // Button 1
|
|
||||||
if ((btns & 0x2000) != 0) padState &= 0xDF; // Button 2
|
|
||||||
|
|
||||||
// THE FIX: Constantly update the gamepad state, even when it's 0xFF!
|
|
||||||
_machine.IoBus.Joypad1Gamepad = padState;
|
|
||||||
}
|
|
||||||
// --------------------------------
|
|
||||||
_machine.RunFrame();
|
|
||||||
|
|
||||||
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
|
|
||||||
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(); });
|
|
||||||
|
|
||||||
// 3. Safety catch
|
|
||||||
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
|
|
||||||
{
|
|
||||||
IsRunning = false;
|
|
||||||
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Calculate TRUE Frame Time (Compute Time) BEFORE we go to sleep!
|
|
||||||
FrameTime = _stopwatch.Elapsed.TotalMilliseconds - frameStartTime;
|
|
||||||
|
|
||||||
// --- HIGH PRECISION THROTTLE ---
|
|
||||||
while (_stopwatch.Elapsed.TotalMilliseconds < nextFrameTargetTime)
|
|
||||||
{
|
|
||||||
if (nextFrameTargetTime - _stopwatch.Elapsed.TotalMilliseconds > 2.0)
|
|
||||||
{
|
|
||||||
Thread.Sleep(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Thread.SpinWait(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
|
|
||||||
TotalFrameCount++;
|
|
||||||
framesThisSecond++;
|
|
||||||
|
|
||||||
if (currentTime - lastFpsUpdate >= 1000.0)
|
|
||||||
{
|
|
||||||
FramesPerSecond = framesThisSecond / ((currentTime - lastFpsUpdate) / 1000.0);
|
|
||||||
framesThisSecond = 0;
|
|
||||||
lastFpsUpdate = currentTime;
|
|
||||||
|
|
||||||
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
|
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
|
||||||
{
|
{
|
||||||
this.Text = $"Parsons Master System 2026 - {_currentRomName} [FPS/FT: {FramesPerSecond:F0}/{FrameTime:F1}]";
|
targetFps = isTurboMode ? 1000 : 59.94;
|
||||||
|
TargetFrameTime = 1000.0 / targetFps;
|
||||||
});
|
});
|
||||||
|
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
|
// --- POLL PHYSICAL CONTROLLER ---
|
||||||
|
if (XInput.XInputGetState(0, out XInput.XINPUT_STATE state) == 0)
|
||||||
|
{
|
||||||
|
ushort btns = state.Gamepad.wButtons;
|
||||||
|
byte padState = 0xFF;
|
||||||
|
|
||||||
|
if ((btns & 0x0001) != 0) padState &= 0xFE; // Up
|
||||||
|
if ((btns & 0x0002) != 0) padState &= 0xFD; // Down
|
||||||
|
if ((btns & 0x0004) != 0) padState &= 0xFB; // Left
|
||||||
|
if ((btns & 0x0008) != 0) padState &= 0xF7; // Right
|
||||||
|
if ((btns & 0x1000) != 0) padState &= 0xEF; // Button 1
|
||||||
|
if ((btns & 0x2000) != 0) padState &= 0xDF; // Button 2
|
||||||
|
|
||||||
|
// THE FIX: Constantly update the gamepad state, even when it's 0xFF!
|
||||||
|
_machine.IoBus.Joypad1Gamepad = padState;
|
||||||
|
}
|
||||||
|
// --------------------------------
|
||||||
|
_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!
|
||||||
|
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { DrawScreen(frozenFrame); });
|
||||||
|
|
||||||
|
// 3. Safety catch
|
||||||
|
if (_machine.Breakpoint.HasValue && _machine.Cpu.PC == _machine.Breakpoint.Value)
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||||
|
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate { _debugger?.uiUpdateTimer_Tick(null, EventArgs.Empty); });
|
||||||
|
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Calculate TRUE Frame Time (Compute Time) BEFORE we go to sleep!
|
||||||
|
FrameTime = _stopwatch.Elapsed.TotalMilliseconds - frameStartTime;
|
||||||
|
|
||||||
|
// --- HIGH PRECISION THROTTLE ---
|
||||||
|
while (_stopwatch.Elapsed.TotalMilliseconds < nextFrameTargetTime)
|
||||||
|
{
|
||||||
|
if (nextFrameTargetTime - _stopwatch.Elapsed.TotalMilliseconds > 2.0)
|
||||||
|
{
|
||||||
|
Thread.Sleep(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Thread.SpinWait(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double currentTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
TotalFrameCount++;
|
||||||
|
framesThisSecond++;
|
||||||
|
|
||||||
|
if (currentTime - lastFpsUpdate >= 1000.0)
|
||||||
|
{
|
||||||
|
FramesPerSecond = framesThisSecond / ((currentTime - lastFpsUpdate) / 1000.0);
|
||||||
|
framesThisSecond = 0;
|
||||||
|
lastFpsUpdate = currentTime;
|
||||||
|
|
||||||
|
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
|
||||||
|
{
|
||||||
|
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextFrameTargetTime += TargetFrameTime;
|
||||||
|
|
||||||
|
if (currentTime > nextFrameTargetTime + TargetFrameTime)
|
||||||
|
{
|
||||||
|
nextFrameTargetTime = currentTime + TargetFrameTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsRunning = false;
|
||||||
|
|
||||||
nextFrameTargetTime += TargetFrameTime;
|
this.Invoke((System.Windows.Forms.MethodInvoker)delegate
|
||||||
|
|
||||||
if (currentTime > nextFrameTargetTime + TargetFrameTime)
|
|
||||||
{
|
{
|
||||||
nextFrameTargetTime = currentTime + TargetFrameTime;
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -194,7 +254,8 @@ namespace Desktop
|
|||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
private async void LoadRomAndStart(string filePath)
|
|
||||||
|
private async void LoadRomDataAndStart(byte[] romData, string fileName, string uniqueId)
|
||||||
{
|
{
|
||||||
StopEmulator();
|
StopEmulator();
|
||||||
if (_emulatorTask != null)
|
if (_emulatorTask != null)
|
||||||
@@ -202,35 +263,49 @@ namespace Desktop
|
|||||||
await _emulatorTask;
|
await _emulatorTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. SAVE THE PREVIOUS GAME!
|
|
||||||
SaveCurrentSram();
|
SaveCurrentSram();
|
||||||
|
|
||||||
// 2. Load the file
|
// Blast the bytes straight into the virtual cartridge slot!
|
||||||
byte[] rom = File.ReadAllBytes(filePath);
|
_machine.LoadCartridge(romData);
|
||||||
|
|
||||||
// 3. Jam it into the Sega Mapper
|
// Check the hardware mode based on the file name
|
||||||
_machine.LoadCartridge(rom);
|
bool isGG = fileName.EndsWith(".gg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
_machine.VideoProcessor.IsGameGear = isGG;
|
||||||
|
|
||||||
// 4. Update the path tracking
|
// We use uniqueId to track the SRAM file path (works for both real paths and resource names)
|
||||||
_currentRomPath = filePath;
|
_currentRomPath = uniqueId;
|
||||||
_currentRomName = Path.GetFileNameWithoutExtension(filePath);
|
_currentRomName = Path.GetFileNameWithoutExtension(fileName);
|
||||||
this.Text = $"Parsons Master System - {_currentRomName}";
|
|
||||||
|
// Update the window title dynamically
|
||||||
|
_romType = isGG ? "GG" : "MS";
|
||||||
|
this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}";
|
||||||
|
|
||||||
// 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER!
|
|
||||||
string savPath = GetSaveFilePath();
|
string savPath = GetSaveFilePath();
|
||||||
if (savPath != null)
|
if (savPath != null)
|
||||||
{
|
{
|
||||||
_machine.MemoryBus.LoadSaveData(savPath);
|
_machine.MemoryBus.LoadSaveData(savPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Turn the power on!
|
|
||||||
StartEmulator();
|
StartEmulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadRomAndStart(string filePath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath)) return;
|
||||||
|
|
||||||
|
// Read from the hard drive, then pass to the universal loader
|
||||||
|
byte[] romData = File.ReadAllBytes(filePath);
|
||||||
|
LoadRomDataAndStart(romData, Path.GetFileName(filePath), filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private string GetSaveFilePath()
|
private string GetSaveFilePath()
|
||||||
{
|
{
|
||||||
// Don't try to save if a game hasn't been loaded yet!
|
// Don't try to save if a game hasn't been loaded yet!
|
||||||
if (string.IsNullOrEmpty(_currentRomName) || _currentRomName == "No ROM")
|
if (string.IsNullOrEmpty(_currentRomName) || _currentRomName == "No ROM")
|
||||||
|
#pragma warning disable CS8603 // Possible null reference return.
|
||||||
return null;
|
return null;
|
||||||
|
#pragma warning restore CS8603 // Possible null reference return.
|
||||||
|
|
||||||
// Application.StartupPath is the exact folder containing your .exe
|
// Application.StartupPath is the exact folder containing your .exe
|
||||||
string exeFolder = Application.StartupPath;
|
string exeFolder = Application.StartupPath;
|
||||||
@@ -252,34 +327,87 @@ namespace Desktop
|
|||||||
|
|
||||||
private void PopulateIncludedRomsMenu()
|
private void PopulateIncludedRomsMenu()
|
||||||
{
|
{
|
||||||
// The folder you used for Golden Axe Warrior
|
string romsDirectory = "Desktop.ROMS";
|
||||||
string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\";
|
string[] files = GetFilesInResourceDirectory(romsDirectory);
|
||||||
|
|
||||||
if (Directory.Exists(romsDirectory))
|
foreach (string resourceName in files)
|
||||||
{
|
{
|
||||||
string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms");
|
// 1. Strip the "Desktop.ROMS." prefix
|
||||||
|
string cleanFileName = resourceName.Replace("Desktop.ROMS.", "");
|
||||||
|
|
||||||
foreach (string file in romFiles)
|
// 2. Strip the extension for a beautiful menu item (e.g. "Sonic")
|
||||||
|
string menuName = Path.GetFileNameWithoutExtension(cleanFileName);
|
||||||
|
|
||||||
|
ToolStripMenuItem romMenuItem = new ToolStripMenuItem(menuName);
|
||||||
|
|
||||||
|
// 3. Point the click event to our new extraction helper!
|
||||||
|
romMenuItem.Click += (sender, e) => LoadEmbeddedRom(resourceName);
|
||||||
|
|
||||||
|
// 4. Sort into the correct menu based on extension!
|
||||||
|
if (resourceName.EndsWith(".gg", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Create a new dropdown item for each .sms file it finds
|
gameGearToolStripMenuItem.DropDownItems.Add(romMenuItem);
|
||||||
string romName = Path.GetFileNameWithoutExtension(file);
|
}
|
||||||
ToolStripMenuItem romMenuItem = new ToolStripMenuItem(romName);
|
else if (resourceName.EndsWith(".sms", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
// When clicked, pass the file path to our helper method
|
masterSystemToolStripMenuItem.DropDownItems.Add(romMenuItem);
|
||||||
romMenuItem.Click += (sender, e) => LoadRomAndStart(file);
|
|
||||||
|
|
||||||
// Add it to the "Included" submenu (make sure the name matches your designer element!)
|
|
||||||
includedToolStripMenuItem.DropDownItems.Add(romMenuItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadEmbeddedRom(string resourceName)
|
||||||
|
{
|
||||||
|
// 1. Strip off the ugly prefix to get the real file name for the UI and the .gg check
|
||||||
|
string cleanFileName = resourceName.Replace("Desktop.ROMS.", "");
|
||||||
|
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
|
||||||
|
// 2. Open a direct pipe to the internal resource
|
||||||
|
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
|
||||||
|
{
|
||||||
|
if (stream == null) return;
|
||||||
|
|
||||||
|
// 3. Copy the stream entirely in RAM—never touching the hard drive!
|
||||||
|
using (MemoryStream ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
stream.CopyTo(ms);
|
||||||
|
byte[] romData = ms.ToArray();
|
||||||
|
|
||||||
|
// 4. Pass the raw bytes to our universal loader
|
||||||
|
LoadRomDataAndStart(romData, cleanFileName, resourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string[] GetFilesInResourceDirectory(string directoryPrefix)
|
||||||
|
{
|
||||||
|
// Get the assembly containing the embedded resources.
|
||||||
|
// Change this if your resources are in a different project/assembly.
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
|
||||||
|
// Ensure the prefix ends with a dot so we don't accidentally match
|
||||||
|
// similarly named folders (e.g., matching "Folder" but not "FolderTwo")
|
||||||
|
if (!directoryPrefix.EndsWith("."))
|
||||||
|
{
|
||||||
|
directoryPrefix += ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all resource names and filter by our "directory" prefix
|
||||||
|
string[] allResources = assembly.GetManifestResourceNames();
|
||||||
|
|
||||||
|
string[] filesInDirectory = allResources
|
||||||
|
.Where(resource => resource.StartsWith(directoryPrefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return filesInDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
private void selectROMToolStripMenuItem_Click(object sender, EventArgs e)
|
private void selectROMToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
using (OpenFileDialog ofd = new OpenFileDialog())
|
using (OpenFileDialog ofd = new OpenFileDialog())
|
||||||
{
|
{
|
||||||
ofd.Title = "Select Master System ROM";
|
ofd.Title = "Select Master System ROM";
|
||||||
ofd.Filter = "SMS ROMs (*.sms)|*.sms|All Files (*.*)|*.*";
|
ofd.Filter = "SMS ROMs (*.sms)|*.sms|GG ROMs (*.gg)|*.gg|All Files (*.*)|*.*";
|
||||||
|
|
||||||
if (ofd.ShowDialog() == DialogResult.OK)
|
if (ofd.ShowDialog() == DialogResult.OK)
|
||||||
{
|
{
|
||||||
@@ -313,14 +441,10 @@ namespace Desktop
|
|||||||
_vramViewer.Show();
|
_vramViewer.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void includedToolStripMenuItem_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
this.Close();
|
this.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Form1_KeyDown(object sender, KeyEventArgs e)
|
private void Form1_KeyDown(object sender, KeyEventArgs e)
|
||||||
@@ -400,5 +524,34 @@ namespace Desktop
|
|||||||
// 3. Resume
|
// 3. Resume
|
||||||
StartEmulator();
|
StartEmulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void turboModeToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
turboModeToolStripMenuItem.Checked = !turboModeToolStripMenuItem.Checked;
|
||||||
|
isTurboMode = turboModeToolStripMenuItem.Checked ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
$"First full release of Parsons Master System Emulator 2026!\r\n\r\nFeatures (v1.0):\r\n\r\n" +
|
||||||
|
$" *Full compatibility with all Master System and Game Gear ROMs\r\n" +
|
||||||
|
$" *Full window scaling with fixed aspect ratio (SMS & GG)\r\n" +
|
||||||
|
$" *Full 4 channel SN76489 implememtation using NAudio\r\n" +
|
||||||
|
$" *SRAM auto save and load (Battery Backed RAM)\r\n" +
|
||||||
|
$" *Keyboard and XInput control pad support\r\n" +
|
||||||
|
$" *Comprehensive Debugger\r\n" +
|
||||||
|
$" *VRAM Viewer\r\n" +
|
||||||
|
$" *Single point Save State\r\n" +
|
||||||
|
$" *Turbo mode\r\n" +
|
||||||
|
$" *Includes a selection of commercial ROMs\r\n\r\n" +
|
||||||
|
$"Planned updates (v1.1):\r\n\r\n" +
|
||||||
|
$" *Architectural double buffering (prevent screen tear)\r\n" +
|
||||||
|
$" *Graphics shaders (scanlines etc...)\r\n" +
|
||||||
|
$" *Add multiple Save States per game\r\n\r\n\r\n" +
|
||||||
|
$"(c) Marc Parsons 2026","Parsons Master System 2026 v1.0",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Desktop/ROMS/Aladdin (USA, Europe, Brazil).gg
Normal file
BIN
Desktop/ROMS/Aladdin (USA, Europe, Brazil).gg
Normal file
Binary file not shown.
BIN
Desktop/ROMS/Bonanza Bros. (Europe).sms
Normal file
BIN
Desktop/ROMS/Bonanza Bros. (Europe).sms
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Desktop/ROMS/Micro Machines (Europe).sms
Normal file
BIN
Desktop/ROMS/Micro Machines (Europe).sms
Normal file
Binary file not shown.
BIN
Desktop/ROMS/Olympic Gold - Barcelona '92 (Europe).sms
Normal file
BIN
Desktop/ROMS/Olympic Gold - Barcelona '92 (Europe).sms
Normal file
Binary file not shown.
Binary file not shown.
BIN
Desktop/ROMS/Prince of Persia (Europe).sms
Normal file
BIN
Desktop/ROMS/Prince of Persia (Europe).sms
Normal file
Binary file not shown.
BIN
Desktop/ROMS/Sonic 1 GG.gg
Normal file
BIN
Desktop/ROMS/Sonic 1 GG.gg
Normal file
Binary file not shown.
Binary file not shown.
BIN
Desktop/ROMS/Sonic The Hedgehog 2 (World).gg
Normal file
BIN
Desktop/ROMS/Sonic The Hedgehog 2 (World).gg
Normal file
Binary file not shown.
Binary file not shown.
@@ -45,7 +45,6 @@ namespace Desktop
|
|||||||
int tileX = (tile % 32) * 8;
|
int tileX = (tile % 32) * 8;
|
||||||
int tileY = (tile / 32) * 8;
|
int tileY = (tile / 32) * 8;
|
||||||
int tileAddress = tile * 32;
|
int tileAddress = tile * 32;
|
||||||
|
|
||||||
// Decode the 8x8 pixels
|
// Decode the 8x8 pixels
|
||||||
for (int y = 0; y < 8; y++)
|
for (int y = 0; y < 8; y++)
|
||||||
{
|
{
|
||||||
@@ -57,19 +56,19 @@ namespace Desktop
|
|||||||
for (int x = 0; x < 8; x++)
|
for (int x = 0; x < 8; x++)
|
||||||
{
|
{
|
||||||
int shift = 7 - x;
|
int shift = 7 - x;
|
||||||
int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
int colourIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
||||||
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
||||||
|
|
||||||
// Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly
|
// Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly
|
||||||
byte smsColor = _vdp.CRAM[16 + colorIndex];
|
int colour = _vdp.GetRgbColour(16, colourIndex);
|
||||||
int r = (smsColor & 0x03) * 85;
|
//int r = (smsColor & 0x03) * 85;
|
||||||
int g = ((smsColor >> 2) & 0x03) * 85;
|
//int g = ((smsColor >> 2) & 0x03) * 85;
|
||||||
int b = ((smsColor >> 4) & 0x03) * 85;
|
//int b = ((smsColor >> 4) & 0x03) * 85;
|
||||||
|
|
||||||
// Background color (Index 0) is technically transparent for sprites, so we render it as magenta
|
// Background color (Index 0) is technically transparent for sprites, so we render it as magenta
|
||||||
if (colorIndex == 0) { r = 255; g = 0; b = 255; }
|
//if (colourIndex == 0) { int r = 255; int g = 0; int b = 255; }
|
||||||
|
|
||||||
_pixelData[((tileY + y) * 256) + (tileX + x)] = (255 << 24) | (r << 16) | (g << 8) | b;
|
_pixelData[((tileY + y) * 256) + (tileX + x)] = colourIndex == 0 ? (255 << 24) | (255 << 16) | (0 << 8) | 255 : colour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Desktop/favicon.ico
Normal file
BIN
Desktop/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
22
Parsons.Steamdeck/Parsons.Steamdeck.csproj
Normal file
22
Parsons.Steamdeck/Parsons.Steamdeck.csproj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<PublishAot>true</PublishAot>
|
||||||
|
<OptimizationPreference>Size</OptimizationPreference>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Raylib-cs" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Core\Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
125
Parsons.Steamdeck/Program.cs
Normal file
125
Parsons.Steamdeck/Program.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using Core;
|
||||||
|
using Raylib_cs;
|
||||||
|
|
||||||
|
namespace Parsons.SteamDeck
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// 1. Initialize Cross-Platform OpenGL Window
|
||||||
|
// THE FIX: Use PascalCase C# enums
|
||||||
|
Raylib.SetConfigFlags(ConfigFlags.ResizableWindow);
|
||||||
|
Raylib.InitWindow(1280, 800, "Parsons Master System"); // Native Steam Deck Resolution
|
||||||
|
Raylib.InitAudioDevice();
|
||||||
|
|
||||||
|
// 2. Initialize Hardware-Agnostic Audio Stream (44.1kHz, 32-bit float, Mono)
|
||||||
|
AudioStream audioStream = Raylib.LoadAudioStream(44100, 32, 1);
|
||||||
|
Raylib.PlayAudioStream(audioStream);
|
||||||
|
|
||||||
|
// 3. Boot the Core Emulator
|
||||||
|
SmsMachine machine = new SmsMachine();
|
||||||
|
RaylibAudioPlayer audioPlayer = new RaylibAudioPlayer();
|
||||||
|
machine.AudioProcessor.AudioDevice = audioPlayer;
|
||||||
|
|
||||||
|
// Load a ROM (Passed via command line argument, e.g. ./Parsons.SteamDeck sonic.sms)
|
||||||
|
if (args.Length > 0 && File.Exists(args[0]))
|
||||||
|
{
|
||||||
|
machine.LoadCartridge(File.ReadAllBytes(args[0]));
|
||||||
|
bool isGG = args[0].EndsWith(".gg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
machine.VideoProcessor.IsGameGear = isGG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock the engine loop to 60 FPS natively
|
||||||
|
Raylib.SetTargetFPS(60);
|
||||||
|
|
||||||
|
// 4. Prepare the Video Texture Pipeline
|
||||||
|
// THE FIX: Use Color.Black
|
||||||
|
Image img = Raylib.GenImageColor(256, 192, Color.Black);
|
||||||
|
Texture2D vdpTexture = Raylib.LoadTextureFromImage(img);
|
||||||
|
byte[] pixelBuffer = new byte[256 * 192 * 4]; // Fast RGBA translation array
|
||||||
|
|
||||||
|
// --- THE MAIN GAME LOOP ---
|
||||||
|
while (!Raylib.WindowShouldClose())
|
||||||
|
{
|
||||||
|
// A. OS-Agnostic Input Polling (Works on Keyboard AND Steam Deck Controller)
|
||||||
|
// THE FIX: PascalCase KeyboardKey and GamepadButton enums
|
||||||
|
byte pad = 0xFF;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.Up) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceUp)) pad &= 0xFE;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.Down) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceDown)) pad &= 0xFD;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.Left) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceLeft)) pad &= 0xFB;
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.Right) || Raylib.IsGamepadButtonDown(0, GamepadButton.LeftFaceRight)) pad &= 0xF7;
|
||||||
|
|
||||||
|
// Steam Deck "A" Button is RightFaceDown. "B" Button is RightFaceRight.
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.Z) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceDown)) pad &= 0xEF; // B1 / A Button
|
||||||
|
if (Raylib.IsKeyDown(KeyboardKey.X) || Raylib.IsGamepadButtonDown(0, GamepadButton.RightFaceRight)) pad &= 0xDF; // B2 / B Button
|
||||||
|
|
||||||
|
machine.IoBus.Joypad1Keyboard = pad;
|
||||||
|
|
||||||
|
// B. Execute exactly one frame of T-States
|
||||||
|
machine.RunFrame();
|
||||||
|
|
||||||
|
// C. Flush Audio Buffer to the Sound Card
|
||||||
|
if (Raylib.IsAudioStreamProcessed(audioStream))
|
||||||
|
{
|
||||||
|
float[] samples = audioPlayer.GetQueuedSamples();
|
||||||
|
if (samples.Length > 0)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (float* p = samples)
|
||||||
|
{
|
||||||
|
Raylib.UpdateAudioStream(audioStream, p, samples.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// D. Fast Pixel Translation (ARGB int -> RGBA bytes)
|
||||||
|
int[] frameBuffer = machine.VideoProcessor.FrameBuffer;
|
||||||
|
for (int i = 0; i < frameBuffer.Length; i++)
|
||||||
|
{
|
||||||
|
int argb = frameBuffer[i];
|
||||||
|
pixelBuffer[i * 4 + 0] = (byte)((argb >> 16) & 0xFF); // R
|
||||||
|
pixelBuffer[i * 4 + 1] = (byte)((argb >> 8) & 0xFF); // G
|
||||||
|
pixelBuffer[i * 4 + 2] = (byte)(argb & 0xFF); // B
|
||||||
|
pixelBuffer[i * 4 + 3] = 255; // A
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push bytes directly to the GPU Texture
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
fixed (byte* p = pixelBuffer)
|
||||||
|
{
|
||||||
|
Raylib.UpdateTexture(vdpTexture, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// E. Render to Screen (Perfect Aspect Ratio Scaling)
|
||||||
|
Raylib.BeginDrawing();
|
||||||
|
Raylib.ClearBackground(Color.Black);
|
||||||
|
|
||||||
|
float scale = Math.Min((float)Raylib.GetScreenWidth() / 256, (float)Raylib.GetScreenHeight() / 192);
|
||||||
|
Rectangle sourceRec = new Rectangle(0, 0, 256, 192);
|
||||||
|
Rectangle destRec = new Rectangle(
|
||||||
|
(Raylib.GetScreenWidth() - (256 * scale)) * 0.5f,
|
||||||
|
(Raylib.GetScreenHeight() - (192 * scale)) * 0.5f,
|
||||||
|
256 * scale, 192 * scale);
|
||||||
|
|
||||||
|
// Draw the VDP Frame
|
||||||
|
Raylib.DrawTexturePro(vdpTexture, sourceRec, destRec, new Vector2(0, 0), 0.0f, Color.White);
|
||||||
|
|
||||||
|
Raylib.EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up hardware resources on exit
|
||||||
|
Raylib.UnloadTexture(vdpTexture);
|
||||||
|
Raylib.UnloadAudioStream(audioStream);
|
||||||
|
Raylib.CloseAudioDevice();
|
||||||
|
Raylib.CloseWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Parsons.Steamdeck/RaylibAudioPlayer.cs
Normal file
28
Parsons.Steamdeck/RaylibAudioPlayer.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Core.Interfaces;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace Parsons.SteamDeck
|
||||||
|
{
|
||||||
|
public class RaylibAudioPlayer : IAudioDevice
|
||||||
|
{
|
||||||
|
private ConcurrentQueue<float> _sampleQueue = new ConcurrentQueue<float>();
|
||||||
|
|
||||||
|
public void AddSample(float sample)
|
||||||
|
{
|
||||||
|
// Queue the sample from the emulator core
|
||||||
|
_sampleQueue.Enqueue(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] GetQueuedSamples()
|
||||||
|
{
|
||||||
|
// Pull all pending samples out of the queue to send to the sound card
|
||||||
|
int count = _sampleQueue.Count;
|
||||||
|
float[] buffer = new float[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
_sampleQueue.TryDequeue(out buffer[i]);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.14.37216.2 d17.14
|
VisualStudioVersion = 17.14.37216.2
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{E245F3A5-E541-43BB-B64E-B4B2BB3D8C51}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{B0A741E4-CE3E-4DF0-96B2-E314973F9201}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "Core\Core.csproj", "{B0A741E4-CE3E-4DF0-96B2-E314973F9201}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Parsons.Steamdeck", "Parsons.Steamdeck\Parsons.Steamdeck.csproj", "{B619DF68-7EBF-4C6A-AC24-AD5250404943}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -21,6 +23,10 @@ Global
|
|||||||
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B0A741E4-CE3E-4DF0-96B2-E314973F9201}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{B619DF68-7EBF-4C6A-AC24-AD5250404943}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
Reference in New Issue
Block a user