6 Commits

Author SHA1 Message Date
Marc Parsons
66f18d0510 Updated VDP to handle GameGear. Added Turbo mode. Versio 1.0 me thinks! 2026-05-18 14:57:19 +01:00
Marc Parsons
be43019fb0 Added Sonic Game Gear to see what's gwannin 2026-05-17 15:15:55 +01:00
Marc Parsons
1730687009 Added a very poorly implemented PAL/NTSC toggle 2026-05-17 15:02:31 +01:00
Marc Parsons
f1140fa115 Added H Counter to try and fix Sonic demo - didn't work 2026-05-16 20:26:13 +01:00
Marc Parsons
b1ff22c9e6 Tried to fix Sonic 1 demo loop 2026-05-16 20:18:07 +01:00
parsons
aa9cc552e6 Added window scaling 2026-05-16 00:49:55 +01:00
16 changed files with 348 additions and 133 deletions

View File

@@ -1303,6 +1303,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;

View File

@@ -25,7 +25,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.

View File

@@ -23,7 +23,7 @@ namespace Core
//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();

View File

@@ -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,11 +27,14 @@ 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
@@ -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!
_controlWord++;
} }
public void WriteControlPort(byte value) // Port 0xBF public void WriteControlPort(byte value) // Port 0xBF
@@ -104,10 +118,28 @@ namespace Core.Video
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 +177,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 +201,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 +216,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 +258,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 +322,53 @@ 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 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 +381,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 +401,7 @@ namespace Core.Video
// ADD THESE: // ADD THESE:
_latchedHScroll = br.ReadInt32(); _latchedHScroll = br.ReadInt32();
_latchedVScroll = br.ReadInt32(); _latchedVScroll = br.ReadInt32();
IsGameGear = br.ReadBoolean();
} }
} }
} }

View File

@@ -15,6 +15,14 @@
<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</AssemblyName>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<Title>Sega Master System EMulator 2026</Title>
<Version>0.9</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>
@@ -42,9 +50,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" />
@@ -65,7 +74,8 @@
<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" />
@@ -77,10 +87,11 @@
<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.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,6 +114,10 @@
<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>
@@ -176,15 +191,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>
@@ -245,9 +259,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>
@@ -281,6 +294,7 @@
<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>
@@ -290,9 +304,7 @@
<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 +365,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" />

View File

