Coleco Turbo Controller

This commit is contained in:
alyosha-tas 2017-03-01 10:44:05 +08:00 committed by GitHub
parent 14a0d114b8
commit 39808f793d
6 changed files with 452 additions and 73 deletions

View File

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using BizHawk.Common;
using BizHawk.Common.ReflectionExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.ColecoVision
{
public class ColecoVisionControllerDeck
{
public ColecoVisionControllerDeck(string controller1Name, string controller2Name)
{
if (!ValidControllerTypes.ContainsKey(controller1Name))
{
throw new InvalidOperationException("Invalid controller type: " + controller1Name);
}
if (!ValidControllerTypes.ContainsKey(controller2Name))
{
throw new InvalidOperationException("Invalid controller type: " + controller2Name);
}
Port1 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller1Name], 1);
Port2 = (IPort)Activator.CreateInstance(ValidControllerTypes[controller2Name], 2); ;
Definition = new ControllerDefinition
{
Name = "ColecoVision Basic Controller",
BoolButtons = Port1.Definition.BoolButtons
.Concat(Port2.Definition.BoolButtons)
.ToList()
};
Definition.FloatControls.AddRange(Port1.Definition.FloatControls);
Definition.FloatControls.AddRange(Port2.Definition.FloatControls);
Definition.FloatRanges.AddRange(Port1.Definition.FloatRanges);
Definition.FloatRanges.AddRange(Port2.Definition.FloatRanges);
}
public byte ReadPort1(IController c, bool left_mode)
{
return Port1.Read(c, left_mode);
}
public byte ReadPort2(IController c, bool left_mode)
{
return Port2.Read(c, left_mode);
}
public ControllerDefinition Definition { get; private set; }
public void SyncState(Serializer ser)
{
ser.BeginSection("Port1");
Port1.SyncState(ser);
ser.EndSection();
ser.BeginSection("Port2");
Port2.SyncState(ser);
ser.EndSection();
}
private readonly IPort Port1;
private readonly IPort Port2;
private static Dictionary<string, Type> _controllerTypes = null;
public static Dictionary<string, Type> ValidControllerTypes
{
get
{
if (_controllerTypes == null)
{
_controllerTypes = typeof(ColecoVisionControllerDeck).Assembly
.GetTypes()
.Where(t => typeof(IPort).IsAssignableFrom(t))
.Where(t => !t.IsAbstract && !t.IsInterface)
.ToDictionary(tkey => tkey.DisplayName());
}
return _controllerTypes;
}
}
public static string DefaultControllerName
{
get { return typeof(StandardController).DisplayName(); }
}
}
}

View File

