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(); + } + } + } +}