@@ -39,10 +39,13 @@
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();
masterSystemToolStripMenuItem = new ToolStripMenuItem();
gameGearToolStripMenuItem = 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,34 @@
// //
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(224, 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(224, 26);
includedToolStripMenuItem.Text = "Included"; includedToolStripMenuItem.Text = "Included";
includedToolStripMenuItem.Click += includedToolStripMenuItem_Click;
// //
// selectROMToolStripMenuItem1 // selectROMToolStripMenuItem1
// //
selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1"; selectROMToolStripMenuItem1.Name = "selectROMToolStripMenuItem1";
selectROMToolStripMenuItem1.Size = new Size(218, 34); selectROMToolStripMenuItem1.Size = new Size(224, 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(224, 26);
exitToolStripMenuItem.Text = "Exit"; exitToolStripMenuItem.Text = "Exit";
exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; exitToolStripMenuItem.Click += exitToolStripMenuItem_Click;
// //
@@ -95,72 +99,90 @@
// //
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(224, 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(224, 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(224, 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(224, 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(133, 26);
aboutToolStripMenuItem.Text = "About";
//
// masterSystemToolStripMenuItem
//
masterSystemToolStripMenuItem.Name = "masterSystemToolStripMenuItem";
masterSystemToolStripMenuItem.Size = new Size(224, 26);
masterSystemToolStripMenuItem.Text = "Master System";
//
// gameGearToolStripMenuItem
//
gameGearToolStripMenuItem.Name = "gameGearToolStripMenuItem";
gameGearToolStripMenuItem.Size = new Size(224, 26);
gameGearToolStripMenuItem.Text = "Game Gear";
//
// 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 +208,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;
} }
} }

View File

@@ -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 = "";
private bool isTurboMode = false;
public bool IsRunning { get; private set; } = false; public bool IsRunning { get; private set; } = false;
public ushort? Breakpoint public ushort? Breakpoint
@@ -37,23 +37,19 @@ namespace Desktop
public ParsonsForm1() public ParsonsForm1()
{ {
InitializeComponent(); InitializeComponent();
// These are perfectly safe for the Visual Studio Designer!
this.KeyPreview = true; this.KeyPreview = true;
this.KeyDown += Form1_KeyDown; this.KeyDown += Form1_KeyDown;
this.KeyUp += Form1_KeyUp; this.KeyUp += Form1_KeyUp;
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 = $"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;
@@ -70,43 +66,101 @@ namespace Desktop
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);
} }
} }
//protected override void OnPaint(PaintEventArgs e)
//{
// base.OnPaint(e);
// if (_screenBitmap != null && menuStrip1 != null)
// {
// // 1. Maintain perfect, chunky retro pixels
// e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
// e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
// // 2. Calculate the actual usable window space
// int topOffset = menuStrip1.Height;
// int availableWidth = this.ClientSize.Width;
// int availableHeight = this.ClientSize.Height - topOffset;
// // 3. Calculate the maximum scale factor that fits perfectly
// float scaleX = (float)availableWidth / 256f;
// float scaleY = (float)availableHeight / 192f;
// // 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)(256 * scale);
// int newHeight = (int)(192 * scale);
// // 5. Center the image (Letterboxing / Pillarboxing)
// int offsetX = (availableWidth - newWidth) / 2;
// int offsetY = topOffset + ((availableHeight - newHeight) / 2);
// Rectangle renderArea = new Rectangle(offsetX, offsetY, newWidth, newHeight);
// // 6. Draw the scaled image
// e.Graphics.DrawImage(_screenBitmap, renderArea);
// }
//}
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;
@@ -115,7 +169,13 @@ namespace Desktop
while (IsRunning) while (IsRunning)
{ {
// Mark exactly when the emulator starts thinking //targetFps = isTurboMode ? 1000 : 59.94;
//TargetFrameTime = 1000.0 / targetFps;
BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{
targetFps = isTurboMode ? 1000 : 59.94;
TargetFrameTime = 1000.0 / targetFps;
});
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds; double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
// --- POLL PHYSICAL CONTROLLER --- // --- POLL PHYSICAL CONTROLLER ---
@@ -176,7 +236,7 @@ namespace Desktop
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}]"; this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName} [FPS: {FramesPerSecond:F0}]";
}); });
} }
@@ -201,6 +261,18 @@ namespace Desktop
{ {
await _emulatorTask; await _emulatorTask;
} }
_romType = Path.GetExtension(filePath);
if (_romType == ".sms")
{
_machine.VideoProcessor.IsGameGear = false;
}
else if (_romType == ".gg")
{
_machine.VideoProcessor.IsGameGear = true;
}
else
throw new Exception("Incorrect ROM Type!");
_romType = _machine.VideoProcessor.IsGameGear ? "GG" : "SMS";
// 1. SAVE THE PREVIOUS GAME! // 1. SAVE THE PREVIOUS GAME!
SaveCurrentSram(); SaveCurrentSram();
@@ -214,7 +286,7 @@ namespace Desktop
// 4. Update the path tracking // 4. Update the path tracking
_currentRomPath = filePath; _currentRomPath = filePath;
_currentRomName = Path.GetFileNameWithoutExtension(filePath); _currentRomName = Path.GetFileNameWithoutExtension(filePath);
this.Text = $"Parsons Master System - {_currentRomName}"; //this.Text = $"{_romType} Parsons Master System 2026 - {_currentRomName}";
// 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER! // 5. LOAD THE NEW SAVE DATA FROM THE EXE FOLDER!
string savPath = GetSaveFilePath(); string savPath = GetSaveFilePath();
@@ -252,13 +324,11 @@ namespace Desktop
private void PopulateIncludedRomsMenu() private void PopulateIncludedRomsMenu()
{ {
// The folder you used for Golden Axe Warrior
string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\"; string romsDirectory = @"C:\Parsons\Local Code Projects\ParsonsMasterSystem2026\Desktop\ROMS\";
if (Directory.Exists(romsDirectory)) if (Directory.Exists(romsDirectory))
{ {
string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms"); string[] romFiles = Directory.GetFiles(romsDirectory, "*.sms");
foreach (string file in romFiles) foreach (string file in romFiles)
{ {
// Create a new dropdown item for each .sms file it finds // Create a new dropdown item for each .sms file it finds
@@ -269,8 +339,20 @@ namespace Desktop
romMenuItem.Click += (sender, e) => LoadRomAndStart(file); romMenuItem.Click += (sender, e) => LoadRomAndStart(file);
// Add it to the "Included" submenu (make sure the name matches your designer element!) // Add it to the "Included" submenu (make sure the name matches your designer element!)
includedToolStripMenuItem.DropDownItems.Add(romMenuItem); masterSystemToolStripMenuItem.DropDownItems.Add(romMenuItem);
} }
romFiles = Directory.GetFiles(romsDirectory, "*.gg");
foreach (string file in romFiles)
{
// Create a new dropdown item for each .gg file it finds
string romName = Path.GetFileNameWithoutExtension(file);
ToolStripMenuItem romMenuItem = new ToolStripMenuItem(romName);
// When clicked, pass the file path to the helper method
romMenuItem.Click += (sender, e) => LoadRomAndStart(file);
gameGearToolStripMenuItem.DropDownItems.Add(romMenuItem);
}
} }
} }
@@ -279,7 +361,7 @@ namespace Desktop
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 +395,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 +478,11 @@ 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;
}
} }
} }

Binary file not shown.

BIN
Desktop/ROMS/Sonic 1 GG.gg Normal file

Binary file not shown.

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
logo1.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 KiB