@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using BizHawk.Common;
using BizHawk.Common.ReflectionExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.ColecoVision
{
/// <summary>
/// Represents a controller plugged into a controller port on the intellivision
/// </summary>
public interface IPort
{
byte Read(IController c, bool left_mode);
ControllerDefinition Definition { get; }
void SyncState(Serializer ser);
int PortNum { get; }
}
[DisplayName("Unplugged Controller")]
public class UnpluggedController : IPort
{
public UnpluggedController(int portNum)
{
PortNum = portNum;
Definition = new ControllerDefinition
{
BoolButtons = new List<string>()
};
}
public byte Read(IController c, bool left_mode)
{
return 0; // needs checking
}
public ControllerDefinition Definition { get; private set; }
public void SyncState(Serializer ser)
{
// Do nothing
}
public int PortNum { get; private set; }
}
[DisplayName("ColecoVision Basic Controller")]
public class StandardController : IPort
{
public StandardController(int portNum)
{
PortNum = portNum;
Definition = new ControllerDefinition
{
BoolButtons = BaseDefinition
.Select(b => "P" + PortNum + " " + b)
.ToList()
};
}
public int PortNum { get; private set; }
public byte Read(IController c, bool left_mode)
{
if (left_mode)
{
byte retval = 0x7F;
if (c.IsPressed(Definition.BoolButtons[0])) retval &= 0xFE;
if (c.IsPressed(Definition.BoolButtons[1])) retval &= 0xFD;
if (c.IsPressed(Definition.BoolButtons[2])) retval &= 0xFB;
if (c.IsPressed(Definition.BoolButtons[3])) retval &= 0xF7;
if (c.IsPressed(Definition.BoolButtons[4])) retval &= 0x3F;
return retval;
}
else
{
byte retval = 0xF;
// 0x00;
if (c.IsPressed(Definition.BoolButtons[14])) retval = 0x01;
if (c.IsPressed(Definition.BoolButtons[10])) retval = 0x02;
if (c.IsPressed(Definition.BoolButtons[11])) retval = 0x03;
// 0x04;
if (c.IsPressed(Definition.BoolButtons[13])) retval = 0x05;
if (c.IsPressed(Definition.BoolButtons[16])) retval = 0x06;
if (c.IsPressed(Definition.BoolButtons[8])) retval = 0x07;
// 0x08;
if (c.IsPressed(Definition.BoolButtons[17])) retval = 0x09;
if (c.IsPressed(Definition.BoolButtons[6])) retval = 0x0A;
if (c.IsPressed(Definition.BoolButtons[15])) retval = 0x0B;
if (c.IsPressed(Definition.BoolButtons[9])) retval = 0x0C;
if (c.IsPressed(Definition.BoolButtons[7])) retval = 0x0D;
if (c.IsPressed(Definition.BoolButtons[12])) retval = 0x0E;
if (c.IsPressed(Definition.BoolButtons[5]) == false) retval |= 0x40;
retval |= 0x30; // always set these bits
return retval;
}
}
public ControllerDefinition Definition { get; private set; }
public void SyncState(Serializer ser)
{
// Nothing todo, I think
}
private static readonly string[] BaseDefinition =
{
"Up", "Right", "Down", "Left", "L", "R",
"Key 0", "Key 1", "Key 2", "Key 3", "Key 4", "Key 5",
"Key 6", "Key 7", "Key 8", "Key 9", "Pound", "Star"
};
}
[DisplayName("Turbo Controller")]
public class ColecoTurboController : IPort
{
public ColecoTurboController(int portNum)
{
PortNum = portNum;
Definition = new ControllerDefinition
{
BoolButtons = BaseBoolDefinition
.Select(b => "P" + PortNum + " " + b)
.ToList(),
FloatControls = { "P" + PortNum + " Disc X", "P" + PortNum + " Disc Y" },
FloatRanges = { new[] { -127.0f, 0, 127.0f }, new[] { -127.0f, 0, 127.0f } }
};
}
public int PortNum { get; private set; }
public ControllerDefinition Definition { get; private set; }
public byte Read(IController c, bool left_mode)
{
if (left_mode)
{
byte retval = 0x7B;
if (c.IsPressed(Definition.BoolButtons[0])) retval &= 0x3F;
/*
if (c.IsPressed(Definition.BoolButtons[1])) retval &= 0xF7;
if (c.IsPressed(Definition.BoolButtons[2])) retval &= 0xFB;
if (c.IsPressed(Definition.BoolButtons[3])) retval &= 0xFD;
if (c.IsPressed(Definition.BoolButtons[4])) retval &= 0xFE;
if (c.IsPressed(Definition.BoolButtons[5])) retval &= 0x7F;
if (c.IsPressed(Definition.BoolButtons[6])) retval &= 0xDF;
if (c.IsPressed(Definition.BoolButtons[7])) retval &= 0xEF;
*/
int x = (int)c.GetFloat(Definition.FloatControls[0]);
int y = (int)c.GetFloat(Definition.FloatControls[1]);
retval &= CalcDirection(x, y);
//Console.WriteLine(retval);
return retval;
} else
{
byte retval = 0x7B;
if (c.IsPressed(Definition.BoolButtons[0])) retval &= 0x3F;
/*
if (c.IsPressed(Definition.BoolButtons[1])) retval &= 0xF7;
if (c.IsPressed(Definition.BoolButtons[2])) retval &= 0xFB;
if (c.IsPressed(Definition.BoolButtons[3])) retval &= 0xFD;
if (c.IsPressed(Definition.BoolButtons[4])) retval &= 0xFE;
if (c.IsPressed(Definition.BoolButtons[5])) retval &= 0x7F;
if (c.IsPressed(Definition.BoolButtons[6])) retval &= 0xDF;
if (c.IsPressed(Definition.BoolButtons[7])) retval &= 0xEF;
*/
int x = (int)c.GetFloat(Definition.FloatControls[0]);
int y = (int)c.GetFloat(Definition.FloatControls[1]);
retval &= CalcDirection(x, y);
//Console.WriteLine(retval);
return retval;
}
}
public void SyncState(Serializer ser)
{
// Nothing todo, I think
}
private static readonly string[] BaseBoolDefinition =
{
"Pedal", "T1", "T2", "T3", "T4", "T5", "T6", "T7"
};
// x and y are both assumed to be in [-127, 127]
// x increases from left to right
// y increases from top to bottom
private static byte CalcDirection(int x, int y)
{
y = -y; // vflip to match the arrangement of FloatControllerButtons
// deadzone: if we're less than ? units from the origin, return no direction
if (x * x + y * y < Deadzone * Deadzone)
{
return 0x7F; // nothing pressed
}
double t = Math.Atan2(y, x) * 8.0 / Math.PI;
int i = (int)Math.Round(t);
return FloatControllerButtons[i & 15];
}
private const int Deadzone = 50;
private static byte[] FloatControllerButtons = new byte[]
{
0x6F, // E
0x4F, // ENE
0x4F, // NE
0x4F, // NNE
0x4F, // N
0x5F, // NNW
0x5F, // NW
0x5F, // WNW
0x5F, // W
0x7F, // WSW
0x7F, // SW
0x7F, // SSW
0x7F, // S
0x6F, // SSE
0x6F, // SE
0x6F, // ESE
};
}
}

