Added acanline rendering. Fixed interrupts. Added XInput support
This commit is contained in:
@@ -9,8 +9,9 @@ namespace Core.Io
|
|||||||
// public Psg AudioProcessor { get; set; }
|
// public Psg AudioProcessor { get; set; }
|
||||||
|
|
||||||
// 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 Joypad1State { get; set; } = 0xFF;
|
public byte Joypad1Keyboard = 0xFF;
|
||||||
public byte Joypad2State { get; set; } = 0xFF;
|
public byte Joypad1Gamepad = 0xFF;
|
||||||
|
public byte Joypad2State = 0xFF;
|
||||||
|
|
||||||
public byte ReadPort(ushort port)
|
public byte ReadPort(ushort port)
|
||||||
{
|
{
|
||||||
@@ -30,16 +31,9 @@ namespace Core.Io
|
|||||||
if ((lowerPort & 0x01) == 0) return VideoProcessor.ReadDataPort();
|
if ((lowerPort & 0x01) == 0) return VideoProcessor.ReadDataPort();
|
||||||
else return VideoProcessor.ReadControlPort();
|
else return VideoProcessor.ReadControlPort();
|
||||||
}
|
}
|
||||||
if (lowerPort == 0xDC)
|
if (lowerPort == 0xDC) return (byte)(Joypad1Keyboard & Joypad1Gamepad);
|
||||||
{
|
|
||||||
// Port 0xDC: Player 1 (Up, Down, Left, Right, 1, 2) + Player 2 (Up, Down)
|
if (lowerPort == 0xDD) return Joypad2State;
|
||||||
return Joypad1State;
|
|
||||||
}
|
|
||||||
if (lowerPort == 0xDD)
|
|
||||||
{
|
|
||||||
// Port 0xDD: Player 2 (Left, Right, 1, 2) + Reset Button
|
|
||||||
return Joypad2State;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0xFF; // Floating bus
|
return 0xFF; // Floating bus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,74 +38,33 @@ namespace Core
|
|||||||
Cpu.Reset();
|
Cpu.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int StepMachine()
|
|
||||||
{
|
|
||||||
// 1. Tick the CPU
|
|
||||||
int tStates = Cpu.Step();
|
|
||||||
|
|
||||||
// 2. Tell the VDP how much time just passed
|
|
||||||
VideoProcessor.Update(tStates);
|
|
||||||
|
|
||||||
// 3. Trigger interrupts if the VDP hit scanline 192
|
|
||||||
if (VideoProcessor.InterruptPending)
|
|
||||||
{
|
|
||||||
tStates += Cpu.RequestInterrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tStates;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RunFrame()
|
public void RunFrame()
|
||||||
{
|
{
|
||||||
long currentFrameTStates = 0;
|
int tStatesThisFrame = 0;
|
||||||
|
while (tStatesThisFrame < 59736) // Standard NTSC frame time
|
||||||
while (currentFrameTStates < TStatesPerFrame)
|
|
||||||
{
|
{
|
||||||
currentFrameTStates += StepMachine();
|
// 1. Run one CPU instruction
|
||||||
string filePath = "captured_data.txt";
|
int cycles = Cpu.Step();
|
||||||
|
tStatesThisFrame += cycles;
|
||||||
|
|
||||||
// Mock data to loop through
|
// 2. Tell the VDP to catch up
|
||||||
//List<ushort> sensorReadings = new List<ushort> { Cpu.PC, Cpu.AF.Word, Cpu.BC.Word, Cpu.DE.Word, Cpu.HL.Word, Cpu.SP};
|
VideoProcessor.Update(cycles);
|
||||||
//List<string> type = new List<string> {"PC: 0x", "AF: 0x", "BC: 0x", "DE: 0x", "HL: 0x", "SP: 0x" };
|
|
||||||
|
|
||||||
//try
|
// 3. Check if the VDP is begging for attention!
|
||||||
//{
|
if (VideoProcessor.InterruptPending && Cpu.IFF1)
|
||||||
// // 2. Initialize StreamWriter within a 'using' block
|
{
|
||||||
// // The 'true' parameter means "append" to the file. Use 'false' to overwrite.
|
int intCycles = Cpu.RequestInterrupt();
|
||||||
// using (StreamWriter writer = new StreamWriter(filePath, append: true))
|
tStatesThisFrame += intCycles;
|
||||||
// {
|
VideoProcessor.Update(intCycles); // Keep VDP perfectly in sync
|
||||||
// foreach (int reading in sensorReadings)
|
}
|
||||||
// {
|
|
||||||
// string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
||||||
|
|
||||||
// // 3. Construct your string and write it
|
// 4. THE RESTORED BREAKPOINT TRAP
|
||||||
// foreach (string _type in type)
|
|
||||||
// {
|
|
||||||
// string line = $"{timestamp} | {_type} {reading}";
|
|
||||||
// writer.WriteLine(line);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// // Optional: Console feedback
|
|
||||||
// //Console.WriteLine($"Logged: {line}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // File is automatically closed and saved here
|
|
||||||
|
|
||||||
// //Console.WriteLine("Data capture complete.");
|
|
||||||
//}
|
|
||||||
//catch (IOException e)
|
|
||||||
//{
|
|
||||||
// Console.WriteLine($"An error occurred: {e.Message}");
|
|
||||||
//}
|
|
||||||
|
|
||||||
// THE TRIPWIRE: Check the breakpoint after EVERY single instruction!
|
|
||||||
if (Breakpoint.HasValue && Cpu.PC == Breakpoint.Value)
|
if (Breakpoint.HasValue && Cpu.PC == Breakpoint.Value)
|
||||||
{
|
{
|
||||||
break; // Abort the frame loop immediately!
|
break; // Instantly abort the frame so the debugger can take over!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,37 +113,32 @@ namespace Core.Video
|
|||||||
if (_tStateCounter >= 228)
|
if (_tStateCounter >= 228)
|
||||||
{
|
{
|
||||||
_tStateCounter -= 228;
|
_tStateCounter -= 228;
|
||||||
//// --- LINE INTERRUPT LOGIC ---
|
|
||||||
//if (_currentScanline <= 192)
|
|
||||||
//{
|
|
||||||
// _lineCounter--;
|
|
||||||
// if (_lineCounter < 0)
|
|
||||||
// {
|
|
||||||
// _lineCounter = Registers[10]; // Reload counter
|
|
||||||
// _statusRegister |= 0x40; // Set Line Interrupt Flag (Bit 6)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// _lineCounter = Registers[10]; // Reload outside active display
|
|
||||||
//}
|
|
||||||
_currentScanline++;
|
|
||||||
|
|
||||||
// Line 192 is the exact moment the screen finishes drawing!
|
// --- MISSING LINE INTERRUPT COUNTDOWN ---
|
||||||
if (_currentScanline == 192)
|
if (_currentScanline <= 192)
|
||||||
{
|
{
|
||||||
_statusRegister |= 0x80; // Set Bit 7 (VBlank Flag) to 1
|
_lineCounter--;
|
||||||
|
if (_lineCounter < 0)
|
||||||
if ((Registers[1] & 0x40) != 0)
|
|
||||||
{
|
{
|
||||||
RenderBackground();
|
_lineCounter = Registers[10]; // Reload counter
|
||||||
RenderSprites();
|
_statusRegister |= 0x40; // Set Line Interrupt Flag (Bit 6)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Screen is off! Fill it with black (or the background color)
|
_lineCounter = Registers[10]; // Reload outside active display
|
||||||
Array.Fill(FrameBuffer, unchecked((int)0xFF000000));
|
|
||||||
}
|
}
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
_currentScanline++;
|
||||||
|
|
||||||
|
if (_currentScanline < 192)
|
||||||
|
{
|
||||||
|
RenderScanline(_currentScanline);
|
||||||
|
}
|
||||||
|
else if (_currentScanline == 192)
|
||||||
|
{
|
||||||
|
_statusRegister |= 0x80; // Set VBlank Flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of the NTSC frame (262 lines)
|
// End of the NTSC frame (262 lines)
|
||||||
@@ -154,22 +149,45 @@ namespace Core.Video
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderBackground()
|
private void RenderScanline(int screenY)
|
||||||
{
|
{
|
||||||
|
// If the display is disabled, fill the line with black and exit
|
||||||
|
if ((Registers[1] & 0x40) == 0)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < 256; x++) FrameBuffer[(screenY * 256) + x] = unchecked((int)0xFF000000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 1. RENDER BACKGROUND LINE ---
|
||||||
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
|
ushort nameTableBase = (ushort)((Registers[2] & 0x0E) << 10);
|
||||||
byte scrollX = Registers[8];
|
byte scrollX = Registers[8];
|
||||||
byte scrollY = Registers[9];
|
byte scrollY = Registers[9];
|
||||||
|
|
||||||
bool lockRowScroll = (Registers[0] & 0x80) != 0;
|
// THE FIX: The bits are now in the correct order!
|
||||||
bool lockColScroll = (Registers[0] & 0x40) != 0;
|
bool lockColScroll = (Registers[0] & 0x80) != 0; // Bit 7: Locks right 8 columns (Fixes R-Type!)
|
||||||
|
bool lockRowScroll = (Registers[0] & 0x40) != 0; // Bit 6: Locks top 2 rows (Fixes Bart!)
|
||||||
|
bool maskLeftCol = (Registers[0] & 0x20) != 0; // Bit 5: Hides leftmost column (Fixes Sonic 2!)
|
||||||
|
|
||||||
// Clear the priority mask for the new frame!
|
|
||||||
Array.Clear(_priorityBuffer, 0, _priorityBuffer.Length);
|
|
||||||
|
|
||||||
for (int screenY = 0; screenY < 192; screenY++)
|
|
||||||
{
|
|
||||||
for (int screenX = 0; screenX < 256; screenX++)
|
for (int screenX = 0; screenX < 256; screenX++)
|
||||||
{
|
{
|
||||||
|
// --- LEFT COLUMN MASKING (OVERSCAN CURTAIN) ---
|
||||||
|
if (maskLeftCol && screenX < 8)
|
||||||
|
{
|
||||||
|
// Draw the physical backdrop color (from Sprite Palette + Reg 7 index)
|
||||||
|
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;
|
||||||
|
FrameBuffer[bgAddress] = (255 << 24) | (bgR << 16) | (bgG << 8) | bgB;
|
||||||
|
|
||||||
|
// Flag it as priority so sprites also hide behind the curtain!
|
||||||
|
_priorityBuffer[bgAddress] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Vertical Scrolling (R-Type HUD protection)
|
||||||
int effectiveScrollY = scrollY;
|
int effectiveScrollY = scrollY;
|
||||||
if (lockColScroll && screenX >= 192) effectiveScrollY = 0;
|
if (lockColScroll && screenX >= 192) effectiveScrollY = 0;
|
||||||
|
|
||||||
@@ -177,6 +195,7 @@ namespace Core.Video
|
|||||||
int row = vdpY / 8;
|
int row = vdpY / 8;
|
||||||
int tileY = vdpY % 8;
|
int tileY = vdpY % 8;
|
||||||
|
|
||||||
|
// Apply Horizontal Scrolling (Bart's sky protection)
|
||||||
int effectiveScrollX = scrollX;
|
int effectiveScrollX = scrollX;
|
||||||
if (lockRowScroll && screenY < 16) effectiveScrollX = 0;
|
if (lockRowScroll && screenY < 16) effectiveScrollX = 0;
|
||||||
|
|
||||||
@@ -184,20 +203,15 @@ namespace Core.Video
|
|||||||
int col = vdpX / 8;
|
int col = vdpX / 8;
|
||||||
int tileX = vdpX % 8;
|
int tileX = vdpX % 8;
|
||||||
|
|
||||||
// 1. Read the 16-bit Tile instruction
|
|
||||||
ushort nameTableAddr = (ushort)(nameTableBase + (row * 64) + (col * 2));
|
ushort nameTableAddr = (ushort)(nameTableBase + (row * 64) + (col * 2));
|
||||||
byte lowByte = VRAM[nameTableAddr];
|
ushort tileData = (ushort)((VRAM[nameTableAddr + 1] << 8) | VRAM[nameTableAddr]);
|
||||||
byte highByte = VRAM[nameTableAddr + 1];
|
|
||||||
ushort tileData = (ushort)((highByte << 8) | lowByte);
|
|
||||||
|
|
||||||
// 2. EXTRACT ALL THE HARDWARE BITS!
|
|
||||||
int tileIndex = tileData & 0x01FF;
|
int tileIndex = tileData & 0x01FF;
|
||||||
bool flipH = (tileData & 0x0200) != 0; // Bit 9
|
bool flipH = (tileData & 0x0200) != 0;
|
||||||
bool flipV = (tileData & 0x0400) != 0; // Bit 10
|
bool flipV = (tileData & 0x0400) != 0;
|
||||||
bool useSpritePalette = (tileData & 0x0800) != 0; // Bit 11
|
bool useSpritePalette = (tileData & 0x0800) != 0;
|
||||||
bool priority = (tileData & 0x1000) != 0; // Bit 12
|
bool priority = (tileData & 0x1000) != 0;
|
||||||
|
|
||||||
// 3. Apply Vertical Flip (Read from the bottom of the tile instead of the top)
|
|
||||||
int readY = flipV ? (7 - tileY) : tileY;
|
int readY = flipV ? (7 - tileY) : tileY;
|
||||||
ushort tileAddress = (ushort)(tileIndex * 32);
|
ushort tileAddress = (ushort)(tileIndex * 32);
|
||||||
|
|
||||||
@@ -206,12 +220,9 @@ namespace Core.Video
|
|||||||
byte bp2 = VRAM[tileAddress + (readY * 4) + 2];
|
byte bp2 = VRAM[tileAddress + (readY * 4) + 2];
|
||||||
byte bp3 = VRAM[tileAddress + (readY * 4) + 3];
|
byte bp3 = VRAM[tileAddress + (readY * 4) + 3];
|
||||||
|
|
||||||
// 4. Apply Horizontal Flip (Shift from right-to-left instead of left-to-right)
|
|
||||||
int readX = flipH ? tileX : (7 - tileX);
|
int readX = flipH ? tileX : (7 - tileX);
|
||||||
int colorIndex = ((bp0 >> readX) & 1) |
|
int colorIndex = ((bp0 >> readX) & 1) | (((bp1 >> readX) & 1) << 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];
|
byte smsColor = CRAM[paletteOffset + colorIndex];
|
||||||
@@ -221,62 +232,47 @@ namespace Core.Video
|
|||||||
int b = ((smsColor >> 4) & 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
|
||||||
FrameBuffer[screenAddress] = (255 << 24) | (r << 16) | (g << 8) | b;
|
FrameBuffer[screenAddress] = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||||
|
_priorityBuffer[screenAddress] = (priority && colorIndex != 0);
|
||||||
// 5. FLAG THE PRIORITY PIXEL!
|
|
||||||
// If this tile has priority AND the pixel isn't transparent (color 0),
|
|
||||||
// tell the sprite renderer not to draw over it!
|
|
||||||
if (priority && colorIndex != 0)
|
|
||||||
{
|
|
||||||
_priorityBuffer[screenAddress] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderSprites()
|
// --- 2. RENDER SPRITE LINE ---
|
||||||
{
|
|
||||||
// 1. Find the Sprite Attribute Table (SAT)
|
|
||||||
// Register 5 contains the base address bits (Mask 0x7E, shifted by 7)
|
|
||||||
ushort satBaseAddress = (ushort)((Registers[5] & 0x7E) << 7);
|
ushort satBaseAddress = (ushort)((Registers[5] & 0x7E) << 7);
|
||||||
|
|
||||||
// 2. Register 6 determines where the Sprite Tile graphics are stored in VRAM
|
|
||||||
ushort spritePatternBase = (ushort)((Registers[6] & 0x04) << 11);
|
ushort spritePatternBase = (ushort)((Registers[6] & 0x04) << 11);
|
||||||
|
|
||||||
// 3. Register 1 determines sprite size (8x8 or 8x16)
|
|
||||||
bool is8x16 = (Registers[1] & 0x02) != 0;
|
bool is8x16 = (Registers[1] & 0x02) != 0;
|
||||||
bool shiftSpritesLeft = (Registers[0] & 0x08) != 0;
|
bool shiftSpritesLeft = (Registers[0] & 0x08) != 0;
|
||||||
|
int spriteHeight = is8x16 ? 16 : 8;
|
||||||
|
|
||||||
// The SMS can draw a maximum of 64 sprites
|
// Step A: Find the visible sprites for THIS specific line
|
||||||
|
var visibleSprites = new System.Collections.Generic.List<int>();
|
||||||
for (int i = 0; i < 64; i++)
|
for (int i = 0; i < 64; i++)
|
||||||
{
|
{
|
||||||
// Read the Y coordinate from the first part of the SAT
|
|
||||||
byte y = VRAM[satBaseAddress + i];
|
byte y = VRAM[satBaseAddress + i];
|
||||||
|
if (y == 208) break; // End of Sprite List
|
||||||
|
|
||||||
// HARDWARE QUIRK: If Y == 208 in standard 192-line mode,
|
int spriteY = y + 1; // Physical hardware 1-pixel shift
|
||||||
// it acts as a "Stop Drawing" marker. The VDP aborts the rest of the list!
|
if (screenY >= spriteY && screenY < spriteY + spriteHeight)
|
||||||
if (y == 208) break;
|
{
|
||||||
|
visibleSprites.Add(i);
|
||||||
|
// HARDWARE QUIRK: VDP stops drawing after 8 sprites on a single line!
|
||||||
|
if (visibleSprites.Count == 8) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The X coordinates and Tile Indices are interleaved starting at SAT + 0x80
|
// Step B: Draw them backward so Sprite 0 (highest priority) draws LAST and stays on top
|
||||||
|
for (int v = visibleSprites.Count - 1; v >= 0; v--)
|
||||||
|
{
|
||||||
|
int i = visibleSprites[v];
|
||||||
|
byte y = VRAM[satBaseAddress + i];
|
||||||
byte x = VRAM[satBaseAddress + 0x80 + (i * 2)];
|
byte x = VRAM[satBaseAddress + 0x80 + (i * 2)];
|
||||||
byte tileIndex = VRAM[satBaseAddress + 0x80 + (i * 2) + 1];
|
byte tileIndex = VRAM[satBaseAddress + 0x80 + (i * 2) + 1];
|
||||||
|
|
||||||
// If sprites are 8x16, the Tile Index always drops the lowest bit (forces even alignment)
|
|
||||||
if (is8x16) tileIndex = (byte)(tileIndex & 0xFE);
|
if (is8x16) tileIndex = (byte)(tileIndex & 0xFE);
|
||||||
|
|
||||||
// Calculate the pixel height for the drawing loop
|
// Calculate which row of the sprite we are physically on
|
||||||
int spriteHeight = is8x16 ? 16 : 8;
|
int py = screenY - (y + 1);
|
||||||
|
|
||||||
// Draw the 8x8 (or 8x16) sprite block
|
|
||||||
for (int py = 0; py < spriteHeight; py++)
|
|
||||||
{
|
|
||||||
// Master System Sprites are physically shifted down 1 pixel on the CRT
|
|
||||||
int screenY = y + 1 + py;
|
|
||||||
|
|
||||||
// If this row of the sprite is off the bottom of the screen, skip it
|
|
||||||
if (screenY >= 192) continue;
|
|
||||||
|
|
||||||
// Calculate where the 4 bitplanes are for this specific row of the sprite
|
|
||||||
ushort tileAddress = (ushort)(spritePatternBase + (tileIndex * 32) + (py * 4));
|
ushort tileAddress = (ushort)(spritePatternBase + (tileIndex * 32) + (py * 4));
|
||||||
|
|
||||||
byte bp0 = VRAM[tileAddress + 0];
|
byte bp0 = VRAM[tileAddress + 0];
|
||||||
@@ -287,42 +283,25 @@ namespace Core.Video
|
|||||||
for (int px = 0; px < 8; px++)
|
for (int px = 0; px < 8; px++)
|
||||||
{
|
{
|
||||||
int screenX = x + px;
|
int screenX = x + px;
|
||||||
// THE FIX: Shift the pixel left if commanded!
|
|
||||||
if (shiftSpritesLeft) screenX -= 8;
|
if (shiftSpritesLeft) screenX -= 8;
|
||||||
|
|
||||||
// If it shifted off the left edge, skip it!
|
|
||||||
if (screenX < 0 || screenX >= 256) continue;
|
if (screenX < 0 || screenX >= 256) continue;
|
||||||
|
|
||||||
// If this pixel is off the right side of the screen, skip it
|
|
||||||
if (screenX >= 256) continue;
|
|
||||||
|
|
||||||
int shift = 7 - px;
|
|
||||||
int colorIndex = ((bp0 >> shift) & 1) |
|
|
||||||
(((bp1 >> shift) & 1) << 1) |
|
|
||||||
(((bp2 >> shift) & 1) << 2) |
|
|
||||||
(((bp3 >> shift) & 1) << 3);
|
|
||||||
|
|
||||||
// HARDWARE TRANSPARENCY:
|
|
||||||
// If the color index is 0, DO NOT draw it! Let the background show through.
|
|
||||||
if (colorIndex == 0) continue;
|
|
||||||
|
|
||||||
// If the background tile at this exact pixel claimed priority, hide the sprite!
|
|
||||||
if (_priorityBuffer[(screenY * 256) + screenX]) continue;
|
if (_priorityBuffer[(screenY * 256) + screenX]) continue;
|
||||||
|
|
||||||
// Sprites ALWAYS use the second half of CRAM (Palette 1: Indices 16-31)
|
int shift = 7 - px;
|
||||||
byte smsColor = CRAM[16 + colorIndex];
|
int colorIndex = ((bp0 >> shift) & 1) | (((bp1 >> shift) & 1) << 1) |
|
||||||
|
(((bp2 >> shift) & 1) << 2) | (((bp3 >> shift) & 1) << 3);
|
||||||
|
|
||||||
|
if (colorIndex == 0) continue;
|
||||||
|
|
||||||
|
byte smsColor = CRAM[16 + colorIndex];
|
||||||
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;
|
||||||
|
|
||||||
// Because we only draw non-zero pixels, this safely overwrites the
|
|
||||||
// background FrameBuffer exactly where the sprite stands!
|
|
||||||
FrameBuffer[(screenY * 256) + screenX] = (255 << 24) | (r << 16) | (g << 8) | b;
|
FrameBuffer[(screenY * 256) + screenX] = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
27
Desktop/Controller.cs
Normal file
27
Desktop/Controller.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
public static class XInput
|
||||||
|
{
|
||||||
|
// Reach into the Windows OS API
|
||||||
|
[DllImport("xinput1_4.dll")]
|
||||||
|
public static extern int XInputGetState(int dwUserIndex, out XINPUT_STATE pState);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XINPUT_STATE
|
||||||
|
{
|
||||||
|
public uint dwPacketNumber;
|
||||||
|
public XINPUT_GAMEPAD Gamepad;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct XINPUT_GAMEPAD
|
||||||
|
{
|
||||||
|
public ushort wButtons;
|
||||||
|
public byte bLeftTrigger;
|
||||||
|
public byte bRightTrigger;
|
||||||
|
public short sThumbLX;
|
||||||
|
public short sThumbLY;
|
||||||
|
public short sThumbRX;
|
||||||
|
public short sThumbRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ namespace Desktop
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Ask the main form to step the WHOLE machine, not just the Z80!
|
// Ask the main form to step the WHOLE machine, not just the Z80!
|
||||||
_mainForm.StepEmulator();
|
//_mainForm.StepEmulator();
|
||||||
UpdateDisplay();
|
UpdateDisplay();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -100,7 +100,23 @@ namespace Desktop
|
|||||||
// Mark exactly when the emulator starts thinking
|
// Mark exactly when the emulator starts thinking
|
||||||
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
|
double frameStartTime = _stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
|
||||||
// 1. Do the heavy lifting (Z80 and VDP)
|
// --- 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();
|
_machine.RunFrame();
|
||||||
|
|
||||||
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
|
// 2. FIRE AND FORGET! Tell Windows to draw, but DO NOT WAIT for it to finish!
|
||||||
@@ -161,12 +177,6 @@ namespace Desktop
|
|||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void StepEmulator()
|
|
||||||
{
|
|
||||||
_machine.StepMachine();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void LoadRomAndStart(string filePath)
|
private async void LoadRomAndStart(string filePath)
|
||||||
{
|
{
|
||||||
StopEmulator();
|
StopEmulator();
|
||||||
@@ -282,13 +292,11 @@ namespace Desktop
|
|||||||
|
|
||||||
if (isPressed)
|
if (isPressed)
|
||||||
{
|
{
|
||||||
// Active-Low: Clear the specific bit to 0 using a bitwise AND with a NOT mask
|
_machine.IoBus.Joypad1Keyboard &= (byte)~bitMask; // Target Keyboard!
|
||||||
_machine.IoBus.Joypad1State &= (byte)~bitMask;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Active-Low: Reset the specific bit to 1 using a bitwise OR mask
|
_machine.IoBus.Joypad1Keyboard |= bitMask; // Target Keyboard!
|
||||||
_machine.IoBus.Joypad1State |= bitMask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user