BizHawk/EMU7800/Core/Maria.cs

1152 lines
40 KiB
C#

/*
* Maria.cs
*
* The Maria display device.
*
* Derived from much of Dan Boris' work with 7800 emulation
* within the MESS emulator.
*
* Thanks to Matthias Luedtke <matthias@atari8bit.de> for correcting
* the BuildLineRAM320B() method to correspond closer to real hardware.
* (Matthias credited an insightful response by Eckhard Stolberg on a forum on
* Atari Age circa June 2005.)
*
* Copyright © 2004-2012 Mike Murphy
*
*/
using System;
namespace EMU7800.Core
{
public sealed class Maria : IDevice
{
#region Constants
const int
INPTCTRL= 0x01, // Write: input port control (VBLANK in TIA)
INPT0 = 0x08, // Read pot port: D7
INPT1 = 0x09, // Read pot port: D7
INPT2 = 0x0a, // Read pot port: D7
INPT3 = 0x0b, // Read pot port: D7
INPT4 = 0x0c, // Read P1 joystick trigger: D7
INPT5 = 0x0d, // Read P2 joystick trigger: D7
AUDC0 = 0x15, // Write: audio control 0 (D3-0)
AUDC1 = 0x16, // Write: audio control 1 (D4-0)
AUDF0 = 0x17, // Write: audio frequency 0 (D4-0)
AUDF1 = 0x18, // Write: audio frequency 1 (D3-0)
AUDV0 = 0x19, // Write: audio volume 0 (D3-0)
AUDV1 = 0x1a, // Write: audio volume 1 (D3-0)
BACKGRND= 0x20, // Background color
P0C1 = 0x21, // Palette 0 - color 1
P0C2 = 0x22, // Palette 0 - color 2
P0C3 = 0x23, // Palette 0 - color 3
WSYNC = 0x24, // Wait for sync
P1C1 = 0x25, // Palette 1 - color 1
P1C2 = 0x26, // Palette 1 - color 2
P1C3 = 0x27, // Palette 1 - color 3
MSTAT = 0x28, // Maria status
P2C1 = 0x29, // Palette 2 - color 1
P2C2 = 0x2a, // Palette 2 - color 2
P2C3 = 0x2b, // Palette 2 - color 3
DPPH = 0x2c, // Display list list point high
P3C1 = 0x2d, // Palette 3 - color 1
P3C2 = 0x2e, // Palette 3 - color 2
P3C3 = 0x2f, // Palette 3 - color 3
DPPL = 0x30, // Display list list point low
P4C1 = 0x31, // Palette 4 - color 1
P4C2 = 0x32, // Palette 4 - color 2
P4C3 = 0x33, // Palette 4 - color 3
CHARBASE= 0x34, // Character base address
P5C1 = 0x35, // Palette 5 - color 1
P5C2 = 0x36, // Palette 5 - color 2
P5C3 = 0x37, // Palette 5 - color 3
OFFSET = 0x38, // Future expansion (store zero here)
P6C1 = 0x39, // Palette 6 - color 1
P6C2 = 0x3a, // Palette 6 - color 2
P6C3 = 0x3b, // Palette 6 - color 3
CTRL = 0x3c, // Maria control register
P7C1 = 0x3d, // Palette 7 - color 1
P7C2 = 0x3e, // Palette 7 - color 2
P7C3 = 0x3f; // Palette 7 - color 3
const int CPU_TICKS_PER_AUDIO_SAMPLE = 57;
#endregion
#region Fields
readonly byte[] LineRAM = new byte[0x200];
readonly byte[] Registers = new byte[0x40];
readonly Machine7800 M;
readonly TIASound TIASound;
ulong _startOfFrameCpuClock;
int Scanline { get { return (int)(M.CPU.Clock - _startOfFrameCpuClock) / 114; } }
int HPos { get { return (int)(M.CPU.Clock - _startOfFrameCpuClock) % 114; } }
int FirstVisibleScanline, LastVisibleScanline;
int _dmaClocks;
bool _isPal;
// For lightgun emulation.
// Transient state, serialization unnecessary.
ulong _lightgunFirstSampleCpuClock;
int _lightgunFrameSamples, _lightgunSampledScanline, _lightgunSampledVisibleHpos;
bool WM;
ushort DLL;
ushort DL;
int Offset;
int Holey;
int Width;
byte HPOS;
int PaletteNo;
bool INDMode;
bool CtrlLock;
// MARIA CNTL
bool DMAEnabled;
bool ColorKill;
bool CWidth;
bool BCntl;
bool Kangaroo;
byte RM;
#endregion
#region Public Members
public void Reset()
{
CtrlLock = false;
DMAEnabled = false;
ColorKill = false;
CWidth = false;
BCntl = false;
Kangaroo = false;
RM = 0;
TIASound.Reset();
Log("{0} reset", this);
}
public byte this[ushort addr]
{
get { return peek(addr); }
set { poke(addr, value); }
}
public override string ToString()
{
return GetType().Name;
}
public void StartFrame()
{
_startOfFrameCpuClock = M.CPU.Clock + (ulong)(M.CPU.RunClocks / M.CPU.RunClocksMultiple);
_lightgunFirstSampleCpuClock = 0;
AssertDebug(M.CPU.RunClocks <= 0 && (M.CPU.RunClocks % M.CPU.RunClocksMultiple) == 0);
AssertDebug((_startOfFrameCpuClock % (114 * (ulong)M.FrameBuffer.Scanlines)) == 0);
TIASound.StartFrame();
}
public int DoDMAProcessing()
{
OutputLineRAM();
var sl = Scanline;
if (!DMAEnabled || sl < FirstVisibleScanline || sl >= LastVisibleScanline)
return 0;
_dmaClocks = 0;
if (DMAEnabled && sl == FirstVisibleScanline)
{
// DMA TIMING: End of VBLANK: DMA Startup + long shutdown
_dmaClocks += 15;
DLL = WORD(Registers[DPPL], Registers[DPPH]);
ConsumeNextDLLEntry();
}
// DMA TIMING: DMA Startup, 5-9 cycles
_dmaClocks += 5;
BuildLineRAM();
if (--Offset < 0)
{
ConsumeNextDLLEntry();
// DMA TIMING: DMA Shutdown: Last line of zone, 10-13 cycles
_dmaClocks += 10;
}
else
{
// DMA TIMING: DMA Shutdown: Other line of zone, 4-7 cycles
_dmaClocks += 4;
}
return _dmaClocks;
}
public void EndFrame()
{
TIASound.EndFrame();
}
#endregion
#region Constructors
private Maria()
{
}
public Maria(Machine7800 m, int scanlines)
{
if (m == null)
throw new ArgumentNullException("m");
M = m;
InitializeVisibleScanlineValues(scanlines);
TIASound = new TIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE);
}
#endregion
#region Scanline Builders
void BuildLineRAM()
{
var dl = DL;
// Iterate through Display List (DL)
while (true)
{
var modeByte = DmaRead(dl + 1);
if ((modeByte & 0x5f) == 0)
break;
INDMode = false;
ushort graphaddr;
if ((modeByte & 0x1f) == 0)
{
// Extended DL header
var dl0 = DmaRead(dl++); // low address
var dl1 = DmaRead(dl++); // mode
var dl2 = DmaRead(dl++); // high address
var dl3 = DmaRead(dl++); // palette(7-5)/width(4-0)
var dl4 = DmaRead(dl++); // horizontal position
graphaddr = WORD(dl0, dl2);
WM = (dl1 & 0x80) != 0;
INDMode = (dl1 & 0x20) != 0;
PaletteNo = (dl3 & 0xe0) >> 3;
Width = (~dl3 & 0x1f) + 1;
HPOS = dl4;
// DMA TIMING: DL 5 byte header
_dmaClocks += 10;
}
else
{
// Normal DL header
var dl0 = DmaRead(dl++); // low address
var dl1 = DmaRead(dl++); // palette(7-5)/width(4-0)
var dl2 = DmaRead(dl++); // high address
var dl3 = DmaRead(dl++); // horizontal position
graphaddr = WORD(dl0, dl2);
PaletteNo = (dl1 & 0xe0) >> 3;
Width = (~dl1 & 0x1f) + 1;
HPOS = dl3;
// DMA TIMING: DL 4 byte header
_dmaClocks += 8;
}
// DMA TIMING: Graphic reads
if (RM != 1)
_dmaClocks += (Width * (INDMode ? (CWidth ? 9 : 6) : 3));
switch (RM)
{
case 0:
if (WM) BuildLineRAM160B(graphaddr); else BuildLineRAM160A(graphaddr);
break;
case 1:
continue;
case 2:
if (WM) BuildLineRAM320B(graphaddr); else BuildLineRAM320D(graphaddr);
break;
case 3:
if (WM) BuildLineRAM320C(graphaddr); else BuildLineRAM320A(graphaddr);
break;
}
}
}
void BuildLineRAM160A(ushort graphaddr)
{
var indbytes = (INDMode && CWidth) ? 2 : 1;
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
for (var i=0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
for (var j=0; j < indbytes; j++)
{
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 8;
dataaddr++;
AssertDebug(!Kangaroo);
continue;
}
int d = DmaRead(dataaddr++);
var c = (d & 0xc0) >> 6;
if (c != 0)
{
var val = (byte)(PaletteNo | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
AssertDebug(c != 0 || c == 0 && !Kangaroo);
hpos += 2;
c = (d & 0x30) >> 4;
if (c != 0)
{
var val = (byte)(PaletteNo | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
AssertDebug(c != 0 || c == 0 && !Kangaroo);
hpos += 2;
c = (d & 0x0c) >> 2;
if (c != 0)
{
var val = (byte)(PaletteNo | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
AssertDebug(c != 0 || c == 0 && !Kangaroo);
hpos += 2;
c = d & 0x03;
if (c != 0)
{
var val = (byte)(PaletteNo | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
AssertDebug(c != 0 || c == 0 && !Kangaroo);
hpos += 2;
}
}
}
void BuildLineRAM160B(ushort graphaddr)
{
var indbytes = (INDMode && CWidth) ? 2 : 1;
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
for (var i = 0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
for (var j=0; j < indbytes; j++)
{
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 4;
dataaddr++;
continue;
}
int d = DmaRead(dataaddr++);
var c = (d & 0xc0) >> 6;
if (c != 0)
{
var p = ((PaletteNo >> 2) & 0x04) | ((d & 0x0c) >> 2);
var val = (byte)((p << 2) | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
else if (Kangaroo)
{
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = 0;
}
hpos += 2;
c = (d & 0x30) >> 4;
if (c != 0)
{
var p = ((PaletteNo >> 2) & 0x04) | (d & 0x03);
var val = (byte)((p << 2) | c);
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = val;
}
else if (Kangaroo)
{
LineRAM[hpos & 0x1ff] = LineRAM[(hpos+1) & 0x1ff] = 0;
}
hpos += 2;
}
}
}
void BuildLineRAM320A(ushort graphaddr)
{
var color = (byte)(PaletteNo | 2);
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
AssertDebug(!CWidth);
for (var i = 0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 8;
dataaddr++;
continue;
}
int d = DmaRead(dataaddr++);
if ((d & 0x80) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x40) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x20) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x10) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x08) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x04) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x02) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x01) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
}
}
void BuildLineRAM320B(ushort graphaddr)
{
var indbytes = (INDMode && CWidth) ? 2 : 1;
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
for (var i = 0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
for (var j=0; j < indbytes; j++)
{
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 4;
dataaddr++;
continue;
}
int d = DmaRead(dataaddr++);
var c = ((d & 0x80) >> 6) | ((d & 0x08) >> 3);
if (c != 0)
{
if ((d & 0xc0) != 0 || Kangaroo)
LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c);
}
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
else if ((d & 0xcc) != 0)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x40) >> 5) | ((d & 0x04) >> 2);
if (c != 0)
{
if ((d & 0xc0) != 0 || Kangaroo)
LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c);
}
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
else if ((d & 0xcc) != 0)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x20) >> 4) | ((d & 0x02) >> 1);
if (c != 0)
{
if ((d & 0x30) != 0 || Kangaroo)
LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c);
}
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
else if ((d & 0x33) != 0)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x10) >> 3) | (d & 0x01);
if (c != 0)
{
if ((d & 0x30) != 0 || Kangaroo)
LineRAM[hpos & 0x1ff] = (byte)(PaletteNo | c);
}
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
else if ((d & 0x33) != 0)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
}
}
}
void BuildLineRAM320C(ushort graphaddr)
{
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
AssertDebug(!CWidth);
for (var i = 0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 4;
dataaddr++;
continue;
}
int d = DmaRead(dataaddr++);
var color = (byte)(((((d & 0x0c) >> 2) | ((PaletteNo >> 2) & 0x04)) << 2) | 2);
if ((d & 0x80) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x40) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
color = (byte)((((d & 0x03) | ((PaletteNo >> 2) & 0x04)) << 2) | 2);
if ((d & 0x20) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
if ((d & 0x10) != 0)
LineRAM[hpos & 0x1ff] = color;
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
}
}
void BuildLineRAM320D(ushort graphaddr)
{
var indbytes = (INDMode && CWidth) ? 2 : 1;
var hpos = HPOS << 1;
var dataaddr = (ushort)(graphaddr + (Offset << 8));
for (var i = 0; i < Width; i++)
{
if (INDMode)
{
dataaddr = WORD(DmaRead(graphaddr + i), Registers[CHARBASE] + Offset);
}
for (var j=0; j < indbytes; j++)
{
if (Holey == 0x02 && ((dataaddr & 0x9000) == 0x9000) || Holey == 0x01 && ((dataaddr & 0x8800) == 0x8800))
{
hpos += 8;
dataaddr++;
continue;
}
int d = DmaRead(dataaddr++);
var c = ((d & 0x80) >> 6) | (((PaletteNo >> 2) & 2) >> 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x40) >> 5) | ((PaletteNo >> 2) & 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x20) >> 4) | (((PaletteNo >> 2) & 2) >> 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x10) >> 3) | ((PaletteNo >> 2) & 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x08) >> 2) | (((PaletteNo >> 2) & 2) >> 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x04) >> 1) | ((PaletteNo >> 2) & 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = (d & 0x02) | (((PaletteNo >> 2) & 2) >> 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
c = ((d & 0x01) << 1) | ((PaletteNo >> 2) & 1);
if (c != 0)
LineRAM[hpos & 0x1ff] = (byte)((PaletteNo & 0x10) | c);
else if (Kangaroo)
LineRAM[hpos & 0x1ff] = 0;
hpos++;
}
}
}
void OutputLineRAM()
{
var fbi = ((Scanline + 1) * M.FrameBuffer.VisiblePitch) % M.FrameBuffer.VideoBufferByteLength;
for (int i = 0; i < M.FrameBuffer.VisiblePitch; i++)
{
var colorIndex = LineRAM[i];
M.FrameBuffer.VideoBuffer[fbi++] = Registers[BACKGRND + ((colorIndex & 3) == 0 ? 0 : colorIndex)];
if (fbi == M.FrameBuffer.VideoBufferByteLength)
fbi = 0;
}
for (var i = 0; i < LineRAM.Length; i++)
{
LineRAM[i] = 0;
}
}
#endregion
#region Maria Peek
byte peek(ushort addr)
{
addr &= 0x3f;
var mi = M.InputState;
switch(addr)
{
case MSTAT:
var sl = Scanline;
return (sl < FirstVisibleScanline || sl >= LastVisibleScanline)
? (byte)0x80 // VBLANK ON
: (byte)0; // VBLANK OFF
case INPT0: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? (byte)0x80 : (byte)0; // player1,button R
case INPT1: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger2) ? (byte)0x80 : (byte)0; // player1,button L
case INPT2: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? (byte)0x80 : (byte)0; // player2,button R
case INPT3: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger2) ? (byte)0x80 : (byte)0; // player2,button L
case INPT4: return SampleINPTLatched(4) ? (byte)0 : (byte)0x80; // player1,button L/R
case INPT5: return SampleINPTLatched(5) ? (byte)0 : (byte)0x80; // player2,button L/R
default:
LogDebug("Maria: Unhandled peek at ${0:x4}, PC=${1:x4}", addr, M.CPU.PC);
var retval = Registers[addr];
return retval;
}
}
#endregion
#region Maria Poke
void poke(ushort addr, byte data)
{
addr &= 0x3f;
switch (addr)
{
// INPUT PORT CONTROL
// Only the first four bits of INPTCTRL are used:
// D0: lock mode (after this bit has been set high, no more mode changes can be done until the console is turned off)
// D1: 0=disable MARIA (only RIOT RAM is available); 1=enable MARIA (also enables system RAM)
// D2: 0=enable BIOS at $8000-$FFFF (actually NTSC only uses 4KB and PAL uses 16KB); 1=disable BIOS and enable cartridge
// D3: 0=disable TIA video pull-ups (video output is MARIA instead of TIA); 1=enable TIA video pull-ups (video output is TIA instead of MARIA)
//
case INPTCTRL:
if (CtrlLock)
{
Log("Maria: INPTCTRL: LOCKED: Ignoring: ${0:x2}, PC=${1:x4}", data, M.CPU.PC);
break;
}
CtrlLock = (data & (1 << 0)) != 0;
var mariaEnable = (data & (1 << 1)) != 0;
var biosDisable = (data & (1 << 2)) != 0;
var tiaopEnable = (data & (1 << 3)) != 0;
Log("Maria: INPTCTRL: ${0:x2}, PC=${1:x4}, lockMode={2}, mariaEnable={3} biosDisable={4} tiaOutput={5}",
data, M.CPU.PC, CtrlLock, mariaEnable, biosDisable, tiaopEnable);
if (biosDisable)
{
M.SwapOutBIOS();
}
else
{
M.SwapInBIOS();
}
break;
case WSYNC:
// Request a CPU preemption to service the delay request
M.CPU.EmulatorPreemptRequest = true;
break;
case CTRL:
ColorKill = (data & 0x80) != 0;
DMAEnabled = (data & 0x60) == 0x40;
CWidth = (data & 0x10) != 0;
BCntl = (data & 0x08) != 0;
Kangaroo = (data & 0x04) != 0;
RM = (byte)(data & 0x03);
break;
case MSTAT:
break;
case CHARBASE:
case DPPH:
case DPPL:
Registers[addr] = data;
break;
case BACKGRND:
case P0C1:
case P0C2:
case P0C3:
case P1C1:
case P1C2:
case P1C3:
case P2C1:
case P2C2:
case P2C3:
case P3C1:
case P3C2:
case P3C3:
case P4C1:
case P4C2:
case P4C3:
case P5C1:
case P5C2:
case P5C3:
case P6C1:
case P6C2:
case P6C3:
case P7C1:
case P7C2:
case P7C3:
Registers[addr] = data;
break;
case AUDC0:
case AUDC1:
case AUDF0:
case AUDF1:
case AUDV0:
case AUDV1:
TIASound.Update(addr, data);
break;
case OFFSET:
Log("Maria: OFFSET: ROM wrote ${0:x2}, PC=${1:x4} (reserved for future expansion)", data, M.CPU.PC);
break;
default:
Registers[addr] = data;
LogDebug("Maria: Unhandled poke:${0:x4} w/${1:x2}, PC=${2:x4}", addr, data, M.CPU.PC);
break;
}
}
#endregion
#region Input Helpers
bool SampleINPTLatched(int inpt)
{
var mi = M.InputState;
var playerNo = inpt - 4;
switch (playerNo == 0 ? mi.LeftControllerJack : mi.RightControllerJack)
{
case Controller.Joystick:
return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger);
case Controller.ProLineJoystick:
var portbline = 4 << (playerNo << 1);
if ((M.PIA.DDRB & portbline) != 0 && (M.PIA.WrittenPortB & portbline) == 0)
return false;
return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger)
|| mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger2);
case Controller.Lightgun:
// This is one area where always running fixed at the faster CPU frequency creates emulation challenges.
// Fortunately since lightgun sampling is a dedicated activity on a frame, the job of compensating is tractable.
// Track the number of samples this frame, the time of the first sample, and capture the lightgun location.
if (_lightgunFirstSampleCpuClock == 0)
{
_lightgunFirstSampleCpuClock = M.CPU.Clock;
_lightgunFrameSamples = 0;
mi.SampleCapturedLightGunPosition(playerNo, out _lightgunSampledScanline, out _lightgunSampledVisibleHpos);
}
_lightgunFrameSamples++;
// Magic Adjustment Factor
// Seems sufficient to account for the timing impact of successive lightrun reads (i.e., 'slow' memory accesses.)
// Obtained through through trial-and-error.
const float magicAdjustmentFactor = 2.135f;
var firstLightgunSampleMariaFrameClock = (int)((_lightgunFirstSampleCpuClock - _startOfFrameCpuClock) << 2);
var mariaClocksSinceFirstLightgunSample = (int)((M.CPU.Clock - _lightgunFirstSampleCpuClock) << 2);
var adjustmentMariaClocks = (int)Math.Round(_lightgunFrameSamples * magicAdjustmentFactor);
var actualMariaFrameClock = firstLightgunSampleMariaFrameClock + mariaClocksSinceFirstLightgunSample + adjustmentMariaClocks;
var actualScanline = actualMariaFrameClock / 456;
var actualHpos = actualMariaFrameClock % 456;
// Lightgun sampling looks intended to begin at the start of the scanline.
// Compensate with another magic constant since we're always off by a fixed amount.
actualHpos -= 62;
if (actualHpos < 0)
{
actualHpos += 456;
actualScanline--;
}
var sampledScanline = _lightgunSampledScanline;
var sampledVisibleHpos = _lightgunSampledVisibleHpos;
// Seems reasonable the gun sees more than a single pixel (more like a circle or oval) and triggers sooner accordingly.
// These adjustments were obtained through trial-and-error.
if (_isPal)
{
sampledScanline -= 19;
}
else
{
sampledScanline -= 16;
sampledVisibleHpos += 4;
}
return (actualScanline >= sampledScanline) && (actualHpos >= (sampledVisibleHpos + 136 /* HBLANK clocks */));
}
return false;
}
#endregion
#region Serialization Members
public Maria(DeserializationContext input, Machine7800 m, int scanlines)
{
if (input == null)
throw new ArgumentNullException("input");
if (m == null)
throw new ArgumentNullException("m");
M = m;
InitializeVisibleScanlineValues(scanlines);
TIASound = new TIASound(input, M, CPU_TICKS_PER_AUDIO_SAMPLE);
var version = input.CheckVersion(1, 2);
LineRAM = input.ReadExpectedBytes(512);
if (version == 1)
{
// formerly persisted values, MariaPalette[8,4]
for (var i = 0; i < 32; i++)
input.ReadByte();
}
Registers = input.ReadExpectedBytes(0x40);
if (version == 1)
{
// formerly persisted value, Scanline
input.ReadInt32();
}
switch (version)
{
case 1:
WM = (input.ReadByte() != 0);
break;
case 2:
WM = input.ReadBoolean();
break;
}
DLL = input.ReadUInt16();
DL = input.ReadUInt16();
Offset = input.ReadInt32();
Holey = input.ReadInt32();
Width = input.ReadInt32();
HPOS = input.ReadByte();
PaletteNo = input.ReadByte();
INDMode = input.ReadBoolean();
if (version == 1)
{
// formerly persisted value (DLI)
input.ReadBoolean();
}
CtrlLock = input.ReadBoolean();
if (version == 1)
{
// formerly persisted value (VBLANK)
input.ReadByte();
}
DMAEnabled = input.ReadBoolean();
if (version == 1)
{
// formerly persisted value (DMAOn)
input.ReadBoolean();
}
ColorKill = input.ReadBoolean();
CWidth = input.ReadBoolean();
BCntl = input.ReadBoolean();
Kangaroo = input.ReadBoolean();
RM = input.ReadByte();
}
public void GetObjectData(SerializationContext output)
{
if (output == null)
throw new ArgumentNullException("output");
output.Write(TIASound);
output.WriteVersion(2);
output.Write(LineRAM);
output.Write(Registers);
output.Write(WM);
output.Write(DLL);
output.Write(DL);
output.Write(Offset);
output.Write(Holey);
output.Write(Width);
output.Write(HPOS);
output.Write((byte)PaletteNo);
output.Write(INDMode);
output.Write(CtrlLock);
output.Write(DMAEnabled);
output.Write(ColorKill);
output.Write(CWidth);
output.Write(BCntl);
output.Write(Kangaroo);
output.Write(RM);
}
#endregion
#region Helpers
void ConsumeNextDLLEntry()
{
// Display List List (DLL) entry
var dll0 = DmaRead(DLL++); // DLI, Holey, Offset
var dll1 = DmaRead(DLL++); // High DL address
var dll2 = DmaRead(DLL++); // Low DL address
var dli = (dll0 & 0x80) != 0;
Holey = (dll0 & 0x60) >> 5;
Offset = dll0 & 0x0f;
// Update current Display List (DL)
DL = WORD(dll2, dll1);
if (dli)
{
M.CPU.NMIInterruptRequest = true;
// DMA TIMING: One tick between DMA Shutdown and DLI
_dmaClocks += 1;
}
}
void InitializeVisibleScanlineValues(int scanlines)
{
switch (scanlines)
{
case 262: // NTSC
FirstVisibleScanline = 11;
LastVisibleScanline = FirstVisibleScanline + 242;
_isPal = false;
break;
case 312: // PAL
FirstVisibleScanline = 11;
LastVisibleScanline = FirstVisibleScanline + 292;
_isPal = true;
break;
default:
throw new ArgumentException("scanlines must be 262 or 312.");
}
}
void Log(string format, params object[] args)
{
if (M == null || M.Logger == null)
return;
M.Logger.WriteLine(format, args);
}
// convenience overload
static ushort WORD(int lsb, int msb)
{
return WORD((byte)lsb, (byte)msb);
}
static ushort WORD(byte lsb, byte msb)
{
return (ushort)(lsb | msb << 8);
}
// convenience overload
byte DmaRead(int addr)
{
return DmaRead((ushort)addr);
}
byte DmaRead(ushort addr)
{
#if DEBUG
if (addr < 0x1800)
LogDebug("Maria: Questionable DMA read at ${0:x4} by PC=${1:x4}", addr, M.CPU.PC);
#endif
return M.Mem[addr];
}
[System.Diagnostics.Conditional("DEBUG")]
void LogDebug(string format, params object[] args)
{
if (M == null || M.Logger == null)
return;
M.Logger.WriteLine(format, args);
}
[System.Diagnostics.Conditional("DEBUG")]
void AssertDebug(bool cond)
{
if (!cond)
System.Diagnostics.Debugger.Break();
}
#endregion
}
}