View File

@ -1,12 +1,16 @@
using BizHawk.Emulation.Common;
using System;
using Newtonsoft.Json;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.ColecoVision
{
public partial class ColecoVision : ISettable<object, ColecoVision.ColecoSyncSettings>
public partial class ColecoVision : IEmulator, IStatable, ISettable<ColecoVision.ColecoSettings, ColecoVision.ColecoSyncSettings>
{
public object GetSettings()
public ColecoSettings GetSettings()
{
return null;
return Settings.Clone();
}
public ColecoSyncSettings GetSyncSettings()
@ -14,28 +18,77 @@ namespace BizHawk.Emulation.Cores.ColecoVision
return _syncSettings.Clone();
}
public bool PutSettings(object o)
public bool PutSettings(ColecoSettings o)
{
Settings = o;
return false;
}
public bool PutSyncSettings(ColecoSyncSettings o)
{
bool ret = o.SkipBiosIntro != _syncSettings.SkipBiosIntro;
ret |= ColecoSyncSettings.NeedsReboot(_syncSettings, o);
_syncSettings = o;
return ret;
}
private ColecoSyncSettings _syncSettings;
public class ColecoSettings
{
public ColecoSettings Clone()
{
return (ColecoSettings)MemberwiseClone();
}
}
public ColecoSettings Settings = new ColecoSettings();
public ColecoSyncSettings _syncSettings = new ColecoSyncSettings();
public class ColecoSyncSettings
{
public bool SkipBiosIntro { get; set; }
private string _port1 = ColecoVisionControllerDeck.DefaultControllerName;
private string _port2 = ColecoVisionControllerDeck.DefaultControllerName;
[JsonIgnore]
public string Port1
{
get { return _port1; }
set
{
if (!ColecoVisionControllerDeck.ValidControllerTypes.ContainsKey(value))
{
throw new InvalidOperationException("Invalid controller type: " + value);
}
_port1 = value;
}
}
[JsonIgnore]
public string Port2
{
get { return _port2; }
set
{
if (!ColecoVisionControllerDeck.ValidControllerTypes.ContainsKey(value))
{
throw new InvalidOperationException("Invalid controller type: " + value);
}
_port2 = value;
}
}
public ColecoSyncSettings Clone()
{
return (ColecoSyncSettings)MemberwiseClone();
}
public static bool NeedsReboot(ColecoSyncSettings x, ColecoSyncSettings y)
{
return !DeepEquality.DeepEquals(x, y);
}
}
}
}

View File

