Atari 2600 - another round of cleanup and reorg
This commit is contained in:
parent
27daa82464
commit
a230210565
|
@ -207,9 +207,13 @@
|
||||||
<Compile Include="Consoles\Atari\2600\Mappers\Multicart.cs" />
|
<Compile Include="Consoles\Atari\2600\Mappers\Multicart.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\Mappers\mX07.cs" />
|
<Compile Include="Consoles\Atari\2600\Mappers\mX07.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\M6532.cs" />
|
<Compile Include="Consoles\Atari\2600\M6532.cs" />
|
||||||
<Compile Include="Consoles\Atari\2600\TIA.cs">
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.Audio.cs" />
|
||||||
<SubType>Code</SubType>
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.BallData.cs" />
|
||||||
</Compile>
|
<Compile Include="Consoles\Atari\2600\Tia\TIA.cs" />
|
||||||
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.HMoveData.cs" />
|
||||||
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.MissleData.cs" />
|
||||||
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.PlayerData.cs" />
|
||||||
|
<Compile Include="Consoles\Atari\2600\Tia\Tia.PlayfieldData.cs" />
|
||||||
<Compile Include="Consoles\Atari\7800\Atari7800.Core.cs" />
|
<Compile Include="Consoles\Atari\7800\Atari7800.Core.cs" />
|
||||||
<Compile Include="Consoles\Atari\7800\Atari7800.cs" />
|
<Compile Include="Consoles\Atari\7800\Atari7800.cs" />
|
||||||
<Compile Include="Consoles\Atari\7800\Atari7800Control.cs" />
|
<Compile Include="Consoles\Atari\7800\Atari7800Control.cs" />
|
||||||
|
|
|
@ -240,7 +240,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
_tia.Execute(1);
|
_tia.Execute(1);
|
||||||
_tia.Execute(1);
|
_tia.Execute(1);
|
||||||
|
|
||||||
M6532.timer.tick();
|
M6532.Timer.Tick();
|
||||||
if (CoreComm.Tracer.Enabled)
|
if (CoreComm.Tracer.Enabled)
|
||||||
{
|
{
|
||||||
CoreComm.Tracer.Put(Cpu.TraceState());
|
CoreComm.Tracer.Put(Cpu.TraceState());
|
||||||
|
|
|
@ -5,137 +5,85 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
// Emulates the M6532 RIOT Chip
|
// Emulates the M6532 RIOT Chip
|
||||||
public class M6532
|
public class M6532
|
||||||
{
|
{
|
||||||
public byte ddra = 0x00;
|
private readonly Atari2600 _core;
|
||||||
public byte ddrb = 0x00;
|
|
||||||
|
|
||||||
private readonly Atari2600 core;
|
public byte DDRa = 0x00;
|
||||||
|
public byte DDRb = 0x00;
|
||||||
|
|
||||||
public struct timerData
|
public TimerData Timer;
|
||||||
{
|
|
||||||
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 M6532(Atari2600 core)
|
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
|
// 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.
|
// arbitrary value to start with.
|
||||||
timer.value = 0x73;
|
Timer.Value = 0x73;
|
||||||
timer.prescalerShift = 10;
|
Timer.PrescalerShift = 10;
|
||||||
timer.prescalerCount = 1 << timer.prescalerShift;
|
Timer.PrescalerCount = 1 << Timer.PrescalerShift;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte ReadMemory(ushort addr, bool peek)
|
public byte ReadMemory(ushort addr, bool peek)
|
||||||
{
|
{
|
||||||
// Register Select (?)
|
if ((addr & 0x0200) == 0) // If not register select, read Ram
|
||||||
bool RS = (addr & 0x0200) != 0;
|
|
||||||
|
|
||||||
if (!RS)
|
|
||||||
{
|
{
|
||||||
// Read Ram
|
return _core.Ram[(ushort)(addr & 0x007f)];
|
||||||
ushort maskedAddr = (ushort)(addr & 0x007f);
|
|
||||||
return core.Ram[maskedAddr];
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var registerAddr = (ushort)(addr & 0x0007);
|
||||||
|
if (registerAddr == 0x00)
|
||||||
{
|
{
|
||||||
ushort registerAddr = (ushort)(addr & 0x0007);
|
// Read Output reg A
|
||||||
if (registerAddr == 0x00)
|
// Combine readings from player 1 and player 2
|
||||||
{
|
var temp = (byte)(_core.ReadControls1(peek) & 0xF0 | ((_core.ReadControls2(peek) >> 4) & 0x0F));
|
||||||
// Read Output reg A
|
temp = (byte)(temp & ~DDRa);
|
||||||
// Combine readings from player 1 and player 2
|
return temp;
|
||||||
byte temp = (byte)(core.ReadControls1(peek) & 0xF0 | ((core.ReadControls2(peek) >> 4) & 0x0F));
|
}
|
||||||
temp = (byte)(temp & ~ddra);
|
|
||||||
return temp;
|
if (registerAddr == 0x01)
|
||||||
}
|
{
|
||||||
else if (registerAddr == 0x01)
|
// Read DDRA
|
||||||
{
|
return DDRa;
|
||||||
// Read DDRA
|
}
|
||||||
return ddra;
|
|
||||||
}
|
if (registerAddr == 0x02)
|
||||||
else if (registerAddr == 0x02)
|
{
|
||||||
{
|
// Read Output reg B
|
||||||
// Read Output reg B
|
var temp = _core.ReadConsoleSwitches(peek);
|
||||||
byte temp = core.ReadConsoleSwitches(peek);
|
temp = (byte)(temp & ~DDRb);
|
||||||
temp = (byte)(temp & ~ddrb);
|
return temp;
|
||||||
return temp;
|
}
|
||||||
|
|
||||||
/*
|
if (registerAddr == 0x03) // Read DDRB
|
||||||
// TODO: Rewrite this!
|
{
|
||||||
bool temp = resetOccured;
|
return DDRb;
|
||||||
resetOccured = false;
|
}
|
||||||
return (byte)(0x0A | (temp ? 0x00 : 0x01));
|
|
||||||
* */
|
if ((registerAddr & 0x5) == 0x4)
|
||||||
}
|
{
|
||||||
else if (registerAddr == 0x03)
|
// Bit 0x0080 contains interrupt enable/disable
|
||||||
{
|
Timer.InterruptEnabled = (addr & 0x0080) != 0;
|
||||||
// Read DDRB
|
|
||||||
return ddrb;
|
|
||||||
}
|
|
||||||
else 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
|
// 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
|
// However, the reading of the timer at the same time the interrupt occurs will not reset the interrupt flag
|
||||||
// (M6532 Datasheet)
|
// (M6532 Datasheet)
|
||||||
if (!(timer.prescalerCount == 0 && timer.value == 0))
|
if (!(Timer.PrescalerCount == 0 && Timer.Value == 0))
|
||||||
{
|
|
||||||
timer.interruptFlag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return timer.value;
|
|
||||||
}
|
|
||||||
else if ((registerAddr & 0x5) == 0x5)
|
|
||||||
{
|
{
|
||||||
// Read interrupt flag
|
Timer.InterruptFlag = false;
|
||||||
if (timer.interruptEnabled && timer.interruptFlag)
|
|
||||||
{
|
|
||||||
return 0x80;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0x00;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Timer.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((registerAddr & 0x5) == 0x5)
|
||||||
|
{
|
||||||
|
// Read interrupt flag
|
||||||
|
if (Timer.InterruptEnabled && Timer.InterruptFlag)
|
||||||
|
{
|
||||||
|
return 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0x00;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0x3A;
|
return 0x3A;
|
||||||
|
@ -143,66 +91,61 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
|
|
||||||
public void WriteMemory(ushort addr, byte value)
|
public void WriteMemory(ushort addr, byte value)
|
||||||
{
|
{
|
||||||
// Register Select (?)
|
if ((addr & 0x0200) == 0) // If the RS bit is not set, this is a ram write
|
||||||
bool RS = (addr & 0x0200) != 0;
|
|
||||||
|
|
||||||
// If the RS bit is not set, this is a ram write
|
|
||||||
if (!RS)
|
|
||||||
{
|
{
|
||||||
ushort maskedAddr = (ushort)(addr & 0x007f);
|
_core.Ram[(ushort)(addr & 0x007f)] = value;
|
||||||
core.Ram[maskedAddr] = value;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If bit 0x0010 is set, and bit 0x0004 is set, this is a timer write
|
// If bit 0x0010 is set, and bit 0x0004 is set, this is a timer write
|
||||||
if ((addr & 0x0014) == 0x0014)
|
if ((addr & 0x0014) == 0x0014)
|
||||||
{
|
{
|
||||||
ushort registerAddr = (ushort)(addr & 0x0007);
|
var registerAddr = (ushort)(addr & 0x0007);
|
||||||
|
|
||||||
// Bit 0x0080 contains interrupt enable/disable
|
// 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
|
// The interrupt flag will be reset whenever the Timer is access by a read or a write
|
||||||
// (M6532 datasheet)
|
// (M6532 datasheet)
|
||||||
|
|
||||||
if (registerAddr == 0x04)
|
if (registerAddr == 0x04)
|
||||||
{
|
{
|
||||||
// Write to Timer/1
|
// Write to Timer/1
|
||||||
timer.prescalerShift = 0;
|
Timer.PrescalerShift = 0;
|
||||||
timer.value = value;
|
Timer.Value = value;
|
||||||
timer.prescalerCount = 1 << timer.prescalerShift;
|
Timer.PrescalerCount = 1 << Timer.PrescalerShift;
|
||||||
timer.interruptFlag = false;
|
Timer.InterruptFlag = false;
|
||||||
}
|
}
|
||||||
else if (registerAddr == 0x05)
|
else if (registerAddr == 0x05)
|
||||||
{
|
{
|
||||||
// Write to Timer/8
|
// Write to Timer/8
|
||||||
timer.prescalerShift = 3;
|
Timer.PrescalerShift = 3;
|
||||||
timer.value = value;
|
Timer.Value = value;
|
||||||
timer.prescalerCount = 1 << timer.prescalerShift;
|
Timer.PrescalerCount = 1 << Timer.PrescalerShift;
|
||||||
timer.interruptFlag = false;
|
Timer.InterruptFlag = false;
|
||||||
}
|
}
|
||||||
else if (registerAddr == 0x06)
|
else if (registerAddr == 0x06)
|
||||||
{
|
{
|
||||||
// Write to Timer/64
|
// Write to Timer/64
|
||||||
timer.prescalerShift = 6;
|
Timer.PrescalerShift = 6;
|
||||||
timer.value = value;
|
Timer.Value = value;
|
||||||
timer.prescalerCount = 1 << timer.prescalerShift;
|
Timer.PrescalerCount = 1 << Timer.PrescalerShift;
|
||||||
timer.interruptFlag = false;
|
Timer.InterruptFlag = false;
|
||||||
}
|
}
|
||||||
else if (registerAddr == 0x07)
|
else if (registerAddr == 0x07)
|
||||||
{
|
{
|
||||||
// Write to Timer/1024
|
// Write to Timer/1024
|
||||||
timer.prescalerShift = 10;
|
Timer.PrescalerShift = 10;
|
||||||
timer.value = value;
|
Timer.Value = value;
|
||||||
timer.prescalerCount = 1 << timer.prescalerShift;
|
Timer.PrescalerCount = 1 << Timer.PrescalerShift;
|
||||||
timer.interruptFlag = false;
|
Timer.InterruptFlag = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If bit 0x0004 is not set, bit 0x0010 is ignored and
|
// If bit 0x0004 is not set, bit 0x0010 is ignored and
|
||||||
// these are register writes
|
// these are register writes
|
||||||
else if ((addr & 0x0004) == 0)
|
else if ((addr & 0x0004) == 0)
|
||||||
{
|
{
|
||||||
ushort registerAddr = (ushort)(addr & 0x0007);
|
var registerAddr = (ushort)(addr & 0x0007);
|
||||||
|
|
||||||
if (registerAddr == 0x00)
|
if (registerAddr == 0x00)
|
||||||
{
|
{
|
||||||
|
@ -211,7 +154,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
else if (registerAddr == 0x01)
|
else if (registerAddr == 0x01)
|
||||||
{
|
{
|
||||||
// Write DDRA
|
// Write DDRA
|
||||||
ddra = value;
|
DDRa = value;
|
||||||
}
|
}
|
||||||
else if (registerAddr == 0x02)
|
else if (registerAddr == 0x02)
|
||||||
{
|
{
|
||||||
|
@ -220,7 +163,7 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
else if (registerAddr == 0x03)
|
else if (registerAddr == 0x03)
|
||||||
{
|
{
|
||||||
// Write DDRB
|
// Write DDRB
|
||||||
ddrb = value;
|
DDRb = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,10 +172,50 @@ namespace BizHawk.Emulation.Cores.Atari.Atari2600
|
||||||
public void SyncState(Serializer ser)
|
public void SyncState(Serializer ser)
|
||||||
{
|
{
|
||||||
ser.BeginSection("M6532");
|
ser.BeginSection("M6532");
|
||||||
ser.Sync("ddra", ref ddra);
|
ser.Sync("ddra", ref DDRa);
|
||||||
ser.Sync("ddrb", ref ddrb);
|
ser.Sync("ddrb", ref DDRb);
|
||||||
timer.SyncState(ser);
|
Timer.SyncState(ser);
|
||||||
ser.EndSection();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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<uint[]> _scanlinesBuffer = new List<uint[]>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// call me approx 31k times a second
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>16 bit audio sample</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue