diff --git a/Core/Audio/SmsApu.cs b/Core/Audio/SmsApu.cs
index 338d34c..30e7253 100644
--- a/Core/Audio/SmsApu.cs
+++ b/Core/Audio/SmsApu.cs
@@ -22,6 +22,7 @@ namespace Core.Audio
private int[] _counters = new int[4];
private int[] _polarities = new int[4] { 1, 1, 1, 1 }; // 1 = High, -1 = Low
private ushort _lfsr = 0x8000; // Linear Feedback Shift Register (For Noise)
+ private bool _noiseFlipFlop = false;
// The SN76489 Volume Table reduces amplitude by exactly 2 decibels per step.
private static readonly float[] VolumeTable = {
@@ -83,27 +84,33 @@ namespace Core.Audio
_counters[3]--;
if (_counters[3] <= 0)
{
- // Noise rate depends on Bits 0-1 of Register 6
+ // Reload the counter
int shiftRate = Registers[6] & 0x03;
if (shiftRate == 0) _counters[3] = 0x10; // Fast
else if (shiftRate == 1) _counters[3] = 0x20; // Medium
else if (shiftRate == 2) _counters[3] = 0x40; // Slow
- else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2!
+ else _counters[3] = (Registers[4] == 0) ? 1024 : Registers[4]; // Linked to Tone 2
- // Shift the Noise LFSR
- int tappedBit = _lfsr & 1;
- _lfsr >>= 1;
+ // THE FIX: Toggle the internal clock (Divide by 2!)
+ _noiseFlipFlop = !_noiseFlipFlop;
- if (tappedBit == 1)
+ // Only shift the random static when the clock goes High
+ if (_noiseFlipFlop)
{
- bool isWhiteNoise = (Registers[6] & 0x04) != 0;
- // The Sega Master System physically tapped bits 0 and 3 for its white noise
- if (isWhiteNoise) _lfsr ^= 0x0009;
+ int tappedBit = _lfsr & 1;
+ _lfsr >>= 1;
- _lfsr |= 0x8000; // Inject the high bit
+ if (tappedBit == 1)
+ {
+ bool isWhiteNoise = (Registers[6] & 0x04) != 0;
+ if (isWhiteNoise) _lfsr ^= 0x0009; // SMS-specific XOR mask
+
+ _lfsr |= 0x8000; // Inject the high bit
+ }
+
+ // The noise channel output is driven directly by the tapped bit
+ _polarities[3] = (tappedBit == 1) ? 1 : -1;
}
-
- _polarities[3] = (tappedBit == 1) ? 1 : -1;
}
}
@@ -149,12 +156,14 @@ namespace Core.Audio
// Volume registers (1, 3, 5, 7) and Noise Control (6) only hold 4 bits total.
// We completely overwrite them.
Registers[_latchedRegister] = (ushort)data;
+ if (_latchedRegister == 6) _lfsr = 0x8000;
}
else
{
// Tone registers (0, 2, 4) hold 10 bits.
// A Latch byte ONLY overwrites the bottom 4 bits and leaves the top 6 alone!
Registers[_latchedRegister] = (ushort)((Registers[_latchedRegister] & 0x03F0) | data);
+ if (_latchedRegister == 6) _lfsr = 0x8000;
}
}
else
diff --git a/Desktop/Desktop.csproj b/Desktop/Desktop.csproj
index 9b6c28d..3441ec4 100644
--- a/Desktop/Desktop.csproj
+++ b/Desktop/Desktop.csproj
@@ -90,255 +90,257 @@
+
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
- Always
+ Never
+
- Always
+ Never
diff --git a/Desktop/Form1.Designer.cs b/Desktop/Form1.Designer.cs
index fa9b97c..6786aa3 100644
--- a/Desktop/Form1.Designer.cs
+++ b/Desktop/Form1.Designer.cs
@@ -40,6 +40,7 @@
resetToolStripMenuItem = new ToolStripMenuItem();
helpToolStripMenuItem = new ToolStripMenuItem();
aboutToolStripMenuItem = new ToolStripMenuItem();
+ vRAMViewerToolStripMenuItem = new ToolStripMenuItem();
menuStrip1.SuspendLayout();
SuspendLayout();
//
@@ -90,7 +91,7 @@
//
// viewToolStripMenuItem
//
- viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem });
+ viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { debuggerToolStripMenuItem, vRAMViewerToolStripMenuItem });
viewToolStripMenuItem.Name = "viewToolStripMenuItem";
viewToolStripMenuItem.Size = new Size(65, 29);
viewToolStripMenuItem.Text = "View";
@@ -98,7 +99,7 @@
// debuggerToolStripMenuItem
//
debuggerToolStripMenuItem.Name = "debuggerToolStripMenuItem";
- debuggerToolStripMenuItem.Size = new Size(194, 34);
+ debuggerToolStripMenuItem.Size = new Size(270, 34);
debuggerToolStripMenuItem.Text = "Debugger";
debuggerToolStripMenuItem.Click += debuggerToolStripMenuItem_Click;
//
@@ -129,6 +130,13 @@
aboutToolStripMenuItem.Size = new Size(164, 34);
aboutToolStripMenuItem.Text = "About";
//
+ // vRAMViewerToolStripMenuItem
+ //
+ vRAMViewerToolStripMenuItem.Name = "vRAMViewerToolStripMenuItem";
+ vRAMViewerToolStripMenuItem.Size = new Size(270, 34);
+ vRAMViewerToolStripMenuItem.Text = "VRAM Viewer";
+ vRAMViewerToolStripMenuItem.Click += vramViewerToolStripMenuItem_Click;
+ //
// ParsonsForm1
//
AutoScaleDimensions = new SizeF(10F, 25F);
@@ -159,5 +167,6 @@
private ToolStripMenuItem aboutToolStripMenuItem;
private ToolStripMenuItem includedToolStripMenuItem;
private ToolStripMenuItem selectROMToolStripMenuItem1;
+ private ToolStripMenuItem vRAMViewerToolStripMenuItem;
}
}
diff --git a/Desktop/Form1.cs b/Desktop/Form1.cs
index be26a00..b9859c8 100644
--- a/Desktop/Form1.cs
+++ b/Desktop/Form1.cs
@@ -14,6 +14,7 @@ namespace Desktop
{
public SmsMachine _machine = null!;
private DebuggerForm _debugger;
+ private VramViewerForm _vramViewer;
private Bitmap _screenBitmap = new Bitmap(256, 192, PixelFormat.Format32bppArgb);
private NAudioPlayer _audioPlayer;
private Task _emulatorTask;
@@ -296,6 +297,16 @@ namespace Desktop
_debugger.Show();
}
+ private void vramViewerToolStripMenuItem_Click(object sender, EventArgs e)
+ {
+ if (_vramViewer == null || _vramViewer.IsDisposed)
+ {
+ // Pass the VDP directly to the viewer!
+ _vramViewer = new VramViewerForm(_machine.VideoProcessor);
+ }
+ _vramViewer.Show();
+ }
+
private void includedToolStripMenuItem_Click(object sender, EventArgs e)
{
diff --git a/Desktop/ROMS/Golden Axe Warrior.sav b/Desktop/ROMS/Golden Axe Warrior.sav
new file mode 100644
index 0000000..724dc97
Binary files /dev/null and b/Desktop/ROMS/Golden Axe Warrior.sav differ
diff --git a/Desktop/VramViewerForm.cs b/Desktop/VramViewerForm.cs
new file mode 100644
index 0000000..22c37e1
--- /dev/null
+++ b/Desktop/VramViewerForm.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Core.Video;
+
+namespace Desktop
+{
+ public class VramViewerForm : Form
+ {
+ private readonly SmsVdp _vdp;
+ private readonly System.Windows.Forms.Timer _refreshTimer;
+ private readonly Bitmap _vramBitmap;
+ private readonly int[] _pixelData;
+
+ public VramViewerForm(SmsVdp vdp)
+ {
+ _vdp = vdp;
+ this.Text = "VRAM Viewer (512 Tiles)";
+
+ // 256x128 native resolution, scaled up by 2 for visibility!
+ this.ClientSize = new Size(512, 256);
+ this.DoubleBuffered = true;
+ this.FormBorderStyle = FormBorderStyle.FixedToolWindow;
+
+ _vramBitmap = new Bitmap(256, 128, PixelFormat.Format32bppArgb);
+ _pixelData = new int[256 * 128];
+
+ // Update the window 10 times a second so it doesn't drain CPU power
+ _refreshTimer = new System.Windows.Forms.Timer();
+ _refreshTimer.Interval = 100;
+ _refreshTimer.Tick += (s, e) => UpdateVramImage();
+ _refreshTimer.Start();
+ }
+
+ private void UpdateVramImage()
+ {
+ if (_vdp == null) return;
+
+ // Loop through all 512 tiles in VRAM
+ for (int tile = 0; tile < 512; tile++)
+ {
+ // Calculate grid position (32 columns, 16 rows)
+ int tileX = (tile % 32) * 8;
+ int tileY = (tile / 32) * 8;
+ int tileAddress = tile * 32;
+
+ // Decode the 8x8 pixels
+ for (int y = 0; y < 8; y++)
+ {
+ byte bp0 = _vdp.VRAM[tileAddress + (y * 4) + 0];
+ byte bp1 = _vdp.VRAM[tileAddress + (y * 4) + 1];
+ byte bp2 = _vdp.VRAM[tileAddress + (y * 4) + 2];
+ byte bp3 = _vdp.VRAM[tileAddress + (y * 4) + 3];
+
+ for (int x = 0; x < 8; x++)
+ {
+ int shift = 7 - x;
+ int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
+ (((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
+
+ // Use the Sprite Palette (Offset 16) so characters and enemies are colored correctly
+ byte smsColor = _vdp.CRAM[16 + colorIndex];
+ int r = (smsColor & 0x03) * 85;
+ int g = ((smsColor >> 2) & 0x03) * 85;
+ int b = ((smsColor >> 4) & 0x03) * 85;
+
+ // Background color (Index 0) is technically transparent for sprites, so we render it as magenta
+ if (colorIndex == 0) { r = 255; g = 0; b = 255; }
+
+ _pixelData[((tileY + y) * 256) + (tileX + x)] = (255 << 24) | (r << 16) | (g << 8) | b;
+ }
+ }
+ }
+
+ // Blast the array into the Bitmap
+ var data = _vramBitmap.LockBits(new Rectangle(0, 0, 256, 128), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(_pixelData, 0, data.Scan0, _pixelData.Length);
+ _vramBitmap.UnlockBits(data);
+
+ this.Invalidate();
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ base.OnPaint(e);
+ // Draw it chunky and pixel-perfect
+ e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
+ e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
+ e.Graphics.DrawImage(_vramBitmap, new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height));
+ }
+
+ protected override void OnFormClosing(FormClosingEventArgs e)
+ {
+ _refreshTimer.Stop();
+ base.OnFormClosing(e);
+ }
+ }
+}
\ No newline at end of file