using System; using System.Linq; using BizHawk.Common; namespace BizHawk.Emulation.Cores.Atari.Atari2600 { /** Cartridge class used for DPC+. There are six 4K program banks, a 4K display bank, 1K frequency table and the DPC chip. For complete details on the DPC chip see David P. Crane's United States Patent Number 4,644,495. */ internal class mDPCPlus : MapperBase { // TODO: PokeMem, and everything else public mDPCPlus() { throw new NotImplementedException(); } private IntBuffer _counters = new IntBuffer(8); private ByteBuffer _tops = new ByteBuffer(8); private ByteBuffer _flags = new ByteBuffer(8); private ByteBuffer _bottoms = new ByteBuffer(8); private bool[] _musicModes = new bool[3]; private int _bank4K; private byte _currentRandomVal; private int _elapsedCycles = 85; // 85 compensates for a slight timing issue when ClockCpu is first run, 85 puts BizHawk back on track with Stella on elapsed timing values private float _fractionalClocks; // Fractional DPC music OSC clocks unused during the last update private byte[] _dspData; public byte[] DspData => _dspData ?? (_dspData = Core.Rom.Skip(8192).Take(2048).ToArray()); // Table for computing the input bit of the random number generator's // shift register (it's the NOT of the EOR of four bits) private readonly byte[] _randomInputBits = { 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 }; public override void Dispose() { base.Dispose(); _counters.Dispose(); _tops.Dispose(); _flags.Dispose(); _bottoms.Dispose(); } public override void SyncState(Serializer ser) { base.SyncState(ser); ser.Sync("counters", ref _counters); ser.Sync("tops", ref _tops); ser.Sync("flags", ref _flags); ser.Sync("bottoms", ref _bottoms); ser.Sync("musicMode0", ref _musicModes[0]); // Silly, but I didn't want to support bool[] in Serializer just for this one variable ser.Sync("musicMode1", ref _musicModes[1]); ser.Sync("musicMode2", ref _musicModes[2]); ser.Sync("bank_4k", ref _bank4K); ser.Sync("currentRandomVal", ref _currentRandomVal); ser.Sync("elapsedCycles", ref _elapsedCycles); ser.Sync("fractionalClocks", ref _fractionalClocks); } public override void HardReset() { _counters = new IntBuffer(8); _tops = new ByteBuffer(8); _flags = new ByteBuffer(8); _bottoms = new ByteBuffer(8); _musicModes = new bool[3]; _bank4K = 0; _currentRandomVal = 0; _elapsedCycles = 85; _fractionalClocks = 0; base.HardReset(); } public override void ClockCpu() { _elapsedCycles++; } private byte ReadMem(ushort addr, bool peek) { if (addr < 0x1000) { return base.ReadMemory(addr); } if (!peek) { Address(addr); ClockRandomNumberGenerator(); } if (addr < 0x1040) { byte result; // Get the index of the data fetcher that's being accessed var index = addr & 0x07; var function = (addr >> 3) & 0x07; // Update flag register for selected data fetcher if ((_counters[index] & 0x00ff) == _tops[index]) { _flags[index] = 0xff; } else if ((_counters[index] & 0x00ff) == _bottoms[index]) { _flags[index] = 0x00; } switch (function) { case 0x00: if (index < 4) { result = _currentRandomVal; } else // No, it's a music read { var musicAmplitudes = new byte[] { 0x00, 0x04, 0x05, 0x09, 0x06, 0x0a, 0x0b, 0x0f }; // Update the music data fetchers (counter & flag) UpdateMusicModeDataFetchers(); byte i = 0; if (_musicModes[0] && _flags[5] > 0) { i |= 0x01; } if (_musicModes[1] && _flags[6] > 0) { i |= 0x02; } if (_musicModes[2] && _flags[7] > 0) { i |= 0x04; } result = musicAmplitudes[i]; } break; // DFx display data read case 0x01: result = DspData[2047 - _counters[index]]; break; // DFx display data read AND'd w/flag case 0x02: result = (byte)(DspData[2047 - _counters[index]] & _flags[index]); break; // DFx flag case 0x07: result = _flags[index]; break; default: result = 0; break; } // Clock the selected data fetcher's counter if needed if ((index < 5) || ((index >= 5) && (!_musicModes[index - 5]))) { _counters[index] = (_counters[index] - 1) & 0x07ff; } return result; } return Core.Rom[(_bank4K << 12) + (addr & 0xFFF)]; } public override byte ReadMemory(ushort addr) { return ReadMem(addr, false); } public override byte PeekMemory(ushort addr) { return ReadMem(addr, true); } public override void WriteMemory(ushort addr, byte value) { if (addr < 0x1000) { base.WriteMemory(addr, value); return; } Address(addr); ClockRandomNumberGenerator(); if (addr >= 0x1040 && addr < 0x1080) { var index = addr & 0x07; var function = (addr >> 3) & 0x07; switch (function) { // DFx top count case 0x00: _tops[index] = value; _flags[index] = 0x00; break; // DFx bottom count case 0x01: _bottoms[index] = value; break; // DFx counter low case 0x02: if ((index >= 5) && _musicModes[index - 5]) { // Data fetcher is in music mode so its low counter value // should be loaded from the top register not the poked value _counters[index] = (_counters[index] & 0x0700) | _tops[index]; } else { // Data fetcher is either not a music mode data fetcher or it // isn't in music mode so it's low counter value should be loaded // with the poked value _counters[index] = (_counters[index] & 0x0700) | value; } break; // DFx counter high case 0x03: _counters[index] = (ushort)(((value & 0x07) << 8) | (_counters[index] & 0x00ff)); // Execute special code for music mode data fetchers if (index >= 5) { _musicModes[index - 5] = (value & 0x10) > 0; // NOTE: We are not handling the clock source input for // the music mode data fetchers. We're going to assume // they always use the OSC input. } break; // Random Number Generator Reset case 0x06: _currentRandomVal = 1; break; } } } private void Address(ushort addr) { if (addr == 0x1FF6) { _bank4K = 0; } else if (addr == 0x1FF7) { _bank4K = 1; } else if (addr == 0x1FF8) { _bank4K = 2; } else if (addr == 0x1FF9) { _bank4K = 3; } else if (addr == 0x1FFA) { _bank4K = 4; } else if (addr == 0x1FFB) { _bank4K = 5; } } private void ClockRandomNumberGenerator() { // Using bits 7, 5, 4, & 3 of the shift register compute the input // bit for the shift register var bit = _randomInputBits[((_currentRandomVal >> 3) & 0x07) | (((_currentRandomVal & 0x80) > 0) ? 0x08 : 0x00)]; // Update the shift register _currentRandomVal = (byte)((_currentRandomVal << 1) | bit); } private void UpdateMusicModeDataFetchers() { // Calculate the number of cycles since the last update var cycles = _elapsedCycles; _elapsedCycles = 0; // Calculate the number of DPC OSC clocks since the last update var clocks = ((20000.0 * cycles) / 1193191.66666667) + _fractionalClocks; var wholeClocks = (int)clocks; _fractionalClocks = (float)(clocks - wholeClocks); if (wholeClocks <= 0) { return; } // Let's update counters and flags of the music mode data fetchers for (var x = 5; x <= 7; ++x) { // Update only if the data fetcher is in music mode if (_musicModes[x - 5]) { var top = _tops[x] + 1; var newLow = _counters[x] & 0x00ff; if (_tops[x] != 0) { newLow -= wholeClocks % top; if (newLow < 0) { newLow += top; } } else { newLow = 0; } // Update flag register for this data fetcher if (newLow <= _bottoms[x]) { _flags[x] = 0x00; } else if (newLow <= _tops[x]) { _flags[x] = 0xff; } _counters[x] = (_counters[x] & 0x0700) | (ushort)newLow; } } } } }