@ -1,6 +1,7 @@
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components;
using BizHawk.Emulation.Cores.Components.Z80;
using System;
namespace BizHawk.Emulation.Cores.ColecoVision
{
@ -11,7 +12,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision
isReleased: true
)]
[ServiceNotApplicable(typeof(ISaveRam), typeof(IDriveLight))]
public sealed partial class ColecoVision : IEmulator, IDebuggable, IInputPollable, IStatable, ISettable<object, ColecoVision.ColecoSyncSettings>
public sealed partial class ColecoVision : IEmulator, IDebuggable, IInputPollable, IStatable, ISettable<ColecoVision.ColecoSettings, ColecoVision.ColecoSyncSettings>
{
// ROM
public byte[] RomData;
@ -21,7 +22,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision
// Machine
public Z80A Cpu;
public TMS9918A VDP;
public byte[] Ram = new byte[1024];
private readonly TraceBuffer Tracer = new TraceBuffer();
@ -41,12 +42,15 @@ namespace BizHawk.Emulation.Cores.ColecoVision
Cpu.WriteHardware = WritePort;
Cpu.MemoryCallbacks = MemoryCallbacks;
VDP = new TMS9918A(Cpu);
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(VDP);
PSG = new SN76489();
_fakeSyncSound = new FakeSyncSound(PSG, 735);
(ServiceProvider as BasicServiceProvider).Register<ISoundProvider>(_fakeSyncSound);
ControllerDeck = new ColecoVisionControllerDeck(this._syncSettings.Port1, this._syncSettings.Port2);
VDP = new TMS9918A(Cpu, ControllerDeck);
(ServiceProvider as BasicServiceProvider).Register<IVideoProvider>(VDP);
// TODO: hack to allow bios-less operation would be nice, no idea if its feasible
BiosRom = CoreComm.CoreFileProvider.GetFirmware("Coleco", "Bios", true, "Coleco BIOS file is required.");
@ -61,10 +65,19 @@ namespace BizHawk.Emulation.Cores.ColecoVision
var serviceProvider = ServiceProvider as BasicServiceProvider;
serviceProvider.Register<IDisassemblable>(new Disassembler());
serviceProvider.Register<ITraceable>(Tracer);
VDP.Controller = Controller;
}
public IEmulatorServiceProvider ServiceProvider { get; private set; }
public ControllerDefinition ControllerDefinition
{
get { return ControllerDeck.Definition; }
}
public ColecoVisionControllerDeck ControllerDeck { get; private set; }
public IController Controller { get; set; }
const ushort RamSizeMask = 0x03FF;
public void FrameAdvance(bool render, bool renderSound)
@ -78,7 +91,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision
{
Cpu.Logger = (s) => Tracer.Put(s);
}
VDP.Controller = Controller;
VDP.ExecuteFrame();
PSG.EndFrame(Cpu.TotalExecutedCycles);

View File

