diff --git a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
index db24d8af9f..48ff2d409b 100644
--- a/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
+++ b/BizHawk.Emulation.Cores/BizHawk.Emulation.Cores.csproj
@@ -207,9 +207,13 @@
-
- Code
-
+
+
+
+
+
+
+
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs
index d2c709f9c0..2817d31ab8 100644
--- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Atari2600.Core.cs
@@ -240,7 +240,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
_tia.Execute(1);
_tia.Execute(1);
- M6532.timer.tick();
+ M6532.Timer.Tick();
if (CoreComm.Tracer.Enabled)
{
CoreComm.Tracer.Put(Cpu.TraceState());
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/M6532.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/M6532.cs
index 0fd9ad2428..f6a4687c4f 100644
--- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/M6532.cs
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/M6532.cs
@@ -5,137 +5,85 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
// Emulates the M6532 RIOT Chip
public class M6532
{
- public byte ddra = 0x00;
- public byte ddrb = 0x00;
+ private readonly Atari2600 _core;
- private readonly Atari2600 core;
+ public byte DDRa = 0x00;
+ public byte DDRb = 0x00;
- public struct timerData
- {
- public int prescalerCount;
- public byte prescalerShift;
-
- public byte value;
-
- public bool interruptEnabled;
- public bool interruptFlag;
-
- public void tick()
- {
- if (prescalerCount == 0)
- {
- value--;
- prescalerCount = 1 << prescalerShift;
- }
-
- prescalerCount--;
- if (prescalerCount == 0)
- {
- if (value == 0)
- {
- interruptFlag = true;
- prescalerShift = 0;
- }
- }
- }
-
- public void SyncState(Serializer ser)
- {
- ser.Sync("prescalerCount", ref prescalerCount);
- ser.Sync("prescalerShift", ref prescalerShift);
- ser.Sync("value", ref value);
- ser.Sync("interruptEnabled", ref interruptEnabled);
- ser.Sync("interruptFlag", ref interruptFlag);
- }
-
- };
-
- public timerData timer;
+ public TimerData Timer;
public M6532(Atari2600 core)
{
- this.core = core;
+ _core = core;
// Apparently starting the timer at 0 will break for some games (Solaris and H.E.R.O.). To avoid that, we pick an
// arbitrary value to start with.
- timer.value = 0x73;
- timer.prescalerShift = 10;
- timer.prescalerCount = 1 << timer.prescalerShift;
+ Timer.Value = 0x73;
+ Timer.PrescalerShift = 10;
+ Timer.PrescalerCount = 1 << Timer.PrescalerShift;
}
public byte ReadMemory(ushort addr, bool peek)
{
- // Register Select (?)
- bool RS = (addr & 0x0200) != 0;
-
- if (!RS)
+ if ((addr & 0x0200) == 0) // If not register select, read Ram
{
- // Read Ram
- ushort maskedAddr = (ushort)(addr & 0x007f);
- return core.Ram[maskedAddr];
+ return _core.Ram[(ushort)(addr & 0x007f)];
}
- else
+
+ var registerAddr = (ushort)(addr & 0x0007);
+ if (registerAddr == 0x00)
{
- ushort registerAddr = (ushort)(addr & 0x0007);
- if (registerAddr == 0x00)
- {
- // Read Output reg A
- // Combine readings from player 1 and player 2
- byte temp = (byte)(core.ReadControls1(peek) & 0xF0 | ((core.ReadControls2(peek) >> 4) & 0x0F));
- temp = (byte)(temp & ~ddra);
- return temp;
- }
- else if (registerAddr == 0x01)
- {
- // Read DDRA
- return ddra;
- }
- else if (registerAddr == 0x02)
- {
- // Read Output reg B
- byte temp = core.ReadConsoleSwitches(peek);
- temp = (byte)(temp & ~ddrb);
- return temp;
+ // Read Output reg A
+ // Combine readings from player 1 and player 2
+ var temp = (byte)(_core.ReadControls1(peek) & 0xF0 | ((_core.ReadControls2(peek) >> 4) & 0x0F));
+ temp = (byte)(temp & ~DDRa);
+ return temp;
+ }
+
+ if (registerAddr == 0x01)
+ {
+ // Read DDRA
+ return DDRa;
+ }
+
+ if (registerAddr == 0x02)
+ {
+ // Read Output reg B
+ var temp = _core.ReadConsoleSwitches(peek);
+ temp = (byte)(temp & ~DDRb);
+ return temp;
+ }
- /*
- // TODO: Rewrite this!
- bool temp = resetOccured;
- resetOccured = false;
- return (byte)(0x0A | (temp ? 0x00 : 0x01));
- * */
- }
- else if (registerAddr == 0x03)
- {
- // Read DDRB
- return ddrb;
- }
- else if ((registerAddr & 0x5) == 0x4)
- {
- // Bit 0x0080 contains interrupt enable/disable
- timer.interruptEnabled = (addr & 0x0080) != 0;
+ if (registerAddr == 0x03) // Read DDRB
+ {
+ return DDRb;
+ }
+
+ if ((registerAddr & 0x5) == 0x4)
+ {
+ // Bit 0x0080 contains interrupt enable/disable
+ Timer.InterruptEnabled = (addr & 0x0080) != 0;
- // The interrupt flag will be reset whenever the Timer is access by a read or a write
- // However, the reading of the timer at the same time the interrupt occurs will not reset the interrupt flag
- // (M6532 Datasheet)
- if (!(timer.prescalerCount == 0 && timer.value == 0))
- {
- timer.interruptFlag = false;
- }
-
- return timer.value;
- }
- else if ((registerAddr & 0x5) == 0x5)
+ // The interrupt flag will be reset whenever the Timer is access by a read or a write
+ // However, the reading of the timer at the same time the interrupt occurs will not reset the interrupt flag
+ // (M6532 Datasheet)
+ if (!(Timer.PrescalerCount == 0 && Timer.Value == 0))
{
- // Read interrupt flag
- if (timer.interruptEnabled && timer.interruptFlag)
- {
- return 0x80;
- }
- else
- {
- return 0x00;
- }
+ Timer.InterruptFlag = false;
}
+
+ return Timer.Value;
+ }
+
+ if ((registerAddr & 0x5) == 0x5)
+ {
+ // Read interrupt flag
+ if (Timer.InterruptEnabled && Timer.InterruptFlag)
+ {
+ return 0x80;
+ }
+
+ return 0x00;
}
return 0x3A;
@@ -143,66 +91,61 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
public void WriteMemory(ushort addr, byte value)
{
- // Register Select (?)
- bool RS = (addr & 0x0200) != 0;
-
- // If the RS bit is not set, this is a ram write
- if (!RS)
+ if ((addr & 0x0200) == 0) // If the RS bit is not set, this is a ram write
{
- ushort maskedAddr = (ushort)(addr & 0x007f);
- core.Ram[maskedAddr] = value;
+ _core.Ram[(ushort)(addr & 0x007f)] = value;
}
else
{
// If bit 0x0010 is set, and bit 0x0004 is set, this is a timer write
if ((addr & 0x0014) == 0x0014)
{
- ushort registerAddr = (ushort)(addr & 0x0007);
+ var registerAddr = (ushort)(addr & 0x0007);
// Bit 0x0080 contains interrupt enable/disable
- timer.interruptEnabled = (addr & 0x0080) != 0;
+ Timer.InterruptEnabled = (addr & 0x0080) != 0;
// The interrupt flag will be reset whenever the Timer is access by a read or a write
// (M6532 datasheet)
-
if (registerAddr == 0x04)
{
// Write to Timer/1
- timer.prescalerShift = 0;
- timer.value = value;
- timer.prescalerCount = 1 << timer.prescalerShift;
- timer.interruptFlag = false;
+ Timer.PrescalerShift = 0;
+ Timer.Value = value;
+ Timer.PrescalerCount = 1 << Timer.PrescalerShift;
+ Timer.InterruptFlag = false;
}
else if (registerAddr == 0x05)
{
// Write to Timer/8
- timer.prescalerShift = 3;
- timer.value = value;
- timer.prescalerCount = 1 << timer.prescalerShift;
- timer.interruptFlag = false;
+ Timer.PrescalerShift = 3;
+ Timer.Value = value;
+ Timer.PrescalerCount = 1 << Timer.PrescalerShift;
+ Timer.InterruptFlag = false;
}
else if (registerAddr == 0x06)
{
// Write to Timer/64
- timer.prescalerShift = 6;
- timer.value = value;
- timer.prescalerCount = 1 << timer.prescalerShift;
- timer.interruptFlag = false;
+ Timer.PrescalerShift = 6;
+ Timer.Value = value;
+ Timer.PrescalerCount = 1 << Timer.PrescalerShift;
+ Timer.InterruptFlag = false;
}
else if (registerAddr == 0x07)
{
// Write to Timer/1024
- timer.prescalerShift = 10;
- timer.value = value;
- timer.prescalerCount = 1 << timer.prescalerShift;
- timer.interruptFlag = false;
+ Timer.PrescalerShift = 10;
+ Timer.Value = value;
+ Timer.PrescalerCount = 1 << Timer.PrescalerShift;
+ Timer.InterruptFlag = false;
}
}
+
// If bit 0x0004 is not set, bit 0x0010 is ignored and
// these are register writes
else if ((addr & 0x0004) == 0)
{
- ushort registerAddr = (ushort)(addr & 0x0007);
+ var registerAddr = (ushort)(addr & 0x0007);
if (registerAddr == 0x00)
{
@@ -211,7 +154,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
else if (registerAddr == 0x01)
{
// Write DDRA
- ddra = value;
+ DDRa = value;
}
else if (registerAddr == 0x02)
{
@@ -220,7 +163,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
else if (registerAddr == 0x03)
{
// Write DDRB
- ddrb = value;
+ DDRb = value;
}
}
}
@@ -229,10 +172,50 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
public void SyncState(Serializer ser)
{
ser.BeginSection("M6532");
- ser.Sync("ddra", ref ddra);
- ser.Sync("ddrb", ref ddrb);
- timer.SyncState(ser);
+ ser.Sync("ddra", ref DDRa);
+ ser.Sync("ddrb", ref DDRb);
+ Timer.SyncState(ser);
ser.EndSection();
}
+
+ public struct TimerData
+ {
+ public int PrescalerCount;
+ public byte PrescalerShift;
+
+ public byte Value;
+
+ public bool InterruptEnabled;
+ public bool InterruptFlag;
+
+ public void Tick()
+ {
+ if (PrescalerCount == 0)
+ {
+ Value--;
+ PrescalerCount = 1 << PrescalerShift;
+ }
+
+ PrescalerCount--;
+ if (PrescalerCount == 0)
+ {
+ if (Value == 0)
+ {
+ InterruptFlag = true;
+ PrescalerShift = 0;
+ }
+ }
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("prescalerCount", ref PrescalerCount);
+ ser.Sync("prescalerShift", ref PrescalerShift);
+ ser.Sync("value", ref Value);
+ ser.Sync("interruptEnabled", ref InterruptEnabled);
+ ser.Sync("interruptFlag", ref InterruptFlag);
+ }
+
+ }
}
}
\ No newline at end of file
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/TIA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/TIA.cs
deleted file mode 100644
index 309ece7030..0000000000
--- a/BizHawk.Emulation.Cores/Consoles/Atari/2600/TIA.cs
+++ /dev/null
@@ -1,1388 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-using BizHawk.Common;
-using BizHawk.Emulation.Common;
-
-namespace BizHawk.Emulation.Cores.Atari.Atari2600
-{
- // Emulates the TIA
- public class TIA : IVideoProvider, ISoundProvider
- {
- private const byte CXP0 = 0x01;
- private const byte CXP1 = 0x02;
- private const byte CXM0 = 0x04;
- private const byte CXM1 = 0x08;
- private const byte CXPF = 0x10;
- private const byte CXBL = 0x20;
-
- private readonly Atari2600 _core;
- private byte _hsyncCnt;
- private int _capChargeStart;
- private bool _capCharging;
-
- private PlayerData player0;
- private PlayerData player1;
- private PlayfieldData playField;
- private HmoveData hmove;
- private BallData ball;
-
- private bool vblankEnabled;
- private bool vsyncEnabled;
-
- private uint[] scanline = new uint[160];
-
- public TIA(Atari2600 core)
- {
- _core = core;
- player0.scanCnt = 8;
- player1.scanCnt = 8;
- }
-
- public bool FrameComplete { get; set; }
-
- private struct MissileData
- {
- public bool Enabled;
- public bool ResetToPlayer;
- public byte HPosCnt;
- public byte Size;
- public byte Number;
- public byte Hm;
- public byte Collisions;
-
- public bool Tick()
- {
- var result = false;
-
- // At hPosCnt == 0, start drawing the missile, if enabled
- if (HPosCnt < (1 << Size))
- {
- if (Enabled && !ResetToPlayer)
- {
- // Draw the missile
- result = true;
- }
- }
-
- if ((Number & 0x07) == 0x01 || ((Number & 0x07) == 0x03))
- {
- if (HPosCnt >= 16 && HPosCnt <= (16 + (1 << Size) - 1) )
- {
- if (Enabled && !ResetToPlayer)
- {
- // Draw the missile
- result = true;
- }
- }
- }
-
- if ((Number & 0x07) == 0x02 || ((Number & 0x07) == 0x03) || ((Number & 0x07) == 0x06))
- {
- if (HPosCnt >= 32 && HPosCnt <= (32 + (1 << Size) - 1) )
- {
- if (Enabled && !ResetToPlayer)
- {
- // Draw the missile
- result = true;
- }
- }
- }
-
- if ((Number & 0x07) == 0x04 || (Number & 0x07) == 0x06)
- {
- if (HPosCnt >= 64 && HPosCnt <= (64 + (1 << Size) - 1) )
- {
- if (Enabled && !ResetToPlayer)
- {
- // Draw the missile
- result = true;
- }
- }
- }
-
- // Increment the counter
- HPosCnt++;
-
- // Counter loops at 160
- HPosCnt %= 160;
-
- return result;
- }
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("Missile");
- ser.Sync("enabled", ref Enabled);
- ser.Sync("resetToPlayer", ref ResetToPlayer);
- ser.Sync("hPosCnt", ref HPosCnt);
- ser.Sync("size", ref Size);
- ser.Sync("number", ref Number);
- ser.Sync("HM", ref Hm);
- ser.Sync("collisions", ref Collisions);
- ser.EndSection();
- }
- }
-
- private struct PlayerData
- {
- public MissileData Missile;
- public byte grp;
- public byte dgrp;
- public byte color;
- public byte hPosCnt;
- public byte scanCnt;
- public byte scanStrchCnt;
- public byte HM;
- public bool reflect;
- public bool delay;
- public byte nusiz;
- public bool reset;
- public byte resetCnt;
- public byte collisions;
-
- public bool Tick()
- {
- var result = false;
- if (scanCnt < 8)
- {
- // Make the mask to check the graphic
- byte playerMask = (byte)(1 << (8 - 1 - scanCnt));
-
- // Reflect it if needed
- if (reflect)
- {
- playerMask = (byte)ReverseBits(playerMask, 8);
- }
-
- // Check the graphic (depending on delay)
- if (!delay)
- {
- if ((grp & playerMask) != 0)
- {
- result = true;
- }
- }
- else
- {
- if ((dgrp & playerMask) != 0)
- {
- result = true;
- }
- }
-
- // Reset missile, if desired
- if (scanCnt == 0x04 && hPosCnt <= 16 && Missile.ResetToPlayer)
- {
- Missile.HPosCnt = 0;
- }
-
- // Increment counter
- // When this reaches 8, we've run out of pixel
-
- // If we're drawing a stretched player, only incrememnt the
- // counter every 2 or 4 clocks
-
- // Double size player
- if ((nusiz & 0x07) == 0x05)
- {
- scanStrchCnt++;
- scanStrchCnt %= 2;
- }
-
- // Quad size player
- else if ((nusiz & 0x07) == 0x07)
- {
- scanStrchCnt++;
- scanStrchCnt %= 4;
- }
-
- // Single size player
- else
- {
- scanStrchCnt = 0;
- }
-
- if (scanStrchCnt == 0)
- {
- scanCnt++;
- }
- }
-
- // At counter position 0 we should start drawing, a pixel late
- // Set the scan counter at 0, and at the next pixel the graphic will start drawing
- if (hPosCnt == 0 && !reset)
- {
- scanCnt = 0;
- if ((nusiz & 0x07) == 0x05 || (nusiz & 0x07) == 0x07)
- {
- scanStrchCnt = 0;
- }
- }
-
- if (hPosCnt == 16 && ((nusiz & 0x07) == 0x01 || ((nusiz & 0x07) == 0x03)))
- {
- scanCnt = 0;
- }
-
- if (hPosCnt == 32 && ((nusiz & 0x07) == 0x02 || ((nusiz & 0x07) == 0x03) || ((nusiz & 0x07) == 0x06)))
- {
- scanCnt = 0;
- }
-
- if (hPosCnt == 64 && ((nusiz & 0x07) == 0x04 || ((nusiz & 0x07) == 0x06)))
- {
- scanCnt = 0;
- }
-
- // Reset is no longer in effect
- reset = false;
-
- // Increment the counter
- hPosCnt++;
-
- // Counter loops at 160
- hPosCnt %= 160;
-
- if (resetCnt < 4)
- {
- resetCnt++;
- }
-
- if (resetCnt == 4)
- {
- hPosCnt = 0;
- reset = true;
- resetCnt++;
- }
-
- return result;
- }
-
- public void SyncState(Serializer ser)
- {
- Missile.SyncState(ser);
- ser.Sync("grp", ref grp);
- ser.Sync("dgrp", ref dgrp);
- ser.Sync("color", ref color);
- ser.Sync("hPosCnt", ref hPosCnt);
- ser.Sync("scanCnt", ref scanCnt);
- ser.Sync("scanStrchCnt", ref scanStrchCnt);
- ser.Sync("HM", ref HM);
- ser.Sync("reflect", ref reflect);
- ser.Sync("delay", ref delay);
- ser.Sync("nusiz", ref nusiz);
- ser.Sync("reset", ref reset);
- ser.Sync("resetCnt", ref resetCnt);
- ser.Sync("collisions", ref collisions);
- }
- }
-
- private struct BallData
- {
- public bool enabled;
- public bool denabled;
- public bool delay;
- public byte size;
- public byte HM;
- public byte hPosCnt;
- public byte collisions;
-
- public bool Tick()
- {
- bool result = false;
- if (hPosCnt < (1 << size))
- {
- if (!delay && enabled)
- {
- // Draw the ball!
- result = true;
- }
- else if (delay && denabled)
- {
- // Draw the ball!
- result = true;
- }
- }
-
- // Increment the counter
- hPosCnt++;
-
- // Counter loops at 160
- hPosCnt %= 160;
-
- return result;
- }
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("Ball");
- ser.Sync("enabled", ref enabled);
- ser.Sync("denabled", ref denabled);
- ser.Sync("delay", ref delay);
- ser.Sync("size", ref size);
- ser.Sync("HM", ref HM);
- ser.Sync("hPosCnt", ref hPosCnt);
- ser.Sync("collisions", ref collisions);
- ser.EndSection();
- }
- }
-
- private struct PlayfieldData
- {
- public uint grp;
- public byte pfColor;
- public byte bkColor;
- public bool reflect;
- public bool score;
- public bool priority;
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("PlayField");
- ser.Sync("grp", ref grp);
- ser.Sync("pfColor", ref pfColor);
- ser.Sync("bkColor", ref bkColor);
- ser.Sync("reflect", ref reflect);
- ser.Sync("score", ref score);
- ser.Sync("priority", ref priority);
- ser.EndSection();
- }
- }
-
- private struct HmoveData
- {
- public bool hmoveEnabled;
- public bool hmoveJustStarted;
- public bool lateHBlankReset;
- public bool decCntEnabled;
-
- public bool player0Latch;
- public bool player1Latch;
- public bool missile0Latch;
- public bool missile1Latch;
- public bool ballLatch;
-
- public byte hmoveDelayCnt;
-
- public byte hmoveCnt;
-
- public byte player0Cnt;
- public byte player1Cnt;
- public byte missile0Cnt;
- public byte missile1Cnt;
- public byte ballCnt;
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("HMove");
- ser.Sync("hmoveEnabled", ref hmoveEnabled);
- ser.Sync("hmoveJustStarted", ref hmoveJustStarted);
- ser.Sync("lateHBlankReset", ref lateHBlankReset);
- ser.Sync("decCntEnabled", ref decCntEnabled);
- ser.Sync("player0Latch", ref player0Latch);
- ser.Sync("player1Latch", ref player1Latch);
- ser.Sync("missile0Latch", ref missile0Latch);
- ser.Sync("missile1Latch", ref missile1Latch);
- ser.Sync("ballLatch", ref ballLatch);
- ser.Sync("hmoveDelayCnt", ref hmoveDelayCnt);
- ser.Sync("hmoveCnt", ref hmoveCnt);
- ser.Sync("player0Cnt", ref player0Cnt);
- ser.Sync("player1Cnt", ref player1Cnt);
- ser.Sync("missile0Cnt", ref missile0Cnt);
- ser.Sync("missile1Cnt", ref missile1Cnt);
- ser.Sync("ballCnt", ref ballCnt);
- ser.EndSection();
- }
- };
-
- private readonly List scanlinesBuffer = new List();
-
- private readonly uint[] palette = new uint[]
- {
- 0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0,
- 0xaaaaaa, 0, 0xc0c0c0, 0, 0xd6d6d6, 0, 0xececec, 0,
- 0x484800, 0, 0x69690f, 0, 0x86861d, 0, 0xa2a22a, 0,
- 0xbbbb35, 0, 0xd2d240, 0, 0xe8e84a, 0, 0xfcfc54, 0,
- 0x7c2c00, 0, 0x904811, 0, 0xa26221, 0, 0xb47a30, 0,
- 0xc3903d, 0, 0xd2a44a, 0, 0xdfb755, 0, 0xecc860, 0,
- 0x901c00, 0, 0xa33915, 0, 0xb55328, 0, 0xc66c3a, 0,
- 0xd5824a, 0, 0xe39759, 0, 0xf0aa67, 0, 0xfcbc74, 0,
- 0x940000, 0, 0xa71a1a, 0, 0xb83232, 0, 0xc84848, 0,
- 0xd65c5c, 0, 0xe46f6f, 0, 0xf08080, 0, 0xfc9090, 0,
- 0x840064, 0, 0x97197a, 0, 0xa8308f, 0, 0xb846a2, 0,
- 0xc659b3, 0, 0xd46cc3, 0, 0xe07cd2, 0, 0xec8ce0, 0,
- 0x500084, 0, 0x68199a, 0, 0x7d30ad, 0, 0x9246c0, 0,
- 0xa459d0, 0, 0xb56ce0, 0, 0xc57cee, 0, 0xd48cfc, 0,
- 0x140090, 0, 0x331aa3, 0, 0x4e32b5, 0, 0x6848c6, 0,
- 0x7f5cd5, 0, 0x956fe3, 0, 0xa980f0, 0, 0xbc90fc, 0,
- 0x000094, 0, 0x181aa7, 0, 0x2d32b8, 0, 0x4248c8, 0,
- 0x545cd6, 0, 0x656fe4, 0, 0x7580f0, 0, 0x8490fc, 0,
- 0x001c88, 0, 0x183b9d, 0, 0x2d57b0, 0, 0x4272c2, 0,
- 0x548ad2, 0, 0x65a0e1, 0, 0x75b5ef, 0, 0x84c8fc, 0,
- 0x003064, 0, 0x185080, 0, 0x2d6d98, 0, 0x4288b0, 0,
- 0x54a0c5, 0, 0x65b7d9, 0, 0x75cceb, 0, 0x84e0fc, 0,
- 0x004030, 0, 0x18624e, 0, 0x2d8169, 0, 0x429e82, 0,
- 0x54b899, 0, 0x65d1ae, 0, 0x75e7c2, 0, 0x84fcd4, 0,
- 0x004400, 0, 0x1a661a, 0, 0x328432, 0, 0x48a048, 0,
- 0x5cba5c, 0, 0x6fd26f, 0, 0x80e880, 0, 0x90fc90, 0,
- 0x143c00, 0, 0x355f18, 0, 0x527e2d, 0, 0x6e9c42, 0,
- 0x87b754, 0, 0x9ed065, 0, 0xb4e775, 0, 0xc8fc84, 0,
- 0x303800, 0, 0x505916, 0, 0x6d762b, 0, 0x88923e, 0,
- 0xa0ab4f, 0, 0xb7c25f, 0, 0xccd86e, 0, 0xe0ec7c, 0,
- 0x482c00, 0, 0x694d14, 0, 0x866a26, 0, 0xa28638, 0,
- 0xbb9f47, 0, 0xd2b656, 0, 0xe8cc63, 0, 0xfce070, 0
- };
-
- public int[] frameBuffer = new int[320 * 262];
- public int[] GetVideoBuffer() { return frameBuffer; }
- public int VirtualWidth { get { return 320; } }
- public int BufferWidth { get { return 320; } }
- public int BufferHeight { get { return 262; } }
- public int BackgroundColor { get { return 0; } }
-
- public class Audio
- {
-
- // noise/division control
- public byte AUDC = 0;
-
- // frequency divider
- public byte AUDF = 1;
-
- // volume
- public byte AUDV = 0;
-
- // 2 state counter
- bool sr1 = true;
-
- // 4 bit shift register
- int sr4 = 0x0f;
-
- // 5 bit shift register
- int sr5 = 0x1f;
-
- // 3 state counter
- int sr3 = 2;
-
- // counter based off AUDF
- byte freqcnt;
-
- // latched audio value
- private bool on = true;
-
- private bool run_3()
- {
- sr3++;
- if (sr3 == 3)
- {
- sr3 = 0;
- return true;
- }
-
- return false;
- }
-
- private bool run_4()
- {
- bool ret = (sr4 & 1) != 0;
- bool c = (sr4 & 1) != 0 ^ (sr4 & 2) != 0;
- sr4 = (sr4 >> 1) | (c ? 8 : 0);
- return ret;
- }
-
- private bool run_5()
- {
- bool ret = (sr5 & 1) != 0;
- bool c = (sr5 & 1) != 0 ^ (sr5 & 4) != 0;
- sr5 = (sr5 >> 1) | (c ? 16 : 0);
- return ret;
- }
-
- private bool one_4()
- {
- bool ret = (sr4 & 1) != 0;
- sr4 = (sr4 >> 1) | 8;
- return ret;
- }
-
- private bool one_5()
- {
- bool ret = (sr5 & 1) != 0;
- sr5 = (sr5 >> 1) | 16;
- return ret;
- }
-
- private bool run_1()
- {
- sr1 = !sr1;
- return !sr1;
- }
-
- private bool run_9()
- {
- bool ret = (sr4 & 1) != 0;
- bool c = (sr5 & 1) != 0 ^ (sr4 & 1) != 0;
- sr4 = (sr4 >> 1) | ((sr5 & 1) != 0 ? 8 : 0);
- sr5 = (sr5 >> 1) | (c ? 16 : 0);
- return ret;
- }
-
- ///
- /// call me approx 31k times a second
- ///
- /// 16 bit audio sample
- public short Cycle()
- {
- if (++freqcnt == AUDF)
- {
- freqcnt = 0;
- switch (AUDC)
- {
- case 0x00:
- case 0x0b:
- // Both have a 1 s
- one_5();
- on = one_4();
- break;
-
- case 0x01:
- // Both run, but the 5 bit is ignored
- on = run_4();
- run_5();
- break;
-
- case 0x02:
- if ((sr5 & 0x0f) == 0 || (sr5 & 0x0f) == 0x0f)
- {
- on = run_4();
- break;
- }
- run_5();
- break;
-
- case 0x03:
- if (run_5())
- {
- on = run_4();
- break;
- }
- break;
-
- case 0x04:
- run_5();
- one_4();
- on = run_1();
- break;
-
- case 0x05:
- one_5();
- run_4();
- on = run_1();
- break;
-
- case 0x06:
- case 0x0a:
- run_4(); // ???
- run_5();
- if ((sr5 & 0x0f) == 0)
- on = false;
- else if ((sr5 & 0x0f) == 0x0f)
- on = true;
- break;
-
- case 0x07:
- case 0x09:
- run_4(); // ???
- on = run_5();
- break;
- case 0x08:
- on = run_9();
- break;
- case 0x0c:
- case 0x0d:
- if (run_3())
- on = run_1();
- break;
- case 0x0e:
- if (run_3())
- goto case 0x06;
- break;
- case 0x0f:
- if (run_3())
- goto case 0x07;
- break;
- }
- }
- return (short)(on ? AUDV * 1092 : 0);
- }
-
- public void SyncState(Serializer ser)
- {
- ser.Sync("AUDC", ref AUDC);
- ser.Sync("AUDF", ref AUDF);
- ser.Sync("AUDV", ref AUDV);
- ser.Sync("sr1", ref sr1);
- ser.Sync("sr3", ref sr3);
- ser.Sync("sr4", ref sr4);
- ser.Sync("sr5", ref sr5);
- ser.Sync("freqcnt", ref freqcnt);
- ser.Sync("on", ref on);
- }
- }
-
- public Audio[] AUD = { new Audio(), new Audio() };
-
- public void GetSamples(short[] samples)
- {
- var moreSamples = new short[523];
- for (int i = 0; i < moreSamples.Length; i++)
- {
- for (int snd = 0; snd < 2; snd++)
- {
- moreSamples[i] += AUD[snd].Cycle();
- }
- }
-
- for (int i = 0; i < samples.Length / 2; i++)
- {
- samples[i * 2] = moreSamples[(int)(((double)moreSamples.Length / (double)(samples.Length / 2)) * i)];
- samples[(i * 2) + 1] = samples[i * 2];
- }
- }
- public void DiscardSamples() { }
- public int MaxVolume { get; set; }
-
-
- // Execute TIA cycles
- public void Execute(int cycles)
- {
- // Still ignoring cycles...
-
- // Assume we're on the left side of the screen for now
- bool rightSide = false;
-
- // ---- Things that happen only in the drawing section ----
- // TODO: Remove this magic number (17). It depends on the HMOVE
- if ((_hsyncCnt / 4) >= (hmove.lateHBlankReset ? 19 : 17))
- {
- // TODO: Remove this magic number
- if ((_hsyncCnt / 4) >= 37)
- {
- rightSide = true;
- }
-
- // The bit number of the PF data which we want
- int pfBit = ((_hsyncCnt / 4) - 17) % 20;
-
- // Create the mask for the bit we want
- // Note that bits are arranged 0 1 2 3 4 .. 19
- int pfMask = 1 << (20 - 1 - pfBit);
-
- // Reverse the mask if on the right and playfield is reflected
- if (rightSide && playField.reflect)
- {
- pfMask = ReverseBits(pfMask, 20);
- }
-
- // Calculate collisions
- byte collisions = 0x00;
-
- if ((playField.grp & pfMask) != 0)
- {
- collisions |= CXPF;
- }
-
-
- // ---- Player 0 ----
- collisions |= (player0.Tick() ? CXP0 : (byte)0x00);
-
- // ---- Missile 0 ----
- collisions |= (player0.Missile.Tick() ? CXM0 : (byte)0x00);
-
- // ---- Player 1 ----
- collisions |= (player1.Tick() ? CXP1 : (byte)0x00);
-
- // ---- Missile 0 ----
- collisions |= (player1.Missile.Tick() ? CXM1 : (byte)0x00);
-
- // ---- Ball ----
- collisions |= (ball.Tick() ? CXBL : (byte)0x00);
-
-
- // Pick the pixel color from collisions
- uint pixelColor = 0x000000;
- if (_core.Settings.ShowBG)
- {
- pixelColor = palette[playField.bkColor];
- }
-
- if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
- {
- if (playField.score)
- {
- if (!rightSide)
- {
- pixelColor = palette[player0.color];
- }
- else
- {
- pixelColor = palette[player1.color];
- }
- }
- else
- {
- pixelColor = palette[playField.pfColor];
- }
- }
-
- if ((collisions & CXBL) != 0)
- {
- ball.collisions |= collisions;
- if (_core.Settings.ShowBall)
- {
- pixelColor = palette[playField.pfColor];
- }
- }
-
- if ((collisions & CXM1) != 0)
- {
- player1.Missile.Collisions |= collisions;
- if (_core.Settings.ShowMissle2)
- {
- pixelColor = palette[player1.color];
- }
- }
-
- if ((collisions & CXP1) != 0)
- {
- player1.collisions |= collisions;
- if (_core.Settings.ShowPlayer2)
- {
- pixelColor = palette[player1.color];
- }
- }
-
- if ((collisions & CXM0) != 0)
- {
- player0.Missile.Collisions |= collisions;
- if (_core.Settings.ShowMissle1)
- {
- pixelColor = palette[player0.color];
- }
- }
-
- if ((collisions & CXP0) != 0)
- {
- player0.collisions |= collisions;
- if (_core.Settings.ShowPlayer1)
- {
- pixelColor = palette[player0.color];
- }
- }
-
- if (playField.priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
- {
- if (playField.score)
- {
- pixelColor = !rightSide ? palette[player0.color] : palette[player1.color];
- }
- else
- {
- pixelColor = palette[playField.pfColor];
- }
- }
-
- // Handle vblank
- if (vblankEnabled)
- {
- pixelColor = 0x000000;
- }
-
- // Add the pixel to the scanline
- // TODO: Remove this magic number (68)
- scanline[_hsyncCnt - 68] = pixelColor;
- }
-
-
- // ---- Things that happen every time ----
-
- // Handle HMOVE
- if (hmove.hmoveEnabled)
- {
- // On the first time, set the latches and counters
- if (hmove.hmoveJustStarted)
- {
- hmove.player0Latch = true;
- hmove.player0Cnt = 0;
-
- hmove.missile0Latch = true;
- hmove.missile0Cnt = 0;
-
- hmove.player1Latch = true;
- hmove.player1Cnt = 0;
-
- hmove.missile1Latch = true;
- hmove.missile1Cnt = 0;
-
- hmove.ballLatch = true;
- hmove.ballCnt = 0;
-
- hmove.hmoveCnt = 0;
-
- hmove.hmoveJustStarted = false;
- hmove.lateHBlankReset = true;
- hmove.decCntEnabled = false;
- }
-
- if (hmove.decCntEnabled)
- {
- // Actually do stuff only evey 4 pulses
- if (hmove.hmoveCnt == 0)
- {
- // If the latch is still set
- if (hmove.player0Latch)
- {
- // If the move counter still has a bit in common with the HM register
- if (((15 - hmove.player0Cnt) ^ ((player0.HM & 0x07) | ((~(player0.HM & 0x08)) & 0x08))) != 0x0F)
- {
- // "Clock-Stuffing"
- player0.Tick();
-
- // Increase by 1, max of 15
- hmove.player0Cnt++;
- hmove.player0Cnt %= 16;
- }
- else
- {
- hmove.player0Latch = false;
- }
- }
-
- if (hmove.missile0Latch)
- {
- if (hmove.missile0Cnt == 15)
- { }
-
- // If the move counter still has a bit in common with the HM register
- if (((15 - hmove.missile0Cnt) ^ ((player0.Missile.Hm & 0x07) | ((~(player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
- {
- // "Clock-Stuffing"
- player0.Missile.Tick();
-
- // Increase by 1, max of 15
- hmove.missile0Cnt++;
- hmove.missile0Cnt %= 16;
- }
- else
- {
- hmove.missile0Latch = false;
- hmove.missile0Cnt = 0;
- }
- }
-
- if (hmove.player1Latch)
- {
- // If the move counter still has a bit in common with the HM register
- if (((15 - hmove.player1Cnt) ^ ((player1.HM & 0x07) | ((~(player1.HM & 0x08)) & 0x08))) != 0x0F)
- {
- // "Clock-Stuffing"
- player1.Tick();
-
- // Increase by 1, max of 15
- hmove.player1Cnt++;
- hmove.player1Cnt %= 16;
- }
- else
- {
- hmove.player1Latch = false;
- }
- }
-
- if (hmove.missile1Latch)
- {
- // If the move counter still has a bit in common with the HM register
- if (((15 - hmove.missile1Cnt) ^ ((player1.Missile.Hm & 0x07) | ((~(player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
- {
- // "Clock-Stuffing"
- player1.Missile.Tick();
-
- // Increase by 1, max of 15
- hmove.missile1Cnt++;
- hmove.missile1Cnt %= 16;
- }
- else
- {
- hmove.missile1Latch = false;
- }
- }
-
- if (hmove.ballLatch)
- {
- // If the move counter still has a bit in common with the HM register
- if (((15 - hmove.ballCnt) ^ ((ball.HM & 0x07) | ((~(ball.HM & 0x08)) & 0x08))) != 0x0F)
- {
- // "Clock-Stuffing"
- ball.Tick();
-
- // Increase by 1, max of 15
- hmove.ballCnt++;
- hmove.ballCnt %= 16;
- }
- else
- {
- hmove.ballLatch = false;
- }
- }
-
- if (!hmove.player0Latch && !hmove.player1Latch && !hmove.ballLatch && !hmove.missile0Latch && !hmove.missile1Latch)
- {
- hmove.hmoveEnabled = false;
- hmove.decCntEnabled = false;
- hmove.hmoveDelayCnt = 0;
- }
- }
-
- hmove.hmoveCnt++;
- hmove.hmoveCnt %= 4;
- }
-
- if (hmove.hmoveDelayCnt < 6)
- {
- hmove.hmoveDelayCnt++;
- }
-
- if (hmove.hmoveDelayCnt == 6)
- {
- hmove.hmoveDelayCnt++;
- hmove.hmoveCnt = 0;
- hmove.decCntEnabled = true;
- }
- }
-
- // Increment the hsync counter
- _hsyncCnt++;
- _hsyncCnt %= 228;
-
- // End of the line? Add it to the buffer!
- if (_hsyncCnt == 0)
- {
- hmove.lateHBlankReset = false;
- scanlinesBuffer.Add(scanline);
- scanline = new uint[160];
- }
- if (scanlinesBuffer.Count >= 1024)
- {
- /* if a rom never toggles vsync, FrameAdvance() will hang while consuming
- * huge amounts of ram. this is most certainly due to emulation defects
- * that need to be fixed; but it's preferable to not crash the emulator
- * in such situations
- */
- OutputFrame();
- scanlinesBuffer.Clear();
- FrameComplete = true;
- }
- }
-
- // TODO: Remove the magic numbers from this function to allow for a variable height screen
- public void OutputFrame()
- {
- for (int row = 0; row < 262; row++)
- {
- for (int col = 0; col < 320; col++)
- {
- if (scanlinesBuffer.Count > row)
- {
- frameBuffer[(row * 320) + col] = (int)(scanlinesBuffer[row][col / 2] | 0xFF000000);
- }
- else
- {
- frameBuffer[(row * 320) + col] = unchecked((int)0xFF000000);
- }
- }
- }
- }
-
- public byte ReadMemory(ushort addr, bool peek)
- {
- ushort maskedAddr = (ushort)(addr & 0x000F);
- if (maskedAddr == 0x00) // CXM0P
- {
- return (byte)((((player0.Missile.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((player0.Missile.Collisions & CXP0) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x01) // CXM1P
- {
- return (byte)((((player1.Missile.Collisions & CXP0) != 0) ? 0x80 : 0x00) | (((player1.Missile.Collisions & CXP1) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x02) // CXP0FB
- {
- return (byte)((((player0.collisions & CXPF) != 0) ? 0x80 : 0x00) | (((player0.collisions & CXBL) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x03) // CXP1FB
- {
- return (byte)((((player1.collisions & CXPF) != 0) ? 0x80 : 0x00) | (((player1.collisions & CXBL) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x04) // CXM0FB
- {
- return (byte)((((player0.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((player0.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x05) // CXM1FB
- {
- return (byte)((((player1.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((player1.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x06) // CXBLPF
- {
- return (byte)(((ball.collisions & CXPF) != 0) ? 0x80 : 0x00);
- }
-
- if (maskedAddr == 0x07) // CXPPMM
- {
- return (byte)((((player0.collisions & CXP1) != 0) ? 0x80 : 0x00) | (((player0.Missile.Collisions & CXM1) != 0) ? 0x40 : 0x00));
- }
-
- if (maskedAddr == 0x08) // INPT0
- {
- // Changing the hard coded value will change the paddle position. The range seems to be roughly 0-56000 according to values from stella
- // 6105 roughly centers the paddle in Breakout
- if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105)
- {
- return 0x80;
- }
- else
- {
- return 0x00;
- }
- }
-
- if (maskedAddr == 0x0C) // INPT4
- {
- return (byte)((_core.ReadControls1(peek) & 0x08) != 0 ? 0x80 : 0x00);
- }
-
- if (maskedAddr == 0x0D) // INPT5
- {
- return (byte)((_core.ReadControls2(peek) & 0x08) != 0 ? 0x80 : 0x00);
- }
-
- return 0x00;
- }
-
- public void WriteMemory(ushort addr, byte value)
- {
- ushort maskedAddr = (ushort)(addr & 0x3f);
-
- if (maskedAddr == 0x00) // VSYNC
- {
- if ((value & 0x02) != 0)
- {
- // Frame is complete, output to buffer
- vsyncEnabled = true;
- }
- else if (vsyncEnabled)
- {
- // When VSYNC is disabled, this will be the first line of the new frame
-
- // write to frame buffer
- OutputFrame();
-
- // Clear all from last frame
- scanlinesBuffer.Clear();
-
- // Frame is done
- FrameComplete = true;
-
- vsyncEnabled = false;
-
- // Do not reset hsync, since we're on the first line of the new frame
- // hsyncCnt = 0;
- }
- }
- else if (maskedAddr == 0x01) // VBLANK
- {
- vblankEnabled = (value & 0x02) != 0;
- _capCharging = (value & 0x80) == 0;
- if ((value & 0x80) == 0)
- {
- _capChargeStart = _core.Cpu.TotalExecutedCycles;
- }
- }
- else if (maskedAddr == 0x02) // WSYNC
- {
- int count = 0;
- while (_hsyncCnt > 0)
- {
- count++;
- Execute(1);
-
- // Add a cycle to the cpu every 3 TIA clocks (corrects timer error in M6532)
- if (count % 3 == 0)
- {
- _core.M6532.timer.tick();
- }
- }
- }
- else if (maskedAddr == 0x04) // NUSIZ0
- {
- player0.nusiz = (byte)(value & 0x37);
- player0.Missile.Size = (byte)((value & 0x30) >> 4);
- player0.Missile.Number = (byte)(value & 0x07);
- }
- else if (maskedAddr == 0x05) // NUSIZ1
- {
- player1.nusiz = (byte)(value & 0x37);
- player1.Missile.Size = (byte)((value & 0x30) >> 4);
- player1.Missile.Number = (byte)(value & 0x07);
- }
- else if (maskedAddr == 0x06) // COLUP0
- {
- player0.color = (byte)(value & 0xFE);
- }
- else if (maskedAddr == 0x07) // COLUP1
- {
- player1.color = (byte)(value & 0xFE);
- }
- else if (maskedAddr == 0x08) // COLUPF
- {
- playField.pfColor = (byte)(value & 0xFE);
- }
- else if (maskedAddr == 0x09) // COLUBK
- {
- playField.bkColor = (byte)(value & 0xFE);
- }
- else if (maskedAddr == 0x0A) // CTRLPF
- {
- playField.reflect = (value & 0x01) != 0;
- playField.priority = (value & 0x04) != 0;
-
- ball.size = (byte)((value & 0x30) >> 4);
- }
- else if (maskedAddr == 0x0B) // REFP0
- {
- player0.reflect = (value & 0x08) != 0;
- }
- else if (maskedAddr == 0x0C) // REFP1
- {
- player1.reflect = (value & 0x08) != 0;
- }
- else if (maskedAddr == 0x0D) // PF0
- {
- playField.grp = (uint)((playField.grp & 0x0FFFF) + ((ReverseBits(value, 8) & 0x0F) << 16));
- }
- else if (maskedAddr == 0x0E) // PF1
- {
- playField.grp = (uint)((playField.grp & 0xF00FF) + (value << 8));
- }
- else if (maskedAddr == 0x0F) // PF2
- {
- playField.grp = (uint)((playField.grp & 0xFFF00) + ReverseBits(value,8));
- }
- else if (maskedAddr == 0x10) // RESP0
- {
- // Borrowed from EMU7800. Apparently resetting between 68 and 76 has strange results.
- if (_hsyncCnt < 69)
- {
- player0.hPosCnt = 0;
- player0.resetCnt = 0;
- player0.reset = true;
- }
- else if (_hsyncCnt == 69)
- {
- player0.resetCnt = 3;
- }
- else if (_hsyncCnt == 72)
- {
- player0.resetCnt = 2;
- }
- else if (_hsyncCnt == 75)
- {
- player0.resetCnt = 1;
- }
- else
- {
- player0.resetCnt = 0;
- }
- }
- else if (maskedAddr == 0x11) // RESP1
- {
- // Borrowed from EMU7800. Apparently resetting between 68 and 76 has strange results.
- // This fixes some graphic glitches with Frostbite
- if (_hsyncCnt < 69)
- {
- player1.hPosCnt = 0;
- player1.resetCnt = 0;
- player1.reset = true;
- }
- else if (_hsyncCnt == 69)
- {
- player1.resetCnt = 3;
- }
- else if (_hsyncCnt == 72)
- {
- player1.resetCnt = 2;
- }
- else if (_hsyncCnt == 75)
- {
- player1.resetCnt = 1;
- }
- else
- {
- player1.resetCnt = 0;
- }
- }
- else if (maskedAddr == 0x12) // RESM0
- {
- player0.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
- }
- else if (maskedAddr == 0x13) // RESM1
- {
- player1.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
- }
- else if (maskedAddr == 0x14) // RESBL
- {
- ball.hPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
- }
- else if (maskedAddr == 0x15) // AUDC0
- {
- AUD[0].AUDC = (byte)(value & 15);
- }
- else if (maskedAddr == 0x16) // AUDC1
- {
- AUD[1].AUDC = (byte)(value & 15);
- }
- else if (maskedAddr == 0x17) // AUDF0
- {
- AUD[0].AUDF = (byte)((value & 31) + 1);
- }
- else if (maskedAddr == 0x18) // AUDF1
- {
- AUD[1].AUDF = (byte)((value & 31) + 1);
- }
- else if (maskedAddr == 0x19) // AUDV0
- {
- AUD[0].AUDV = (byte)(value & 15);
- }
- else if (maskedAddr == 0x1A) // AUDV1
- {
- AUD[1].AUDV = (byte)(value & 15);
- }
- else if (maskedAddr == 0x1B) // GRP0
- {
- player0.grp = value;
- player1.dgrp = player1.grp;
- }
- else if (maskedAddr == 0x1C) // GRP1
- {
- player1.grp = value;
- player0.dgrp = player0.grp;
-
- // TODO: Find a game that uses this functionality and test it
- ball.denabled = ball.enabled;
- }
- else if (maskedAddr == 0x1D) // ENAM0
- {
- player0.Missile.Enabled = (value & 0x02) != 0;
- }
- else if (maskedAddr == 0x1E) // ENAM1
- {
- player1.Missile.Enabled = (value & 0x02) != 0;
- }
- else if (maskedAddr == 0x1F) // ENABL
- {
- ball.enabled = (value & 0x02) != 0;
- }
- else if (maskedAddr == 0x20) // HMP0
- {
- player0.HM = (byte)((value & 0xF0) >> 4);
- }
- else if (maskedAddr == 0x21) // HMP1
- {
- player1.HM = (byte)((value & 0xF0) >> 4);
- }
- else if (maskedAddr == 0x22) // HMM0
- {
- player0.Missile.Hm = (byte)((value & 0xF0) >> 4);
- }
- else if (maskedAddr == 0x23) // HMM1
- {
- player1.Missile.Hm = (byte)((value & 0xF0) >> 4);
- }
- else if (maskedAddr == 0x24) // HMBL
- {
- ball.HM = (byte)((value & 0xF0) >> 4);
- }
- else if (maskedAddr == 0x25) // VDELP0
- {
- player0.delay = (value & 0x01) != 0;
- }
- else if (maskedAddr == 0x26) // VDELP1
- {
- player1.delay = (value & 0x01) != 0;
- }
- else if (maskedAddr == 0x27) // VDELBL
- {
- ball.delay = (value & 0x01) != 0;
- }
- else if (maskedAddr == 0x28) // RESMP0
- {
- player0.Missile.ResetToPlayer = (value & 0x02) != 0;
- }
- else if (maskedAddr == 0x29) // RESMP1
- {
- player1.Missile.ResetToPlayer = (value & 0x02) != 0;
- }
- else if (maskedAddr == 0x2A) // HMOVE
- {
- hmove.hmoveEnabled = true;
- hmove.hmoveJustStarted = true;
- hmove.hmoveDelayCnt = 0;
- }
- else if (maskedAddr == 0x2B) // HMCLR
- {
- player0.HM = 0;
- player0.Missile.Hm = 0;
- player1.HM = 0;
- player1.Missile.Hm = 0;
- ball.HM = 0;
- }
- else if (maskedAddr == 0x2C) // CXCLR
- {
- player0.collisions = 0;
- player0.Missile.Collisions = 0;
- player1.collisions = 0;
- player1.Missile.Collisions = 0;
- ball.collisions = 0;
- }
- }
-
- private static int ReverseBits(int value, int bits)
- {
- int result = 0;
- for (int i = 0; i < bits; i++)
- {
- result = (result << 1) | ((value >> i) & 0x01);
- }
- return result;
- }
-
- public void SyncState(Serializer ser)
- {
- ser.BeginSection("TIA");
- ball.SyncState(ser);
- hmove.SyncState(ser);
- ser.Sync("hsyncCnt", ref _hsyncCnt);
- ser.BeginSection("Player0");
- player0.SyncState(ser);
- ser.EndSection();
- ser.BeginSection("Player1");
- player1.SyncState(ser);
- ser.EndSection();
- playField.SyncState(ser);
- ser.EndSection();
- }
- }
-}
\ No newline at end of file
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs
new file mode 100644
index 0000000000..42454bcb22
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/TIA.cs
@@ -0,0 +1,858 @@
+using System.Collections.Generic;
+
+using BizHawk.Common;
+using BizHawk.Emulation.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ // Emulates the TIA
+ public partial class TIA : IVideoProvider, ISoundProvider
+ {
+ private const byte CXP0 = 0x01;
+ private const byte CXP1 = 0x02;
+ private const byte CXM0 = 0x04;
+ private const byte CXM1 = 0x08;
+ private const byte CXPF = 0x10;
+ private const byte CXBL = 0x20;
+
+ private readonly Atari2600 _core;
+ private readonly List _scanlinesBuffer = new List();
+ private readonly uint[] _palette = new uint[]
+ {
+ 0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0,
+ 0xaaaaaa, 0, 0xc0c0c0, 0, 0xd6d6d6, 0, 0xececec, 0,
+ 0x484800, 0, 0x69690f, 0, 0x86861d, 0, 0xa2a22a, 0,
+ 0xbbbb35, 0, 0xd2d240, 0, 0xe8e84a, 0, 0xfcfc54, 0,
+ 0x7c2c00, 0, 0x904811, 0, 0xa26221, 0, 0xb47a30, 0,
+ 0xc3903d, 0, 0xd2a44a, 0, 0xdfb755, 0, 0xecc860, 0,
+ 0x901c00, 0, 0xa33915, 0, 0xb55328, 0, 0xc66c3a, 0,
+ 0xd5824a, 0, 0xe39759, 0, 0xf0aa67, 0, 0xfcbc74, 0,
+ 0x940000, 0, 0xa71a1a, 0, 0xb83232, 0, 0xc84848, 0,
+ 0xd65c5c, 0, 0xe46f6f, 0, 0xf08080, 0, 0xfc9090, 0,
+ 0x840064, 0, 0x97197a, 0, 0xa8308f, 0, 0xb846a2, 0,
+ 0xc659b3, 0, 0xd46cc3, 0, 0xe07cd2, 0, 0xec8ce0, 0,
+ 0x500084, 0, 0x68199a, 0, 0x7d30ad, 0, 0x9246c0, 0,
+ 0xa459d0, 0, 0xb56ce0, 0, 0xc57cee, 0, 0xd48cfc, 0,
+ 0x140090, 0, 0x331aa3, 0, 0x4e32b5, 0, 0x6848c6, 0,
+ 0x7f5cd5, 0, 0x956fe3, 0, 0xa980f0, 0, 0xbc90fc, 0,
+ 0x000094, 0, 0x181aa7, 0, 0x2d32b8, 0, 0x4248c8, 0,
+ 0x545cd6, 0, 0x656fe4, 0, 0x7580f0, 0, 0x8490fc, 0,
+ 0x001c88, 0, 0x183b9d, 0, 0x2d57b0, 0, 0x4272c2, 0,
+ 0x548ad2, 0, 0x65a0e1, 0, 0x75b5ef, 0, 0x84c8fc, 0,
+ 0x003064, 0, 0x185080, 0, 0x2d6d98, 0, 0x4288b0, 0,
+ 0x54a0c5, 0, 0x65b7d9, 0, 0x75cceb, 0, 0x84e0fc, 0,
+ 0x004030, 0, 0x18624e, 0, 0x2d8169, 0, 0x429e82, 0,
+ 0x54b899, 0, 0x65d1ae, 0, 0x75e7c2, 0, 0x84fcd4, 0,
+ 0x004400, 0, 0x1a661a, 0, 0x328432, 0, 0x48a048, 0,
+ 0x5cba5c, 0, 0x6fd26f, 0, 0x80e880, 0, 0x90fc90, 0,
+ 0x143c00, 0, 0x355f18, 0, 0x527e2d, 0, 0x6e9c42, 0,
+ 0x87b754, 0, 0x9ed065, 0, 0xb4e775, 0, 0xc8fc84, 0,
+ 0x303800, 0, 0x505916, 0, 0x6d762b, 0, 0x88923e, 0,
+ 0xa0ab4f, 0, 0xb7c25f, 0, 0xccd86e, 0, 0xe0ec7c, 0,
+ 0x482c00, 0, 0x694d14, 0, 0x866a26, 0, 0xa28638, 0,
+ 0xbb9f47, 0, 0xd2b656, 0, 0xe8cc63, 0, 0xfce070, 0
+ };
+
+ private byte _hsyncCnt;
+ private int _capChargeStart;
+ private bool _capCharging;
+ private bool _vblankEnabled;
+ private bool _vsyncEnabled;
+ private uint[] _scanline = new uint[160];
+
+ private PlayerData _player0;
+ private PlayerData _player1;
+ private PlayfieldData _playField;
+ private HMoveData _hmove;
+ private BallData _ball;
+
+ public int[] FrameBuffer = new int[320 * 262];
+ public Audio[] AUD = { new Audio(), new Audio() };
+
+ public TIA(Atari2600 core)
+ {
+ _core = core;
+ _player0.ScanCnt = 8;
+ _player1.ScanCnt = 8;
+ }
+
+ public bool FrameComplete { get; set; }
+ public int MaxVolume { get; set; }
+
+ public int VirtualWidth
+ {
+ get { return 320; }
+ }
+
+ public int BufferWidth
+ {
+ get { return 320; }
+ }
+
+ public int BufferHeight
+ {
+ get { return 262; }
+ }
+
+ public int BackgroundColor
+ {
+ get { return 0; }
+ }
+
+ public int[] GetVideoBuffer()
+ {
+ return FrameBuffer;
+ }
+
+ public void GetSamples(short[] samples)
+ {
+ var moreSamples = new short[523];
+ for (int i = 0; i < moreSamples.Length; i++)
+ {
+ for (int snd = 0; snd < 2; snd++)
+ {
+ moreSamples[i] += AUD[snd].Cycle();
+ }
+ }
+
+ for (int i = 0; i < samples.Length / 2; i++)
+ {
+ samples[i * 2] = moreSamples[(int)(((double)moreSamples.Length / (double)(samples.Length / 2)) * i)];
+ samples[(i * 2) + 1] = samples[i * 2];
+ }
+ }
+
+ public void DiscardSamples() { }
+
+ // Execute TIA cycles
+ public void Execute(int cycles)
+ {
+ // Still ignoring cycles...
+
+ // Assume we're on the left side of the screen for now
+ var rightSide = false;
+
+ // ---- Things that happen only in the drawing section ----
+ // TODO: Remove this magic number (17). It depends on the HMOVE
+ if ((_hsyncCnt / 4) >= (_hmove.LateHBlankReset ? 19 : 17))
+ {
+ // TODO: Remove this magic number
+ if ((_hsyncCnt / 4) >= 37)
+ {
+ rightSide = true;
+ }
+
+ // The bit number of the PF data which we want
+ int pfBit = ((_hsyncCnt / 4) - 17) % 20;
+
+ // Create the mask for the bit we want
+ // Note that bits are arranged 0 1 2 3 4 .. 19
+ int pfMask = 1 << (20 - 1 - pfBit);
+
+ // Reverse the mask if on the right and playfield is reflected
+ if (rightSide && _playField.Reflect)
+ {
+ pfMask = ReverseBits(pfMask, 20);
+ }
+
+ // Calculate collisions
+ byte collisions = 0x00;
+
+ if ((_playField.Grp & pfMask) != 0)
+ {
+ collisions |= CXPF;
+ }
+
+
+ // ---- Player 0 ----
+ collisions |= _player0.Tick() ? CXP0 : (byte)0x00;
+
+ // ---- Missile 0 ----
+ collisions |= _player0.Missile.Tick() ? CXM0 : (byte)0x00;
+
+ // ---- Player 1 ----
+ collisions |= _player1.Tick() ? CXP1 : (byte)0x00;
+
+ // ---- Missile 0 ----
+ collisions |= _player1.Missile.Tick() ? CXM1 : (byte)0x00;
+
+ // ---- Ball ----
+ collisions |= _ball.Tick() ? CXBL : (byte)0x00;
+
+
+ // Pick the pixel color from collisions
+ uint pixelColor = 0x000000;
+ if (_core.Settings.ShowBG)
+ {
+ pixelColor = _palette[_playField.BkColor];
+ }
+
+ if ((collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
+ {
+ if (_playField.Score)
+ {
+ if (!rightSide)
+ {
+ pixelColor = _palette[_player0.Color];
+ }
+ else
+ {
+ pixelColor = _palette[_player1.Color];
+ }
+ }
+ else
+ {
+ pixelColor = _palette[_playField.PfColor];
+ }
+ }
+
+ if ((collisions & CXBL) != 0)
+ {
+ _ball.Collisions |= collisions;
+ if (_core.Settings.ShowBall)
+ {
+ pixelColor = _palette[_playField.PfColor];
+ }
+ }
+
+ if ((collisions & CXM1) != 0)
+ {
+ _player1.Missile.Collisions |= collisions;
+ if (_core.Settings.ShowMissle2)
+ {
+ pixelColor = _palette[_player1.Color];
+ }
+ }
+
+ if ((collisions & CXP1) != 0)
+ {
+ _player1.Collisions |= collisions;
+ if (_core.Settings.ShowPlayer2)
+ {
+ pixelColor = _palette[_player1.Color];
+ }
+ }
+
+ if ((collisions & CXM0) != 0)
+ {
+ _player0.Missile.Collisions |= collisions;
+ if (_core.Settings.ShowMissle1)
+ {
+ pixelColor = _palette[_player0.Color];
+ }
+ }
+
+ if ((collisions & CXP0) != 0)
+ {
+ _player0.Collisions |= collisions;
+ if (_core.Settings.ShowPlayer1)
+ {
+ pixelColor = _palette[_player0.Color];
+ }
+ }
+
+ if (_playField.Priority && (collisions & CXPF) != 0 && _core.Settings.ShowPlayfield)
+ {
+ if (_playField.Score)
+ {
+ pixelColor = !rightSide ? _palette[_player0.Color] : _palette[_player1.Color];
+ }
+ else
+ {
+ pixelColor = _palette[_playField.PfColor];
+ }
+ }
+
+ // Handle vblank
+ if (_vblankEnabled)
+ {
+ pixelColor = 0x000000;
+ }
+
+ // Add the pixel to the scanline
+ // TODO: Remove this magic number (68)
+ _scanline[_hsyncCnt - 68] = pixelColor;
+ }
+
+ // ---- Things that happen every time ----
+
+ // Handle HMOVE
+ if (_hmove.HMoveEnabled)
+ {
+ // On the first time, set the latches and counters
+ if (_hmove.HMoveJustStarted)
+ {
+ _hmove.Player0Latch = true;
+ _hmove.Player0Cnt = 0;
+
+ _hmove.Missile0Latch = true;
+ _hmove.Missile0Cnt = 0;
+
+ _hmove.Player1Latch = true;
+ _hmove.Player1Cnt = 0;
+
+ _hmove.Missile1Latch = true;
+ _hmove.Missile1Cnt = 0;
+
+ _hmove.BallLatch = true;
+ _hmove.BallCnt = 0;
+
+ _hmove.HMoveCnt = 0;
+
+ _hmove.HMoveJustStarted = false;
+ _hmove.LateHBlankReset = true;
+ _hmove.DecCntEnabled = false;
+ }
+
+ if (_hmove.DecCntEnabled)
+ {
+ // Actually do stuff only evey 4 pulses
+ if (_hmove.HMoveCnt == 0)
+ {
+ // If the latch is still set
+ if (_hmove.Player0Latch)
+ {
+ // If the move counter still has a bit in common with the HM register
+ if (((15 - _hmove.Player0Cnt) ^ ((_player0.HM & 0x07) | ((~(_player0.HM & 0x08)) & 0x08))) != 0x0F)
+ {
+ // "Clock-Stuffing"
+ _player0.Tick();
+
+ // Increase by 1, max of 15
+ _hmove.Player0Cnt++;
+ _hmove.Player0Cnt %= 16;
+ }
+ else
+ {
+ _hmove.Player0Latch = false;
+ }
+ }
+
+ if (_hmove.Missile0Latch)
+ {
+ if (_hmove.Missile0Cnt == 15)
+ { }
+
+ // If the move counter still has a bit in common with the HM register
+ if (((15 - _hmove.Missile0Cnt) ^ ((_player0.Missile.Hm & 0x07) | ((~(_player0.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
+ {
+ // "Clock-Stuffing"
+ _player0.Missile.Tick();
+
+ // Increase by 1, max of 15
+ _hmove.Missile0Cnt++;
+ _hmove.Missile0Cnt %= 16;
+ }
+ else
+ {
+ _hmove.Missile0Latch = false;
+ _hmove.Missile0Cnt = 0;
+ }
+ }
+
+ if (_hmove.Player1Latch)
+ {
+ // If the move counter still has a bit in common with the HM register
+ if (((15 - _hmove.Player1Cnt) ^ ((_player1.HM & 0x07) | ((~(_player1.HM & 0x08)) & 0x08))) != 0x0F)
+ {
+ // "Clock-Stuffing"
+ _player1.Tick();
+
+ // Increase by 1, max of 15
+ _hmove.Player1Cnt++;
+ _hmove.Player1Cnt %= 16;
+ }
+ else
+ {
+ _hmove.Player1Latch = false;
+ }
+ }
+
+ if (_hmove.Missile1Latch)
+ {
+ // If the move counter still has a bit in common with the HM register
+ if (((15 - _hmove.Missile1Cnt) ^ ((_player1.Missile.Hm & 0x07) | ((~(_player1.Missile.Hm & 0x08)) & 0x08))) != 0x0F)
+ {
+ // "Clock-Stuffing"
+ _player1.Missile.Tick();
+
+ // Increase by 1, max of 15
+ _hmove.Missile1Cnt++;
+ _hmove.Missile1Cnt %= 16;
+ }
+ else
+ {
+ _hmove.Missile1Latch = false;
+ }
+ }
+
+ if (_hmove.BallLatch)
+ {
+ // If the move counter still has a bit in common with the HM register
+ if (((15 - _hmove.BallCnt) ^ ((_ball.HM & 0x07) | ((~(_ball.HM & 0x08)) & 0x08))) != 0x0F)
+ {
+ // "Clock-Stuffing"
+ _ball.Tick();
+
+ // Increase by 1, max of 15
+ _hmove.BallCnt++;
+ _hmove.BallCnt %= 16;
+ }
+ else
+ {
+ _hmove.BallLatch = false;
+ }
+ }
+
+ if (!_hmove.Player0Latch && !_hmove.Player1Latch && !_hmove.BallLatch && !_hmove.Missile0Latch && !_hmove.Missile1Latch)
+ {
+ _hmove.HMoveEnabled = false;
+ _hmove.DecCntEnabled = false;
+ _hmove.HMoveDelayCnt = 0;
+ }
+ }
+
+ _hmove.HMoveCnt++;
+ _hmove.HMoveCnt %= 4;
+ }
+
+ if (_hmove.HMoveDelayCnt < 6)
+ {
+ _hmove.HMoveDelayCnt++;
+ }
+
+ if (_hmove.HMoveDelayCnt == 6)
+ {
+ _hmove.HMoveDelayCnt++;
+ _hmove.HMoveCnt = 0;
+ _hmove.DecCntEnabled = true;
+ }
+ }
+
+ // Increment the hsync counter
+ _hsyncCnt++;
+ _hsyncCnt %= 228;
+
+ // End of the line? Add it to the buffer!
+ if (_hsyncCnt == 0)
+ {
+ _hmove.LateHBlankReset = false;
+ _scanlinesBuffer.Add(_scanline);
+ _scanline = new uint[160];
+ }
+
+ if (_scanlinesBuffer.Count >= 1024)
+ {
+ /* if a rom never toggles vsync, FrameAdvance() will hang while consuming
+ * huge amounts of ram. this is most certainly due to emulation defects
+ * that need to be fixed; but it's preferable to not crash the emulator
+ * in such situations
+ */
+ OutputFrame();
+ _scanlinesBuffer.Clear();
+ FrameComplete = true;
+ }
+ }
+
+ // TODO: Remove the magic numbers from this function to allow for a variable height screen
+ public void OutputFrame()
+ {
+ for (int row = 0; row < 262; row++)
+ {
+ for (int col = 0; col < 320; col++)
+ {
+ if (_scanlinesBuffer.Count > row)
+ {
+ FrameBuffer[(row * 320) + col] = (int)(_scanlinesBuffer[row][col / 2] | 0xFF000000);
+ }
+ else
+ {
+ FrameBuffer[(row * 320) + col] = unchecked((int)0xFF000000);
+ }
+ }
+ }
+ }
+
+ public byte ReadMemory(ushort addr, bool peek)
+ {
+ var maskedAddr = (ushort)(addr & 0x000F);
+ if (maskedAddr == 0x00) // CXM0P
+ {
+ return (byte)((((_player0.Missile.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXP0) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x01) // CXM1P
+ {
+ return (byte)((((_player1.Missile.Collisions & CXP0) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXP1) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x02) // CXP0FB
+ {
+ return (byte)((((_player0.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Collisions & CXBL) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x03) // CXP1FB
+ {
+ return (byte)((((_player1.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Collisions & CXBL) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x04) // CXM0FB
+ {
+ return (byte)((((_player0.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x05) // CXM1FB
+ {
+ return (byte)((((_player1.Missile.Collisions & CXPF) != 0) ? 0x80 : 0x00) | (((_player1.Missile.Collisions & CXBL) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x06) // CXBLPF
+ {
+ return (byte)(((_ball.Collisions & CXPF) != 0) ? 0x80 : 0x00);
+ }
+
+ if (maskedAddr == 0x07) // CXPPMM
+ {
+ return (byte)((((_player0.Collisions & CXP1) != 0) ? 0x80 : 0x00) | (((_player0.Missile.Collisions & CXM1) != 0) ? 0x40 : 0x00));
+ }
+
+ if (maskedAddr == 0x08) // INPT0
+ {
+ // Changing the hard coded value will change the paddle position. The range seems to be roughly 0-56000 according to values from stella
+ // 6105 roughly centers the paddle in Breakout
+ if (_capCharging && _core.Cpu.TotalExecutedCycles - _capChargeStart >= 6105)
+ {
+ return 0x80;
+ }
+
+ return 0x00;
+ }
+
+ if (maskedAddr == 0x0C) // INPT4
+ {
+ return (byte)((_core.ReadControls1(peek) & 0x08) != 0 ? 0x80 : 0x00);
+ }
+
+ if (maskedAddr == 0x0D) // INPT5
+ {
+ return (byte)((_core.ReadControls2(peek) & 0x08) != 0 ? 0x80 : 0x00);
+ }
+
+ return 0x00;
+ }
+
+ public void WriteMemory(ushort addr, byte value)
+ {
+ var maskedAddr = (ushort)(addr & 0x3f);
+
+ if (maskedAddr == 0x00) // VSYNC
+ {
+ if ((value & 0x02) != 0)
+ {
+ // Frame is complete, output to buffer
+ _vsyncEnabled = true;
+ }
+ else if (_vsyncEnabled)
+ {
+ // When VSYNC is disabled, this will be the first line of the new frame
+
+ // write to frame buffer
+ OutputFrame();
+
+ // Clear all from last frame
+ _scanlinesBuffer.Clear();
+
+ // Frame is done
+ FrameComplete = true;
+
+ _vsyncEnabled = false;
+
+ // Do not reset hsync, since we're on the first line of the new frame
+ // hsyncCnt = 0;
+ }
+ }
+ else if (maskedAddr == 0x01) // VBLANK
+ {
+ _vblankEnabled = (value & 0x02) != 0;
+ _capCharging = (value & 0x80) == 0;
+ if ((value & 0x80) == 0)
+ {
+ _capChargeStart = _core.Cpu.TotalExecutedCycles;
+ }
+ }
+ else if (maskedAddr == 0x02) // WSYNC
+ {
+ int count = 0;
+ while (_hsyncCnt > 0)
+ {
+ count++;
+ Execute(1);
+
+ // Add a cycle to the cpu every 3 TIA clocks (corrects timer error in M6532)
+ if (count % 3 == 0)
+ {
+ _core.M6532.Timer.Tick();
+ }
+ }
+ }
+ else if (maskedAddr == 0x04) // NUSIZ0
+ {
+ _player0.Nusiz = (byte)(value & 0x37);
+ _player0.Missile.Size = (byte)((value & 0x30) >> 4);
+ _player0.Missile.Number = (byte)(value & 0x07);
+ }
+ else if (maskedAddr == 0x05) // NUSIZ1
+ {
+ _player1.Nusiz = (byte)(value & 0x37);
+ _player1.Missile.Size = (byte)((value & 0x30) >> 4);
+ _player1.Missile.Number = (byte)(value & 0x07);
+ }
+ else if (maskedAddr == 0x06) // COLUP0
+ {
+ _player0.Color = (byte)(value & 0xFE);
+ }
+ else if (maskedAddr == 0x07) // COLUP1
+ {
+ _player1.Color = (byte)(value & 0xFE);
+ }
+ else if (maskedAddr == 0x08) // COLUPF
+ {
+ _playField.PfColor = (byte)(value & 0xFE);
+ }
+ else if (maskedAddr == 0x09) // COLUBK
+ {
+ _playField.BkColor = (byte)(value & 0xFE);
+ }
+ else if (maskedAddr == 0x0A) // CTRLPF
+ {
+ _playField.Reflect = (value & 0x01) != 0;
+ _playField.Priority = (value & 0x04) != 0;
+
+ _ball.Size = (byte)((value & 0x30) >> 4);
+ }
+ else if (maskedAddr == 0x0B) // REFP0
+ {
+ _player0.Reflect = (value & 0x08) != 0;
+ }
+ else if (maskedAddr == 0x0C) // REFP1
+ {
+ _player1.Reflect = (value & 0x08) != 0;
+ }
+ else if (maskedAddr == 0x0D) // PF0
+ {
+ _playField.Grp = (uint)((_playField.Grp & 0x0FFFF) + ((ReverseBits(value, 8) & 0x0F) << 16));
+ }
+ else if (maskedAddr == 0x0E) // PF1
+ {
+ _playField.Grp = (uint)((_playField.Grp & 0xF00FF) + (value << 8));
+ }
+ else if (maskedAddr == 0x0F) // PF2
+ {
+ _playField.Grp = (uint)((_playField.Grp & 0xFFF00) + ReverseBits(value, 8));
+ }
+ else if (maskedAddr == 0x10) // RESP0
+ {
+ // Borrowed from EMU7800. Apparently resetting between 68 and 76 has strange results.
+ if (_hsyncCnt < 69)
+ {
+ _player0.HPosCnt = 0;
+ _player0.ResetCnt = 0;
+ _player0.Reset = true;
+ }
+ else if (_hsyncCnt == 69)
+ {
+ _player0.ResetCnt = 3;
+ }
+ else if (_hsyncCnt == 72)
+ {
+ _player0.ResetCnt = 2;
+ }
+ else if (_hsyncCnt == 75)
+ {
+ _player0.ResetCnt = 1;
+ }
+ else
+ {
+ _player0.ResetCnt = 0;
+ }
+ }
+ else if (maskedAddr == 0x11) // RESP1
+ {
+ // Borrowed from EMU7800. Apparently resetting between 68 and 76 has strange results.
+ // This fixes some graphic glitches with Frostbite
+ if (_hsyncCnt < 69)
+ {
+ _player1.HPosCnt = 0;
+ _player1.ResetCnt = 0;
+ _player1.Reset = true;
+ }
+ else if (_hsyncCnt == 69)
+ {
+ _player1.ResetCnt = 3;
+ }
+ else if (_hsyncCnt == 72)
+ {
+ _player1.ResetCnt = 2;
+ }
+ else if (_hsyncCnt == 75)
+ {
+ _player1.ResetCnt = 1;
+ }
+ else
+ {
+ _player1.ResetCnt = 0;
+ }
+ }
+ else if (maskedAddr == 0x12) // RESM0
+ {
+ _player0.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
+ }
+ else if (maskedAddr == 0x13) // RESM1
+ {
+ _player1.Missile.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
+ }
+ else if (maskedAddr == 0x14) // RESBL
+ {
+ _ball.HPosCnt = (byte)(_hsyncCnt < 68 ? 160 - 2 : 160 - 4);
+ }
+ else if (maskedAddr == 0x15) // AUDC0
+ {
+ AUD[0].AUDC = (byte)(value & 15);
+ }
+ else if (maskedAddr == 0x16) // AUDC1
+ {
+ AUD[1].AUDC = (byte)(value & 15);
+ }
+ else if (maskedAddr == 0x17) // AUDF0
+ {
+ AUD[0].AUDF = (byte)((value & 31) + 1);
+ }
+ else if (maskedAddr == 0x18) // AUDF1
+ {
+ AUD[1].AUDF = (byte)((value & 31) + 1);
+ }
+ else if (maskedAddr == 0x19) // AUDV0
+ {
+ AUD[0].AUDV = (byte)(value & 15);
+ }
+ else if (maskedAddr == 0x1A) // AUDV1
+ {
+ AUD[1].AUDV = (byte)(value & 15);
+ }
+ else if (maskedAddr == 0x1B) // GRP0
+ {
+ _player0.Grp = value;
+ _player1.Dgrp = _player1.Grp;
+ }
+ else if (maskedAddr == 0x1C) // GRP1
+ {
+ _player1.Grp = value;
+ _player0.Dgrp = _player0.Grp;
+
+ // TODO: Find a game that uses this functionality and test it
+ _ball.Denabled = _ball.Enabled;
+ }
+ else if (maskedAddr == 0x1D) // ENAM0
+ {
+ _player0.Missile.Enabled = (value & 0x02) != 0;
+ }
+ else if (maskedAddr == 0x1E) // ENAM1
+ {
+ _player1.Missile.Enabled = (value & 0x02) != 0;
+ }
+ else if (maskedAddr == 0x1F) // ENABL
+ {
+ _ball.Enabled = (value & 0x02) != 0;
+ }
+ else if (maskedAddr == 0x20) // HMP0
+ {
+ _player0.HM = (byte)((value & 0xF0) >> 4);
+ }
+ else if (maskedAddr == 0x21) // HMP1
+ {
+ _player1.HM = (byte)((value & 0xF0) >> 4);
+ }
+ else if (maskedAddr == 0x22) // HMM0
+ {
+ _player0.Missile.Hm = (byte)((value & 0xF0) >> 4);
+ }
+ else if (maskedAddr == 0x23) // HMM1
+ {
+ _player1.Missile.Hm = (byte)((value & 0xF0) >> 4);
+ }
+ else if (maskedAddr == 0x24) // HMBL
+ {
+ _ball.HM = (byte)((value & 0xF0) >> 4);
+ }
+ else if (maskedAddr == 0x25) // VDELP0
+ {
+ _player0.Delay = (value & 0x01) != 0;
+ }
+ else if (maskedAddr == 0x26) // VDELP1
+ {
+ _player1.Delay = (value & 0x01) != 0;
+ }
+ else if (maskedAddr == 0x27) // VDELBL
+ {
+ _ball.Delay = (value & 0x01) != 0;
+ }
+ else if (maskedAddr == 0x28) // RESMP0
+ {
+ _player0.Missile.ResetToPlayer = (value & 0x02) != 0;
+ }
+ else if (maskedAddr == 0x29) // RESMP1
+ {
+ _player1.Missile.ResetToPlayer = (value & 0x02) != 0;
+ }
+ else if (maskedAddr == 0x2A) // HMOVE
+ {
+ _hmove.HMoveEnabled = true;
+ _hmove.HMoveJustStarted = true;
+ _hmove.HMoveDelayCnt = 0;
+ }
+ else if (maskedAddr == 0x2B) // HMCLR
+ {
+ _player0.HM = 0;
+ _player0.Missile.Hm = 0;
+ _player1.HM = 0;
+ _player1.Missile.Hm = 0;
+ _ball.HM = 0;
+ }
+ else if (maskedAddr == 0x2C) // CXCLR
+ {
+ _player0.Collisions = 0;
+ _player0.Missile.Collisions = 0;
+ _player1.Collisions = 0;
+ _player1.Missile.Collisions = 0;
+ _ball.Collisions = 0;
+ }
+ }
+
+ private static int ReverseBits(int value, int bits)
+ {
+ int result = 0;
+ for (int i = 0; i < bits; i++)
+ {
+ result = (result << 1) | ((value >> i) & 0x01);
+ }
+
+ return result;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("TIA");
+ _ball.SyncState(ser);
+ _hmove.SyncState(ser);
+ ser.Sync("hsyncCnt", ref _hsyncCnt);
+ ser.BeginSection("Player0");
+ _player0.SyncState(ser);
+ ser.EndSection();
+ ser.BeginSection("Player1");
+ _player1.SyncState(ser);
+ ser.EndSection();
+ _playField.SyncState(ser);
+ ser.EndSection();
+ }
+ }
+}
\ No newline at end of file
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.Audio.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.Audio.cs
new file mode 100644
index 0000000000..42a53b9204
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.Audio.cs
@@ -0,0 +1,212 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ public class Audio
+ {
+ // noise/division control
+ public byte AUDC = 0;
+
+ // frequency divider
+ public byte AUDF = 1;
+
+ // volume
+ public byte AUDV = 0;
+
+ // 2 state counter
+ private bool _sr1 = true;
+
+ // 4 bit shift register
+ private int _sr4 = 0x0f;
+
+ // 5 bit shift register
+ private int _sr5 = 0x1f;
+
+ // 3 state counter
+ private int _sr3 = 2;
+
+ // counter based off AUDF
+ private byte _freqcnt;
+
+ // latched audio value
+ private bool _on = true;
+
+ private bool Run3()
+ {
+ _sr3++;
+ if (_sr3 == 3)
+ {
+ _sr3 = 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool Run4()
+ {
+ bool ret = (_sr4 & 1) != 0;
+ bool c = (_sr4 & 1) != 0 ^ (_sr4 & 2) != 0;
+ _sr4 = (_sr4 >> 1) | (c ? 8 : 0);
+ return ret;
+ }
+
+ private bool Run5()
+ {
+ bool ret = (_sr5 & 1) != 0;
+ bool c = (_sr5 & 1) != 0 ^ (_sr5 & 4) != 0;
+ _sr5 = (_sr5 >> 1) | (c ? 16 : 0);
+ return ret;
+ }
+
+ private bool One4()
+ {
+ bool ret = (_sr4 & 1) != 0;
+ _sr4 = (_sr4 >> 1) | 8;
+ return ret;
+ }
+
+ private bool One5()
+ {
+ bool ret = (_sr5 & 1) != 0;
+ _sr5 = (_sr5 >> 1) | 16;
+ return ret;
+ }
+
+ private bool Run1()
+ {
+ _sr1 = !_sr1;
+ return !_sr1;
+ }
+
+ private bool Run9()
+ {
+ bool ret = (_sr4 & 1) != 0;
+ bool c = (_sr5 & 1) != 0 ^ (_sr4 & 1) != 0;
+ _sr4 = (_sr4 >> 1) | ((_sr5 & 1) != 0 ? 8 : 0);
+ _sr5 = (_sr5 >> 1) | (c ? 16 : 0);
+ return ret;
+ }
+
+ ///
+ /// call me approx 31k times a second
+ ///
+ /// 16 bit audio sample
+ public short Cycle()
+ {
+ if (++_freqcnt == AUDF)
+ {
+ _freqcnt = 0;
+ switch (AUDC)
+ {
+ case 0x00:
+ case 0x0b:
+ // Both have a 1 s
+ One5();
+ _on = One4();
+ break;
+
+ case 0x01:
+ // Both run, but the 5 bit is ignored
+ _on = Run4();
+ Run5();
+ break;
+
+ case 0x02:
+ if ((_sr5 & 0x0f) == 0 || (_sr5 & 0x0f) == 0x0f)
+ {
+ _on = Run4();
+ break;
+ }
+
+ Run5();
+ break;
+
+ case 0x03:
+ if (Run5())
+ {
+ _on = Run4();
+ }
+
+ break;
+
+ case 0x04:
+ Run5();
+ One4();
+ _on = Run1();
+ break;
+
+ case 0x05:
+ One5();
+ Run4();
+ _on = Run1();
+ break;
+
+ case 0x06:
+ case 0x0a:
+ Run4(); // ???
+ Run5();
+ if ((_sr5 & 0x0f) == 0)
+ {
+ _on = false;
+ }
+ else if ((_sr5 & 0x0f) == 0x0f)
+ {
+ _on = true;
+ }
+
+ break;
+
+ case 0x07:
+ case 0x09:
+ Run4(); // ???
+ _on = Run5();
+ break;
+ case 0x08:
+ _on = Run9();
+ break;
+ case 0x0c:
+ case 0x0d:
+ if (Run3())
+ {
+ _on = Run1();
+ }
+
+ break;
+ case 0x0e:
+ if (Run3())
+ {
+ goto case 0x06;
+ }
+
+ break;
+ case 0x0f:
+ if (Run3())
+ {
+ goto case 0x07;
+ }
+
+ break;
+ }
+ }
+
+ return (short)(_on ? AUDV * 1092 : 0);
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.Sync("AUDC", ref AUDC);
+ ser.Sync("AUDF", ref AUDF);
+ ser.Sync("AUDV", ref AUDV);
+ ser.Sync("sr1", ref _sr1);
+ ser.Sync("sr3", ref _sr3);
+ ser.Sync("sr4", ref _sr4);
+ ser.Sync("sr5", ref _sr5);
+ ser.Sync("freqcnt", ref _freqcnt);
+ ser.Sync("on", ref _on);
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.BallData.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.BallData.cs
new file mode 100644
index 0000000000..93d7505343
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.BallData.cs
@@ -0,0 +1,57 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ private struct BallData
+ {
+ public bool Enabled;
+ public bool Denabled;
+ public bool Delay;
+ public byte Size;
+ public byte HM;
+ public byte HPosCnt;
+ public byte Collisions;
+
+ public bool Tick()
+ {
+ bool result = false;
+ if (HPosCnt < (1 << Size))
+ {
+ if (!Delay && Enabled)
+ {
+ // Draw the ball!
+ result = true;
+ }
+ else if (Delay && Denabled)
+ {
+ // Draw the ball!
+ result = true;
+ }
+ }
+
+ // Increment the counter
+ HPosCnt++;
+
+ // Counter loops at 160
+ HPosCnt %= 160;
+
+ return result;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("Ball");
+ ser.Sync("enabled", ref Enabled);
+ ser.Sync("denabled", ref Denabled);
+ ser.Sync("delay", ref Delay);
+ ser.Sync("size", ref Size);
+ ser.Sync("HM", ref HM);
+ ser.Sync("hPosCnt", ref HPosCnt);
+ ser.Sync("collisions", ref Collisions);
+ ser.EndSection();
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.HMoveData.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.HMoveData.cs
new file mode 100644
index 0000000000..2b29cec0cf
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.HMoveData.cs
@@ -0,0 +1,52 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ private struct HMoveData
+ {
+ public bool HMoveEnabled;
+ public bool HMoveJustStarted;
+ public bool LateHBlankReset;
+ public bool DecCntEnabled;
+
+ public bool Player0Latch;
+ public bool Player1Latch;
+ public bool Missile0Latch;
+ public bool Missile1Latch;
+ public bool BallLatch;
+
+ public byte HMoveDelayCnt;
+ public byte HMoveCnt;
+
+ public byte Player0Cnt;
+ public byte Player1Cnt;
+ public byte Missile0Cnt;
+ public byte Missile1Cnt;
+ public byte BallCnt;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("HMove");
+ ser.Sync("hmoveEnabled", ref HMoveEnabled);
+ ser.Sync("hmoveJustStarted", ref HMoveJustStarted);
+ ser.Sync("lateHBlankReset", ref LateHBlankReset);
+ ser.Sync("decCntEnabled", ref DecCntEnabled);
+ ser.Sync("player0Latch", ref Player0Latch);
+ ser.Sync("player1Latch", ref Player1Latch);
+ ser.Sync("missile0Latch", ref Missile0Latch);
+ ser.Sync("missile1Latch", ref Missile1Latch);
+ ser.Sync("ballLatch", ref BallLatch);
+ ser.Sync("hmoveDelayCnt", ref HMoveDelayCnt);
+ ser.Sync("hmoveCnt", ref HMoveCnt);
+ ser.Sync("player0Cnt", ref Player0Cnt);
+ ser.Sync("player1Cnt", ref Player1Cnt);
+ ser.Sync("missile0Cnt", ref Missile0Cnt);
+ ser.Sync("missile1Cnt", ref Missile1Cnt);
+ ser.Sync("ballCnt", ref BallCnt);
+ ser.EndSection();
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.MissleData.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.MissleData.cs
new file mode 100644
index 0000000000..7eb125ccff
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.MissleData.cs
@@ -0,0 +1,90 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ private struct MissileData
+ {
+ public bool Enabled;
+ public bool ResetToPlayer;
+ public byte HPosCnt;
+ public byte Size;
+ public byte Number;
+ public byte Hm;
+ public byte Collisions;
+
+ public bool Tick()
+ {
+ var result = false;
+
+ // At hPosCnt == 0, start drawing the missile, if enabled
+ if (HPosCnt < (1 << Size))
+ {
+ if (Enabled && !ResetToPlayer)
+ {
+ // Draw the missile
+ result = true;
+ }
+ }
+
+ if ((Number & 0x07) == 0x01 || ((Number & 0x07) == 0x03))
+ {
+ if (HPosCnt >= 16 && HPosCnt <= (16 + (1 << Size) - 1))
+ {
+ if (Enabled && !ResetToPlayer)
+ {
+ // Draw the missile
+ result = true;
+ }
+ }
+ }
+
+ if ((Number & 0x07) == 0x02 || ((Number & 0x07) == 0x03) || ((Number & 0x07) == 0x06))
+ {
+ if (HPosCnt >= 32 && HPosCnt <= (32 + (1 << Size) - 1))
+ {
+ if (Enabled && !ResetToPlayer)
+ {
+ // Draw the missile
+ result = true;
+ }
+ }
+ }
+
+ if ((Number & 0x07) == 0x04 || (Number & 0x07) == 0x06)
+ {
+ if (HPosCnt >= 64 && HPosCnt <= (64 + (1 << Size) - 1))
+ {
+ if (Enabled && !ResetToPlayer)
+ {
+ // Draw the missile
+ result = true;
+ }
+ }
+ }
+
+ // Increment the counter
+ HPosCnt++;
+
+ // Counter loops at 160
+ HPosCnt %= 160;
+
+ return result;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("Missile");
+ ser.Sync("enabled", ref Enabled);
+ ser.Sync("resetToPlayer", ref ResetToPlayer);
+ ser.Sync("hPosCnt", ref HPosCnt);
+ ser.Sync("size", ref Size);
+ ser.Sync("number", ref Number);
+ ser.Sync("HM", ref Hm);
+ ser.Sync("collisions", ref Collisions);
+ ser.EndSection();
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayerData.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayerData.cs
new file mode 100644
index 0000000000..06204876dc
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayerData.cs
@@ -0,0 +1,162 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ private struct PlayerData
+ {
+ public MissileData Missile;
+
+ public byte Grp;
+ public byte Dgrp;
+ public byte Color;
+ public byte HPosCnt;
+ public byte ScanCnt;
+ public byte ScanStrchCnt;
+ public byte HM;
+ public bool Reflect;
+ public bool Delay;
+ public byte Nusiz;
+ public bool Reset;
+ public byte ResetCnt;
+ public byte Collisions;
+
+ public bool Tick()
+ {
+ var result = false;
+ if (ScanCnt < 8)
+ {
+ // Make the mask to check the graphic
+ byte playerMask = (byte)(1 << (8 - 1 - ScanCnt));
+
+ // Reflect it if needed
+ if (Reflect)
+ {
+ playerMask = (byte)ReverseBits(playerMask, 8);
+ }
+
+ // Check the graphic (depending on delay)
+ if (!Delay)
+ {
+ if ((Grp & playerMask) != 0)
+ {
+ result = true;
+ }
+ }
+ else
+ {
+ if ((Dgrp & playerMask) != 0)
+ {
+ result = true;
+ }
+ }
+
+ // Reset missile, if desired
+ if (ScanCnt == 0x04 && HPosCnt <= 16 && Missile.ResetToPlayer)
+ {
+ Missile.HPosCnt = 0;
+ }
+
+ // Increment counter
+ // When this reaches 8, we've run out of pixel
+
+ // If we're drawing a stretched player, only incrememnt the
+ // counter every 2 or 4 clocks
+
+ // Double size player
+ if ((Nusiz & 0x07) == 0x05)
+ {
+ ScanStrchCnt++;
+ ScanStrchCnt %= 2;
+ }
+
+ // Quad size player
+ else if ((Nusiz & 0x07) == 0x07)
+ {
+ ScanStrchCnt++;
+ ScanStrchCnt %= 4;
+ }
+
+ // Single size player
+ else
+ {
+ ScanStrchCnt = 0;
+ }
+
+ if (ScanStrchCnt == 0)
+ {
+ ScanCnt++;
+ }
+ }
+
+ // At counter position 0 we should start drawing, a pixel late
+ // Set the scan counter at 0, and at the next pixel the graphic will start drawing
+ if (HPosCnt == 0 && !Reset)
+ {
+ ScanCnt = 0;
+ if ((Nusiz & 0x07) == 0x05 || (Nusiz & 0x07) == 0x07)
+ {
+ ScanStrchCnt = 0;
+ }
+ }
+
+ if (HPosCnt == 16 && ((Nusiz & 0x07) == 0x01 || ((Nusiz & 0x07) == 0x03)))
+ {
+ ScanCnt = 0;
+ }
+
+ if (HPosCnt == 32 && ((Nusiz & 0x07) == 0x02 || ((Nusiz & 0x07) == 0x03) || ((Nusiz & 0x07) == 0x06)))
+ {
+ ScanCnt = 0;
+ }
+
+ if (HPosCnt == 64 && ((Nusiz & 0x07) == 0x04 || ((Nusiz & 0x07) == 0x06)))
+ {
+ ScanCnt = 0;
+ }
+
+ // Reset is no longer in effect
+ Reset = false;
+
+ // Increment the counter
+ HPosCnt++;
+
+ // Counter loops at 160
+ HPosCnt %= 160;
+
+ if (ResetCnt < 4)
+ {
+ ResetCnt++;
+ }
+
+ if (ResetCnt == 4)
+ {
+ HPosCnt = 0;
+ Reset = true;
+ ResetCnt++;
+ }
+
+ return result;
+ }
+
+ public void SyncState(Serializer ser)
+ {
+ Missile.SyncState(ser);
+ ser.Sync("grp", ref Grp);
+ ser.Sync("dgrp", ref Dgrp);
+ ser.Sync("color", ref Color);
+ ser.Sync("hPosCnt", ref HPosCnt);
+ ser.Sync("scanCnt", ref ScanCnt);
+ ser.Sync("scanStrchCnt", ref ScanStrchCnt);
+ ser.Sync("HM", ref HM);
+ ser.Sync("reflect", ref Reflect);
+ ser.Sync("delay", ref Delay);
+ ser.Sync("nusiz", ref Nusiz);
+ ser.Sync("reset", ref Reset);
+ ser.Sync("resetCnt", ref ResetCnt);
+ ser.Sync("collisions", ref Collisions);
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayfieldData.cs b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayfieldData.cs
new file mode 100644
index 0000000000..8414c024b8
--- /dev/null
+++ b/BizHawk.Emulation.Cores/Consoles/Atari/2600/Tia/Tia.PlayfieldData.cs
@@ -0,0 +1,29 @@
+using BizHawk.Common;
+
+namespace BizHawk.Emulation.Cores.Atari.Atari2600
+{
+ public partial class TIA
+ {
+ private struct PlayfieldData
+ {
+ public uint Grp;
+ public byte PfColor;
+ public byte BkColor;
+ public bool Reflect;
+ public bool Score;
+ public bool Priority;
+
+ public void SyncState(Serializer ser)
+ {
+ ser.BeginSection("PlayField");
+ ser.Sync("grp", ref Grp);
+ ser.Sync("pfColor", ref PfColor);
+ ser.Sync("bkColor", ref BkColor);
+ ser.Sync("reflect", ref Reflect);
+ ser.Sync("score", ref Score);
+ ser.Sync("priority", ref Priority);
+ ser.EndSection();
+ }
+ }
+ }
+}