1342 lines
30 KiB
C#
1342 lines
30 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Common;
|
|
using System.Reflection;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
|
{
|
|
/*
|
|
* This file covers all NES and Famicom controller related stuff.
|
|
* It supports (or could be easily made to support by adding a new class) every existing
|
|
* controller device I know about. It does not support some things that were theoretically
|
|
* possible with the electronic interface available, but never used.
|
|
*/
|
|
|
|
#region interfaces and such
|
|
|
|
/// <summary>
|
|
/// stores information about the strobe lines controlled by $4016
|
|
/// </summary>
|
|
public struct StrobeInfo
|
|
{
|
|
/// <summary>
|
|
/// the current value of $4016.0; strobes regular controller ports
|
|
/// </summary>
|
|
public readonly int OUT0;
|
|
/// <summary>
|
|
/// the current value of $4016.1; strobes expansion port
|
|
/// </summary>
|
|
public readonly int OUT1;
|
|
/// <summary>
|
|
/// the current value of $4016.2; strobes expansion port
|
|
/// </summary>
|
|
public readonly int OUT2;
|
|
/// <summary>
|
|
/// the previous value or $4016.0 (for edge sensitive equipment)
|
|
/// </summary>
|
|
public readonly int OUT0old;
|
|
/// <summary>
|
|
/// the previous value or $4016.1 (for edge sensitive equipment)
|
|
/// </summary>
|
|
public readonly int OUT1old;
|
|
/// <summary>
|
|
/// the previous value or $4016.2 (for edge sensitive equipment)
|
|
/// </summary>
|
|
public readonly int OUT2old;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="oldvalue">the old latched $4016 byte</param>
|
|
/// <param name="newvalue">the new latched $4016 byte</param>
|
|
public StrobeInfo(byte oldvalue, byte newvalue)
|
|
{
|
|
OUT0old = oldvalue & 1;
|
|
OUT1old = oldvalue >> 1 & 1;
|
|
OUT2old = oldvalue >> 2 & 1;
|
|
OUT0 = newvalue & 1;
|
|
OUT1 = newvalue >> 1 & 1;
|
|
OUT2 = newvalue >> 2 & 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// the main system deck, handling all $4016 writes and $4016/$4017 reads
|
|
/// </summary>
|
|
public interface IControllerDeck
|
|
{
|
|
/// <summary>
|
|
/// call whenever $4016 is written
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
/// <param name="c"></param>
|
|
void Strobe(StrobeInfo s, IController c);
|
|
/// <summary>
|
|
/// call whenever $4016 is read
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns>bits 0-4 are valid</returns>
|
|
byte ReadA(IController c); // D0:D4
|
|
/// <summary>
|
|
/// call whenever $4017 is read
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns>bits 0-4 are valid</returns>
|
|
byte ReadB(IController c); // D0:D4
|
|
ControllerDefinition GetDefinition();
|
|
void SyncState(Serializer ser);
|
|
}
|
|
|
|
/// <summary>
|
|
/// a peripheral that plugs into the famicom expansion port
|
|
/// </summary>
|
|
public interface IFamicomExpansion
|
|
{
|
|
void Strobe(StrobeInfo s, IController c);
|
|
/// <summary>
|
|
/// read data from $4016
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns>only bit 1 is valid</returns>
|
|
byte ReadA(IController c);
|
|
/// <summary>
|
|
/// read data from $4017
|
|
/// </summary>
|
|
/// <param name="c"></param>
|
|
/// <returns>bits 1-4 are valid</returns>
|
|
byte ReadB(IController c);
|
|
ControllerDefinition GetDefinition();
|
|
void SyncState(Serializer ser);
|
|
}
|
|
|
|
/// <summary>
|
|
/// a peripheral that plugs into either of the two NES controller ports
|
|
/// </summary>
|
|
public interface INesPort
|
|
{
|
|
void Strobe(StrobeInfo s, IController c); // only uses OUT0
|
|
byte Read(IController c); // only uses D0, D3, D4
|
|
ControllerDefinition GetDefinition();
|
|
void SyncState(Serializer ser);
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// a NES or AV famicom, with two attached devices
|
|
/// </summary>
|
|
public class NesDeck : IControllerDeck
|
|
{
|
|
INesPort Left;
|
|
INesPort Right;
|
|
ControlDefUnMerger LeftU;
|
|
ControlDefUnMerger RightU;
|
|
ControllerDefinition Definition;
|
|
|
|
public NesDeck(INesPort Left, INesPort Right, Func<int, int, bool> PPUCallback)
|
|
{
|
|
this.Left = Left;
|
|
this.Right = Right;
|
|
List<ControlDefUnMerger> cdum;
|
|
Definition = ControllerDefMerger.GetMerged(new[] { Left.GetDefinition(), Right.GetDefinition() }, out cdum);
|
|
LeftU = cdum[0];
|
|
RightU = cdum[1];
|
|
|
|
// apply hacks
|
|
// if this list gets very long, then something should be changed
|
|
// if it stays short, then no problem
|
|
if (Left is FourScore)
|
|
(Left as FourScore).RightPort = false;
|
|
if (Right is FourScore)
|
|
(Right as FourScore).RightPort = true;
|
|
if (Left is IZapper)
|
|
(Left as IZapper).PPUCallback = PPUCallback;
|
|
if (Right is IZapper)
|
|
(Right as IZapper).PPUCallback = PPUCallback;
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
Left.Strobe(s, LeftU.UnMerge(c));
|
|
Right.Strobe(s, RightU.UnMerge(c));
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
return (byte)(Left.Read(LeftU.UnMerge(c)) & 0x19);
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
return (byte)(Right.Read(RightU.UnMerge(c)) & 0x19);
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection("Left");
|
|
Left.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.BeginSection("Right");
|
|
Right.SyncState(ser);
|
|
ser.EndSection();
|
|
}
|
|
}
|
|
|
|
public class UnpluggedNES : INesPort
|
|
{
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return new ControllerDefinition();
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// a NES controller; also used internally to represent the two famicom controllers
|
|
/// </summary>
|
|
public class ControllerNES : INesPort
|
|
{
|
|
bool resetting = false;
|
|
int latchedvalue = 0;
|
|
|
|
static string[] Buttons =
|
|
{
|
|
"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right"
|
|
};
|
|
static string[] FamicomP2Buttons =
|
|
{
|
|
"0A", "0B", null, null, "0Up", "0Down", "0Left", "0Right"
|
|
};
|
|
|
|
bool FamicomP2Hack;
|
|
|
|
ControllerDefinition Definition;
|
|
|
|
public ControllerNES()
|
|
{
|
|
Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = Buttons
|
|
.OrderBy(x => ButtonOrdinals[x])
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
|
|
Dictionary<string, int> ButtonOrdinals = new Dictionary<string, int>
|
|
{
|
|
{ "0Up", 1 },
|
|
{ "0Down", 2 },
|
|
{ "0Left", 3 },
|
|
{ "0Right", 4 },
|
|
{ "0Start", 5 },
|
|
{ "0Select", 6 },
|
|
{ "0B", 7 },
|
|
{ "0A", 8 },
|
|
};
|
|
|
|
public ControllerNES(bool famicomP2)
|
|
{
|
|
if (famicomP2)
|
|
{
|
|
Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = FamicomP2Buttons
|
|
.Where((s) => s != null)
|
|
.OrderBy(x => ButtonOrdinals[x])
|
|
.ToList()
|
|
};
|
|
}
|
|
else
|
|
{
|
|
Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = Buttons
|
|
.OrderBy(x => ButtonOrdinals[x])
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
FamicomP2Hack = famicomP2;
|
|
}
|
|
|
|
// reset is not edge triggered; so long as it's high, the latch is continuously reloading
|
|
// so we need to latch in two places:
|
|
// 1. when OUT0 goes low, to get the last set
|
|
// 2. wheneven reading with OUT0 high, since new data for controller is always loading
|
|
|
|
void Latch(IController c)
|
|
{
|
|
latchedvalue = SerialUtil.Latch(FamicomP2Hack ? FamicomP2Buttons : Buttons, c);
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedvalue & 1);
|
|
if (!resetting)
|
|
latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// a SNES controller plugged into a NES? heresy
|
|
/// </summary>
|
|
public class ControllerSNES : INesPort
|
|
{
|
|
bool resetting = false;
|
|
int latchedvalue = 0;
|
|
|
|
static readonly string[] Buttons =
|
|
{
|
|
"0B", "0Y", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right",
|
|
"0A", "0X", "0L", "0R", null, null, null, null // 4 0s at end
|
|
};
|
|
|
|
ControllerDefinition Definition;
|
|
|
|
public ControllerSNES()
|
|
{
|
|
Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = Buttons.Where(s => s != null).ToList()
|
|
};
|
|
}
|
|
|
|
// reset is not edge triggered; so long as it's high, the latch is continuously reloading
|
|
// so we need to latch in two places:
|
|
// 1. when OUT0 goes low, to get the last set
|
|
// 2. wheneven reading with OUT0 high, since new data for controller is always loading
|
|
|
|
void Latch(IController c)
|
|
{
|
|
latchedvalue = SerialUtil.Latch(Buttons, c);
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedvalue & 1);
|
|
if (!resetting)
|
|
latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// vaus paddle, the NES (not famicom) version
|
|
/// </summary>
|
|
public class ArkanoidNES : INesPort
|
|
{
|
|
int shiftidx = 0;
|
|
bool resetting = false;
|
|
byte latchedvalue = 0x54 ^ 0xff;
|
|
|
|
static ControllerDefinition Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = { "0Fire" },
|
|
FloatControls = { "0Paddle" },
|
|
FloatRanges = { new[] { 0.0f, 80.0f, 160.0f } }
|
|
};
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (resetting)
|
|
shiftidx = 0;
|
|
if (s.OUT0 > s.OUT0old)
|
|
{
|
|
latchedvalue = (byte)(0x54 + (int)c.GetFloat("0Paddle"));
|
|
latchedvalue ^= 0xff;
|
|
}
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
byte ret = c["0Fire"] ? (byte)0x08 : (byte)0x00;
|
|
if (resetting)
|
|
return ret;
|
|
|
|
byte value = latchedvalue;
|
|
value <<= shiftidx;
|
|
ret |= (byte)(value >> 3 & 0x10);
|
|
shiftidx++;
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("shiftidx", ref shiftidx);
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
public class FourScore : INesPort
|
|
{
|
|
// fourscore is actually one two port thing
|
|
// we emulate it as two separate halves
|
|
// each one behaves slightly differently
|
|
public bool RightPort = false;
|
|
|
|
static string[] Buttons =
|
|
{
|
|
"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right",
|
|
"1A", "1B", "1Select", "1Start", "1Up", "1Down", "1Left", "1Right",
|
|
};
|
|
static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(Buttons) };
|
|
|
|
bool resetting = false;
|
|
int latchedvalue = 0;
|
|
|
|
void Latch(IController c)
|
|
{
|
|
latchedvalue = SerialUtil.Latch(Buttons, c);
|
|
// set signatures
|
|
latchedvalue &= ~0xff0000;
|
|
if (RightPort) // signatures
|
|
latchedvalue |= 0x040000;
|
|
else
|
|
latchedvalue |= 0x080000;
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedvalue & 1);
|
|
if (!resetting)
|
|
latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
public class PowerPad : INesPort
|
|
{
|
|
static string[] D3Buttons = { "0PP2", "0PP1", "0PP5", "0PP9", "0PP6", "0PP10", "0PP11", "0PP7" };
|
|
static string[] D4Buttons = { "0PP4", "0PP3", "0PP12", "0PP8" };
|
|
static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(D3Buttons.Concat(D4Buttons)) };
|
|
|
|
bool resetting = false;
|
|
int latched3 = 0;
|
|
int latched4 = 0;
|
|
|
|
void Latch(IController c)
|
|
{
|
|
latched3 = SerialUtil.Latch(D3Buttons, c);
|
|
latched4 = SerialUtil.Latch(D4Buttons, c);
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
public byte Read(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
int d3 = latched3 & 1;
|
|
int d4 = latched4 & 1;
|
|
if (!resetting)
|
|
{
|
|
latched3 >>= 1; // ASR not LSR, so endless stream of 1s after data
|
|
latched4 >>= 1;
|
|
}
|
|
return (byte)(d3 << 3 | d4 << 4);
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latched3", ref latched3);
|
|
ser.Sync("latched4", ref latched4);
|
|
}
|
|
}
|
|
|
|
// Dummy interface to indicate zapper behavior, used as a means of type checking for zapper functionality
|
|
public interface IZapper
|
|
{
|
|
Func<int, int, bool> PPUCallback { get; set; }
|
|
}
|
|
|
|
public class Zapper : INesPort, IFamicomExpansion, IZapper
|
|
{
|
|
/// <summary>
|
|
/// returns true if light was detected at the ppu coordinates specified
|
|
/// </summary>
|
|
public Func<int, int, bool> PPUCallback { get; set; }
|
|
|
|
static ControllerDefinition Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = { "0Fire" },
|
|
FloatControls = { "0Zapper X", "0Zapper Y" },
|
|
FloatRanges = { new[] { 0.0f, 128.0f, 255.0f }, new[] { 0.0f, 120.0f, 239.0f } }
|
|
};
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
}
|
|
|
|
// NES controller port interface
|
|
public byte Read(IController c)
|
|
{
|
|
byte ret = 0;
|
|
if (c["0Fire"])
|
|
ret |= 0x10;
|
|
if (!PPUCallback((int)c.GetFloat("0Zapper X"), (int)c.GetFloat("0Zapper Y")))
|
|
ret |= 0x08;
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
}
|
|
|
|
// famicom expansion hookups
|
|
public byte ReadA(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
return Read(c);
|
|
}
|
|
}
|
|
|
|
public class VSZapper : INesPort, IZapper
|
|
{
|
|
/// <summary>
|
|
/// returns true if light was detected at the ppu coordinates specified
|
|
/// </summary
|
|
public Func<int, int, bool> PPUCallback { get; set; }
|
|
|
|
bool resetting = false;
|
|
uint latchedvalue = 0;
|
|
|
|
static ControllerDefinition Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = { "0Fire" },
|
|
FloatControls = { "0Zapper X", "0Zapper Y" },
|
|
FloatRanges = { new[] { 0.0f, 128.0f, 255.0f }, new[] { 0.0f, 120.0f, 239.0f } }
|
|
};
|
|
|
|
void Latch(IController c)
|
|
{
|
|
byte ret = 0;
|
|
if (c["0Fire"])
|
|
ret |= 0x80;
|
|
if (PPUCallback((int)c.GetFloat("0Zapper X"), (int)c.GetFloat("0Zapper Y")))
|
|
ret |= 0x40;
|
|
|
|
ret |= 0x10; // always 1
|
|
latchedvalue = ret;
|
|
latchedvalue |= 0xFFFFFF00;
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
// NES controller port interface
|
|
public byte Read(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedvalue & 1);
|
|
if (!resetting)
|
|
latchedvalue >>= 1; // ASR not LSR, so endless stream of 1s after data
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
|
|
// famicom expansion hookups
|
|
public byte ReadA(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
return Read(c);
|
|
}
|
|
}
|
|
|
|
public class FamicomDeck : IControllerDeck
|
|
{
|
|
// two NES controllers are maintained internally
|
|
INesPort Player1 = new ControllerNES(false);
|
|
INesPort Player2 = new ControllerNES(true);
|
|
IFamicomExpansion Player3;
|
|
|
|
ControlDefUnMerger Player1U;
|
|
ControlDefUnMerger Player2U;
|
|
ControlDefUnMerger Player3U;
|
|
|
|
ControllerDefinition Definition;
|
|
|
|
public FamicomDeck(IFamicomExpansion ExpSlot, Func<int, int, bool> PPUCallback)
|
|
{
|
|
Player3 = ExpSlot;
|
|
List<ControlDefUnMerger> cdum;
|
|
Definition = ControllerDefMerger.GetMerged(
|
|
new[] { Player1.GetDefinition(), Player2.GetDefinition(), Player3.GetDefinition() }, out cdum);
|
|
Definition.BoolButtons.Add("P2 Microphone");
|
|
Player1U = cdum[0];
|
|
Player2U = cdum[1];
|
|
Player3U = cdum[2];
|
|
|
|
// hack
|
|
if (Player3 is Zapper)
|
|
(Player3 as Zapper).PPUCallback = PPUCallback;
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
Player1.Strobe(s, Player1U.UnMerge(c));
|
|
Player2.Strobe(s, Player2U.UnMerge(c));
|
|
Player3.Strobe(s, Player3U.UnMerge(c));
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
byte ret = 0;
|
|
ret |= (byte)(Player1.Read(Player1U.UnMerge(c)) & 1);
|
|
ret |= (byte)(Player3.ReadA(Player3U.UnMerge(c)) & 2);
|
|
if (c["P2 Microphone"])
|
|
ret |= 4;
|
|
return ret;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
byte ret = 0;
|
|
ret |= (byte)(Player2.Read(Player2U.UnMerge(c)) & 1);
|
|
ret |= (byte)(Player3.ReadB(Player3U.UnMerge(c)) & 30);
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection("Left");
|
|
Player1.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.BeginSection("Right");
|
|
Player2.SyncState(ser);
|
|
ser.EndSection();
|
|
ser.BeginSection("Expansion");
|
|
Player3.SyncState(ser);
|
|
ser.EndSection();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// vaus controller that plugs into a famicom's expansion port
|
|
/// </summary>
|
|
public class ArkanoidFam : IFamicomExpansion
|
|
{
|
|
int shiftidx = 0;
|
|
bool resetting = false;
|
|
byte latchedvalue = 0x54 ^ 0xff;
|
|
|
|
static ControllerDefinition Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = { "0Fire" },
|
|
FloatControls = { "0Paddle" },
|
|
FloatRanges = { new[] { 0.0f, 80.0f, 160.0f } }
|
|
};
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (resetting)
|
|
shiftidx = 0;
|
|
if (s.OUT0 > s.OUT0old)
|
|
{
|
|
latchedvalue = (byte)(0x54 + (int)c.GetFloat("0Paddle"));
|
|
latchedvalue ^= 0xff;
|
|
}
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
return c["0Fire"] ? (byte)0x02 : (byte)0x00;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
byte ret = 0;
|
|
if (resetting)
|
|
return ret;
|
|
|
|
byte value = latchedvalue;
|
|
value <<= shiftidx;
|
|
ret |= (byte)(value >> 6 & 0x02);
|
|
shiftidx++;
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("shiftidx", ref shiftidx);
|
|
ser.Sync("restting", ref resetting);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
public class FamilyBasicKeyboard : IFamicomExpansion
|
|
{
|
|
#region buttonlookup
|
|
static string[] Buttons =
|
|
{
|
|
"0]",
|
|
"0[",
|
|
"0RETURN",
|
|
"0F8",
|
|
"0STOP",
|
|
"0¥",
|
|
"0RSHIFT",
|
|
"0カナ",
|
|
|
|
"0;",
|
|
"0:",
|
|
"0@",
|
|
"0F7",
|
|
"0^",
|
|
"0-",
|
|
"0/",
|
|
"0_",
|
|
|
|
"0K",
|
|
"0L",
|
|
"0O",
|
|
"0F6",
|
|
"00",
|
|
"0P",
|
|
"0,",
|
|
"0.",
|
|
|
|
"0J",
|
|
"0U",
|
|
"0I",
|
|
"0F5",
|
|
"08",
|
|
"09",
|
|
"0N",
|
|
"0M",
|
|
|
|
"0H",
|
|
"0G",
|
|
"0Y",
|
|
"0F4",
|
|
"06",
|
|
"07",
|
|
"0V",
|
|
"0B",
|
|
|
|
"0D",
|
|
"0R",
|
|
"0T",
|
|
"0F3",
|
|
"04",
|
|
"05",
|
|
"0C",
|
|
"0F",
|
|
|
|
"0A",
|
|
"0S",
|
|
"0W",
|
|
"0F2",
|
|
"03",
|
|
"0E",
|
|
"0Z",
|
|
"0X",
|
|
|
|
"0CTR",
|
|
"0Q",
|
|
"0ESC",
|
|
"0F1",
|
|
"02",
|
|
"01",
|
|
"0GRPH",
|
|
"0LSHIFT",
|
|
|
|
"0LEFT",
|
|
"0RIGHT",
|
|
"0UP",
|
|
"0CLR",
|
|
"0INS",
|
|
"0DEL",
|
|
"0SPACE",
|
|
"0DOWN",
|
|
|
|
};
|
|
#endregion
|
|
|
|
static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(Buttons) };
|
|
|
|
bool active;
|
|
int column;
|
|
int row;
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
active = s.OUT2 != 0;
|
|
column = s.OUT1;
|
|
if (s.OUT1 < s.OUT1old)
|
|
{
|
|
row++;
|
|
if (row == 10)
|
|
row = 0;
|
|
}
|
|
if (s.OUT0 != 0)
|
|
row = 0;
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
if (!active)
|
|
return 0;
|
|
if (row == 9) // empty last row
|
|
return 0;
|
|
int idx = row * 8 + column * 4;
|
|
|
|
byte ret = 0;
|
|
|
|
if (c[Buttons[idx]]) ret |= 16;
|
|
if (c[Buttons[idx + 1]]) ret |= 8;
|
|
if (c[Buttons[idx + 2]]) ret |= 4;
|
|
if (c[Buttons[idx + 3]]) ret |= 2;
|
|
|
|
// nothing is clocked here
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("active", ref active);
|
|
ser.Sync("column", ref column);
|
|
ser.Sync("row", ref row);
|
|
}
|
|
}
|
|
|
|
public class Famicom4P : IFamicomExpansion
|
|
{
|
|
static string[] P1Buttons =
|
|
{
|
|
"0A", "0B", "0Select", "0Start", "0Up", "0Down", "0Left", "0Right"
|
|
};
|
|
static string[] P2Buttons =
|
|
{
|
|
"1A", "1B", "1Select", "1Start", "1Up", "1Down", "1Left", "1Right",
|
|
};
|
|
static ControllerDefinition Definition = new ControllerDefinition { BoolButtons = new List<string>(P1Buttons.Concat(P2Buttons)) };
|
|
|
|
bool resetting = false;
|
|
int latchedp1 = 0;
|
|
int latchedp2 = 0;
|
|
|
|
void Latch(IController c)
|
|
{
|
|
latchedp1 = SerialUtil.Latch(P1Buttons, c);
|
|
latchedp2 = SerialUtil.Latch(P2Buttons, c);
|
|
}
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 != 0;
|
|
if (s.OUT0 < s.OUT0old)
|
|
Latch(c);
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedp1 << 1 & 2);
|
|
if (!resetting)
|
|
latchedp1 >>= 1;
|
|
return ret;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
if (resetting)
|
|
Latch(c);
|
|
byte ret = (byte)(latchedp2 << 1 & 2);
|
|
if (!resetting)
|
|
latchedp2 >>= 1;
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("resetting", ref resetting);
|
|
ser.Sync("latchedp1", ref latchedp1);
|
|
ser.Sync("latchedp2", ref latchedp2);
|
|
}
|
|
}
|
|
|
|
public class OekaKids : IFamicomExpansion
|
|
{
|
|
static ControllerDefinition Definition = new ControllerDefinition
|
|
{
|
|
BoolButtons = { "0Click", "0Touch" },
|
|
FloatControls = { "0Pen X", "0Pen Y" },
|
|
FloatRanges = { new[] { 0.0f, 128.0f, 255.0f }, new[] { 0.0f, 120.0f, 239.0f } }
|
|
};
|
|
|
|
bool resetting;
|
|
int shiftidx;
|
|
int latchedvalue = 0;
|
|
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
resetting = s.OUT0 == 0;
|
|
if (s.OUT0 < s.OUT0old) // H->L: latch
|
|
{
|
|
int x = (int)c.GetFloat("0Pen X");
|
|
int y = (int)c.GetFloat("0Pen Y");
|
|
// http://forums.nesdev.com/viewtopic.php?p=19454#19454
|
|
// it almost feels like the hardware guys got the request for
|
|
// a tablet that returned x in [0, 255] and y in [0, 239] and then
|
|
// accidentally flipped the whole thing sideways
|
|
x = (x + 8) * 240 / 256;
|
|
y = (y - 14) * 256 / 240;
|
|
x &= 255;
|
|
y &= 255;
|
|
latchedvalue = x << 10 | y << 2;
|
|
if (c["0Touch"])
|
|
latchedvalue |= 2;
|
|
if (c["0Click"])
|
|
latchedvalue |= 1;
|
|
}
|
|
if (s.OUT0 > s.OUT0old) // L->H: reset shift
|
|
shiftidx = 0;
|
|
if (s.OUT1 > s.OUT1old) // L->H: increment shift
|
|
shiftidx++;
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
byte ret = (byte)(resetting ? 2 : 0);
|
|
if (resetting)
|
|
return ret;
|
|
|
|
// the shiftidx = 0 read is one off the end
|
|
int bit = latchedvalue >> (16 - shiftidx);
|
|
bit &= 4;
|
|
bit ^= 4; // inverted data
|
|
ret |= (byte)(bit);
|
|
return ret;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return Definition;
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.Sync("resetting", ref resetting);
|
|
ser.Sync("shiftidx", ref shiftidx);
|
|
ser.Sync("latchedvalue", ref latchedvalue);
|
|
}
|
|
}
|
|
|
|
public class UnpluggedFam : IFamicomExpansion
|
|
{
|
|
public void Strobe(StrobeInfo s, IController c)
|
|
{
|
|
}
|
|
|
|
public byte ReadA(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public byte ReadB(IController c)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
public ControllerDefinition GetDefinition()
|
|
{
|
|
return new ControllerDefinition();
|
|
}
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
}
|
|
}
|
|
|
|
public static class SerialUtil
|
|
{
|
|
public static int Latch(string[] values, IController c)
|
|
{
|
|
int ret = 0;
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
if (values.Length > i)
|
|
{
|
|
if (values[i] != null && c[values[i]])
|
|
ret |= 1 << i;
|
|
}
|
|
else
|
|
{
|
|
// 1 in all other bits
|
|
ret |= 1 << i;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#region control definition adapters
|
|
|
|
// the idea here is that various connected peripherals have their controls all merged
|
|
// into one definition, including logic to unmerge the data back so each one can work
|
|
// with it without knowing what else is connected
|
|
|
|
public class ControlDefUnMerger
|
|
{
|
|
Dictionary<string, string> Remaps;
|
|
|
|
public ControlDefUnMerger(Dictionary<string, string> Remaps)
|
|
{
|
|
this.Remaps = Remaps;
|
|
}
|
|
|
|
private class DummyController : IController
|
|
{
|
|
IController src;
|
|
Dictionary<string, string> remaps;
|
|
public DummyController(IController src, Dictionary<string, string> remaps)
|
|
{
|
|
this.src = src;
|
|
this.remaps = remaps;
|
|
}
|
|
|
|
public ControllerDefinition Type { get { throw new NotImplementedException(); } }
|
|
|
|
public bool this[string button] { get { return IsPressed(button); } }
|
|
|
|
public bool IsPressed(string button)
|
|
{
|
|
return src.IsPressed(remaps[button]);
|
|
}
|
|
|
|
public float GetFloat(string name)
|
|
{
|
|
return src.GetFloat(remaps[name]);
|
|
}
|
|
}
|
|
|
|
public IController UnMerge(IController c)
|
|
{
|
|
return new DummyController(c, Remaps);
|
|
}
|
|
|
|
}
|
|
|
|
public static class ControllerDefMerger
|
|
{
|
|
private static string Allocate(string input, ref int plr, ref int plrnext)
|
|
{
|
|
int offset = int.Parse(input.Substring(0, 1));
|
|
int currplr = plr + offset;
|
|
if (currplr >= plrnext)
|
|
plrnext = currplr + 1;
|
|
return string.Format("P{0} {1}", currplr, input.Substring(1));
|
|
}
|
|
|
|
/// <summary>
|
|
/// handles all player number merging
|
|
/// </summary>
|
|
/// <param name="Controllers"></param>
|
|
/// <returns></returns>
|
|
public static ControllerDefinition GetMerged(IEnumerable<ControllerDefinition> Controllers, out List<ControlDefUnMerger> Unmergers)
|
|
{
|
|
ControllerDefinition ret = new ControllerDefinition();
|
|
Unmergers = new List<ControlDefUnMerger>();
|
|
int plr = 1;
|
|
int plrnext = 1;
|
|
foreach (var def in Controllers)
|
|
{
|
|
Dictionary<string, string> remaps = new Dictionary<string, string>();
|
|
|
|
foreach (string s in def.BoolButtons)
|
|
{
|
|
string r = Allocate(s, ref plr, ref plrnext);
|
|
ret.BoolButtons.Add(r);
|
|
remaps[s] = r;
|
|
}
|
|
foreach (string s in def.FloatControls)
|
|
{
|
|
string r = Allocate(s, ref plr, ref plrnext);
|
|
ret.FloatControls.Add(r);
|
|
remaps[s] = r;
|
|
}
|
|
ret.FloatRanges.AddRange(def.FloatRanges);
|
|
plr = plrnext;
|
|
Unmergers.Add(new ControlDefUnMerger(remaps));
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region settings
|
|
|
|
public class NESControlSettings
|
|
{
|
|
static readonly Dictionary<string, Type> FamicomExpansions;
|
|
static readonly Dictionary<string, Type> NesPortDevices;
|
|
|
|
static Dictionary<string, Type> Implementors<T>()
|
|
{
|
|
var assy = typeof(NESControlSettings).Assembly;
|
|
var types = assy.GetTypes().Where(c => typeof(T).IsAssignableFrom(c) && !c.IsAbstract && !c.IsInterface);
|
|
var ret = new Dictionary<string, Type>();
|
|
foreach (Type t in types)
|
|
ret[t.Name] = t;
|
|
return ret;
|
|
}
|
|
|
|
static NESControlSettings()
|
|
{
|
|
FamicomExpansions = Implementors<IFamicomExpansion>();
|
|
NesPortDevices = Implementors<INesPort>();
|
|
}
|
|
|
|
public static IList<string> GetFamicomExpansionValues()
|
|
{
|
|
return new List<string>(FamicomExpansions.Keys).AsReadOnly();
|
|
}
|
|
public static IList<string> GetNesPortValues()
|
|
{
|
|
return new List<string>(NesPortDevices.Keys).AsReadOnly();
|
|
}
|
|
|
|
[JsonIgnore]
|
|
private bool _Famicom;
|
|
public bool Famicom { get { return _Famicom; } set { _Famicom = value; } }
|
|
[JsonIgnore]
|
|
private string _NesLeftPort;
|
|
[JsonIgnore]
|
|
private string _NesRightPort;
|
|
public string NesLeftPort
|
|
{
|
|
get { return _NesLeftPort; }
|
|
set
|
|
{
|
|
if (NesPortDevices.ContainsKey(value))
|
|
_NesLeftPort = value;
|
|
else
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
public string NesRightPort
|
|
{
|
|
get { return _NesRightPort; }
|
|
set
|
|
{
|
|
if (NesPortDevices.ContainsKey(value))
|
|
_NesRightPort = value;
|
|
else
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
[JsonIgnore]
|
|
private string _FamicomExpPort;
|
|
public string FamicomExpPort
|
|
{
|
|
get { return _FamicomExpPort; }
|
|
set
|
|
{
|
|
if (FamicomExpansions.ContainsKey(value))
|
|
_FamicomExpPort = value;
|
|
else
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
|
|
public NESControlSettings()
|
|
{
|
|
Famicom = false;
|
|
FamicomExpPort = typeof(UnpluggedFam).Name;
|
|
NesLeftPort = typeof(ControllerNES).Name;
|
|
NesRightPort = typeof(ControllerNES).Name;
|
|
}
|
|
|
|
public static bool NeedsReboot(NESControlSettings x, NESControlSettings y)
|
|
{
|
|
return !DeepEquality.DeepEquals(x, y);
|
|
}
|
|
|
|
public NESControlSettings Clone()
|
|
{
|
|
return (NESControlSettings)MemberwiseClone();
|
|
}
|
|
|
|
public IControllerDeck Instantiate(Func<int, int, bool> PPUCallback)
|
|
{
|
|
if (Famicom)
|
|
{
|
|
IFamicomExpansion exp = (IFamicomExpansion)Activator.CreateInstance(FamicomExpansions[FamicomExpPort]);
|
|
IControllerDeck ret = new FamicomDeck(exp, PPUCallback);
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
INesPort left = (INesPort)Activator.CreateInstance(NesPortDevices[NesLeftPort]);
|
|
INesPort right = (INesPort)Activator.CreateInstance(NesPortDevices[NesRightPort]);
|
|
IControllerDeck ret = new NesDeck(left, right, PPUCallback);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|