BizHawk/EMU7800/Core/TIA.cs

1350 lines
44 KiB
C#

/*
* TIA.cs
*
* The Television Interface Adaptor device.
*
* Copyright © 2003-2008 Mike Murphy
*
*/
using System;
namespace EMU7800.Core
{
#region Collision Flags
[Flags]
public enum TIACxFlags
{
PF = 1 << 0,
BL = 1 << 1,
M0 = 1 << 2,
M1 = 1 << 3,
P0 = 1 << 4,
P1 = 1 << 5
};
[Flags]
public enum TIACxPairFlags
{
M0P1 = 1 << 0,
M0P0 = 1 << 1,
M1P0 = 1 << 2,
M1P1 = 1 << 3,
P0PF = 1 << 4,
P0BL = 1 << 5,
P1PF = 1 << 6,
P1BL = 1 << 7,
M0PF = 1 << 8,
M0BL = 1 << 9,
M1PF = 1 << 10,
M1BL = 1 << 11,
BLPF = 1 << 12,
P0P1 = 1 << 13,
M0M1 = 1 << 14
};
#endregion
public sealed class TIA : IDevice
{
#region Constants
const int
VSYNC = 0x00, // Write: vertical sync set-clear (D1)
VBLANK = 0x01, // Write: vertical blank set-clear (D7-6,D1)
WSYNC = 0x02, // Write: wait for leading edge of hrz. blank (strobe)
RSYNC = 0x03, // Write: reset hrz. sync counter (strobe)
NUSIZ0 = 0x04, // Write: number-size player-missle 0 (D5-0)
NUSIZ1 = 0x05, // Write: number-size player-missle 1 (D5-0)
COLUP0 = 0x06, // Write: color-lum player 0 (D7-1)
COLUP1 = 0x07, // Write: color-lum player 1 (D7-1)
COLUPF = 0x08, // Write: color-lum playfield (D7-1)
COLUBK = 0x09, // Write: color-lum background (D7-1)
CTRLPF = 0x0a, // Write: cntrl playfield ballsize & coll. (D5-4,D2-0)
REFP0 = 0x0b, // Write: reflect player 0 (D3)
REFP1 = 0x0c, // Write: reflect player 1 (D3)
PF0 = 0x0d, // Write: playfield register byte 0 (D7-4)
PF1 = 0x0e, // Write: playfield register byte 1 (D7-0)
PF2 = 0x0f, // Write: playfield register byte 2 (D7-0)
RESP0 = 0x10, // Write: reset player 0 (strobe)
RESP1 = 0x11, // Write: reset player 1 (strobe)
RESM0 = 0x12, // Write: reset missle 0 (strobe)
RESM1 = 0x13, // Write: reset missle 1 (strobe)
RESBL = 0x14, // Write: reset ball (strobe)
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)
GRP0 = 0x1b, // Write: graphics player 0 (D7-0)
GRP1 = 0x1c, // Write: graphics player 1 (D7-0)
ENAM0 = 0x1d, // Write: graphics (enable) missle 0 (D1)
ENAM1 = 0x1e, // Write: graphics (enable) missle 1 (D1)
ENABL = 0x1f, // Write: graphics (enable) ball (D1)
HMP0 = 0x20, // Write: horizontal motion player 0 (D7-4)
HMP1 = 0x21, // Write: horizontal motion player 1 (D7-4)
HMM0 = 0x22, // Write: horizontal motion missle 0 (D7-4)
HMM1 = 0x23, // Write: horizontal motion missle 1 (D7-4)
HMBL = 0x24, // Write: horizontal motion ball (D7-4)
VDELP0 = 0x25, // Write: vertical delay player 0 (D0)
VDELP1 = 0x26, // Write: vertical delay player 1 (D0)
VDELBL = 0x27, // Write: vertical delay ball (D0)
RESMP0 = 0x28, // Write: reset missle 0 to player 0 (D1)
RESMP1 = 0x29, // Write: reset missle 1 to player 1 (D1)
HMOVE = 0x2a, // Write: apply horizontal motion (strobe)
HMCLR = 0x2b, // Write: clear horizontal motion registers (strobe)
CXCLR = 0x2c, // Write: clear collision latches (strobe)
CXM0P = 0x00, // Read collision: D7=(M0,P1); D6=(M0,P0)
CXM1P = 0x01, // Read collision: D7=(M1,P0); D6=(M1,P1)
CXP0FB = 0x02, // Read collision: D7=(P0,PF); D6=(P0,BL)
CXP1FB = 0x03, // Read collision: D7=(P1,PF); D6=(P1,BL)
CXM0FB = 0x04, // Read collision: D7=(M0,PF); D6=(M0,BL)
CXM1FB = 0x05, // Read collision: D7=(M1,PF); D6=(M1,BL)
CXBLPF = 0x06, // Read collision: D7=(BL,PF); D6=(unused)
CXPPMM = 0x07, // Read collision: D7=(P0,P1); D6=(M0,M1)
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
const int CPU_TICKS_PER_AUDIO_SAMPLE = 38;
#endregion
#region Data Structures
readonly byte[] RegW = new byte[0x40];
readonly MachineBase M;
readonly TIASound TIASound;
delegate void PokeOpTyp(ushort addr, byte data);
PokeOpTyp[] PokeOp;
#endregion
#region Position Counters
// backing fields for properties--dont reference directly
int _HSync, _P0, _P1, _M0, _M1, _BL, _HMoveCounter;
// Horizontal Sync Counter
// this represents HSync of the last rendered CLK
// has period of 57 counts, 0-56, at 1/4 CLK (57*4=228 CLK)
// provide all horizontal timing for constructing a valid TV signal
// other movable object counters can be reset out-of-phase with HSync, hence %228 and not %57
int HSync
{
get { return _HSync; }
set { _HSync = value % 228; }
}
// determines the difference between HSync and PokeOpHSync
int PokeOpHSyncDelta
{
get { return (int)(Clock - LastEndClock); }
}
// this represents the current HSync
int PokeOpHSync
{
get { return (HSync + PokeOpHSyncDelta) % 228; }
}
// scanline last rendered to
int ScanLine;
// current position in the frame buffer
int FrameBufferIndex;
// bytes are batched here for writing to the FrameBuffer
//BufferElement FrameBufferElement;
// signals when to start an HMOVE
ulong StartHMOVEClock;
// indicates where in the HMOVE operation it is
int HMoveCounter
{
get { return _HMoveCounter; }
set { _HMoveCounter = value < 0 ? -1 : value & 0xf; }
}
// true when there is an HMOVE executing on the current scanline
bool HMoveLatch;
// represents the TIA color clock (CLK)
// computed off of the CPU clock, but in real life, the CPU is driven by the color CLK signal
ulong Clock
{
get { return 3 * M.CPU.Clock; }
}
// represents the first CLK of the unrendered scanline segment
ulong StartClock;
// represents the last CLK of the previously rendered scanline segment
ulong LastEndClock
{
get { return StartClock - 1; }
}
#endregion
#region Player 0 Object
// Player 0 Horizontal Position Counter
// has period of 40 counts, 0-39, at 1/4 CLK (40*4=160 CLK=visible scanline length)
// player position counter controls the position of the respective player graphics object on each scanline
// can be reset out-of-phase with HSync, hence %160 and not %40
int P0
{
get { return _P0; }
set { _P0 = value % 160; }
}
// HMOVE "more motion required" latch
bool P0mmr;
// Player 0 graphics registers
byte EffGRP0, OldGRP0;
// Player 0 stretch mode
int P0type;
// 1=currently suppressing copy 1 on Player 0
int P0suppress;
#endregion
#region Player 1 Object
// Player 1 Horizontal Position Counter (identical to P0)
int P1
{
get { return _P1; }
set { _P1 = value % 160; }
}
// HMOVE "more motion required" latch
bool P1mmr;
// Player 1 graphics registers
byte EffGRP1, OldGRP1;
// Player 1 stretch mode
int P1type;
// 1=currently suppressing copy 1 on Player 1
int P1suppress;
#endregion
#region Missile 0 Object
// Missile 0 Horizontal Position Counter
// similar to player position counters
int M0
{
get { return _M0; }
set { _M0 = value % 160; }
}
// HMOVE "more motion required" latch
bool M0mmr;
int M0type, M0size;
bool m0on;
#endregion
#region Missle 1 Object
// Missile 1 Horizontal Position Counter (identical to M0)
int M1
{
get { return _M1; }
set { _M1 = value % 160; }
}
// HMOVE "more motion required" latch
bool M1mmr;
int M1type, M1size;
bool m1on;
#endregion
#region Ball Object
// Ball Horizontal Position Counter
// similar to player position counters
int BL
{
get { return _BL; }
set { _BL = value % 160; }
}
// HMOVE "more motion required" latch
bool BLmmr;
bool OldENABL;
int BLsize;
bool blon;
#endregion
#region Misc
uint PF210;
int PFReflectionState;
// color-luminance for background, playfield, player 0, player 1
byte colubk, colupf, colup0, colup1;
bool vblankon, scoreon, pfpriority;
bool DumpEnabled;
ulong DumpDisabledCycle;
TIACxPairFlags Collisions;
#endregion
#region Public Members
public int WSYNCDelayClocks { get; set; }
public bool EndOfFrame { get; private set; }
public void Reset()
{
for (var i = 0; i < RegW.Length; i++)
{
RegW[i] = 0;
}
vblankon = scoreon = pfpriority = false;
m0on = m1on = blon = false;
colubk = colupf = colup0 = colup1 = 0;
PFReflectionState = 0;
StartClock = Clock;
HSync = -1;
P0 = P1 = M0 = M1 = BL = -1;
P0mmr = P1mmr = M0mmr = M1mmr = BLmmr = false;
StartHMOVEClock = ulong.MaxValue;
HMoveCounter = -1;
FrameBufferIndex = 0;
TIASound.Reset();
Log("{0} reset", this);
}
public override String ToString()
{
return "TIA 1A";
}
public void StartFrame()
{
WSYNCDelayClocks = 0;
EndOfFrame = false;
ScanLine = 0;
FrameBufferIndex %= 160;
RenderFromStartClockTo(Clock);
TIASound.StartFrame();
}
public byte this[ushort addr]
{
get { return peek(addr); }
set { poke(addr, value); }
}
public void EndFrame()
{
TIASound.EndFrame();
}
#endregion
#region Constructors
private TIA()
{
BuildPokeOpTable();
}
public TIA(MachineBase m) : this()
{
if (m == null)
throw new ArgumentNullException("m");
M = m;
TIASound = new TIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE);
}
#endregion
#region Scanline Segment Renderer
void RenderFromStartClockTo(ulong endClock)
{
RenderClock:
if (StartClock >= endClock)
return;
++HSync;
if (StartClock == StartHMOVEClock)
{
// turn on HMOVE
HMoveLatch = true;
HMoveCounter = 0xf;
P0mmr = P1mmr = M0mmr = M1mmr = BLmmr = true;
}
else if (HSync == 0)
{
// just wrapped around, clear late HBLANK
HMoveLatch = false;
}
// position counters are incremented during the visible portion of the scanline
if (HSync >= 68 + (HMoveLatch ? 8 : 0))
{
++P0; ++P1; ++M0; ++M1; ++BL;
}
// HMOVE compare, once every 1/4 CLK when on
if (HMoveCounter >= 0 && (HSync & 3) == 0)
{
if (((HMoveCounter ^ RegW[HMP0]) & 0xf) == 0xf) P0mmr = false;
if (((HMoveCounter ^ RegW[HMP1]) & 0xf) == 0xf) P1mmr = false;
if (((HMoveCounter ^ RegW[HMM0]) & 0xf) == 0xf) M0mmr = false;
if (((HMoveCounter ^ RegW[HMM1]) & 0xf) == 0xf) M1mmr = false;
if (((HMoveCounter ^ RegW[HMBL]) & 0xf) == 0xf) BLmmr = false;
if (HMoveCounter >= 0) HMoveCounter--;
}
// HMOVE increment, once every 1/4 CLK, 2 CLK after first compare when on
if (HMoveCounter < 0xf && (HSync & 3) == 2)
{
if (HSync < 68 + (HMoveLatch ? 8 : 0))
{
if (P0mmr) ++P0;
if (P1mmr) ++P1;
if (M0mmr) ++M0;
if (M1mmr) ++M1;
if (BLmmr) ++BL;
}
}
if (HSync == 68 + 76) PFReflectionState = RegW[CTRLPF] & 0x01;
var fbyte = (byte)0;
var fbyte_colupf = colupf;
TIACxFlags cxflags = 0;
if (vblankon || HSync < 68 + (HMoveLatch ? 8 : 0)) goto WritePixel;
fbyte = colubk;
var colupfon = false;
if ((PF210 & TIATables.PFMask[PFReflectionState][HSync - 68]) != 0)
{
if (scoreon) fbyte_colupf = (HSync - 68) < 80 ? colup0 : colup1;
colupfon = true;
cxflags |= TIACxFlags.PF;
}
if (blon && BL >= 0 && TIATables.BLMask[BLsize][BL])
{
colupfon = true;
cxflags |= TIACxFlags.BL;
}
if (!pfpriority && colupfon)
{
fbyte = fbyte_colupf;
}
if (m1on && M1 >= 0 && TIATables.MxMask[M1size][M1type][M1])
{
fbyte = colup1;
cxflags |= TIACxFlags.M1;
}
if (P1 >= 0 && (TIATables.PxMask[P1suppress][P1type][P1] & EffGRP1) != 0)
{
fbyte = colup1;
cxflags |= TIACxFlags.P1;
}
if (m0on && M0 >= 0 && TIATables.MxMask[M0size][M0type][M0])
{
fbyte = colup0;
cxflags |= TIACxFlags.M0;
}
if (P0 >= 0 && (TIATables.PxMask[P0suppress][P0type][P0] & EffGRP0) != 0)
{
fbyte = colup0;
cxflags |= TIACxFlags.P0;
}
if (pfpriority && colupfon)
{
fbyte = fbyte_colupf;
}
WritePixel:
Collisions |= TIATables.CollisionMask[(int)cxflags];
if (HSync >= 68)
{
M.FrameBuffer.VideoBuffer[FrameBufferIndex++] = fbyte;
if (FrameBufferIndex == M.FrameBuffer.VideoBufferByteLength)
FrameBufferIndex = 0;
if (HSync == 227)
ScanLine++;
}
if (P0 >= 156) P0suppress = 0;
if (P1 >= 156) P1suppress = 0;
// denote this CLK has been completed by incrementing to the next
StartClock++;
goto RenderClock;
}
#endregion
#region TIA Peek
byte peek(ushort addr)
{
var retval = 0;
addr &= 0xf;
RenderFromStartClockTo(Clock);
switch (addr)
{
case CXM0P:
retval |= ((Collisions & TIACxPairFlags.M0P1) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.M0P0) != 0 ? 0x40 : 0);
break;
case CXM1P:
retval |= ((Collisions & TIACxPairFlags.M1P0) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.M1P1) != 0 ? 0x40 : 0);
break;
case CXP0FB:
retval |= ((Collisions & TIACxPairFlags.P0PF) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.P0BL) != 0 ? 0x40 : 0);
break;
case CXP1FB:
retval |= ((Collisions & TIACxPairFlags.P1PF) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.P1BL) != 0 ? 0x40 : 0);
break;
case CXM0FB:
retval |= ((Collisions & TIACxPairFlags.M0PF) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.M0BL) != 0 ? 0x40 : 0);
break;
case CXM1FB:
retval |= ((Collisions & TIACxPairFlags.M1PF) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.M1BL) != 0 ? 0x40 : 0);
break;
case CXBLPF:
retval |= ((Collisions & TIACxPairFlags.BLPF) != 0 ? 0x80 : 0);
break;
case CXPPMM:
retval |= ((Collisions & TIACxPairFlags.P0P1) != 0 ? 0x80 : 0);
retval |= ((Collisions & TIACxPairFlags.M0M1) != 0 ? 0x40 : 0);
break;
case INPT0:
retval = DumpedInputPort(SampleINPT(0));
break;
case INPT1:
retval = DumpedInputPort(SampleINPT(1));
break;
case INPT2:
retval = DumpedInputPort(SampleINPT(2));
break;
case INPT3:
retval = DumpedInputPort(SampleINPT(3));
break;
case INPT4:
var scanline = ScanLine;
var hpos = PokeOpHSync - 68;
if (hpos < 0)
{
hpos += 228;
scanline--;
}
if (SampleINPTLatched(4, scanline, hpos))
{
retval &= 0x7f;
}
else
{
retval |= 0x80;
}
break;
case INPT5:
scanline = ScanLine;
hpos = PokeOpHSync - 68;
if (hpos < 0)
{
hpos += 228;
scanline--;
}
if (SampleINPTLatched(5, scanline, hpos))
{
retval &= 0x7f;
}
else
{
retval |= 0x80;
}
break;
}
return (byte)(retval | (M.Mem.DataBusState & 0x3f));
}
byte DumpedInputPort(int resistance)
{
byte retval = 0;
if (resistance == 0)
{
retval = 0x80;
}
else if (DumpEnabled || resistance == Int32.MaxValue)
{
retval = 0x00;
}
else
{
var chargeTime = 1.6 * resistance * 0.01e-6;
var needed = (ulong)(chargeTime * M.FrameBuffer.Scanlines * 228 * M.FrameHZ / 3);
if (M.CPU.Clock > DumpDisabledCycle + needed)
{
retval = 0x80;
}
}
return retval;
}
#endregion
#region TIA Poke
void poke(ushort addr, byte data)
{
addr &= 0x3f;
var endClock = Clock;
// writes to the TIA may take a few extra CLKs to actually affect TIA state
// such a delay would seem to be applicable across all possible TIA writes
// without hardware to confirm conclusively, this is updated only as seemingly required
switch (addr)
{
case GRP0:
case GRP1:
// stem in the T in activision logo on older titles
endClock += 1;
break;
case PF0:
case PF1:
case PF2:
// +2 prevents minor notching in berzerk walls
// +4 prevents shield fragments floating in fuzz field in yars revenge,
// but creates minor artifact in chopper command
switch (PokeOpHSync & 3)
{
case 0: endClock += 4; break;
case 1: endClock += 3; break;
case 2: endClock += 2; break;
case 3: endClock += 5; break;
}
break;
}
RenderFromStartClockTo(endClock);
PokeOp[addr](addr, data);
}
static void opNULL(ushort addr, byte data)
{
}
void opVSYNC(ushort addr, byte data)
{
// Many games don't appear to supply 3 scanlines of
// VSYNC in accordance with the Atari documentation.
// Enduro turns on VSYNC, then turns it off twice.
// Centipede turns off VSYNC several times, in addition to normal usage.
// One of the Atari Bowling ROMs turns it off, but never turns it on.
// So, we always end the frame if VSYNC is turned on and then off.
// We also end the frame if VSYNC is turned off after scanline 258 to accomodate Bowling.
if ((data & 0x02) == 0)
{
if ((RegW[VSYNC] & 0x02) != 0 || ScanLine > 258)
{
EndOfFrame = true;
M.CPU.EmulatorPreemptRequest = true;
}
}
RegW[VSYNC] = data;
}
void opVBLANK(ushort addr, byte data)
{
if ((RegW[VBLANK] & 0x80) == 0)
{
// dump to ground is clear and will be set
// thus discharging all INPTx capacitors
if ((data & 0x80) != 0)
{
DumpEnabled = true;
}
}
else
{
// dump to ground is set and will be cleared
// thus starting all INPTx capacitors charging
if ((data & 0x80) == 0)
{
DumpEnabled = false;
DumpDisabledCycle = M.CPU.Clock;
}
}
RegW[VBLANK] = data;
vblankon = (data & 0x02) != 0;
}
void opWSYNC(ushort addr, byte data)
{
// on scanline=44, tetris seems to occasionally not get a WSYNC in until 3 clk in on scanline=45 causing jitter
if (PokeOpHSync > 0)
{
// report the number of remaining system clocks on the scanline to delay the CPU
WSYNCDelayClocks = 228 - PokeOpHSync;
// request a CPU preemption to service the delay request (only if there is a delay necessary)
M.CPU.EmulatorPreemptRequest = true;
}
}
void opRSYNC(ushort addr, byte data)
{
LogDebug("TIA RSYNC: frame={0} scanline={0} hsync={0}", M.FrameNumber, ScanLine, PokeOpHSync);
}
void opNUSIZ0(ushort addr, byte data)
{
RegW[NUSIZ0] = (byte)(data & 0x37);
M0size = (RegW[NUSIZ0] & 0x30) >> 4;
M0type = RegW[NUSIZ0] & 0x07;
P0type = M0type;
P0suppress = 0;
}
void opNUSIZ1(ushort addr, byte data)
{
RegW[NUSIZ1] = (byte)(data & 0x37);
M1size = (RegW[NUSIZ1] & 0x30) >> 4;
M1type = RegW[NUSIZ1] & 0x07;
P1type = M1type;
P1suppress = 0;
}
void opCOLUBK(ushort addr, byte data)
{
colubk = data;
}
void opCOLUPF(ushort addr, byte data)
{
colupf = data;
}
void opCOLUP0(ushort addr, byte data)
{
colup0 = data;
}
void opCOLUP1(ushort addr, byte data)
{
colup1 = data;
}
void opCTRLPF(ushort addr, byte data)
{
RegW[CTRLPF] = data;
BLsize = (data & 0x30) >> 4;
scoreon = (data & 0x02) != 0;
pfpriority = (data & 0x04) != 0;
}
void SetEffGRP0()
{
var grp0 = RegW[VDELP0] != 0 ? OldGRP0 : RegW[GRP0];
EffGRP0 = RegW[REFP0] != 0 ? TIATables.GRPReflect[grp0] : grp0;
}
void SetEffGRP1()
{
var grp1 = RegW[VDELP1] != 0 ? OldGRP1 : RegW[GRP1];
EffGRP1 = RegW[REFP1] != 0 ? TIATables.GRPReflect[grp1] : grp1;
}
void Setblon()
{
blon = RegW[VDELBL] != 0 ? OldENABL : RegW[ENABL] != 0;
}
void opREFP0(ushort addr, byte data)
{
RegW[REFP0] = (byte)(data & 0x08);
SetEffGRP0();
}
void opREFP1(ushort addr, byte data)
{
RegW[REFP1] = (byte)(data & 0x08);
SetEffGRP1();
}
void opPF(ushort addr, byte data)
{
RegW[addr] = data;
PF210 = (uint)((RegW[PF2] << 12) | (RegW[PF1] << 4) |((RegW[PF0] >> 4) & 0x0f));
}
void opRESP0(ushort addr, byte data)
{
if (PokeOpHSync < 68)
{
P0 = 0;
}
else if (HMoveLatch && PokeOpHSync >= 68 && PokeOpHSync < 76)
{
// this is an attempt to model observed behavior--may not be completely correct
// only three hsync values are really possible:
// 69: parkerbros actionman
// 72: activision grandprix
// 75: barnstorming, riverraid
P0 = -((PokeOpHSync - 68) >> 1);
}
else
{
P0 = -4;
}
P0 -= PokeOpHSyncDelta;
P0suppress = 1;
}
void opRESP1(ushort addr, byte data)
{
if (PokeOpHSync < 68)
{
P1 = 0;
}
else if (HMoveLatch && PokeOpHSync >= 68 && PokeOpHSync < 76)
{
// this is an attempt to model observed behavior--may not be completely correct
// only three hsync values are really possible:
// 69: parkerbros actionman
// 72: parkerbros actionman
// 75: parkerbros actionman
P1 = -((PokeOpHSync - 68) >> 1);
}
else
{
P1 = -4;
}
P1 -= PokeOpHSyncDelta;
P1suppress = 1;
}
void opRESM0(ushort addr, byte data)
{
// -2 to mirror M1
M0 = PokeOpHSync < 68 ? -2 : -4;
M0 -= PokeOpHSyncDelta;
}
void opRESM1(ushort addr, byte data)
{
// -2 cleans up edges on activision pitfall ii
M1 = PokeOpHSync < 68 ? -2 : -4;
M1 -= PokeOpHSyncDelta;
}
void opRESBL(ushort addr, byte data)
{
// -2 cleans up edges on activision boxing
// -4 confirmed via activision choppercommand; used to clean up edges
BL = PokeOpHSync < 68 ? -2 : -4;
BL -= PokeOpHSyncDelta;
}
void opAUD(ushort addr, byte data)
{
RegW[addr] = data;
TIASound.Update(addr, data);
}
void opGRP0(ushort addr, byte data)
{
RegW[GRP0] = data;
OldGRP1 = RegW[GRP1];
SetEffGRP0();
SetEffGRP1();
}
void opGRP1(ushort addr, byte data)
{
RegW[GRP1] = data;
OldGRP0 = RegW[GRP0];
OldENABL = RegW[ENABL] != 0;
SetEffGRP0();
SetEffGRP1();
Setblon();
}
void opENAM0(ushort addr, byte data)
{
RegW[ENAM0] = (byte)(data & 0x02);
m0on = RegW[ENAM0] != 0 && RegW[RESMP0] == 0;
}
void opENAM1(ushort addr, byte data)
{
RegW[ENAM1] = (byte)(data & 0x02);
m1on = RegW[ENAM1] != 0 && RegW[RESMP1] == 0;
}
void opENABL(ushort addr, byte data)
{
RegW[ENABL] = (byte)(data & 0x02);
Setblon();
}
void SetHmr(int hmr, byte data)
{
// marshal via >>4 for compare convenience
RegW[hmr] = (byte)((data ^ 0x80) >> 4);
}
void opHM(ushort addr, byte data)
{
SetHmr(addr, data);
}
void opVDELP0(ushort addr, byte data)
{
RegW[VDELP0] = (byte)(data & 0x01);
SetEffGRP0();
}
void opVDELP1(ushort addr, byte data)
{
RegW[VDELP1] = (byte)(data & 0x01);
SetEffGRP1();
}
void opVDELBL(ushort addr, byte data)
{
RegW[VDELBL] = (byte)(data & 0x01);
Setblon();
}
void opRESMP0(ushort addr, byte data)
{
if (RegW[RESMP0] != 0 && (data & 0x02) == 0)
{
var middle = 4;
switch (RegW[NUSIZ0] & 0x07)
{
case 0x05: middle <<= 1; break; // double size
case 0x07: middle <<= 2; break; // quad size
}
M0 = P0 - middle;
}
RegW[RESMP0] = (byte)(data & 0x02);
m0on = RegW[ENAM0] != 0 && RegW[RESMP0] == 0;
}
void opRESMP1(ushort addr, byte data)
{
if (RegW[RESMP1] != 0 && (data & 0x02) == 0)
{
var middle = 4;
switch (RegW[NUSIZ1] & 0x07)
{
case 0x05: middle <<= 1; break; // double size
case 0x07: middle <<= 2; break; // quad size
}
M1 = P1 - middle;
}
RegW[RESMP1] = (byte)(data & 0x02);
m1on = RegW[ENAM1] != 0 && RegW[RESMP1] == 0;
}
void opHMOVE(ushort addr, byte data)
{
P0suppress = 0;
P1suppress = 0;
StartHMOVEClock = Clock + 3;
// Activision Spiderfighter Cheat (Score and Logo)
// Delaying the start of the HMOVE here results in it completing on the next scanline (to have visible effect.)
// HMOVEing during the visible scanline probably has extra consequences,
// however, it seems not many carts try to do this.
if (PokeOpHSync == 201) StartHMOVEClock++; // any increment >0 works
}
void opHMCLR(ushort addr, byte data)
{
SetHmr(HMP0, 0);
SetHmr(HMP1, 0);
SetHmr(HMM0, 0);
SetHmr(HMM1, 0);
SetHmr(HMBL, 0);
}
void opCXCLR(ushort addr, byte data)
{
Collisions = 0;
}
void BuildPokeOpTable()
{
PokeOp = new PokeOpTyp[64];
for (var i = 0; i < PokeOp.Length; i++)
{
PokeOp[i] = opNULL;
}
PokeOp[VSYNC] = opVSYNC;
PokeOp[VBLANK] = opVBLANK;
PokeOp[WSYNC] = opWSYNC;
PokeOp[RSYNC] = opRSYNC;
PokeOp[NUSIZ0] = opNUSIZ0;
PokeOp[NUSIZ1] = opNUSIZ1;
PokeOp[COLUP0] = opCOLUP0;
PokeOp[COLUP1] = opCOLUP1;
PokeOp[COLUPF] = opCOLUPF;
PokeOp[COLUBK] = opCOLUBK;
PokeOp[CTRLPF] = opCTRLPF;
PokeOp[REFP0] = opREFP0;
PokeOp[REFP1] = opREFP1;
PokeOp[PF0] = opPF;
PokeOp[PF1] = opPF;
PokeOp[PF2] = opPF;
PokeOp[RESP0] = opRESP0;
PokeOp[RESP1] = opRESP1;
PokeOp[RESM0] = opRESM0;
PokeOp[RESM1] = opRESM1;
PokeOp[RESBL] = opRESBL;
PokeOp[AUDC0] = opAUD;
PokeOp[AUDC1] = opAUD;
PokeOp[AUDF0] = opAUD;
PokeOp[AUDF1] = opAUD;
PokeOp[AUDV0] = opAUD;
PokeOp[AUDV1] = opAUD;
PokeOp[GRP0] = opGRP0;
PokeOp[GRP1] = opGRP1;
PokeOp[ENAM0] = opENAM0;
PokeOp[ENAM1] = opENAM1;
PokeOp[ENABL] = opENABL;
PokeOp[HMP0] = opHM;
PokeOp[HMP1] = opHM;
PokeOp[HMM0] = opHM;
PokeOp[HMM1] = opHM;
PokeOp[HMBL] = opHM;
PokeOp[VDELP0] = opVDELP0;
PokeOp[VDELP1] = opVDELP1;
PokeOp[VDELBL] = opVDELBL;
PokeOp[RESMP0] = opRESMP0;
PokeOp[RESMP1] = opRESMP1;
PokeOp[HMOVE] = opHMOVE;
PokeOp[HMCLR] = opHMCLR;
PokeOp[CXCLR] = opCXCLR;
}
#endregion
#region Input Helpers
int SampleINPT(int inpt)
{
var mi = M.InputState;
switch (inpt <= 1 ? mi.LeftControllerJack : mi.RightControllerJack)
{
case Controller.Paddles:
// playerno = inpt
return mi.SampleCapturedOhmState(inpt & 3);
case Controller.ProLineJoystick:
// playerno = inpt/2
switch (inpt & 3)
{
case 0: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger) ? 0 : Int32.MaxValue;
case 1: return mi.SampleCapturedControllerActionState(0, ControllerAction.Trigger2) ? 0 : Int32.MaxValue;
case 2: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger) ? 0 : Int32.MaxValue;
case 3: return mi.SampleCapturedControllerActionState(1, ControllerAction.Trigger2) ? 0 : Int32.MaxValue;
}
break;
case Controller.BoosterGrip:
// playerno = inpt
return mi.SampleCapturedControllerActionState(inpt & 3, ControllerAction.Trigger2) ? 0 : Int32.MaxValue;
case Controller.Keypad:
return SampleKeypadStateDumped(inpt & 3);
}
return int.MaxValue;
}
bool SampleINPTLatched(int inpt, int scanline, int hpos)
{
var mi = M.InputState;
var playerNo = inpt - 4;
switch (playerNo == 0 ? mi.LeftControllerJack : mi.RightControllerJack)
{
case Controller.Joystick:
case Controller.ProLineJoystick:
case Controller.Driving:
case Controller.BoosterGrip:
return mi.SampleCapturedControllerActionState(playerNo, ControllerAction.Trigger);
case Controller.Keypad:
return SampleKeypadStateLatched(playerNo);
case Controller.Lightgun:
int sampledScanline, sampledHpos;
mi.SampleCapturedLightGunPosition(playerNo, out sampledScanline, out sampledHpos);
return ((scanline - 4) >= sampledScanline && (hpos - 23) >= sampledHpos);
}
return false;
}
bool SampleKeypadStateLatched(int deviceno)
{
ControllerAction action;
if ((M.PIA.WrittenPortA & 0x01) == 0)
{
action = ControllerAction.Keypad3;
}
else if ((M.PIA.WrittenPortA & 0x02) == 0)
{
action = ControllerAction.Keypad6;
}
else if ((M.PIA.WrittenPortA & 0x04) == 0)
{
action = ControllerAction.Keypad9;
}
else if ((M.PIA.WrittenPortA & 0x08) == 0)
{
action = ControllerAction.KeypadP;
}
else
{
return false;
}
return M.InputState.SampleCapturedControllerActionState(deviceno, action);
}
int SampleKeypadStateDumped(int inpt)
{
ControllerAction action;
if ((M.PIA.WrittenPortA & 0x01) == 0)
{
action = (inpt & 1) == 0 ? ControllerAction.Keypad1 : ControllerAction.Keypad2;
}
else if ((M.PIA.WrittenPortA & 0x02) == 0)
{
action = (inpt & 1) == 0 ? ControllerAction.Keypad4 : ControllerAction.Keypad5;
}
else if ((M.PIA.WrittenPortA & 0x04) == 0)
{
action = (inpt & 1) == 0 ? ControllerAction.Keypad7 : ControllerAction.Keypad8;
}
else if ((M.PIA.WrittenPortA & 0x08) == 0)
{
action = (inpt & 1) == 0 ? ControllerAction.KeypadA : ControllerAction.Keypad0;
}
else
{
return Int32.MaxValue;
}
// playerno = inpt/2
return M.InputState.SampleCapturedControllerActionState(inpt >> 1, action) ? Int32.MaxValue : 0;
}
#endregion
#region Serialization Members
public TIA(DeserializationContext input, MachineBase m) : this()
{
if (input == null)
throw new ArgumentNullException("input");
if (m == null)
throw new ArgumentNullException("m");
M = m;
TIASound = input.ReadTIASound(M, CPU_TICKS_PER_AUDIO_SAMPLE);
input.CheckVersion(2);
RegW = input.ReadExpectedBytes(0x40);
HSync = input.ReadInt32();
HMoveCounter = input.ReadInt32();
ScanLine = input.ReadInt32();
FrameBufferIndex = input.ReadInt32();
//FrameBufferElement = input.ReadBufferElement();
StartHMOVEClock = input.ReadUInt64();
HMoveLatch = input.ReadBoolean();
StartClock = input.ReadUInt64();
P0 = input.ReadInt32();
P0mmr = input.ReadBoolean();
EffGRP0 = input.ReadByte();
OldGRP0 = input.ReadByte();
P0type = input.ReadInt32();
P0suppress = input.ReadInt32();
P1 = input.ReadInt32();
P1mmr = input.ReadBoolean();
EffGRP1 = input.ReadByte();
OldGRP1 = input.ReadByte();
P1type = input.ReadInt32();
P1suppress = input.ReadInt32();
M0 = input.ReadInt32();
M0mmr = input.ReadBoolean();
M0type = input.ReadInt32();
M0size = input.ReadInt32();
m0on = input.ReadBoolean();
M1 = input.ReadInt32();
M1mmr = input.ReadBoolean();
M1type = input.ReadInt32();
M1size = input.ReadInt32();
m1on = input.ReadBoolean();
BL = input.ReadInt32();
BLmmr = input.ReadBoolean();
OldENABL = input.ReadBoolean();
BLsize = input.ReadInt32();
blon = input.ReadBoolean();
PF210 = input.ReadUInt32();
PFReflectionState = input.ReadInt32();
colubk = input.ReadByte();
colupf = input.ReadByte();
colup0 = input.ReadByte();
colup1 = input.ReadByte();
vblankon = input.ReadBoolean();
scoreon = input.ReadBoolean();
pfpriority = input.ReadBoolean();
DumpEnabled = input.ReadBoolean();
DumpDisabledCycle = input.ReadUInt64();
Collisions = (TIACxPairFlags)input.ReadInt32();
WSYNCDelayClocks = input.ReadInt32();
EndOfFrame = input.ReadBoolean();
}
public void GetObjectData(SerializationContext output)
{
if (output == null)
throw new ArgumentNullException("output");
output.Write(TIASound);
output.WriteVersion(2);
output.Write(RegW);
output.Write(HSync);
output.Write(HMoveCounter);
output.Write(ScanLine);
output.Write(FrameBufferIndex);
//output.Write(FrameBufferElement);
output.Write(StartHMOVEClock);
output.Write(HMoveLatch);
output.Write(StartClock);
output.Write(P0);
output.Write(P0mmr);
output.Write(EffGRP0);
output.Write(OldGRP0);
output.Write(P0type);
output.Write(P0suppress);
output.Write(P1);
output.Write(P1mmr);
output.Write(EffGRP1);
output.Write(OldGRP1);
output.Write(P1type);
output.Write(P1suppress);
output.Write(M0);
output.Write(M0mmr);
output.Write(M0type);
output.Write(M0size);
output.Write(m0on);
output.Write(M1);
output.Write(M1mmr);
output.Write(M1type);
output.Write(M1size);
output.Write(m1on);
output.Write(BL);
output.Write(BLmmr);
output.Write(OldENABL);
output.Write(BLsize);
output.Write(blon);
output.Write(PF210);
output.Write(PFReflectionState);
output.Write(colubk);
output.Write(colupf);
output.Write(colup0);
output.Write(colup1);
output.Write(vblankon);
output.Write(scoreon);
output.Write(pfpriority);
output.Write(DumpEnabled);
output.Write(DumpDisabledCycle);
output.Write((int)Collisions);
output.Write(WSYNCDelayClocks);
output.Write(EndOfFrame);
}
#endregion
#region Helpers
void Log(string format, params object[] args)
{
if (M == null || M.Logger == null)
return;
M.Logger.WriteLine(format, args);
}
[System.Diagnostics.Conditional("DEBUG")]
void LogDebug(string format, params object[] args)
{
if (M == null || M.Logger == null)
return;
M.Logger.WriteLine(format, args);
}
#endregion
}
}