2016-02-25 01:41:55 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2017-10-13 22:00:08 +00:00
|
|
|
|
using BizHawk.Emulation.Cores.Components.Z80A;
|
2018-03-03 15:55:15 +00:00
|
|
|
|
using System;
|
2012-03-25 01:33:05 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.ColecoVision
|
2012-03-25 01:33:05 +00:00
|
|
|
|
{
|
2017-07-12 19:10:55 +00:00
|
|
|
|
[Core(
|
2014-04-25 01:19:57 +00:00
|
|
|
|
"ColecoHawk",
|
|
|
|
|
"Vecna",
|
|
|
|
|
isPorted: false,
|
2017-04-24 17:24:56 +00:00
|
|
|
|
isReleased: true)]
|
2014-12-12 01:58:12 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(ISaveRam), typeof(IDriveLight))]
|
2017-03-01 02:44:05 +00:00
|
|
|
|
public sealed partial class ColecoVision : IEmulator, IDebuggable, IInputPollable, IStatable, ISettable<ColecoVision.ColecoSettings, ColecoVision.ColecoSyncSettings>
|
2012-03-25 01:33:05 +00:00
|
|
|
|
{
|
2014-08-23 19:06:37 +00:00
|
|
|
|
[CoreConstructor("Coleco")]
|
2017-05-06 00:05:36 +00:00
|
|
|
|
public ColecoVision(CoreComm comm, GameInfo game, byte[] rom, object syncSettings)
|
2012-03-25 01:33:05 +00:00
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
var ser = new BasicServiceProvider(this);
|
2017-05-09 02:07:12 +00:00
|
|
|
|
ServiceProvider = ser;
|
2017-08-02 03:05:17 +00:00
|
|
|
|
MemoryCallbacks = new MemoryCallbackSystem(new[] { "System Bus" });
|
2012-12-10 00:43:43 +00:00
|
|
|
|
CoreComm = comm;
|
2017-05-06 00:05:36 +00:00
|
|
|
|
_syncSettings = (ColecoSyncSettings)syncSettings ?? new ColecoSyncSettings();
|
2016-02-25 01:41:55 +00:00
|
|
|
|
bool skipbios = _syncSettings.SkipBiosIntro;
|
2012-12-10 00:43:43 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_cpu = new Z80A
|
2017-04-10 16:24:53 +00:00
|
|
|
|
{
|
2017-10-25 23:17:00 +00:00
|
|
|
|
FetchMemory = ReadMemory,
|
2017-04-10 16:24:53 +00:00
|
|
|
|
ReadMemory = ReadMemory,
|
|
|
|
|
WriteMemory = WriteMemory,
|
|
|
|
|
ReadHardware = ReadPort,
|
|
|
|
|
WriteHardware = WritePort,
|
|
|
|
|
MemoryCallbacks = MemoryCallbacks
|
|
|
|
|
};
|
|
|
|
|
|
2018-03-04 16:00:32 +00:00
|
|
|
|
PSG = new SN76489col();
|
|
|
|
|
SGM_sound = new AY_3_8910_SGM();
|
2018-03-13 20:31:21 +00:00
|
|
|
|
_blip.SetRates(3579545, 44100);
|
2018-03-03 15:55:15 +00:00
|
|
|
|
|
2017-04-10 16:24:53 +00:00
|
|
|
|
ControllerDeck = new ColecoVisionControllerDeck(_syncSettings.Port1, _syncSettings.Port2);
|
2017-03-01 02:44:05 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_vdp = new TMS9918A(_cpu);
|
|
|
|
|
ser.Register<IVideoProvider>(_vdp);
|
2017-03-01 02:44:05 +00:00
|
|
|
|
|
2012-11-17 21:12:51 +00:00
|
|
|
|
// TODO: hack to allow bios-less operation would be nice, no idea if its feasible
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_biosRom = CoreComm.CoreFileProvider.GetFirmware("Coleco", "Bios", true, "Coleco BIOS file is required.");
|
2012-05-06 02:48:39 +00:00
|
|
|
|
|
2013-12-24 23:32:43 +00:00
|
|
|
|
// gamedb can overwrite the syncsettings; this is ok
|
2012-12-10 00:43:43 +00:00
|
|
|
|
if (game["NoSkip"])
|
2017-04-10 16:24:53 +00:00
|
|
|
|
{
|
2012-12-10 00:43:43 +00:00
|
|
|
|
skipbios = false;
|
2017-04-10 16:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-05 20:48:08 +00:00
|
|
|
|
use_SGM = _syncSettings.UseSGM;
|
|
|
|
|
|
2018-12-05 15:01:25 +00:00
|
|
|
|
if (use_SGM)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Using the Super Game Module");
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-22 02:01:15 +00:00
|
|
|
|
LoadRom(rom, skipbios);
|
2012-11-20 00:35:22 +00:00
|
|
|
|
SetupMemoryDomains();
|
2016-02-25 01:41:55 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_tracer.Header = _cpu.TraceHeader;
|
2017-10-13 21:50:54 +00:00
|
|
|
|
ser.Register<IDisassemblable>(_cpu);
|
2017-05-08 16:37:16 +00:00
|
|
|
|
ser.Register<ITraceable>(_tracer);
|
|
|
|
|
}
|
2017-05-06 00:05:36 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private readonly Z80A _cpu;
|
|
|
|
|
private readonly TMS9918A _vdp;
|
|
|
|
|
private readonly byte[] _biosRom;
|
|
|
|
|
private readonly TraceBuffer _tracer = new TraceBuffer();
|
2017-05-06 00:05:36 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private byte[] _romData;
|
|
|
|
|
private byte[] _ram = new byte[1024];
|
2018-03-03 15:55:15 +00:00
|
|
|
|
public byte[] SGM_high_RAM = new byte[0x6000];
|
|
|
|
|
public byte[] SGM_low_RAM = new byte[0x2000];
|
|
|
|
|
|
2018-03-09 00:58:38 +00:00
|
|
|
|
public bool temp_1_prev, temp_2_prev;
|
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private int _frame;
|
2018-03-31 15:23:55 +00:00
|
|
|
|
private IController _controller = NullController.Instance;
|
2017-05-06 00:05:36 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private enum InputPortMode
|
|
|
|
|
{
|
|
|
|
|
Left, Right
|
|
|
|
|
}
|
2017-04-10 16:24:53 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private InputPortMode _inputPortSelection;
|
2012-11-20 00:35:22 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
public ColecoVisionControllerDeck ControllerDeck { get; }
|
2017-05-02 01:09:11 +00:00
|
|
|
|
|
2017-04-10 16:24:53 +00:00
|
|
|
|
private void LoadRom(byte[] rom, bool skipbios)
|
2013-11-11 16:45:45 +00:00
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (rom.Length <= 32768)
|
|
|
|
|
{
|
|
|
|
|
_romData = new byte[0x8000];
|
|
|
|
|
for (int i = 0; i < 0x8000; i++)
|
|
|
|
|
{
|
|
|
|
|
_romData[i] = rom[i % rom.Length];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-04-24 17:24:56 +00:00
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
// all original ColecoVision games had 32k or less of ROM
|
|
|
|
|
// so, if we have more then that, we must be using a MegaCart mapper
|
|
|
|
|
is_MC = true;
|
|
|
|
|
|
|
|
|
|
_romData = rom;
|
2017-04-24 17:24:56 +00:00
|
|
|
|
}
|
2012-11-18 00:40:22 +00:00
|
|
|
|
|
2012-11-22 02:01:15 +00:00
|
|
|
|
// hack to skip colecovision title screen
|
|
|
|
|
if (skipbios)
|
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_romData[0] = 0x55;
|
|
|
|
|
_romData[1] = 0xAA;
|
2012-11-22 02:01:15 +00:00
|
|
|
|
}
|
2013-11-11 16:45:45 +00:00
|
|
|
|
}
|
2012-11-17 21:12:51 +00:00
|
|
|
|
|
2017-04-10 16:24:53 +00:00
|
|
|
|
private byte ReadPort(ushort port)
|
2012-11-17 21:12:51 +00:00
|
|
|
|
{
|
|
|
|
|
port &= 0xFF;
|
|
|
|
|
|
|
|
|
|
if (port >= 0xA0 && port < 0xC0)
|
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
2017-04-10 16:24:53 +00:00
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
return _vdp.ReadData();
|
2017-04-10 16:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
return _vdp.ReadVdpStatus();
|
2012-11-17 21:12:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-11 16:45:45 +00:00
|
|
|
|
if (port >= 0xE0)
|
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
2017-04-10 16:24:53 +00:00
|
|
|
|
{
|
2013-11-11 16:45:45 +00:00
|
|
|
|
return ReadController1();
|
2017-04-10 16:24:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2013-11-11 16:45:45 +00:00
|
|
|
|
return ReadController2();
|
|
|
|
|
}
|
2012-11-18 00:40:22 +00:00
|
|
|
|
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (use_SGM)
|
|
|
|
|
{
|
|
|
|
|
if (port == 0x50)
|
|
|
|
|
{
|
|
|
|
|
return SGM_sound.port_sel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x52)
|
|
|
|
|
{
|
2018-03-04 16:00:32 +00:00
|
|
|
|
return SGM_sound.ReadReg();
|
2018-03-03 15:55:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x53)
|
|
|
|
|
{
|
|
|
|
|
return port_0x53;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x7F)
|
|
|
|
|
{
|
|
|
|
|
return port_0x7F;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-17 21:12:51 +00:00
|
|
|
|
return 0xFF;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-10 16:24:53 +00:00
|
|
|
|
private void WritePort(ushort port, byte value)
|
2012-11-17 21:12:51 +00:00
|
|
|
|
{
|
|
|
|
|
port &= 0xFF;
|
|
|
|
|
|
2013-11-11 16:45:45 +00:00
|
|
|
|
if (port >= 0xA0 && port <= 0xBF)
|
2012-11-17 21:12:51 +00:00
|
|
|
|
{
|
|
|
|
|
if ((port & 1) == 0)
|
2017-04-24 17:24:56 +00:00
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_vdp.WriteVdpData(value);
|
2017-04-24 17:24:56 +00:00
|
|
|
|
}
|
2012-11-17 21:12:51 +00:00
|
|
|
|
else
|
2017-04-24 17:24:56 +00:00
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_vdp.WriteVdpControl(value);
|
2017-04-24 17:24:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-11-17 21:12:51 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-11-11 16:45:45 +00:00
|
|
|
|
if (port >= 0x80 && port <= 0x9F)
|
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_inputPortSelection = InputPortMode.Right;
|
2013-11-11 16:45:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port >= 0xC0 && port <= 0xDF)
|
|
|
|
|
{
|
2017-05-08 16:37:16 +00:00
|
|
|
|
_inputPortSelection = InputPortMode.Left;
|
2013-11-11 16:45:45 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port >= 0xE0)
|
|
|
|
|
{
|
2018-03-04 16:00:32 +00:00
|
|
|
|
PSG.WriteReg(value);
|
2013-11-11 16:45:45 +00:00
|
|
|
|
}
|
2018-03-03 15:55:15 +00:00
|
|
|
|
|
|
|
|
|
if (use_SGM)
|
|
|
|
|
{
|
|
|
|
|
if (port == 0x50)
|
|
|
|
|
{
|
|
|
|
|
SGM_sound.port_sel = (byte)(value & 0xF);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x51)
|
|
|
|
|
{
|
2018-03-04 16:00:32 +00:00
|
|
|
|
SGM_sound.WriteReg(value);
|
2018-03-03 15:55:15 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x53)
|
|
|
|
|
{
|
|
|
|
|
if ((value & 1) > 0)
|
|
|
|
|
{
|
|
|
|
|
enable_SGM_high = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// NOTE: the documentation states that you shouldn't turn RAM back off once enabling it
|
|
|
|
|
// so we won't do anything here
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
port_0x53 = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (port == 0x7F)
|
|
|
|
|
{
|
|
|
|
|
if (value == 0xF)
|
|
|
|
|
{
|
|
|
|
|
enable_SGM_low = false;
|
|
|
|
|
}
|
|
|
|
|
else if (value == 0xD)
|
|
|
|
|
{
|
|
|
|
|
enable_SGM_low = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
port_0x7F = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-11-17 21:12:51 +00:00
|
|
|
|
}
|
2012-03-25 01:33:05 +00:00
|
|
|
|
|
2017-05-08 16:29:09 +00:00
|
|
|
|
private byte ReadController1()
|
2017-04-24 17:24:56 +00:00
|
|
|
|
{
|
2017-05-08 16:29:09 +00:00
|
|
|
|
_isLag = false;
|
|
|
|
|
byte retval;
|
2017-05-08 16:37:16 +00:00
|
|
|
|
if (_inputPortSelection == InputPortMode.Left)
|
2017-05-08 16:29:09 +00:00
|
|
|
|
{
|
2018-03-09 00:58:38 +00:00
|
|
|
|
retval = ControllerDeck.ReadPort1(_controller, true, true);
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
if (_inputPortSelection == InputPortMode.Right)
|
2017-05-08 16:29:09 +00:00
|
|
|
|
{
|
2018-03-09 00:58:38 +00:00
|
|
|
|
retval = ControllerDeck.ReadPort1(_controller, false, true);
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return retval;
|
|
|
|
|
}
|
2017-05-08 16:37:16 +00:00
|
|
|
|
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return 0x7F;
|
2017-04-24 17:24:56 +00:00
|
|
|
|
}
|
2013-11-11 16:45:45 +00:00
|
|
|
|
|
2017-05-08 16:29:09 +00:00
|
|
|
|
private byte ReadController2()
|
2012-11-25 15:41:40 +00:00
|
|
|
|
{
|
2014-12-05 03:16:08 +00:00
|
|
|
|
_isLag = false;
|
2017-05-08 16:29:09 +00:00
|
|
|
|
byte retval;
|
2017-05-08 16:37:16 +00:00
|
|
|
|
if (_inputPortSelection == InputPortMode.Left)
|
2017-05-08 16:29:09 +00:00
|
|
|
|
{
|
2018-03-09 00:58:38 +00:00
|
|
|
|
retval = ControllerDeck.ReadPort2(_controller, true, true);
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return retval;
|
|
|
|
|
}
|
2012-11-17 17:39:33 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
if (_inputPortSelection == InputPortMode.Right)
|
2017-05-08 16:29:09 +00:00
|
|
|
|
{
|
2018-03-09 00:58:38 +00:00
|
|
|
|
retval = ControllerDeck.ReadPort2(_controller, false, true);
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return retval;
|
|
|
|
|
}
|
2017-05-08 16:37:16 +00:00
|
|
|
|
|
2017-05-08 16:29:09 +00:00
|
|
|
|
return 0x7F;
|
|
|
|
|
}
|
2017-04-10 16:24:53 +00:00
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private byte ReadMemory(ushort addr)
|
2017-05-08 16:31:00 +00:00
|
|
|
|
{
|
|
|
|
|
if (addr >= 0x8000)
|
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (!is_MC)
|
|
|
|
|
{
|
|
|
|
|
return _romData[addr & 0x7FFF];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// reading from 0xFFC0 to 0xFFFF triggers bank switching
|
|
|
|
|
// I don't know if it happens before or after the read though
|
|
|
|
|
|
|
|
|
|
if (addr >= 0xFFC0)
|
|
|
|
|
{
|
|
|
|
|
MC_bank = (addr - 0xFFC0) & (_romData.Length / 0x4000 - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// the first 16K of the map is always the last 16k of the ROM
|
|
|
|
|
if (addr < 0xC000)
|
|
|
|
|
{
|
|
|
|
|
return _romData[_romData.Length - 0x4000 + (addr - 0x8000)];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return _romData[MC_bank * 0x4000 + (addr - 0xC000)];
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-08 16:31:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (!enable_SGM_high)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 0x6000)
|
|
|
|
|
{
|
|
|
|
|
return _ram[addr & 1023];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-05-08 16:31:00 +00:00
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (addr >= 0x2000)
|
|
|
|
|
{
|
|
|
|
|
return SGM_high_RAM[addr - 0x2000];
|
|
|
|
|
}
|
2017-05-08 16:31:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (addr < 0x2000)
|
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (!enable_SGM_low)
|
|
|
|
|
{
|
|
|
|
|
return _biosRom[addr];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return SGM_low_RAM[addr];
|
|
|
|
|
}
|
2017-05-08 16:31:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
////Console.WriteLine("Unhandled read at {0:X4}", addr);
|
2017-05-08 16:31:00 +00:00
|
|
|
|
return 0xFF;
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-08 16:37:16 +00:00
|
|
|
|
private void WriteMemory(ushort addr, byte value)
|
2017-05-08 16:31:00 +00:00
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (!enable_SGM_high)
|
|
|
|
|
{
|
|
|
|
|
if (addr >= 0x6000 && addr < 0x8000)
|
|
|
|
|
{
|
|
|
|
|
_ram[addr & 1023] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2017-05-08 16:31:00 +00:00
|
|
|
|
{
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (addr >= 0x2000 && addr < 0x8000)
|
|
|
|
|
{
|
|
|
|
|
SGM_high_RAM[addr - 0x2000] = value;
|
|
|
|
|
}
|
2017-05-08 16:31:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-03 15:55:15 +00:00
|
|
|
|
if (addr < 0x2000)
|
|
|
|
|
{
|
|
|
|
|
if (enable_SGM_low)
|
|
|
|
|
{
|
|
|
|
|
SGM_low_RAM[addr] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-08 16:37:16 +00:00
|
|
|
|
////Console.WriteLine("Unhandled write at {0:X4}:{1:X2}", addr, value);
|
2017-05-08 16:31:00 +00:00
|
|
|
|
}
|
2017-10-06 14:51:40 +00:00
|
|
|
|
|
|
|
|
|
private void HardReset()
|
|
|
|
|
{
|
|
|
|
|
PSG.Reset();
|
|
|
|
|
_cpu.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SoftReset()
|
|
|
|
|
{
|
|
|
|
|
PSG.Reset();
|
|
|
|
|
_cpu.Reset();
|
|
|
|
|
}
|
2012-03-25 01:33:05 +00:00
|
|
|
|
}
|
2017-10-25 23:17:00 +00:00
|
|
|
|
}
|