@ -5,7 +5,7 @@ namespace BizHawk.Emulation.Cores.ColecoVision
{
public partial class ColecoVision
{
public static readonly ControllerDefinition ColecoVisionControllerDefinition = new ControllerDefinition
/* public static readonly ControllerDefinition ColecoVisionControllerDefinition = new ControllerDefinition
{
Name = "ColecoVision Basic Controller",
BoolButtons =
@ -21,53 +21,25 @@ namespace BizHawk.Emulation.Cores.ColecoVision
"P2 Key 6", "P2 Key 7", "P2 Key 8", "P2 Key 9", "P2 Star", "P2 Pound"
}
};
public ControllerDefinition ControllerDefinition { get { return ColecoVisionControllerDefinition; } }
public IController Controller { get; set; }
enum InputPortMode { Left, Right }
*/
public enum InputPortMode { Left, Right }
InputPortMode InputPortSelection;
byte ReadController1()
{
_isLag = false;
byte retval;
if (InputPortSelection == InputPortMode.Left)
{
byte retval = 0x7F;
if (Controller.IsPressed("P1 Up")) retval &= 0xFE;
if (Controller.IsPressed("P1 Right")) retval &= 0xFD;
if (Controller.IsPressed("P1 Down")) retval &= 0xFB;
if (Controller.IsPressed("P1 Left")) retval &= 0xF7;
if (Controller.IsPressed("P1 L")) retval &= 0x3F;
retval = ControllerDeck.ReadPort1(Controller, true);
return retval;
}
if (InputPortSelection == InputPortMode.Right)
{
byte retval = 0xF;
// 0x00;
if (Controller.IsPressed("P1 Key 8")) retval = 0x01;
if (Controller.IsPressed("P1 Key 4")) retval = 0x02;
if (Controller.IsPressed("P1 Key 5")) retval = 0x03;
// 0x04;
if (Controller.IsPressed("P1 Key 7")) retval = 0x05;
if (Controller.IsPressed("P1 Pound")) retval = 0x06;
if (Controller.IsPressed("P1 Key 2")) retval = 0x07;
// 0x08;
if (Controller.IsPressed("P1 Star")) retval = 0x09;
if (Controller.IsPressed("P1 Key 0")) retval = 0x0A;
if (Controller.IsPressed("P1 Key 9")) retval = 0x0B;
if (Controller.IsPressed("P1 Key 3")) retval = 0x0C;
if (Controller.IsPressed("P1 Key 1")) retval = 0x0D;
if (Controller.IsPressed("P1 Key 6")) retval = 0x0E;
if (Controller.IsPressed("P1 R") == false) retval |= 0x40;
retval |= 0x30; // always set these bits
retval = ControllerDeck.ReadPort1(Controller, false);
return retval;
}
return 0x7F;
}
@ -75,42 +47,18 @@ namespace BizHawk.Emulation.Cores.ColecoVision
byte ReadController2()
{
_isLag = false;
byte retval;
if (InputPortSelection == InputPortMode.Left)
{
byte retval = 0x7F;
if (Controller.IsPressed("P2 Up")) retval &= 0xFE;
if (Controller.IsPressed("P2 Right")) retval &= 0xFD;
if (Controller.IsPressed("P2 Down")) retval &= 0xFB;
if (Controller.IsPressed("P2 Left")) retval &= 0xF7;
if (Controller.IsPressed("P2 L")) retval &= 0x3F;
retval = ControllerDeck.ReadPort2(Controller, true);
return retval;
}
if (InputPortSelection == InputPortMode.Right)
{
byte retval = 0xF;
// 0x00;
if (Controller.IsPressed("P2 Key 8")) retval = 0x01;
if (Controller.IsPressed("P2 Key 4")) retval = 0x02;
if (Controller.IsPressed("P2 Key 5")) retval = 0x03;
// 0x04;
if (Controller.IsPressed("P2 Key 7")) retval = 0x05;
if (Controller.IsPressed("P2 Pound")) retval = 0x06;
if (Controller.IsPressed("P2 Key 2")) retval = 0x07;
// 0x08;
if (Controller.IsPressed("P2 Star")) retval = 0x09;
if (Controller.IsPressed("P2 Key 0")) retval = 0x0A;
if (Controller.IsPressed("P2 Key 9")) retval = 0x0B;
if (Controller.IsPressed("P2 Key 3")) retval = 0x0C;
if (Controller.IsPressed("P2 Key 1")) retval = 0x0D;
if (Controller.IsPressed("P2 Key 6")) retval = 0x0E;
if (Controller.IsPressed("P2 R") == false) retval |= 0x40;
retval |= 0x30; // always set these bits
retval = ControllerDeck.ReadPort2(Controller, false);
return retval;
}
return 0x7F;
}

View File

@ -5,6 +5,7 @@ using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Components.Z80;
using BizHawk.Common.NumberExtensions;
namespace BizHawk.Emulation.Cores.ColecoVision
{
@ -20,6 +21,9 @@ namespace BizHawk.Emulation.Cores.ColecoVision
byte VdpBuffer;
int TmsMode;
// interrupt control for quadrature reads
bool spin_on1, spin_on2;
bool Mode1Bit { get { return (Registers[1] & 16) > 0; } }
bool Mode2Bit { get { return (Registers[0] & 2) > 0; } }
bool Mode3Bit { get { return (Registers[1] & 8) > 0; } }
@ -50,12 +54,35 @@ namespace BizHawk.Emulation.Cores.ColecoVision
if (scanLine == 192)
{
InterruptPending = true;
if (EnableInterrupts)
Cpu.NonMaskableInterrupt = true;
}
Cpu.ExecuteCycles(228);
byte temp_ret1 = Deck.ReadPort1(Controller, true);
byte temp_ret2 = Deck.ReadPort2(Controller, true);
if (((temp_ret1.Bit(4) && !spin_on1) | ( temp_ret2.Bit(4) && !spin_on2)) && scanLine == 50)
{
if (EnableInterrupts)
Cpu.NonMaskableInterrupt = true;
if (temp_ret1.Bit(4) && !spin_on1)
spin_on1 = true;
if (temp_ret2.Bit(4) && !spin_on2)
spin_on2 = true;
}
if (!temp_ret1.Bit(4))
spin_on1 = false;
if (!temp_ret2.Bit(4))
spin_on2 = false;
}
}
@ -437,9 +464,13 @@ namespace BizHawk.Emulation.Cores.ColecoVision
}
Z80A Cpu;
public TMS9918A(Z80A cpu)
ColecoVisionControllerDeck Deck;
public IController Controller;
public TMS9918A(Z80A cpu, ColecoVisionControllerDeck deck)
{
this.Cpu = cpu;
this.Deck = deck;
}
public int[] FrameBuffer = new int[256 * 192];