Virtu cleanup (#1846)
* virtu - tabs instead of spaces * virtu - use nooget for Newtonsoft.Json * virtu - target .net 4.8 * resharper - target C# 8 * virtu - remove unused usings * virtu - remove some unused code * virtu - cleanups * virtu - trace logging - add flags to trace logs * virtu - cleanups * virtu - misc cleanups * virtu - make some stuff internal and simplify the api some * virtu - clock down more things and simplify api more * virtu - lock api down more * virtu- simplify, breaks older savestates however * virtu - simplify more * virtu - cleanup * virtu- more cleanup * virtu - reorg some files * virtu - more cleanup * virtu - more reorg * virtu - more reorg * virtu - make more things internal instead of public * virtu - make more things internal * virtu - update dll * cleanup * virtu - reorg * virtu - remove unitialize from machine component since nothing was utilizing it * virtu - simplify api * virtu - cleanup * virtu - cleanup and lock things down * virtu - lock down and cleanup * virtu - cleanup * virtu - simplify - breaks savestates * virtu - make PeripheralCard an interface with default implementation, breaks savestates * virtu - minimize use of machine component * virtu - cleanup * virtu - start minimizing dependencies * virtu - cleanup * virtu - simplify * apple II - simplify * virtu - move some biz logic into bizhawk * virtu - git rid of MachineComponent * virtu - delete no longer needed code * virtu - reorg * move serialization logic out of virtu and into bizhawk, this was our shenanigans * virtu - some small cleanups * virtu - simplify * virtu - dependency minimization * virtu - minimize dependencies * minimize dependencies * virtu - move drivelight property into component that controls it * virtu - minimize dependencies * virtu - minimize depenencies * move some machine logic to memory class * move some initialize logic into constructor * move initialize logic to constructor * move logic from Initialize to constructor * move initialize logic to constructor, simplify api to bizhawk * dll * virtu - movie some logic back into bizhawk * virtu - move Lagged property from machine to memory component * move more biz logic from virtu to biz * virtu - slight reorg * virtu - move some reset logic to constructor * virtu - move some stuff around * virtu - declare dependencies in memory class, no more dependencies on the machine class * move slots from machine to memory class * move some properties from machine to memory * move more things into the memory class * remove Machine.cs from virtu and make an equivalent container in bizhawk * virtu - cleanup * interface the cassette class and create a biz empty cassette "implementation" * move some more dummy logic from virtu to biz and put an interface in virtu * virtu - use an interface for a dependency * virtu - interface more things * virtu - more interfacing of things * virtu - interface more things * apple II/virtu - some reorg * virtu - cleanup * virtu - remove unused usages of disk name in disk classes * virtu- cleanup and simplify api * virtu - remove unused BootDrive property * virtu - cleanup and interface more * cleanup * update virtu dll
This commit is contained in:
parent
758a3e0f15
commit
b3a80965af
|
@ -50,12 +50,8 @@
|
|||
<PackageReference Include="ELFSharp" Version="0.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" PrivateAssets="All" />
|
||||
<PackageReference Include="OpenTK" Version="3.0.1" PrivateAssets="All" />
|
||||
<Reference Include="PeNet"
|
||||
HintPath="$(SolutionDir)References/PeNet.dll"
|
||||
Private="true" />
|
||||
<Reference Include="Virtu"
|
||||
HintPath="$(SolutionDir)References/Virtu.dll"
|
||||
Private="true" />
|
||||
<Reference Include="PeNet" HintPath="$(SolutionDir)References/PeNet.dll" Private="true" />
|
||||
<Reference Include="Virtu" HintPath="$(SolutionDir)References/Virtu.dll" Private="true" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)BizHawk.BizInvoke/BizHawk.BizInvoke.csproj" />
|
||||
|
|
|
@ -9,7 +9,22 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
{
|
||||
public IDictionary<string, RegisterValue> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
var regs = _machine.GetCpuFlagsAndRegisters();
|
||||
var regs = new Dictionary<string, int>
|
||||
{
|
||||
["A"] = _machine.Cpu.RA,
|
||||
["X"] = _machine.Cpu.RX,
|
||||
["Y"] = _machine.Cpu.RY,
|
||||
["S"] = _machine.Cpu.RS,
|
||||
["PC"] = _machine.Cpu.RPC,
|
||||
["Flag C"] = _machine.Cpu.FlagC ? 1 : 0,
|
||||
["Flag Z"] = _machine.Cpu.FlagZ ? 1 : 0,
|
||||
["Flag I"] = _machine.Cpu.FlagI ? 1 : 0,
|
||||
["Flag D"] = _machine.Cpu.FlagD ? 1 : 0,
|
||||
["Flag B"] = _machine.Cpu.FlagB ? 1 : 0,
|
||||
["Flag V"] = _machine.Cpu.FlagV ? 1 : 0,
|
||||
["Flag N"] = _machine.Cpu.FlagN ? 1 : 0,
|
||||
["Flag T"] = _machine.Cpu.FlagT ? 1 : 0
|
||||
};
|
||||
|
||||
var dic = new Dictionary<string, RegisterValue>();
|
||||
|
||||
|
@ -135,18 +150,19 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
var machineInVblank = _machine.Video.IsVBlank;
|
||||
|
||||
|
||||
_machine.Events.HandleEvents(_machine.Cpu.Execute());
|
||||
|
||||
if (!machineInVblank && _machine.Video.IsVBlank) // Check if a frame has passed while stepping
|
||||
{
|
||||
Frame++;
|
||||
if (_machine.Lagged)
|
||||
if (_machine.Memory.Lagged)
|
||||
{
|
||||
LagCount++;
|
||||
}
|
||||
|
||||
_machine.Lagged = true;
|
||||
_machine.DriveLight = false;
|
||||
_machine.Memory.Lagged = true;
|
||||
_machine.Memory.DiskIIController.DriveLight = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_machine.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
public bool IsLagFrame
|
||||
{
|
||||
get => _machine.Lagged;
|
||||
set => _machine.Lagged = value;
|
||||
get => _machine.Memory.Lagged;
|
||||
set => _machine.Memory.Lagged = value;
|
||||
}
|
||||
|
||||
public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
|
||||
|
|
|
@ -13,21 +13,12 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
[Description("Choose a monochrome monitor.")]
|
||||
public bool Monochrome { get; set; }
|
||||
|
||||
public Settings Clone()
|
||||
{
|
||||
return (Settings)MemberwiseClone();
|
||||
}
|
||||
public Settings Clone() => (Settings)MemberwiseClone();
|
||||
}
|
||||
|
||||
public Settings GetSettings()
|
||||
{
|
||||
return _settings.Clone();
|
||||
}
|
||||
public Settings GetSettings() => _settings.Clone();
|
||||
|
||||
public object GetSyncSettings()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
public object GetSyncSettings() => null;
|
||||
|
||||
public bool PutSettings(Settings o)
|
||||
{
|
||||
|
@ -39,9 +30,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
return false;
|
||||
}
|
||||
|
||||
public bool PutSyncSettings(object o)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
public bool PutSyncSettings(object o) => false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,12 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
public void GetSamplesSync(out short[] samples, out int nsamp)
|
||||
{
|
||||
_machine.Speaker.AudioService.GetSamples(out samples, out nsamp);
|
||||
_machine.Memory.Speaker.GetSamples(out samples, out nsamp);
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
_machine.Speaker.AudioService.Clear();
|
||||
_machine.Memory.Speaker.Clear();
|
||||
}
|
||||
|
||||
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
using System.IO;
|
||||
|
||||
using BizHawk.Emulation.Common;
|
||||
|
||||
using Jellyfish.Virtu;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
|
@ -14,7 +14,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(Machine);
|
||||
return objectType == typeof(Components);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
@ -23,8 +23,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
// uses its own serialization context: intentional
|
||||
return Machine.Deserialize(reader);
|
||||
return CreateSerializer().Deserialize<Components>(reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
|
@ -52,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
w.WritePropertyName("NextDiskPressed");
|
||||
w.WriteValue(_nextPressed);
|
||||
w.WritePropertyName("Core");
|
||||
_machine.Serialize(w);
|
||||
CreateSerializer().Serialize(w, _machine);
|
||||
w.WriteEndObject();
|
||||
}
|
||||
|
||||
|
@ -79,7 +78,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
public int CurrentDisk;
|
||||
public bool PreviousDiskPressed;
|
||||
public bool NextDiskPressed;
|
||||
public Machine Core;
|
||||
public Components Core;
|
||||
}
|
||||
|
||||
private void InitSaveStates()
|
||||
|
@ -152,5 +151,33 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
writer.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static JsonSerializer CreateSerializer()
|
||||
{
|
||||
// TODO: converters could be cached for speedup
|
||||
|
||||
var ser = new JsonSerializer
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.All, // leaving out Array is a very important problem, and means that we can't rely on a directly shared array to work.
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||
};
|
||||
|
||||
ser.Converters.Add(new TypeTypeConverter(new[]
|
||||
{
|
||||
// all expected Types to convert are either in this assembly or mscorlib
|
||||
typeof(Memory).Assembly,
|
||||
typeof(object).Assembly
|
||||
}));
|
||||
|
||||
ser.Converters.Add(new DelegateConverter());
|
||||
ser.Converters.Add(new ArrayConverter());
|
||||
|
||||
var cr = new DefaultContractResolver();
|
||||
cr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
|
||||
ser.ContractResolver = cr;
|
||||
|
||||
return ser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
{
|
||||
public partial class AppleII : IVideoProvider
|
||||
{
|
||||
public int[] GetVideoBuffer() => _machine.Video.VideoService.fb;
|
||||
public int[] GetVideoBuffer() => _machine.Video.GetVideoBuffer();
|
||||
|
||||
// put together, these describe a metric on the screen
|
||||
// they should define the smallest size that the buffer can be placed inside such that:
|
||||
|
|
|
@ -47,11 +47,9 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
_diskIIRom = comm.CoreFileProvider.GetFirmware(
|
||||
SystemId, "DiskII", true, "The DiskII firmware is required");
|
||||
|
||||
_machine = new Machine(_appleIIRom, _diskIIRom);
|
||||
_machine = new Components(_appleIIRom, _diskIIRom);
|
||||
|
||||
_machine.BizInitialize();
|
||||
|
||||
// make a writeable memory stream cloned from the rom.
|
||||
// make a writable memory stream cloned from the rom.
|
||||
// for junk.dsk the .dsk is important because it determines the format from that
|
||||
InitDisk();
|
||||
|
||||
|
@ -69,7 +67,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
private readonly List<byte[]> _romSet = new List<byte[]>();
|
||||
private readonly ITraceable _tracer;
|
||||
|
||||
private Machine _machine;
|
||||
private Components _machine;
|
||||
private byte[] _disk1;
|
||||
private readonly byte[] _appleIIRom;
|
||||
private readonly byte[] _diskIIRom;
|
||||
|
@ -109,14 +107,12 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
{
|
||||
_disk1 = _romSet[CurrentDisk];
|
||||
|
||||
// make a writeable memory stream cloned from the rom.
|
||||
// make a writable memory stream cloned from the rom.
|
||||
// for junk.dsk the .dsk is important because it determines the format from that
|
||||
_machine.BootDiskII.Drives[0].InsertDisk("junk.dsk", (byte[])_disk1.Clone(), false);
|
||||
_machine.Memory.DiskIIController.Drive1.InsertDisk("junk.dsk", (byte[])_disk1.Clone(), false);
|
||||
}
|
||||
|
||||
private static readonly List<string> RealButtons = new List<string>(Keyboard.GetKeyNames()
|
||||
//.Where(k => k != "White Apple") // Hack because these buttons aren't wired up yet
|
||||
//.Where(k => k != "Black Apple")
|
||||
.Where(k => k != "Reset"));
|
||||
|
||||
private static readonly List<string> ExtraButtons = new List<string>
|
||||
|
@ -126,7 +122,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
};
|
||||
|
||||
public bool DriveLightEnabled => true;
|
||||
public bool DriveLightOn => _machine.DriveLight;
|
||||
public bool DriveLightOn => _machine.Memory.DiskIIController.DriveLight;
|
||||
|
||||
private bool _nextPressed;
|
||||
private bool _prevPressed;
|
||||
|
@ -140,7 +136,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
});
|
||||
}
|
||||
|
||||
private void FrameAdv(IController controller, bool render, bool rendersound)
|
||||
private void FrameAdv(IController controller, bool render, bool renderSound)
|
||||
{
|
||||
if (_tracer.Enabled)
|
||||
{
|
||||
|
@ -172,7 +168,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
_prevPressed = false;
|
||||
}
|
||||
|
||||
_machine.BizFrameAdvance(RealButtons.Where(controller.IsPressed));
|
||||
MachineAdvance(RealButtons.Where(controller.IsPressed));
|
||||
if (IsLagFrame)
|
||||
{
|
||||
LagCount++;
|
||||
|
@ -181,6 +177,25 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
Frame++;
|
||||
}
|
||||
|
||||
private void MachineAdvance(IEnumerable<string> buttons)
|
||||
{
|
||||
_machine.Memory.Lagged = true;
|
||||
_machine.Memory.DiskIIController.DriveLight = false;
|
||||
_machine.Memory.Keyboard.SetKeys(buttons);
|
||||
|
||||
// frame begins at vsync.. beginning of vblank
|
||||
while (_machine.Video.IsVBlank)
|
||||
{
|
||||
_machine.Events.HandleEvents(_machine.Cpu.Execute());
|
||||
}
|
||||
|
||||
// now, while not vblank, we're in a frame
|
||||
while (!_machine.Video.IsVBlank)
|
||||
{
|
||||
_machine.Events.HandleEvents(_machine.Cpu.Execute());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetCallbacks()
|
||||
{
|
||||
_machine.Memory.ReadCallback = (addr) =>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
using Jellyfish.Virtu;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
/// <summary>
|
||||
/// A container class for the individual machine components
|
||||
/// </summary>
|
||||
public sealed class Components
|
||||
{
|
||||
/// <summary>
|
||||
/// for deserialization only!!
|
||||
/// </summary>
|
||||
public Components() { }
|
||||
|
||||
public Components(byte[] appleIIe, byte[] diskIIRom)
|
||||
{
|
||||
Events = new MachineEvents();
|
||||
Memory = new Memory(appleIIe);
|
||||
Cpu = new Cpu(Memory);
|
||||
Video = new Video(Events, Memory);
|
||||
|
||||
var emptySlot = new EmptyPeripheralCard(Video);
|
||||
|
||||
// Necessary because of tangling dependencies between memory and video classes
|
||||
Memory.Initialize(
|
||||
new Keyboard(),
|
||||
new GamePortComponent(),
|
||||
new EmptyCassetteComponent(),
|
||||
new Speaker(Events, Cpu),
|
||||
Video,
|
||||
new NoSlotClock(Video),
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
new DiskIIController(Video, diskIIRom),
|
||||
emptySlot);
|
||||
|
||||
Cpu.Reset();
|
||||
Memory.Reset();
|
||||
Video.Reset();
|
||||
}
|
||||
|
||||
public MachineEvents Events { get; set; }
|
||||
public Memory Memory { get; private set; }
|
||||
public Cpu Cpu { get; private set; }
|
||||
public Video Video { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Jellyfish.Virtu;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty implementation of ICassette, since we have not current built cassette functionality
|
||||
/// </summary>
|
||||
public class EmptyCassetteComponent : ICassette
|
||||
{
|
||||
// TODO: remove when json serialization is no longer used
|
||||
public EmptyCassetteComponent() { }
|
||||
|
||||
public bool ReadInput() => false;
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using Jellyfish.Virtu;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an unused peripheral card
|
||||
/// </summary>
|
||||
public class EmptyPeripheralCard : IPeripheralCard
|
||||
{
|
||||
// TODO: make readonly once json isn't used
|
||||
private Video _video;
|
||||
|
||||
// TODO: remove when json isn't used
|
||||
public EmptyPeripheralCard() { }
|
||||
|
||||
public EmptyPeripheralCard(Video video)
|
||||
{
|
||||
_video = video;
|
||||
}
|
||||
|
||||
public int ReadIoRegionC0C0(int address) => _video.ReadFloatingBus();
|
||||
public int ReadIoRegionC1C7(int address) => _video.ReadFloatingBus();
|
||||
public int ReadIoRegionC8CF(int address) => _video.ReadFloatingBus();
|
||||
|
||||
public void WriteIoRegionC0C0(int address, int data) { }
|
||||
public void WriteIoRegionC1C7(int address, int data) { }
|
||||
public void WriteIoRegionC8CF(int address, int data) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using Jellyfish.Virtu;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently a default implementation of the GamePort, needs to be built for gamepad support
|
||||
/// </summary>
|
||||
public class GamePortComponent : IGamePort
|
||||
{
|
||||
public bool ReadButton0() => Keyboard.WhiteAppleDown;
|
||||
public bool ReadButton1() => Keyboard.BlackAppleDown;
|
||||
public bool ReadButton2() => false;
|
||||
|
||||
public bool Paddle0Strobe => false;
|
||||
public bool Paddle1Strobe => false;
|
||||
public bool Paddle2Strobe => false;
|
||||
public bool Paddle3Strobe => false;
|
||||
|
||||
public void TriggerTimers() { }
|
||||
}
|
||||
}
|
|
@ -1,305 +1,334 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public class ArrayConverter : JsonConverter
|
||||
{
|
||||
// JSON.NET cannot, when reading, use PreserveReferencesHandling on arrays, although it fully supports it on writing.
|
||||
// Doing so while being able to fully preserve circular references would require storing the length of the array,
|
||||
// or reading ahead in the JSON to compute the length. For arrays that could contain reference types, we choose the latter.
|
||||
// For arrays of primitive types, there is no issue.
|
||||
|
||||
// TODO: on serialization, the type of the object is available, but is the expected type (ie, the one that we'll be fed during deserialization) available?
|
||||
// need this to at least detect covariance cases...
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
if (!typeof(Array).IsAssignableFrom(objectType))
|
||||
return false;
|
||||
|
||||
if (objectType.GetArrayRank() > 1)
|
||||
throw new NotImplementedException();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
private JsonSerializer bareserializer = new JsonSerializer(); // full default settings, separate context
|
||||
|
||||
private static void ReadExpectType(JsonReader reader, JsonToken expected)
|
||||
{
|
||||
if (!reader.Read())
|
||||
throw new InvalidOperationException();
|
||||
if (reader.TokenType != expected)
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
return null;
|
||||
else if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
string prop = reader.Value.ToString();
|
||||
ReadExpectType(reader, JsonToken.String);
|
||||
string id = reader.Value.ToString();
|
||||
if (prop == "$ref")
|
||||
{
|
||||
object ret = serializer.ReferenceResolver.ResolveReference(serializer, id);
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
else if (prop == "$id")
|
||||
{
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
prop = reader.Value.ToString();
|
||||
if (prop == "$length") // complex array
|
||||
{
|
||||
ReadExpectType(reader, JsonToken.Integer);
|
||||
int length = Convert.ToInt32(reader.Value);
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
if (reader.Value.ToString() != "$values")
|
||||
throw new InvalidOperationException();
|
||||
|
||||
Type elementType = objectType.GetElementType();
|
||||
|
||||
Array ret = Array.CreateInstance(elementType, length);
|
||||
// must register reference before deserializing elements to handle possible circular references
|
||||
serializer.ReferenceResolver.AddReference(serializer, id, ret);
|
||||
int index = 0;
|
||||
|
||||
ReadExpectType(reader, JsonToken.StartArray);
|
||||
while (true)
|
||||
{
|
||||
if (!reader.Read())
|
||||
throw new InvalidOperationException();
|
||||
if (reader.TokenType == JsonToken.EndArray)
|
||||
break;
|
||||
ret.SetValue(serializer.Deserialize(reader, elementType), index++);
|
||||
}
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
else if (prop == "$values") // simple array
|
||||
{
|
||||
if (!reader.Read())
|
||||
throw new InvalidOperationException();
|
||||
object ret = bareserializer.Deserialize(reader, objectType);
|
||||
// OK to add this after deserializing, as arrays of primitive types can't contain backrefs
|
||||
serializer.ReferenceResolver.AddReference(serializer, id, ret);
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("$ref");
|
||||
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("$id");
|
||||
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
|
||||
|
||||
var elementType = value.GetType().GetElementType();
|
||||
if (elementType.IsPrimitive)
|
||||
{
|
||||
writer.WritePropertyName("$values");
|
||||
bareserializer.Serialize(writer, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = (Array)value;
|
||||
writer.WritePropertyName("$length");
|
||||
writer.WriteValue(array.Length);
|
||||
|
||||
writer.WritePropertyName("$values");
|
||||
writer.WriteStartArray();
|
||||
foreach (object o in array)
|
||||
{
|
||||
serializer.Serialize(writer, o, elementType);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TypeTypeConverter : JsonConverter
|
||||
{
|
||||
// serialize and deserialize types, ignoring assembly entirely and only using namespace+typename
|
||||
// all types, including generic type arguments to supplied types, must be in one of the declared assemblies (only checked on read!)
|
||||
// the main goal here is to have something with a slight chance of working across versions
|
||||
|
||||
public TypeTypeConverter(IEnumerable<Assembly> ass)
|
||||
{
|
||||
assemblies = ass.ToList();
|
||||
}
|
||||
|
||||
private List<Assembly> assemblies;
|
||||
private Dictionary<string, Type> readlookup = new Dictionary<string, Type>();
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Type).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
private Type GetType(string name)
|
||||
{
|
||||
Type ret;
|
||||
if (!readlookup.TryGetValue(name, out ret))
|
||||
{
|
||||
ret = assemblies.Select(ass => ass.GetType(name, false)).Where(t => t != null).Single();
|
||||
readlookup.Add(name, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static string GetName(Type type)
|
||||
{
|
||||
return string.Format("{0}.{1}", type.Namespace, type.Name);
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
return GetType(reader.Value.ToString());
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.StartArray) // full generic
|
||||
{
|
||||
List<string> vals = serializer.Deserialize<List<string>>(reader);
|
||||
return GetType(vals[0]).MakeGenericType(vals.Skip(1).Select(GetType).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var type = (Type)value;
|
||||
if (type.IsGenericType && !type.IsGenericTypeDefinition)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
writer.WriteValue(GetName(type));
|
||||
foreach (var t in type.GetGenericArguments())
|
||||
{
|
||||
writer.WriteValue(GetName(t));
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(GetName(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DelegateConverter : JsonConverter
|
||||
{
|
||||
// caveats: if used on anonymous delegates and/or closures, brittle to name changes in the generated classes and methods
|
||||
// brittle to type name changes in general
|
||||
// must be serialized in tree with any real classes referred to by closures
|
||||
|
||||
// CAN NOT preserve reference equality of the delegates themselves, because the delegate must be created with
|
||||
// target in one shot, with no possibility to change the target later. We preserve references to targets,
|
||||
// and lose the ability to preserve references to delegates.
|
||||
|
||||
|
||||
// TODO: much of this could be made somewhat smarter and more resilient
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Delegate).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var slug = serializer.Deserialize<Slug>(reader);
|
||||
if (slug == null)
|
||||
return null;
|
||||
return slug.GetDelegate();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var slug = new Slug((Delegate)value);
|
||||
serializer.Serialize(writer, slug);
|
||||
}
|
||||
|
||||
private class Slug
|
||||
{
|
||||
public Type DelegateType;
|
||||
public Type MethodDeclaringType;
|
||||
public string MethodName;
|
||||
public List<Type> MethodParameters;
|
||||
public object Target;
|
||||
|
||||
public Delegate GetDelegate()
|
||||
{
|
||||
var mi = MethodDeclaringType.GetMethod(
|
||||
MethodName,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
|
||||
null,
|
||||
MethodParameters.ToArray(),
|
||||
null);
|
||||
|
||||
return Delegate.CreateDelegate(DelegateType, Target, mi);
|
||||
}
|
||||
|
||||
public Slug() { }
|
||||
|
||||
public Slug(Delegate d)
|
||||
{
|
||||
DelegateType = d.GetType();
|
||||
MethodDeclaringType = d.Method.DeclaringType;
|
||||
MethodName = d.Method.Name;
|
||||
MethodParameters = d.Method.GetParameters().Select(p => p.ParameterType).ToList();
|
||||
Target = d.Target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
public class ArrayConverter : JsonConverter
|
||||
{
|
||||
// JSON.NET cannot, when reading, use PreserveReferencesHandling on arrays, although it fully supports it on writing.
|
||||
// Doing so while being able to fully preserve circular references would require storing the length of the array,
|
||||
// or reading ahead in the JSON to compute the length. For arrays that could contain reference types, we choose the latter.
|
||||
// For arrays of primitive types, there is no issue.
|
||||
|
||||
// TODO: on serialization, the type of the object is available, but is the expected type (ie, the one that we'll be fed during deserialization) available?
|
||||
// need this to at least detect covariance cases...
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
if (!typeof(Array).IsAssignableFrom(objectType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectType.GetArrayRank() > 1)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
private readonly JsonSerializer _bareSerializer = new JsonSerializer(); // full default settings, separate context
|
||||
|
||||
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
|
||||
private static void ReadExpectType(JsonReader reader, JsonToken expected)
|
||||
{
|
||||
if (!reader.Read())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (reader.TokenType != expected)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
string prop = reader.Value.ToString();
|
||||
ReadExpectType(reader, JsonToken.String);
|
||||
string id = reader.Value.ToString();
|
||||
if (prop == "$ref")
|
||||
{
|
||||
object ret = serializer.ReferenceResolver.ResolveReference(serializer, id);
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (prop == "$id")
|
||||
{
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
prop = reader.Value.ToString();
|
||||
if (prop == "$length") // complex array
|
||||
{
|
||||
ReadExpectType(reader, JsonToken.Integer);
|
||||
int length = Convert.ToInt32(reader.Value);
|
||||
ReadExpectType(reader, JsonToken.PropertyName);
|
||||
if (reader.Value.ToString() != "$values")
|
||||
throw new InvalidOperationException();
|
||||
|
||||
Type elementType = objectType.GetElementType();
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
Array ret = Array.CreateInstance(elementType, length);
|
||||
|
||||
// must register reference before deserializing elements to handle possible circular references
|
||||
serializer.ReferenceResolver.AddReference(serializer, id, ret);
|
||||
int index = 0;
|
||||
|
||||
ReadExpectType(reader, JsonToken.StartArray);
|
||||
while (true)
|
||||
{
|
||||
if (!reader.Read())
|
||||
throw new InvalidOperationException();
|
||||
if (reader.TokenType == JsonToken.EndArray)
|
||||
break;
|
||||
ret.SetValue(serializer.Deserialize(reader, elementType), index++);
|
||||
}
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (prop == "$values") // simple array
|
||||
{
|
||||
if (!reader.Read())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
object ret = _bareSerializer.Deserialize(reader, objectType);
|
||||
|
||||
// OK to add this after deserializing, as arrays of primitive types can't contain backrefs
|
||||
serializer.ReferenceResolver.AddReference(serializer, id, ret);
|
||||
ReadExpectType(reader, JsonToken.EndObject);
|
||||
return ret;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (serializer.ReferenceResolver.IsReferenced(serializer, value))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("$ref");
|
||||
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("$id");
|
||||
writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
|
||||
|
||||
var elementType = value.GetType().GetElementType();
|
||||
if (elementType?.IsPrimitive ?? false)
|
||||
{
|
||||
writer.WritePropertyName("$values");
|
||||
_bareSerializer.Serialize(writer, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = (Array)value;
|
||||
writer.WritePropertyName("$length");
|
||||
writer.WriteValue(array.Length);
|
||||
|
||||
writer.WritePropertyName("$values");
|
||||
writer.WriteStartArray();
|
||||
foreach (object o in array)
|
||||
{
|
||||
serializer.Serialize(writer, o, elementType);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TypeTypeConverter : JsonConverter
|
||||
{
|
||||
// serialize and deserialize types, ignoring assembly entirely and only using namespace+typename
|
||||
// all types, including generic type arguments to supplied types, must be in one of the declared assemblies (only checked on read!)
|
||||
// the main goal here is to have something with a slight chance of working across versions
|
||||
public TypeTypeConverter(IEnumerable<Assembly> ass)
|
||||
{
|
||||
_assemblies = ass.ToList();
|
||||
}
|
||||
|
||||
private readonly List<Assembly> _assemblies;
|
||||
private readonly Dictionary<string, Type> _readLookup = new Dictionary<string, Type>();
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Type).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
private Type GetType(string name)
|
||||
{
|
||||
if (!_readLookup.TryGetValue(name, out var ret))
|
||||
{
|
||||
ret = _assemblies.Select(ass => ass.GetType(name, false)).Single(t => t != null);
|
||||
_readLookup.Add(name, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static string GetName(Type type)
|
||||
{
|
||||
return $"{type.Namespace}.{type.Name}";
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
return GetType(reader.Value.ToString());
|
||||
}
|
||||
|
||||
if (reader.TokenType == JsonToken.StartArray) // full generic
|
||||
{
|
||||
List<string> values = serializer.Deserialize<List<string>>(reader);
|
||||
return GetType(values[0]).MakeGenericType(values.Skip(1).Select(GetType).ToArray());
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var type = (Type)value;
|
||||
if (type.IsGenericType && !type.IsGenericTypeDefinition)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
writer.WriteValue(GetName(type));
|
||||
foreach (var t in type.GetGenericArguments())
|
||||
{
|
||||
writer.WriteValue(GetName(t));
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteValue(GetName(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DelegateConverter : JsonConverter
|
||||
{
|
||||
// caveats: if used on anonymous delegates and/or closures, brittle to name changes in the generated classes and methods
|
||||
// brittle to type name changes in general
|
||||
// must be serialized in tree with any real classes referred to by closures
|
||||
|
||||
// CAN NOT preserve reference equality of the delegates themselves, because the delegate must be created with
|
||||
// target in one shot, with no possibility to change the target later. We preserve references to targets,
|
||||
// and lose the ability to preserve references to delegates.
|
||||
|
||||
|
||||
// TODO: much of this could be made somewhat smarter and more resilient
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return typeof(Delegate).IsAssignableFrom(objectType);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var slug = serializer.Deserialize<Slug>(reader);
|
||||
return slug?.GetDelegate();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var slug = new Slug((Delegate)value);
|
||||
serializer.Serialize(writer, slug);
|
||||
}
|
||||
|
||||
private class Slug
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Local
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public Type DelegateType;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Local
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public Type MethodDeclaringType;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Local
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public string MethodName;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Local
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public List<Type> MethodParameters;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Local
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
public object Target;
|
||||
|
||||
public Delegate GetDelegate()
|
||||
{
|
||||
var mi = MethodDeclaringType.GetMethod(
|
||||
MethodName,
|
||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
|
||||
null,
|
||||
MethodParameters.ToArray(),
|
||||
null);
|
||||
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
return Delegate.CreateDelegate(DelegateType, Target, mi);
|
||||
}
|
||||
|
||||
public Slug() { }
|
||||
|
||||
public Slug(Delegate d)
|
||||
{
|
||||
DelegateType = d.GetType();
|
||||
MethodDeclaringType = d.Method.DeclaringType;
|
||||
MethodName = d.Method.Name;
|
||||
MethodParameters = d.Method.GetParameters().Select(p => p.ParameterType).ToList();
|
||||
Target = d.Target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,10 +49,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
// as best as I can tell, the serializers refer to depth, but don't actually need to work except when doing certain error recovery
|
||||
public override int Depth => 0;
|
||||
|
||||
public override string Path
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
public override string Path => throw new NotImplementedException();
|
||||
|
||||
public override Type ValueType => _v?.GetType();
|
||||
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Cassette : MachineComponent
|
||||
{
|
||||
public Cassette() { }
|
||||
public Cassette(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public bool ReadInput()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public partial class Cpu
|
||||
{
|
||||
[JsonIgnore]
|
||||
private Action[] _executeOpCode65N02;
|
||||
[JsonIgnore]
|
||||
private Action[] _executeOpCode65C02;
|
||||
|
||||
private const int Pc = 0x01;
|
||||
private const int Pz = 0x02;
|
||||
private const int Pi = 0x04;
|
||||
private const int Pd = 0x08;
|
||||
private const int Pb = 0x10;
|
||||
private const int Pv = 0x40;
|
||||
private const int Pn = 0x80;
|
||||
|
||||
private static readonly int[] DataPn =
|
||||
{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn
|
||||
};
|
||||
|
||||
private static readonly int[] DataPz =
|
||||
{
|
||||
Pz, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
|
||||
};
|
||||
|
||||
private static readonly int[] DataPnz =
|
||||
{
|
||||
Pz, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn,
|
||||
Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn, Pn
|
||||
};
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,86 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class Cpu
|
||||
{
|
||||
private const int OpCodeCount = 256;
|
||||
|
||||
[JsonIgnore]
|
||||
private Action[] ExecuteOpCode65N02;
|
||||
[JsonIgnore]
|
||||
private Action[] ExecuteOpCode65C02;
|
||||
|
||||
private const int PC = 0x01;
|
||||
private const int PZ = 0x02;
|
||||
private const int PI = 0x04;
|
||||
private const int PD = 0x08;
|
||||
private const int PB = 0x10;
|
||||
private const int PR = 0x20;
|
||||
private const int PV = 0x40;
|
||||
private const int PN = 0x80;
|
||||
|
||||
private const int DataCount = 256;
|
||||
|
||||
private static readonly int[] DataPN = new int[DataCount]
|
||||
{
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN
|
||||
};
|
||||
|
||||
private static readonly int[] DataPZ = new int[DataCount]
|
||||
{
|
||||
PZ, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
|
||||
};
|
||||
|
||||
private static readonly int[] DataPNZ = new int[DataCount]
|
||||
{
|
||||
PZ, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN,
|
||||
PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN, PN
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,56 +1,55 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public abstract class Disk525
|
||||
{
|
||||
internal abstract class Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
// ReSharper disable once PublicConstructorInAbstractClass
|
||||
public Disk525() { }
|
||||
protected Disk525(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
Name = name;
|
||||
Data = data;
|
||||
IsWriteProtected = isWriteProtected;
|
||||
}
|
||||
|
||||
public static Disk525 CreateDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException("name");
|
||||
}
|
||||
protected Disk525(byte[] data, bool isWriteProtected)
|
||||
{
|
||||
Data = data;
|
||||
IsWriteProtected = isWriteProtected;
|
||||
}
|
||||
|
||||
if (name.EndsWith(".do", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) // assumes dos sector skew
|
||||
{
|
||||
return new DiskDsk(name, data, isWriteProtected, SectorSkew.Dos);
|
||||
}
|
||||
else if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskNib(name, data, isWriteProtected);
|
||||
}
|
||||
else if (name.EndsWith(".po", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskDsk(name, data, isWriteProtected, SectorSkew.ProDos);
|
||||
}
|
||||
public static Disk525 CreateDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
if (name == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
if (name.EndsWith(".do", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) // assumes dos sector skew
|
||||
{
|
||||
return new DiskDsk(data, isWriteProtected, SectorSkew.Dos);
|
||||
}
|
||||
|
||||
public abstract void ReadTrack(int number, int fraction, byte[] buffer);
|
||||
public abstract void WriteTrack(int number, int fraction, byte[] buffer);
|
||||
if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskNib(data, isWriteProtected);
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
if (name.EndsWith(".po", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new DiskDsk(data, isWriteProtected, SectorSkew.ProDos);
|
||||
}
|
||||
|
||||
public byte[] Data { get; protected set; }
|
||||
public bool IsWriteProtected { get; private set; }
|
||||
return null;
|
||||
}
|
||||
|
||||
public const int SectorCount = 16;
|
||||
public const int SectorSize = 0x100;
|
||||
public const int TrackCount = 35;
|
||||
public const int TrackSize = 0x1A00;
|
||||
}
|
||||
public abstract void ReadTrack(int number, int fraction, byte[] buffer);
|
||||
public abstract void WriteTrack(int number, int fraction, byte[] buffer);
|
||||
|
||||
public byte[] Data { get; protected set; }
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
public bool IsWriteProtected { get; private set; }
|
||||
|
||||
public const int SectorCount = 16;
|
||||
public const int SectorSize = 0x100;
|
||||
public const int TrackSize = 0x1A00;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,325 +1,333 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public enum SectorSkew { None = 0, Dos, ProDos };
|
||||
internal enum SectorSkew { None = 0, Dos, ProDos };
|
||||
|
||||
public sealed class DiskDsk : Disk525
|
||||
{
|
||||
internal sealed class DiskDsk : Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskDsk() { }
|
||||
public DiskDsk(string name, byte[] data, bool isWriteProtected, SectorSkew sectorSkew) :
|
||||
base(name, data, isWriteProtected)
|
||||
{
|
||||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
|
||||
public DiskDsk(string name, Stream stream, bool isWriteProtected, SectorSkew sectorSkew) :
|
||||
base(name, new byte[TrackCount * SectorCount * SectorSize], isWriteProtected)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
public DiskDsk(byte[] data, bool isWriteProtected, SectorSkew sectorSkew)
|
||||
: base(data, isWriteProtected)
|
||||
{
|
||||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
|
||||
stream.ReadBlock(Data);
|
||||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
int track = number / 2;
|
||||
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
int track = number / 2;
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
WriteNibble(0xFF, 48); // gap 0
|
||||
|
||||
WriteNibble(0xFF, 48); // gap 0
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
WriteNibble(0xD5); // address prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0x96);
|
||||
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
WriteNibble(0xD5); // address prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0x96);
|
||||
WriteNibble44(Volume);
|
||||
WriteNibble44(track);
|
||||
WriteNibble44(sector);
|
||||
WriteNibble44(Volume ^ track ^ sector);
|
||||
|
||||
WriteNibble44(Volume);
|
||||
WriteNibble44(track);
|
||||
WriteNibble44(sector);
|
||||
WriteNibble44(Volume ^ track ^ sector);
|
||||
WriteNibble(0xDE); // address epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 8);
|
||||
|
||||
WriteNibble(0xDE); // address epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 8);
|
||||
WriteNibble(0xD5); // data prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xAD);
|
||||
|
||||
WriteNibble(0xD5); // data prologue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xAD);
|
||||
WriteDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize);
|
||||
|
||||
WriteDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize);
|
||||
WriteNibble(0xDE); // data epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 16);
|
||||
}
|
||||
}
|
||||
|
||||
WriteNibble(0xDE); // data epilogue
|
||||
WriteNibble(0xAA);
|
||||
WriteNibble(0xEB);
|
||||
WriteNibble(0xFF, 16);
|
||||
}
|
||||
}
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
if (IsWriteProtected)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
if (IsWriteProtected)
|
||||
return;
|
||||
int track = number / 2;
|
||||
|
||||
int track = number / 2;
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
int sectorsDone = 0;
|
||||
|
||||
_trackBuffer = buffer;
|
||||
_trackOffset = 0;
|
||||
int sectorsDone = 0;
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0x96, 0x304))
|
||||
{
|
||||
break; // no address prologue
|
||||
}
|
||||
|
||||
for (int sector = 0; sector < SectorCount; sector++)
|
||||
{
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0x96, 0x304))
|
||||
break; // no address prologue
|
||||
/*int readVolume = */
|
||||
ReadNibble44();
|
||||
|
||||
/*int readVolume = */ReadNibble44();
|
||||
int readTrack = ReadNibble44();
|
||||
if (readTrack != track)
|
||||
{
|
||||
break; // bad track number
|
||||
}
|
||||
|
||||
int readTrack = ReadNibble44();
|
||||
if (readTrack != track)
|
||||
break; // bad track number
|
||||
int readSector = ReadNibble44();
|
||||
if (readSector > SectorCount)
|
||||
{
|
||||
break; // bad sector number
|
||||
}
|
||||
|
||||
int readSector = ReadNibble44();
|
||||
if (readSector > SectorCount)
|
||||
break; // bad sector number
|
||||
if ((sectorsDone & (0x1 << readSector)) != 0)
|
||||
break; // already done this sector
|
||||
if ((sectorsDone & (0x1 << readSector)) != 0)
|
||||
{
|
||||
break; // already done this sector
|
||||
}
|
||||
|
||||
if (ReadNibble44() != (Volume ^ readTrack ^ readSector))
|
||||
break; // bad address checksum
|
||||
if (ReadNibble44() != (Volume ^ readTrack ^ readSector))
|
||||
{
|
||||
break; // bad address checksum
|
||||
}
|
||||
|
||||
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
||||
break; // bad address epilogue
|
||||
if (ReadNibble() != 0xDE || ReadNibble() != 0xAA)
|
||||
{
|
||||
break; // bad address epilogue
|
||||
}
|
||||
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0xAD, 0x20))
|
||||
break; // no data prologue
|
||||
if (!Read3Nibbles(0xD5, 0xAA, 0xAD, 0x20))
|
||||
{
|
||||
break; // no data prologue
|
||||
}
|
||||
|
||||
if (!ReadDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize))
|
||||
break; // bad data checksum
|
||||
if (!ReadDataNibbles((track * SectorCount + _sectorSkew[sector]) * SectorSize))
|
||||
{
|
||||
break; // bad data checksum
|
||||
}
|
||||
|
||||
if ((ReadNibble() != 0xDE) || (ReadNibble() != 0xAA))
|
||||
break; // bad data epilogue
|
||||
if (ReadNibble() != 0xDE || ReadNibble() != 0xAA)
|
||||
{
|
||||
break; // bad data epilogue
|
||||
}
|
||||
|
||||
sectorsDone |= 0x1 << sector;
|
||||
}
|
||||
sectorsDone |= 0x1 << sector;
|
||||
}
|
||||
|
||||
if (sectorsDone != 0xFFFF)
|
||||
throw new InvalidOperationException("disk error"); // TODO: we should alert the user and "dump" a NIB
|
||||
}
|
||||
if (sectorsDone != 0xFFFF)
|
||||
{
|
||||
throw new InvalidOperationException("disk error"); // TODO: we should alert the user and "dump" a NIB
|
||||
}
|
||||
}
|
||||
|
||||
private byte ReadNibble()
|
||||
{
|
||||
byte data = _trackBuffer[_trackOffset];
|
||||
if (_trackOffset++ == TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private byte ReadNibble()
|
||||
{
|
||||
byte data = _trackBuffer[_trackOffset];
|
||||
if (_trackOffset++ == TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool Read3Nibbles(byte data1, byte data2, byte data3, int maxReads)
|
||||
{
|
||||
bool result = false;
|
||||
while (--maxReads > 0)
|
||||
{
|
||||
if (ReadNibble() != data1)
|
||||
continue;
|
||||
private bool Read3Nibbles(byte data1, byte data2, byte data3, int maxReads)
|
||||
{
|
||||
bool result = false;
|
||||
while (--maxReads > 0)
|
||||
{
|
||||
if (ReadNibble() != data1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ReadNibble() != data2)
|
||||
continue;
|
||||
if (ReadNibble() != data2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ReadNibble() != data3)
|
||||
continue;
|
||||
if (ReadNibble() != data3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int ReadNibble44()
|
||||
{
|
||||
return (((ReadNibble() << 1) | 0x1) & ReadNibble());
|
||||
}
|
||||
private int ReadNibble44()
|
||||
{
|
||||
return (((ReadNibble() << 1) | 0x1) & ReadNibble());
|
||||
}
|
||||
|
||||
private byte ReadTranslatedNibble()
|
||||
{
|
||||
byte data = NibbleToByte[ReadNibble()];
|
||||
// TODO: check that invalid nibbles aren't used
|
||||
// (put 0xFFs for invalid nibbles in the table)
|
||||
//if (data == 0xFF)
|
||||
//{
|
||||
//throw an exception
|
||||
//}
|
||||
return data;
|
||||
}
|
||||
private byte ReadTranslatedNibble()
|
||||
{
|
||||
byte data = NibbleToByte[ReadNibble()];
|
||||
return data;
|
||||
}
|
||||
|
||||
private bool ReadDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte a, x, y;
|
||||
private bool ReadDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte y = SecondaryBufferLength;
|
||||
byte a = 0;
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _secondaryBuffer[--y] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y > 0);
|
||||
|
||||
y = SecondaryBufferLength;
|
||||
a = 0;
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _secondaryBuffer[--y] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y > 0);
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _primaryBuffer[y++] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y != 0);
|
||||
|
||||
do // fill and de-nibblize secondary buffer
|
||||
{
|
||||
a = _primaryBuffer[y++] = (byte)(a ^ ReadTranslatedNibble());
|
||||
}
|
||||
while (y != 0);
|
||||
int checksum = a ^ ReadTranslatedNibble(); // should be 0
|
||||
|
||||
int checksum = a ^ ReadTranslatedNibble(); // should be 0
|
||||
var x = y = 0;
|
||||
do // decode data
|
||||
{
|
||||
if (x == 0)
|
||||
{
|
||||
x = SecondaryBufferLength;
|
||||
}
|
||||
a = (byte)((_primaryBuffer[y] << 2) | SwapBits[_secondaryBuffer[--x] & 0x03]);
|
||||
_secondaryBuffer[x] >>= 2;
|
||||
Data[sectorOffset + y] = a;
|
||||
}
|
||||
while (++y != 0);
|
||||
|
||||
x = y = 0;
|
||||
do // decode data
|
||||
{
|
||||
if (x == 0)
|
||||
{
|
||||
x = SecondaryBufferLength;
|
||||
}
|
||||
a = (byte)((_primaryBuffer[y] << 2) | SwapBits[_secondaryBuffer[--x] & 0x03]);
|
||||
_secondaryBuffer[x] >>= 2;
|
||||
Data[sectorOffset + y] = a;
|
||||
}
|
||||
while (++y != 0);
|
||||
return (checksum == 0);
|
||||
}
|
||||
|
||||
return (checksum == 0);
|
||||
}
|
||||
private void WriteNibble(int data)
|
||||
{
|
||||
_trackBuffer[_trackOffset++] = (byte)data;
|
||||
}
|
||||
|
||||
private void WriteNibble(int data)
|
||||
{
|
||||
_trackBuffer[_trackOffset++] = (byte)data;
|
||||
}
|
||||
private void WriteNibble(int data, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
WriteNibble(data);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteNibble(int data, int count)
|
||||
{
|
||||
while (count-- > 0)
|
||||
{
|
||||
WriteNibble(data);
|
||||
}
|
||||
}
|
||||
private void WriteNibble44(int data)
|
||||
{
|
||||
WriteNibble((data >> 1) | 0xAA);
|
||||
WriteNibble(data | 0xAA);
|
||||
}
|
||||
|
||||
private void WriteNibble44(int data)
|
||||
{
|
||||
WriteNibble((data >> 1) | 0xAA);
|
||||
WriteNibble(data | 0xAA);
|
||||
}
|
||||
private void WriteDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte a, x;
|
||||
|
||||
private void WriteDataNibbles(int sectorOffset)
|
||||
{
|
||||
byte a, x, y;
|
||||
for (x = 0; x < SecondaryBufferLength; x++)
|
||||
{
|
||||
_secondaryBuffer[x] = 0; // zero secondary buffer
|
||||
}
|
||||
|
||||
for (x = 0; x < SecondaryBufferLength; x++)
|
||||
{
|
||||
_secondaryBuffer[x] = 0; // zero secondary buffer
|
||||
}
|
||||
byte y = 2;
|
||||
do // fill buffers
|
||||
{
|
||||
x = 0;
|
||||
do
|
||||
{
|
||||
a = Data[sectorOffset + --y];
|
||||
_secondaryBuffer[x] = (byte)((_secondaryBuffer[x] << 2) | SwapBits[a & 0x03]); // b1,b0 -> secondary buffer
|
||||
_primaryBuffer[y] = (byte)(a >> 2); // b7-b2 -> primary buffer
|
||||
}
|
||||
while (++x < SecondaryBufferLength);
|
||||
}
|
||||
while (y != 0);
|
||||
|
||||
y = 2;
|
||||
do // fill buffers
|
||||
{
|
||||
x = 0;
|
||||
do
|
||||
{
|
||||
a = Data[sectorOffset + --y];
|
||||
_secondaryBuffer[x] = (byte)((_secondaryBuffer[x] << 2) | SwapBits[a & 0x03]); // b1,b0 -> secondary buffer
|
||||
_primaryBuffer[y] = (byte)(a >> 2); // b7-b2 -> primary buffer
|
||||
}
|
||||
while (++x < SecondaryBufferLength);
|
||||
}
|
||||
while (y != 0);
|
||||
y = SecondaryBufferLength;
|
||||
do // write secondary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[_secondaryBuffer[y] ^ _secondaryBuffer[y - 1]]);
|
||||
}
|
||||
while (--y != 0);
|
||||
|
||||
y = SecondaryBufferLength;
|
||||
do // write secondary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[_secondaryBuffer[y] ^ _secondaryBuffer[y - 1]]);
|
||||
}
|
||||
while (--y != 0);
|
||||
a = _secondaryBuffer[0];
|
||||
do // write primary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[a ^ _primaryBuffer[y]]);
|
||||
a = _primaryBuffer[y];
|
||||
}
|
||||
while (++y != 0);
|
||||
|
||||
a = _secondaryBuffer[0];
|
||||
do // write primary buffer
|
||||
{
|
||||
WriteNibble(ByteToNibble[a ^ _primaryBuffer[y]]);
|
||||
a = _primaryBuffer[y];
|
||||
}
|
||||
while (++y != 0);
|
||||
|
||||
WriteNibble(ByteToNibble[a]); // data checksum
|
||||
}
|
||||
WriteNibble(ByteToNibble[a]); // data checksum
|
||||
}
|
||||
|
||||
private byte[] _trackBuffer;
|
||||
private int _trackOffset;
|
||||
private byte[] _primaryBuffer = new byte[0x100];
|
||||
private const int SecondaryBufferLength = 0x56;
|
||||
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
|
||||
private int[] _sectorSkew;
|
||||
private const int Volume = 0xFE;
|
||||
private byte[] _trackBuffer;
|
||||
private int _trackOffset;
|
||||
private byte[] _primaryBuffer = new byte[0x100];
|
||||
private const int SecondaryBufferLength = 0x56;
|
||||
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
|
||||
private int[] _sectorSkew;
|
||||
private const int Volume = 0xFE;
|
||||
|
||||
private static readonly byte[] SwapBits = { 0, 2, 1, 3 };
|
||||
private static readonly byte[] SwapBits = { 0, 2, 1, 3 };
|
||||
|
||||
private static readonly int[] SectorSkewNone = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
|
||||
};
|
||||
private static readonly int[] SectorSkewNone =
|
||||
{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF
|
||||
};
|
||||
|
||||
private static readonly int[] SectorSkewDos = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
private static readonly int[] SectorSkewDos =
|
||||
{
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
|
||||
private static readonly int[] SectorSkewProDos = new int[SectorCount]
|
||||
{
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
|
||||
};
|
||||
private static readonly int[] SectorSkewProDos =
|
||||
{
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF
|
||||
};
|
||||
|
||||
private const int SectorSkewCount = 3;
|
||||
private static readonly int[][] SectorSkewMode =
|
||||
{
|
||||
SectorSkewNone, SectorSkewDos, SectorSkewProDos
|
||||
};
|
||||
|
||||
private static readonly int[][] SectorSkewMode = new int[SectorSkewCount][]
|
||||
{
|
||||
SectorSkewNone, SectorSkewDos, SectorSkewProDos
|
||||
};
|
||||
private static readonly byte[] ByteToNibble =
|
||||
{
|
||||
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
|
||||
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
|
||||
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
|
||||
};
|
||||
|
||||
private static readonly byte[] ByteToNibble = new byte[]
|
||||
{
|
||||
0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6, 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
|
||||
0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
|
||||
0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
|
||||
0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
|
||||
};
|
||||
private static readonly byte[] NibbleToByte =
|
||||
{
|
||||
// padding for offset (not used)
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
||||
|
||||
private static readonly byte[] NibbleToByte = new byte[]
|
||||
{
|
||||
// padding for offset (not used)
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
|
||||
|
||||
// nibble translate table
|
||||
// nibble translate table
|
||||
0x00, 0x01, 0x98, 0x99, 0x02, 0x03, 0x9C, 0x04, 0x05, 0x06,
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
||||
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
||||
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
|
||||
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
||||
};
|
||||
}
|
||||
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x07, 0x08, 0xA8, 0xA9, 0xAA, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
||||
0xB0, 0xB1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xB8, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
||||
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0x1B, 0xCC, 0x1C, 0x1D, 0x1E,
|
||||
0xD0, 0xD1, 0xD2, 0x1F, 0xD4, 0xD5, 0x20, 0x21, 0xD8, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0x29, 0x2A, 0x2B, 0xE8, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
0xF0, 0xF1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0xF8, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskIIController : PeripheralCard
|
||||
public interface IDiskIIController : IPeripheralCard
|
||||
{
|
||||
bool DriveLight { get; set; }
|
||||
|
||||
// ReSharper disable once UnusedMemberInSuper.Global
|
||||
DiskIIDrive Drive1 { get; }
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed class DiskIIController : IDiskIIController
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IVideo _video;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskIIController() { }
|
||||
public DiskIIController(Machine machine, byte[] diskIIRom) :
|
||||
base(machine)
|
||||
|
||||
public DiskIIController(IVideo video, byte[] diskIIRom)
|
||||
{
|
||||
_video = video;
|
||||
_romRegionC1C7 = diskIIRom;
|
||||
Drive1 = new DiskIIDrive(machine);
|
||||
Drive2 = new DiskIIDrive(machine);
|
||||
|
||||
Drives = new List<DiskIIDrive> { Drive1, Drive2 };
|
||||
|
||||
BootDrive = Drive1;
|
||||
}
|
||||
|
||||
public override void Initialize() { }
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Drive1 = new DiskIIDrive(this);
|
||||
Drive2 = new DiskIIDrive(this);
|
||||
_phaseStates = 0;
|
||||
SetMotorOn(false);
|
||||
SetDriveNumber(0);
|
||||
|
@ -34,7 +32,13 @@ namespace Jellyfish.Virtu
|
|||
_writeMode = false;
|
||||
}
|
||||
|
||||
public override int ReadIoRegionC0C0(int address)
|
||||
public bool DriveLight { get; set; }
|
||||
|
||||
public IList<DiskIIDrive> Drives => new List<DiskIIDrive> { Drive1, Drive2 };
|
||||
|
||||
public void WriteIoRegionC8CF(int address, int data) => _video.ReadFloatingBus();
|
||||
|
||||
public int ReadIoRegionC0C0(int address)
|
||||
{
|
||||
switch (address & 0xF)
|
||||
{
|
||||
|
@ -73,10 +77,8 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
return _latch = Drives[_driveNumber].Read();
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLatch();
|
||||
}
|
||||
|
||||
WriteLatch();
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -116,15 +118,17 @@ namespace Jellyfish.Virtu
|
|||
return _driveSpin ? 0x7E : 0x7F;
|
||||
}
|
||||
|
||||
return ReadFloatingBus();
|
||||
return _video.ReadFloatingBus();
|
||||
}
|
||||
|
||||
public override int ReadIoRegionC1C7(int address)
|
||||
public int ReadIoRegionC1C7(int address)
|
||||
{
|
||||
return _romRegionC1C7[address & 0xFF];
|
||||
}
|
||||
|
||||
public override void WriteIoRegionC0C0(int address, int data)
|
||||
public int ReadIoRegionC8CF(int address) => _video.ReadFloatingBus();
|
||||
|
||||
public void WriteIoRegionC0C0(int address, int data)
|
||||
{
|
||||
switch (address & 0xF)
|
||||
{
|
||||
|
@ -186,6 +190,8 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
}
|
||||
|
||||
public void WriteIoRegionC1C7(int address, int data) { }
|
||||
|
||||
private void WriteLatch()
|
||||
{
|
||||
// write protect is forced if phase 1 is on [F9.7]
|
||||
|
@ -231,17 +237,14 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
public DiskIIDrive Drive1 { get; private set; }
|
||||
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
public DiskIIDrive Drive2 { get; private set; }
|
||||
|
||||
public List<DiskIIDrive> Drives { get; private set; }
|
||||
|
||||
public DiskIIDrive BootDrive { get; private set; }
|
||||
|
||||
private const int Phase0On = 1 << 0;
|
||||
private const int Phase1On = 1 << 1;
|
||||
private const int Phase2On = 1 << 2;
|
||||
private const int Phase3On = 1 << 3;
|
||||
|
||||
private int _latch;
|
||||
private int _phaseStates;
|
||||
|
|
|
@ -1,128 +1,124 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskIIDrive : MachineComponent
|
||||
{
|
||||
public sealed class DiskIIDrive
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IDiskIIController _diskController;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskIIDrive() { }
|
||||
public DiskIIDrive(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
DriveArmStepDelta[0] = new int[] { 0, 0, 1, 1, 0, 0, 1, 1, -1, -1, 0, 0, -1, -1, 0, 0 }; // phase 0
|
||||
DriveArmStepDelta[1] = new int[] { 0, -1, 0, -1, 1, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0 }; // phase 1
|
||||
DriveArmStepDelta[2] = new int[] { 0, 0, -1, -1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0 }; // phase 2
|
||||
DriveArmStepDelta[3] = new int[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3
|
||||
}
|
||||
|
||||
public void InsertDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
DebugService.WriteMessage("Inserting disk '{0}'", name);
|
||||
FlushTrack();
|
||||
_disk = Disk525.CreateDisk(name, data, isWriteProtected);
|
||||
_trackLoaded = false;
|
||||
}
|
||||
public DiskIIDrive(IDiskIIController diskController)
|
||||
{
|
||||
_diskController = diskController;
|
||||
_driveArmStepDelta[0] = new[] { 0, 0, 1, 1, 0, 0, 1, 1, -1, -1, 0, 0, -1, -1, 0, 0 }; // phase 0
|
||||
_driveArmStepDelta[1] = new[] { 0, -1, 0, -1, 1, 0, 1, 0, 0, -1, 0, -1, 1, 0, 1, 0 }; // phase 1
|
||||
_driveArmStepDelta[2] = new[] { 0, 0, -1, -1, 0, 0, -1, -1, 1, 1, 0, 0, 1, 1, 0, 0 }; // phase 2
|
||||
_driveArmStepDelta[3] = new[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3
|
||||
}
|
||||
|
||||
public void RemoveDisk()
|
||||
{
|
||||
if (_disk != null)
|
||||
{
|
||||
DebugService.WriteMessage("Removing disk '{0}'", _disk.Name);
|
||||
_trackLoaded = false;
|
||||
_trackChanged = false;
|
||||
_trackNumber = 0;
|
||||
_trackOffset = 0;
|
||||
_disk = null;
|
||||
}
|
||||
}
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void InsertDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
FlushTrack();
|
||||
_disk = Disk525.CreateDisk(name, data, isWriteProtected);
|
||||
_trackLoaded = false;
|
||||
}
|
||||
|
||||
public void ApplyPhaseChange(int phaseState)
|
||||
{
|
||||
// step the drive head according to stepper magnet changes
|
||||
int delta = DriveArmStepDelta[_trackNumber & 0x3][phaseState];
|
||||
if (delta != 0)
|
||||
{
|
||||
int newTrackNumber = MathHelpers.Clamp(_trackNumber + delta, 0, TrackNumberMax);
|
||||
if (newTrackNumber != _trackNumber)
|
||||
{
|
||||
FlushTrack();
|
||||
_trackNumber = newTrackNumber;
|
||||
_trackOffset = 0;
|
||||
_trackLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
private static int Clamp(int value, int min, int max)
|
||||
{
|
||||
return value < min ? min : value > max ? max : value;
|
||||
}
|
||||
|
||||
public int Read()
|
||||
{
|
||||
if (LoadTrack())
|
||||
{
|
||||
int data = _trackData[_trackOffset++];
|
||||
if (_trackOffset >= Disk525.TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
internal void ApplyPhaseChange(int phaseState)
|
||||
{
|
||||
// step the drive head according to stepper magnet changes
|
||||
int delta = _driveArmStepDelta[_trackNumber & 0x3][phaseState];
|
||||
if (delta != 0)
|
||||
{
|
||||
int newTrackNumber = Clamp(_trackNumber + delta, 0, TrackNumberMax);
|
||||
if (newTrackNumber != _trackNumber)
|
||||
{
|
||||
FlushTrack();
|
||||
_trackNumber = newTrackNumber;
|
||||
_trackOffset = 0;
|
||||
_trackLoaded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Machine.DriveLight = true;
|
||||
return data;
|
||||
}
|
||||
internal int Read()
|
||||
{
|
||||
if (LoadTrack())
|
||||
{
|
||||
int data = _trackData[_trackOffset++];
|
||||
if (_trackOffset >= Disk525.TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
|
||||
_diskController.DriveLight = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
return 0x80;
|
||||
// TODO: WTF was this
|
||||
//return _random.Next(0x01, 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(int data)
|
||||
{
|
||||
if (LoadTrack())
|
||||
{
|
||||
_trackChanged = true;
|
||||
_trackData[_trackOffset++] = (byte)data;
|
||||
if (_trackOffset >= Disk525.TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
internal void Write(int data)
|
||||
{
|
||||
if (LoadTrack())
|
||||
{
|
||||
_trackChanged = true;
|
||||
_trackData[_trackOffset++] = (byte)data;
|
||||
if (_trackOffset >= Disk525.TrackSize)
|
||||
{
|
||||
_trackOffset = 0;
|
||||
}
|
||||
|
||||
Machine.DriveLight = true;
|
||||
}
|
||||
}
|
||||
_diskController.DriveLight = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadTrack()
|
||||
{
|
||||
if (!_trackLoaded && (_disk != null))
|
||||
{
|
||||
_disk.ReadTrack(_trackNumber, 0, _trackData);
|
||||
_trackLoaded = true;
|
||||
}
|
||||
private bool LoadTrack()
|
||||
{
|
||||
if (!_trackLoaded && (_disk != null))
|
||||
{
|
||||
_disk.ReadTrack(_trackNumber, 0, _trackData);
|
||||
_trackLoaded = true;
|
||||
}
|
||||
|
||||
return _trackLoaded;
|
||||
}
|
||||
return _trackLoaded;
|
||||
}
|
||||
|
||||
public void FlushTrack()
|
||||
{
|
||||
if (_trackChanged)
|
||||
{
|
||||
_disk.WriteTrack(_trackNumber, 0, _trackData);
|
||||
_trackChanged = false;
|
||||
}
|
||||
}
|
||||
internal void FlushTrack()
|
||||
{
|
||||
if (_trackChanged)
|
||||
{
|
||||
_disk.WriteTrack(_trackNumber, 0, _trackData);
|
||||
_trackChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
public bool IsWriteProtected { get { return _disk.IsWriteProtected; } }
|
||||
[JsonIgnore]
|
||||
public bool IsWriteProtected => _disk.IsWriteProtected;
|
||||
|
||||
private const int TrackNumberMax = 0x44;
|
||||
private const int TrackNumberMax = 0x44;
|
||||
|
||||
private const int PhaseCount = 4;
|
||||
private const int PhaseCount = 4;
|
||||
|
||||
private int[][] DriveArmStepDelta = new int[PhaseCount][];
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private int[][] _driveArmStepDelta = new int[PhaseCount][];
|
||||
|
||||
private bool _trackLoaded;
|
||||
private bool _trackChanged;
|
||||
private int _trackNumber;
|
||||
private int _trackOffset;
|
||||
private byte[] _trackData = new byte[Disk525.TrackSize];
|
||||
private Disk525 _disk;
|
||||
}
|
||||
private bool _trackLoaded;
|
||||
private bool _trackChanged;
|
||||
private int _trackNumber;
|
||||
private int _trackOffset;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private byte[] _trackData = new byte[Disk525.TrackSize];
|
||||
|
||||
private Disk525 _disk;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,36 +1,25 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskNib : Disk525
|
||||
{
|
||||
internal sealed class DiskNib : Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskNib() { }
|
||||
public DiskNib(string name, byte[] data, bool isWriteProtected) :
|
||||
base(name, data, isWriteProtected)
|
||||
{
|
||||
}
|
||||
|
||||
public DiskNib(string name, Stream stream, bool isWriteProtected) :
|
||||
base(name, new byte[TrackCount * TrackSize], isWriteProtected)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
public DiskNib(byte[] data, bool isWriteProtected)
|
||||
: base(data, isWriteProtected)
|
||||
{
|
||||
}
|
||||
|
||||
stream.ReadBlock(Data);
|
||||
}
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(Data, (number / 2) * TrackSize, buffer, 0, TrackSize);
|
||||
}
|
||||
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(Data, (number / 2) * TrackSize, buffer, 0, TrackSize);
|
||||
}
|
||||
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, 0, Data, (number / 2) * TrackSize, TrackSize);
|
||||
}
|
||||
}
|
||||
public override void WriteTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, 0, Data, (number / 2) * TrackSize, TrackSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,256 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class GamePort : MachineComponent
|
||||
{
|
||||
// TODO: ressurect this
|
||||
public bool ReadButton0() { return Keyboard.WhiteAppleDown; }
|
||||
public bool ReadButton1() { return Keyboard.BlackAppleDown; }
|
||||
public bool ReadButton2() { return false; }
|
||||
|
||||
public bool Paddle0Strobe { get { return false; } }
|
||||
public bool Paddle1Strobe { get { return false; } }
|
||||
public bool Paddle2Strobe { get { return false; } }
|
||||
public bool Paddle3Strobe { get { return false; } }
|
||||
|
||||
public void TriggerTimers() { }
|
||||
|
||||
public GamePort() { }
|
||||
public GamePort(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
/*
|
||||
_resetPaddle0StrobeEvent = ResetPaddle0StrobeEvent; // cache delegates; avoids garbage
|
||||
_resetPaddle1StrobeEvent = ResetPaddle1StrobeEvent;
|
||||
_resetPaddle2StrobeEvent = ResetPaddle2StrobeEvent;
|
||||
_resetPaddle3StrobeEvent = ResetPaddle3StrobeEvent;
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
public override void Initialize()
|
||||
{
|
||||
_keyboardService = Machine.Services.GetService<KeyboardService>();
|
||||
_gamePortService = Machine.Services.GetService<GamePortService>();
|
||||
|
||||
JoystickDeadZone = 0.4f;
|
||||
|
||||
InvertPaddles = true; // Raster Blaster
|
||||
SwapPaddles = true;
|
||||
Joystick0TouchX = 0.35f;
|
||||
Joystick0TouchY = 0.6f;
|
||||
Joystick0TouchWidth = 0.25f;
|
||||
Joystick0TouchHeight = 0.4f;
|
||||
Joystick0TouchRadius = 0.2f;
|
||||
Joystick0TouchKeepLast = true;
|
||||
Button0TouchX = 0;
|
||||
Button0TouchY = 0;
|
||||
Button0TouchWidth = 0.5f;
|
||||
Button0TouchHeight = 1;
|
||||
Button1TouchX = 0.5f;
|
||||
Button1TouchY = 0;
|
||||
Button1TouchWidth = 0.5f;
|
||||
Button1TouchHeight = 1;
|
||||
Button2TouchX = 0.75f;
|
||||
Button2TouchY = 0;
|
||||
Button2TouchWidth = 0.25f;
|
||||
Button2TouchHeight = 0.25f;
|
||||
Button2TouchOrder = 1;
|
||||
}
|
||||
|
||||
public bool ReadButton0()
|
||||
{
|
||||
return (_gamePortService.IsButton0Down || _keyboardService.IsOpenAppleKeyDown ||
|
||||
(UseKeyboard && (Button0Key > 0) && _keyboardService.IsKeyDown(Button0Key)));
|
||||
}
|
||||
|
||||
public bool ReadButton1()
|
||||
{
|
||||
return (_gamePortService.IsButton1Down || _keyboardService.IsCloseAppleKeyDown ||
|
||||
(UseKeyboard && (Button1Key > 0) && _keyboardService.IsKeyDown(Button1Key)));
|
||||
}
|
||||
|
||||
public bool ReadButton2()
|
||||
{
|
||||
return (_gamePortService.IsButton2Down || (UseShiftKeyMod && !_keyboardService.IsShiftKeyDown) || // Shift' [TN9]
|
||||
(UseKeyboard && (Button2Key > 0) && _keyboardService.IsKeyDown(Button2Key)));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
public void TriggerTimers()
|
||||
{
|
||||
int paddle0 = _gamePortService.Paddle0;
|
||||
int paddle1 = _gamePortService.Paddle1;
|
||||
int paddle2 = _gamePortService.Paddle2;
|
||||
int paddle3 = _gamePortService.Paddle3;
|
||||
|
||||
if (UseKeyboard) // override
|
||||
{
|
||||
if (((Joystick0UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0UpLeftKey)) ||
|
||||
((Joystick0LeftKey > 0) && _keyboardService.IsKeyDown(Joystick0LeftKey)) ||
|
||||
((Joystick0DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0DownLeftKey)))
|
||||
{
|
||||
paddle0 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick0UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick0UpRightKey)) ||
|
||||
((Joystick0RightKey > 0) && _keyboardService.IsKeyDown(Joystick0RightKey)) ||
|
||||
((Joystick0DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick0DownRightKey)))
|
||||
{
|
||||
paddle0 += PaddleScale;
|
||||
}
|
||||
if (((Joystick0UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0UpLeftKey)) ||
|
||||
((Joystick0UpKey > 0) && _keyboardService.IsKeyDown(Joystick0UpKey)) ||
|
||||
((Joystick0UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick0UpRightKey)))
|
||||
{
|
||||
paddle1 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick0DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick0DownLeftKey)) ||
|
||||
((Joystick0DownKey > 0) && _keyboardService.IsKeyDown(Joystick0DownKey)) ||
|
||||
((Joystick0DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick0DownRightKey)))
|
||||
{
|
||||
paddle1 += PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1UpLeftKey)) ||
|
||||
((Joystick1LeftKey > 0) && _keyboardService.IsKeyDown(Joystick1LeftKey)) ||
|
||||
((Joystick1DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1DownLeftKey)))
|
||||
{
|
||||
paddle2 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick1UpRightKey)) ||
|
||||
((Joystick1RightKey > 0) && _keyboardService.IsKeyDown(Joystick1RightKey)) ||
|
||||
((Joystick1DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick1DownRightKey)))
|
||||
{
|
||||
paddle2 += PaddleScale;
|
||||
}
|
||||
if (((Joystick1UpLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1UpLeftKey)) ||
|
||||
((Joystick1UpKey > 0) && _keyboardService.IsKeyDown(Joystick1UpKey)) ||
|
||||
((Joystick1UpRightKey > 0) && _keyboardService.IsKeyDown(Joystick1UpRightKey)))
|
||||
{
|
||||
paddle3 -= PaddleScale;
|
||||
}
|
||||
if (((Joystick1DownLeftKey > 0) && _keyboardService.IsKeyDown(Joystick1DownLeftKey)) ||
|
||||
((Joystick1DownKey > 0) && _keyboardService.IsKeyDown(Joystick1DownKey)) ||
|
||||
((Joystick1DownRightKey > 0) && _keyboardService.IsKeyDown(Joystick1DownRightKey)))
|
||||
{
|
||||
paddle3 += PaddleScale;
|
||||
}
|
||||
}
|
||||
if (InvertPaddles)
|
||||
{
|
||||
paddle0 = 2 * PaddleScale - paddle0;
|
||||
paddle1 = 2 * PaddleScale - paddle1;
|
||||
paddle2 = 2 * PaddleScale - paddle2;
|
||||
paddle3 = 2 * PaddleScale - paddle3;
|
||||
}
|
||||
|
||||
Paddle0Strobe = true;
|
||||
Paddle1Strobe = true;
|
||||
Paddle2Strobe = true;
|
||||
Paddle3Strobe = true;
|
||||
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle1 : paddle0) * CyclesPerValue, _resetPaddle0StrobeEvent); // [7-29]
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle0 : paddle1) * CyclesPerValue, _resetPaddle1StrobeEvent);
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle3 : paddle2) * CyclesPerValue, _resetPaddle2StrobeEvent);
|
||||
Machine.Events.AddEvent(MathHelpers.ClampByte(SwapPaddles ? paddle2 : paddle3) * CyclesPerValue, _resetPaddle3StrobeEvent);
|
||||
}
|
||||
|
||||
private void ResetPaddle0StrobeEvent()
|
||||
{
|
||||
Paddle0Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle1StrobeEvent()
|
||||
{
|
||||
Paddle1Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle2StrobeEvent()
|
||||
{
|
||||
Paddle2Strobe = false;
|
||||
}
|
||||
|
||||
private void ResetPaddle3StrobeEvent()
|
||||
{
|
||||
Paddle3Strobe = false;
|
||||
}
|
||||
|
||||
public const int PaddleScale = 128;
|
||||
|
||||
public bool InvertPaddles { get; set; }
|
||||
public bool SwapPaddles { get; set; }
|
||||
public bool UseShiftKeyMod { get; set; }
|
||||
public float JoystickDeadZone { get; set; }
|
||||
|
||||
public bool UseKeyboard { get; set; }
|
||||
public int Joystick0UpLeftKey { get; set; }
|
||||
public int Joystick0UpKey { get; set; }
|
||||
public int Joystick0UpRightKey { get; set; }
|
||||
public int Joystick0LeftKey { get; set; }
|
||||
public int Joystick0RightKey { get; set; }
|
||||
public int Joystick0DownLeftKey { get; set; }
|
||||
public int Joystick0DownKey { get; set; }
|
||||
public int Joystick0DownRightKey { get; set; }
|
||||
public int Joystick1UpLeftKey { get; set; }
|
||||
public int Joystick1UpKey { get; set; }
|
||||
public int Joystick1UpRightKey { get; set; }
|
||||
public int Joystick1LeftKey { get; set; }
|
||||
public int Joystick1RightKey { get; set; }
|
||||
public int Joystick1DownLeftKey { get; set; }
|
||||
public int Joystick1DownKey { get; set; }
|
||||
public int Joystick1DownRightKey { get; set; }
|
||||
public int Button0Key { get; set; }
|
||||
public int Button1Key { get; set; }
|
||||
public int Button2Key { get; set; }
|
||||
|
||||
public bool UseTouch { get; set; }
|
||||
public float Joystick0TouchX { get; set; }
|
||||
public float Joystick0TouchY { get; set; }
|
||||
public float Joystick0TouchWidth { get; set; }
|
||||
public float Joystick0TouchHeight { get; set; }
|
||||
public int Joystick0TouchOrder { get; set; }
|
||||
public float Joystick0TouchRadius { get; set; }
|
||||
public bool Joystick0TouchKeepLast { get; set; }
|
||||
public float Joystick1TouchX { get; set; }
|
||||
public float Joystick1TouchY { get; set; }
|
||||
public float Joystick1TouchWidth { get; set; }
|
||||
public float Joystick1TouchHeight { get; set; }
|
||||
public int Joystick1TouchOrder { get; set; }
|
||||
public float Joystick1TouchRadius { get; set; }
|
||||
public bool Joystick1TouchKeepLast { get; set; }
|
||||
public float Button0TouchX { get; set; }
|
||||
public float Button0TouchY { get; set; }
|
||||
public float Button0TouchWidth { get; set; }
|
||||
public float Button0TouchHeight { get; set; }
|
||||
public int Button0TouchOrder { get; set; }
|
||||
public float Button1TouchX { get; set; }
|
||||
public float Button1TouchY { get; set; }
|
||||
public float Button1TouchWidth { get; set; }
|
||||
public float Button1TouchHeight { get; set; }
|
||||
public int Button1TouchOrder { get; set; }
|
||||
public float Button2TouchX { get; set; }
|
||||
public float Button2TouchY { get; set; }
|
||||
public float Button2TouchWidth { get; set; }
|
||||
public float Button2TouchHeight { get; set; }
|
||||
public int Button2TouchOrder { get; set; }
|
||||
|
||||
public bool Paddle0Strobe { get; private set; }
|
||||
public bool Paddle1Strobe { get; private set; }
|
||||
public bool Paddle2Strobe { get; private set; }
|
||||
public bool Paddle3Strobe { get; private set; }
|
||||
|
||||
private const int CyclesPerValue = 11;
|
||||
|
||||
private Action _resetPaddle0StrobeEvent;
|
||||
private Action _resetPaddle1StrobeEvent;
|
||||
private Action _resetPaddle2StrobeEvent;
|
||||
private Action _resetPaddle3StrobeEvent;
|
||||
|
||||
private KeyboardService _keyboardService;
|
||||
private GamePortService _gamePortService;*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public interface ICassette
|
||||
{
|
||||
bool ReadInput();
|
||||
void ToggleOutput();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public interface IGamePort
|
||||
{
|
||||
bool ReadButton0();
|
||||
bool ReadButton1();
|
||||
bool ReadButton2();
|
||||
|
||||
bool Paddle0Strobe { get; }
|
||||
bool Paddle1Strobe { get; }
|
||||
bool Paddle2Strobe { get; }
|
||||
bool Paddle3Strobe { get; }
|
||||
|
||||
void TriggerTimers();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public interface IPeripheralCard
|
||||
{
|
||||
// read Device Select' address $C0nX; n = slot number + 8
|
||||
int ReadIoRegionC0C0(int address);
|
||||
|
||||
// read I/O Select' address $CsXX; s = slot number
|
||||
int ReadIoRegionC1C7(int address);
|
||||
|
||||
// read I/O Strobe' address $C800-$CFFF
|
||||
int ReadIoRegionC8CF(int address);
|
||||
|
||||
// write Device Select' address $C0nX; n = slot number + 8
|
||||
void WriteIoRegionC0C0(int address, int data);
|
||||
|
||||
// write I/O Select' address $CsXX; s = slot number
|
||||
void WriteIoRegionC1C7(int address, int data);
|
||||
|
||||
// write I/O Strobe' address $C800-$CFFF
|
||||
void WriteIoRegionC8CF(int address, int data);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -143,10 +141,28 @@ namespace Jellyfish.Virtu
|
|||
Reset = 2305843009213693952UL,
|
||||
}
|
||||
|
||||
public sealed class Keyboard
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Keyboard() { }
|
||||
|
||||
public sealed class Keyboard : MachineComponent
|
||||
{
|
||||
private static readonly uint[] KeyAsciiData = new uint[]
|
||||
static Keyboard()
|
||||
{
|
||||
for (int i = 0; i < 62; i++)
|
||||
{
|
||||
// http://stackoverflow.com/questions/2650080/how-to-get-c-sharp-enum-description-from-value
|
||||
Keys value = (Keys)(1UL << i);
|
||||
var fi = typeof(Keys).GetField(value.ToString());
|
||||
var attr = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
string name = attr[0].Description;
|
||||
DescriptionsToKeys[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public static IEnumerable<string> GetKeyNames() => DescriptionsToKeys.Keys.ToList();
|
||||
|
||||
private static readonly uint[] KeyAsciiData =
|
||||
{
|
||||
// https://archive.org/stream/Apple_IIe_Technical_Reference_Manual#page/n47/mode/2up
|
||||
// 0xNNCCSSBB normal, control, shift both
|
||||
|
@ -217,27 +233,9 @@ namespace Jellyfish.Virtu
|
|||
return (int)(KeyAsciiData[key] >> s & 0x7f);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>();
|
||||
|
||||
static Keyboard()
|
||||
{
|
||||
for (int i = 0; i < 62; i++)
|
||||
{
|
||||
// http://stackoverflow.com/questions/2650080/how-to-get-c-sharp-enum-description-from-value
|
||||
Keys value = (Keys)(1UL << i);
|
||||
var fi = typeof(Keys).GetField(value.ToString());
|
||||
var attr = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
string name = attr[0].Description;
|
||||
DescriptionsToKeys[name] = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetKeyNames()
|
||||
{
|
||||
return DescriptionsToKeys.Keys.ToList();
|
||||
}
|
||||
|
||||
private static Keys FromStrings(IEnumerable<string> keynames)
|
||||
{
|
||||
Keys ret = 0;
|
||||
|
@ -245,38 +243,23 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
ret |= DescriptionsToKeys[s];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public Keyboard() { }
|
||||
public Keyboard(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool WhiteAppleDown;
|
||||
public static bool BlackAppleDown;
|
||||
|
||||
/// <summary>
|
||||
/// Call this at 60hz with all of the currently pressed keys
|
||||
/// </summary>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void SetKeys(IEnumerable<string> keynames)
|
||||
{
|
||||
Keys keys = FromStrings(keynames);
|
||||
|
||||
if (keys.HasFlag(Keys.WhiteApple))
|
||||
WhiteAppleDown = true;
|
||||
else
|
||||
WhiteAppleDown = false;
|
||||
|
||||
if (keys.HasFlag(Keys.BlackApple))
|
||||
BlackAppleDown = true;
|
||||
else
|
||||
BlackAppleDown = false;
|
||||
WhiteAppleDown = keys.HasFlag(Keys.WhiteApple);
|
||||
BlackAppleDown = keys.HasFlag(Keys.BlackApple);
|
||||
|
||||
if (keys.HasFlag(Keys.Reset) && keys.HasFlag(Keys.Control)) { } // TODO: reset console
|
||||
|
||||
|
@ -284,11 +267,11 @@ namespace Jellyfish.Virtu
|
|||
bool shift = keys.HasFlag(Keys.Shift);
|
||||
|
||||
bool caps = keys.HasFlag(Keys.CapsLock);
|
||||
if (caps && !CurrentCapsLockState) // leading edge: toggle capslock
|
||||
if (caps && !_currentCapsLockState) // leading edge: toggle CapsLock
|
||||
{
|
||||
CapsActive ^= true;
|
||||
}
|
||||
CurrentCapsLockState = caps;
|
||||
_currentCapsLockState = caps;
|
||||
shift ^= CapsActive;
|
||||
|
||||
// work with only the first 56 real keys
|
||||
|
@ -298,7 +281,7 @@ namespace Jellyfish.Virtu
|
|||
|
||||
if (!IsAnyKeyDown)
|
||||
{
|
||||
CurrentKeyPressed = -1;
|
||||
_currentKeyPressed = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -306,76 +289,68 @@ namespace Jellyfish.Virtu
|
|||
// that would be somehow resolved by the scan pattern. we don't emulate that.
|
||||
|
||||
// instead, just arbitrarily choose the lowest key in our list
|
||||
|
||||
|
||||
// BSF
|
||||
int NewKeyPressed = 0;
|
||||
int newKeyPressed = 0;
|
||||
while ((k & 1) == 0)
|
||||
{
|
||||
k >>= 1;
|
||||
NewKeyPressed++;
|
||||
newKeyPressed++;
|
||||
}
|
||||
|
||||
if (NewKeyPressed != CurrentKeyPressed)
|
||||
if (newKeyPressed != _currentKeyPressed)
|
||||
{
|
||||
// strobe, start new repeat cycle
|
||||
Strobe = true;
|
||||
Latch = KeyToAscii(NewKeyPressed, control, shift);
|
||||
//if (Latch >= 0x20 && Latch < 0x7f)
|
||||
// Console.WriteLine("Latch: {0:x2}, {1}", Latch, (char)Latch);
|
||||
//else
|
||||
// Console.WriteLine("Latch: {0:x2}", Latch);
|
||||
FramesToRepeat = KeyRepeatStart;
|
||||
Latch = KeyToAscii(newKeyPressed, control, shift);
|
||||
_framesToRepeat = KeyRepeatStart;
|
||||
}
|
||||
else
|
||||
{
|
||||
// check for repeat
|
||||
FramesToRepeat--;
|
||||
if (FramesToRepeat == 0)
|
||||
_framesToRepeat--;
|
||||
if (_framesToRepeat == 0)
|
||||
{
|
||||
Strobe = true;
|
||||
Latch = KeyToAscii(NewKeyPressed, control, shift);
|
||||
//if (Latch >= 0x20 && Latch < 0x7f)
|
||||
// Console.WriteLine("Latch: {0:x2}, {1}", Latch, (char)Latch);
|
||||
//else
|
||||
// Console.WriteLine("Latch: {0:x2}", Latch);
|
||||
FramesToRepeat = KeyRepeatRate;
|
||||
Latch = KeyToAscii(newKeyPressed, control, shift);
|
||||
_framesToRepeat = KeyRepeatRate;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentKeyPressed = NewKeyPressed;
|
||||
_currentKeyPressed = newKeyPressed;
|
||||
}
|
||||
|
||||
public void ResetStrobe()
|
||||
{
|
||||
Strobe = false;
|
||||
}
|
||||
|
||||
public void ResetStrobe()
|
||||
{
|
||||
Strobe = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// true if any of the 56 basic keys are pressed
|
||||
/// </summary>
|
||||
public bool IsAnyKeyDown { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// the currently latched key; 7 bits.
|
||||
/// </summary>
|
||||
public int Latch { get; private set; }
|
||||
public bool Strobe { get; private set; }
|
||||
public bool Strobe { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// true if caps lock is active
|
||||
/// </summary>
|
||||
public bool CapsActive { get; private set; }
|
||||
private bool CapsActive { get; set; }
|
||||
|
||||
private bool CurrentCapsLockState;
|
||||
private bool _currentCapsLockState;
|
||||
|
||||
/// <summary>
|
||||
/// 0-55, -1 = none
|
||||
/// </summary>
|
||||
private int CurrentKeyPressed;
|
||||
private int _currentKeyPressed;
|
||||
|
||||
private int FramesToRepeat;
|
||||
private int _framesToRepeat;
|
||||
|
||||
private const int KeyRepeatRate = 6; // 10hz
|
||||
private const int KeyRepeatStart = 40; // ~666ms?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public abstract class DisposableBase : IDisposable
|
||||
{
|
||||
protected DisposableBase()
|
||||
{
|
||||
}
|
||||
|
||||
~DisposableBase()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class MathHelpers
|
||||
{
|
||||
public static int Clamp(int value, int min, int max)
|
||||
{
|
||||
return (value < min) ? min : (value > max) ? max : value;
|
||||
}
|
||||
|
||||
public static int ClampByte(int value)
|
||||
{
|
||||
return Clamp(value, byte.MinValue, byte.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
|
||||
namespace Jellyfish.Library
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
public static int ReadBlock(this Stream stream, byte[] buffer, int offset, ref int count)
|
||||
{
|
||||
int read = ReadBlock(stream, buffer, offset, count, count);
|
||||
count -= read;
|
||||
return read;
|
||||
}
|
||||
|
||||
public static int ReadBlock(this Stream stream, byte[] buffer, int offset = 0, int count = int.MaxValue, int minCount = int.MaxValue)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
if (buffer == null)
|
||||
{
|
||||
throw new ArgumentNullException("buffer");
|
||||
}
|
||||
|
||||
count = Math.Min(count, buffer.Length - offset);
|
||||
minCount = Math.Min(minCount, buffer.Length - offset);
|
||||
|
||||
int total = 0;
|
||||
int read;
|
||||
do
|
||||
{
|
||||
total += read = stream.Read(buffer, offset + total, count - total);
|
||||
}
|
||||
while ((read > 0) && (total < count));
|
||||
|
||||
if (total < minCount)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public static int ReadWord(this Stream stream, bool optional = false)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
int lowByte = stream.ReadByte();
|
||||
int highByte = stream.ReadByte();
|
||||
int word = lowByte | (highByte << 8);
|
||||
if ((word < 0) && !optional)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return word;
|
||||
}
|
||||
|
||||
public static void SkipBlock(this Stream stream, int count)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
stream.Seek(count, SeekOrigin.Current);
|
||||
}
|
||||
else
|
||||
{
|
||||
int total = 0;
|
||||
int read;
|
||||
do
|
||||
{
|
||||
total += read = stream.Read(_skipBuffer, 0, Math.Min(count - total, SkipBufferSize));
|
||||
}
|
||||
while ((read > 0) && (total < count));
|
||||
}
|
||||
}
|
||||
|
||||
private const int SkipBufferSize = 1024;
|
||||
|
||||
private static byte[] _skipBuffer = new byte[SkipBufferSize];
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Machine : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// for deserialization only!!
|
||||
/// </summary>
|
||||
public Machine() { }
|
||||
|
||||
public Machine(byte[] appleIIe, byte[] diskIIRom)
|
||||
{
|
||||
Events = new MachineEvents();
|
||||
|
||||
Cpu = new Cpu(this);
|
||||
Memory = new Memory(this, appleIIe);
|
||||
Keyboard = new Keyboard(this);
|
||||
GamePort = new GamePort(this);
|
||||
Cassette = new Cassette(this);
|
||||
Speaker = new Speaker(this);
|
||||
Video = new Video(this);
|
||||
NoSlotClock = new NoSlotClock(this);
|
||||
|
||||
var emptySlot = new PeripheralCard(this);
|
||||
Slot1 = emptySlot;
|
||||
Slot2 = emptySlot;
|
||||
Slot3 = emptySlot;
|
||||
Slot4 = emptySlot;
|
||||
Slot5 = emptySlot;
|
||||
Slot6 = new DiskIIController(this, diskIIRom);
|
||||
Slot7 = emptySlot;
|
||||
|
||||
Slots = new List<PeripheralCard> { null, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 };
|
||||
Components = new List<MachineComponent> { Cpu, Memory, Keyboard, GamePort, Cassette, Speaker, Video, NoSlotClock, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 };
|
||||
|
||||
BootDiskII = Slots.OfType<DiskIIController>().Last();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
DebugService.WriteMessage("Resetting machine '{0}'", component.GetType().Name);
|
||||
component.Reset();
|
||||
//DebugService.WriteMessage("Reset machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
DebugService.WriteMessage("Initializing machine '{0}'", component.GetType().Name);
|
||||
component.Initialize();
|
||||
//DebugService.WriteMessage("Initialized machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
private void Uninitialize()
|
||||
{
|
||||
foreach (var component in Components)
|
||||
{
|
||||
DebugService.WriteMessage("Uninitializing machine '{0}'", component.GetType().Name);
|
||||
component.Uninitialize();
|
||||
//DebugService.WriteMessage("Uninitialized machine '{0}'", component.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
public void BizInitialize()
|
||||
{
|
||||
Initialize();
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void BizFrameAdvance(IEnumerable<string> buttons)
|
||||
{
|
||||
Lagged = true;
|
||||
DriveLight = false;
|
||||
|
||||
Keyboard.SetKeys(buttons);
|
||||
|
||||
//frame begins at vsync.. beginning of vblank
|
||||
while (Video.IsVBlank)
|
||||
{
|
||||
/*
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendFormat("{0} ", Cpu);
|
||||
for (int i = 0; i < 256; i++)
|
||||
sb.AppendFormat("{0:x2} ", Memory.Read(i));
|
||||
tw.WriteLine(sb.ToString());*/
|
||||
Events.HandleEvents(Cpu.Execute());
|
||||
}
|
||||
//now, while not vblank, we're in a frame
|
||||
while (!Video.IsVBlank)
|
||||
{
|
||||
/*
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.AppendFormat("{0} ", Cpu);
|
||||
for (int i = 0; i < 256; i++)
|
||||
sb.AppendFormat("{0:x2} ", Memory.Read(i));
|
||||
tw.WriteLine(sb.ToString()); */
|
||||
|
||||
Events.HandleEvents(Cpu.Execute());
|
||||
}
|
||||
}
|
||||
|
||||
public void BizShutdown()
|
||||
{
|
||||
Uninitialize();
|
||||
}
|
||||
|
||||
private static JsonSerializer CreateSerializer()
|
||||
{
|
||||
// TODO: converters could be cached for speedup
|
||||
|
||||
var ser = new JsonSerializer
|
||||
{
|
||||
TypeNameHandling = TypeNameHandling.Auto,
|
||||
PreserveReferencesHandling = PreserveReferencesHandling.All, // leaving out Array is a very important problem, and means that we can't rely on a directly shared array to work.
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
|
||||
};
|
||||
|
||||
ser.Converters.Add(new TypeTypeConverter(new[]
|
||||
{
|
||||
// all expected Types to convert are either in this assembly or mscorlib
|
||||
typeof(Machine).Assembly,
|
||||
typeof(object).Assembly
|
||||
}));
|
||||
|
||||
ser.Converters.Add(new DelegateConverter());
|
||||
ser.Converters.Add(new ArrayConverter());
|
||||
|
||||
var cr = new DefaultContractResolver();
|
||||
cr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
|
||||
ser.ContractResolver = cr;
|
||||
|
||||
return ser;
|
||||
}
|
||||
|
||||
public void Serialize(JsonWriter w)
|
||||
{
|
||||
CreateSerializer().Serialize(w, this);
|
||||
}
|
||||
|
||||
public static Machine Deserialize(JsonReader r)
|
||||
{
|
||||
return CreateSerializer().Deserialize<Machine>(r);
|
||||
}
|
||||
|
||||
public const string Version = "0.9.4.0";
|
||||
|
||||
public MachineEvents Events { get; private set; }
|
||||
|
||||
public Cpu Cpu { get; private set; }
|
||||
public Memory Memory { get; private set; }
|
||||
public Keyboard Keyboard { get; private set; }
|
||||
public GamePort GamePort { get; private set; }
|
||||
public Cassette Cassette { get; private set; }
|
||||
public Speaker Speaker { get; private set; }
|
||||
public Video Video { get; private set; }
|
||||
public NoSlotClock NoSlotClock { get; private set; }
|
||||
|
||||
public PeripheralCard Slot1 { get; private set; }
|
||||
public PeripheralCard Slot2 { get; private set; }
|
||||
public PeripheralCard Slot3 { get; private set; }
|
||||
public PeripheralCard Slot4 { get; private set; }
|
||||
public PeripheralCard Slot5 { get; private set; }
|
||||
public PeripheralCard Slot6 { get; private set; }
|
||||
public PeripheralCard Slot7 { get; private set; }
|
||||
|
||||
public IList<PeripheralCard> Slots { get; private set; }
|
||||
public IList<MachineComponent> Components { get; private set; }
|
||||
|
||||
public DiskIIController BootDiskII { get; private set; }
|
||||
|
||||
public bool Lagged { get; set; }
|
||||
public bool DriveLight { get; set; }
|
||||
|
||||
public IDictionary<string, int> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new Dictionary<string, int>
|
||||
{
|
||||
{ "A", Cpu.RA },
|
||||
{ "X", Cpu.RX },
|
||||
{ "Y", Cpu.RY },
|
||||
{ "S", Cpu.RS },
|
||||
{ "PC", Cpu.RPC },
|
||||
{ "Flag C", Cpu.FlagC ? 1 : 0 },
|
||||
{ "Flag Z", Cpu.FlagZ ? 1 : 0 },
|
||||
{ "Flag I", Cpu.FlagI ? 1 : 0 },
|
||||
{ "Flag D", Cpu.FlagD ? 1 : 0 },
|
||||
{ "Flag B", Cpu.FlagB ? 1 : 0 },
|
||||
{ "Flag V", Cpu.FlagV ? 1 : 0 },
|
||||
{ "Flag N", Cpu.FlagN ? 1 : 0 },
|
||||
{ "Flag T", Cpu.FlagT ? 1 : 0 }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public abstract class MachineComponent
|
||||
{
|
||||
public MachineComponent() { }
|
||||
protected MachineComponent(Machine machine)
|
||||
{
|
||||
_machine = machine;
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Uninitialize()
|
||||
{
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
private Machine _machine;
|
||||
|
||||
public Machine Machine
|
||||
{
|
||||
get { return _machine; }
|
||||
set { _machine = value; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,124 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class MachineEvent
|
||||
{
|
||||
public MachineEvent(int delta, Action action)
|
||||
{
|
||||
Delta = delta;
|
||||
Action = action;
|
||||
}
|
||||
internal sealed class MachineEvent
|
||||
{
|
||||
public MachineEvent(int delta, Action action)
|
||||
{
|
||||
Delta = delta;
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "Delta = {0} Action = {{{1}.{2}}}", Delta, Action.Method.DeclaringType.Name, Action.Method.Name);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.InvariantCulture, "Delta = {0} Action = {{{1}.{2}}}", Delta, Action.Method.DeclaringType?.Name, Action.Method.Name);
|
||||
}
|
||||
|
||||
public int Delta { get; set; }
|
||||
public Action Action { get; set; }
|
||||
}
|
||||
public int Delta { get; set; }
|
||||
public Action Action { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MachineEvents
|
||||
{
|
||||
public void AddEvent(int delta, Action action)
|
||||
{
|
||||
//Console.WriteLine("+{0} @ {1}", action.Method.Name, delta);
|
||||
public sealed class MachineEvents
|
||||
{
|
||||
public void AddEvent(int delta, Action action)
|
||||
{
|
||||
var node = _used.First;
|
||||
for (; node != null; node = node.Next)
|
||||
{
|
||||
if (delta < node.Value.Delta)
|
||||
{
|
||||
node.Value.Delta -= delta;
|
||||
break;
|
||||
}
|
||||
if (node.Value.Delta > 0)
|
||||
{
|
||||
delta -= node.Value.Delta;
|
||||
}
|
||||
}
|
||||
|
||||
var node = _used.First;
|
||||
for (; node != null; node = node.Next)
|
||||
{
|
||||
if (delta < node.Value.Delta)
|
||||
{
|
||||
node.Value.Delta -= delta;
|
||||
break;
|
||||
}
|
||||
if (node.Value.Delta > 0)
|
||||
{
|
||||
delta -= node.Value.Delta;
|
||||
}
|
||||
}
|
||||
var newNode = _free.First;
|
||||
if (newNode != null)
|
||||
{
|
||||
_free.RemoveFirst();
|
||||
newNode.Value.Delta = delta;
|
||||
newNode.Value.Action = action;
|
||||
}
|
||||
else
|
||||
{
|
||||
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, action));
|
||||
}
|
||||
|
||||
var newNode = _free.First;
|
||||
if (newNode != null)
|
||||
{
|
||||
_free.RemoveFirst();
|
||||
newNode.Value.Delta = delta;
|
||||
newNode.Value.Action = action;
|
||||
}
|
||||
else
|
||||
{
|
||||
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, action));
|
||||
}
|
||||
if (node != null)
|
||||
{
|
||||
_used.AddBefore(node, newNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_used.AddLast(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
{
|
||||
_used.AddBefore(node, newNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_used.AddLast(newNode);
|
||||
}
|
||||
}
|
||||
public int FindEvent(Action action)
|
||||
{
|
||||
int delta = 0;
|
||||
|
||||
public int FindEvent(Action action)
|
||||
{
|
||||
int delta = 0;
|
||||
|
||||
for (var node = _used.First; node != null; node = node.Next)
|
||||
{
|
||||
delta += node.Value.Delta;
|
||||
for (var node = _used.First; node != null; node = node.Next)
|
||||
{
|
||||
delta += node.Value.Delta;
|
||||
|
||||
var other = node.Value.Action;
|
||||
|
||||
if (other.Method == action.Method && other.Target == action.Target)
|
||||
if (other.Method == action.Method && other.Target == action.Target)
|
||||
{
|
||||
//Console.WriteLine("={0} @ {1}", action.Method.Name, delta);
|
||||
return delta;
|
||||
}
|
||||
}
|
||||
|
||||
// our delegate serializer doesn't preserve reference equality
|
||||
//if (object.ReferenceEquals(node.Value.Action, action)) // assumes delegate cached
|
||||
//{
|
||||
// return delta;
|
||||
//}
|
||||
}
|
||||
|
||||
//Console.WriteLine("=???? @ 0");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void HandleEvents(int delta)
|
||||
{
|
||||
//Console.WriteLine("[{0}]", delta);
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void HandleEvents(int delta)
|
||||
{
|
||||
var node = _used.First;
|
||||
node.Value.Delta -= delta;
|
||||
|
||||
var node = _used.First;
|
||||
node.Value.Delta -= delta;
|
||||
|
||||
while (node.Value.Delta <= 0)
|
||||
{
|
||||
//Console.WriteLine("!{0} @ {1}", node.Value.Action.Method.Name, node.Value.Delta);
|
||||
while (node.Value.Delta <= 0)
|
||||
{
|
||||
node.Value.Action();
|
||||
RemoveEvent(node);
|
||||
node = _used.First;
|
||||
}
|
||||
}
|
||||
RemoveEvent(node);
|
||||
node = _used.First;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEvent(LinkedListNode<MachineEvent> node)
|
||||
{
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Value.Delta += node.Value.Delta;
|
||||
}
|
||||
private void RemoveEvent(LinkedListNode<MachineEvent> node)
|
||||
{
|
||||
if (node.Next != null)
|
||||
{
|
||||
node.Next.Value.Delta += node.Value.Delta;
|
||||
}
|
||||
|
||||
_used.Remove(node);
|
||||
_free.AddFirst(node); // cache node; avoids garbage
|
||||
}
|
||||
_used.Remove(node);
|
||||
_free.AddFirst(node); // cache node; avoids garbage
|
||||
}
|
||||
|
||||
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
}
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class Memory
|
||||
{
|
||||
private const int BankMain = 0;
|
||||
private const int BankAux = 1;
|
||||
|
||||
private const int RegionCount = 12;
|
||||
|
||||
private const int Region0001 = 0;
|
||||
private const int Region02BF = 1;
|
||||
private const int Region0407 = 2;
|
||||
private const int Region080B = 3;
|
||||
private const int Region203F = 4;
|
||||
private const int Region405F = 5;
|
||||
private const int RegionC0C0 = 6;
|
||||
private const int RegionC1C7 = 7;
|
||||
private const int RegionC3C3 = 8;
|
||||
private const int RegionC8CF = 9;
|
||||
private const int RegionD0DF = 10;
|
||||
private const int RegionE0FF = 11;
|
||||
|
||||
private static readonly int[] RegionBaseAddress =
|
||||
{
|
||||
0x0000, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0xC000, 0xC100, 0xC100, 0xC100, 0xD000, 0xE000
|
||||
};
|
||||
|
||||
private static readonly int[] PageRegion =
|
||||
{
|
||||
Region0001, Region0001, Region02BF, Region02BF, Region0407, Region0407, Region0407, Region0407,
|
||||
Region080B, Region080B, Region080B, Region080B, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
RegionC0C0, RegionC1C7, RegionC1C7, RegionC3C3, RegionC1C7, RegionC1C7, RegionC1C7, RegionC1C7,
|
||||
RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF
|
||||
};
|
||||
|
||||
private const int State80Col = 0x000001;
|
||||
private const int StateText = 0x000002;
|
||||
private const int StateMixed = 0x000004;
|
||||
private const int StateHires = 0x000008;
|
||||
private const int StateDRes = 0x000010;
|
||||
private const int State80Store = 0x000020;
|
||||
private const int StateAltChrSet = 0x000040;
|
||||
private const int StateAltZP = 0x000080;
|
||||
private const int StateBank1 = 0x000100;
|
||||
private const int StateHRamRd = 0x000200;
|
||||
private const int StateHRamPreWrt = 0x000400;
|
||||
private const int StateHRamWrt = 0x000800;
|
||||
private const int StatePage2 = 0x001000;
|
||||
private const int StateRamRd = 0x002000;
|
||||
private const int StateRamWrt = 0x004000;
|
||||
private const int StateSlotC3Rom = 0x008000;
|
||||
private const int StateIntC8Rom = 0x010000; // [5-28]
|
||||
private const int StateIntCXRom = 0x020000;
|
||||
private const int StateAn0 = 0x040000;
|
||||
private const int StateAn1 = 0x080000;
|
||||
private const int StateAn2 = 0x100000;
|
||||
private const int StateAn3 = 0x200000;
|
||||
private const int StateVideo = State80Col | StateText | StateMixed | StateHires | StateDRes;
|
||||
|
||||
private static readonly int[] StateVideoMode =
|
||||
{
|
||||
Video.Mode0, Video.Mode0, Video.Mode1, Video.Mode2, Video.Mode3, Video.Mode4, Video.Mode1, Video.Mode2,
|
||||
Video.Mode5, Video.Mode5, Video.Mode1, Video.Mode2, Video.Mode6, Video.Mode7, Video.Mode1, Video.Mode2,
|
||||
Video.Mode8, Video.Mode9, Video.Mode1, Video.Mode2, Video.ModeA, Video.ModeB, Video.Mode1, Video.Mode2,
|
||||
Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
private Action<int, byte>[][][] WriteRamModeBankRegion;
|
||||
}
|
||||
}
|
|
@ -1,36 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Jellyfish.Library;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public enum MonitorType { Unknown, Standard, Enhanced };
|
||||
public enum MonitorType { Unknown, Standard, Enhanced }
|
||||
|
||||
public sealed partial class Memory : MachineComponent
|
||||
public interface IMemoryBus
|
||||
{
|
||||
// ReSharper disable once UnusedMemberInSuper.Global
|
||||
bool Lagged { get; }
|
||||
|
||||
int ReadRomRegionE0FF(int address);
|
||||
int ReadRamMainRegion02BF(int address);
|
||||
int ReadRamAuxRegion02BF(int address);
|
||||
|
||||
int Read(int address);
|
||||
int Peek(int address);
|
||||
int ReadOpcode(int address);
|
||||
int ReadZeroPage(int address);
|
||||
void WriteZeroPage(int address, int data);
|
||||
void Write(int address, int data);
|
||||
|
||||
bool IsText { get; }
|
||||
bool IsMixed { get; }
|
||||
bool IsHires { get; }
|
||||
bool IsVideoPage2 { get; }
|
||||
bool IsCharSetAlternate { get; }
|
||||
|
||||
int VideoMode { get; }
|
||||
MonitorType Monitor { get; }
|
||||
}
|
||||
|
||||
public sealed partial class Memory : IMemoryBus
|
||||
{
|
||||
private IGamePort _gamePort;
|
||||
private ICassette _cassette;
|
||||
private IVideo _video;
|
||||
private ISlotClock _noSlotClock;
|
||||
|
||||
private IPeripheralCard _slot1;
|
||||
private IPeripheralCard _slot2;
|
||||
private IPeripheralCard _slot3;
|
||||
private IPeripheralCard _slot4;
|
||||
private IPeripheralCard _slot5;
|
||||
private IPeripheralCard _slot7;
|
||||
|
||||
// TODO: this shouldn't be in savestates!
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private byte[] _appleIIe;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Memory()
|
||||
{
|
||||
InitializeWriteDelegates();
|
||||
}
|
||||
|
||||
public Memory(Machine machine, byte[] appleIIe) :
|
||||
base(machine)
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Memory(byte[] appleIIe)
|
||||
{
|
||||
_appleIIe = appleIIe;
|
||||
InitializeWriteDelegates();
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Initialize(
|
||||
Keyboard keyboard,
|
||||
IGamePort gamePort,
|
||||
ICassette cassette,
|
||||
ISpeaker speaker,
|
||||
IVideo video,
|
||||
ISlotClock noSlotClock,
|
||||
IPeripheralCard slot1,
|
||||
IPeripheralCard slot2,
|
||||
IPeripheralCard slot3,
|
||||
IPeripheralCard slot4,
|
||||
IPeripheralCard slot5,
|
||||
IDiskIIController slot6,
|
||||
IPeripheralCard slot7)
|
||||
{
|
||||
Keyboard = keyboard;
|
||||
_gamePort = gamePort;
|
||||
_cassette = cassette;
|
||||
Speaker = speaker;
|
||||
_video = video;
|
||||
_noSlotClock = noSlotClock;
|
||||
|
||||
_slot1 = slot1;
|
||||
_slot2 = slot2;
|
||||
_slot3 = slot3;
|
||||
_slot4 = slot4;
|
||||
_slot5 = slot5;
|
||||
DiskIIController = slot6;
|
||||
_slot7 = slot7;
|
||||
|
||||
// TODO: this is a lazy and more complicated way to do this
|
||||
_romInternalRegionC1CF = _appleIIe
|
||||
.Skip(0x100)
|
||||
.Take(_romInternalRegionC1CF.Length)
|
||||
.ToArray();
|
||||
|
||||
_romRegionD0DF = _appleIIe
|
||||
.Skip(0x100 + _romInternalRegionC1CF.Length)
|
||||
.Take(_romRegionD0DF.Length)
|
||||
.ToArray();
|
||||
|
||||
_romRegionE0FF = _appleIIe
|
||||
.Skip(0x100 + _romInternalRegionC1CF.Length + _romRegionD0DF.Length)
|
||||
.Take(_romRegionE0FF.Length)
|
||||
.ToArray();
|
||||
|
||||
if (ReadRomRegionE0FF(0xFBB3) == 0x06 && ReadRomRegionE0FF(0xFBBF) == 0xC1)
|
||||
{
|
||||
Monitor = MonitorType.Standard;
|
||||
}
|
||||
else if (ReadRomRegionE0FF(0xFBB3) == 0x06
|
||||
&& ReadRomRegionE0FF(0xFBBF) == 0x00
|
||||
&& ReadRomRegionE0FF(0xFBC0) == 0xE0)
|
||||
{
|
||||
Monitor = MonitorType.Enhanced;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Lagged { get; set; }
|
||||
|
||||
private IList<IPeripheralCard> Slots => new List<IPeripheralCard>
|
||||
{
|
||||
null,
|
||||
_slot1,
|
||||
_slot2,
|
||||
_slot3,
|
||||
_slot4,
|
||||
_slot5,
|
||||
DiskIIController,
|
||||
_slot7
|
||||
};
|
||||
|
||||
public IDiskIIController DiskIIController { get; private set; }
|
||||
|
||||
public Keyboard Keyboard { get; private set; }
|
||||
|
||||
public ISpeaker Speaker { get; private set; }
|
||||
|
||||
private void InitializeWriteDelegates()
|
||||
{
|
||||
WriteRamModeBankRegion = new Action<int, byte>[Video.ModeCount][][];
|
||||
for (int mode = 0; mode < Video.ModeCount; mode++)
|
||||
{
|
||||
WriteRamModeBankRegion[mode] = new Action<int, byte>[BankCount][]
|
||||
WriteRamModeBankRegion[mode] = new[]
|
||||
{
|
||||
new Action<int, byte>[RegionCount], new Action<int, byte>[RegionCount]
|
||||
};
|
||||
|
@ -99,44 +217,8 @@ namespace Jellyfish.Virtu
|
|||
_writeRomRegionD0FF = WriteRomRegionD0FF;
|
||||
}
|
||||
|
||||
private byte[] _appleIIe;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_keyboard = Machine.Keyboard;
|
||||
_gamePort = Machine.GamePort;
|
||||
_cassette = Machine.Cassette;
|
||||
_speaker = Machine.Speaker;
|
||||
_video = Machine.Video;
|
||||
_noSlotClock = Machine.NoSlotClock;
|
||||
|
||||
// TODO: this is a lazy and more compicated way to do this
|
||||
_romInternalRegionC1CF = _appleIIe
|
||||
.Skip(0x100)
|
||||
.Take(_romInternalRegionC1CF.Length)
|
||||
.ToArray();
|
||||
|
||||
_romRegionD0DF = _appleIIe
|
||||
.Skip(0x100 + _romInternalRegionC1CF.Length)
|
||||
.Take(_romRegionD0DF.Length)
|
||||
.ToArray();
|
||||
|
||||
_romRegionE0FF = _appleIIe
|
||||
.Skip(0x100 + _romInternalRegionC1CF.Length + _romRegionD0DF.Length)
|
||||
.Take(_romRegionE0FF.Length)
|
||||
.ToArray();
|
||||
|
||||
if ((ReadRomRegionE0FF(0xFBB3) == 0x06) && (ReadRomRegionE0FF(0xFBBF) == 0xC1))
|
||||
{
|
||||
Monitor = MonitorType.Standard;
|
||||
}
|
||||
else if ((ReadRomRegionE0FF(0xFBB3) == 0x06) && (ReadRomRegionE0FF(0xFBBF) == 0x00) && (ReadRomRegionE0FF(0xFBC0) == 0xE0))
|
||||
{
|
||||
Monitor = MonitorType.Enhanced;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset() // [7-3]
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Reset() // [7-3]
|
||||
{
|
||||
ResetState(State80Col | State80Store | StateAltChrSet | StateAltZP | StateBank1 | StateHRamRd | StateHRamPreWrt | StateHRamWrt | // HRamWrt' [5-23]
|
||||
StateHires | StatePage2 | StateRamRd | StateRamWrt | StateIntCXRom | StateSlotC3Rom | StateIntC8Rom | StateAn0 | StateAn1 | StateAn2 | StateAn3);
|
||||
|
@ -148,88 +230,38 @@ namespace Jellyfish.Virtu
|
|||
MapRegionD0FF();
|
||||
}
|
||||
|
||||
public void LoadPrg(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
int startAddress = stream.ReadWord();
|
||||
SetWarmEntry(startAddress); // assumes autostart monitor
|
||||
Load(stream, startAddress);
|
||||
}
|
||||
|
||||
public void LoadXex(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException("stream");
|
||||
}
|
||||
|
||||
const int Marker = 0xFFFF;
|
||||
int marker = stream.ReadWord(); // mandatory marker
|
||||
if (marker != Marker)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Marker ${0:X04} not found.", Marker));
|
||||
}
|
||||
int startAddress = stream.ReadWord();
|
||||
int endAddress = stream.ReadWord();
|
||||
SetWarmEntry(startAddress); // assumes autostart monitor
|
||||
|
||||
do
|
||||
{
|
||||
if (startAddress > endAddress)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Invalid address range ${0:X04}-${1:X04}.", startAddress, endAddress));
|
||||
}
|
||||
Load(stream, startAddress, endAddress - startAddress + 1);
|
||||
marker = stream.ReadWord(optional: true); // optional marker
|
||||
startAddress = (marker != Marker) ? marker : stream.ReadWord(optional: true);
|
||||
endAddress = stream.ReadWord(optional: true);
|
||||
}
|
||||
while ((startAddress >= 0) && (endAddress >= 0));
|
||||
}
|
||||
|
||||
#region Core Read & Write
|
||||
|
||||
public int ReadOpcode(int address)
|
||||
{
|
||||
int region = PageRegion[address >> 8];
|
||||
var result = ((address & 0xF000) != 0xC000) ? _regionRead[region][address - RegionBaseAddress[region]] : ReadIoRegionC0CF(address);
|
||||
if (ExecuteCallback != null)
|
||||
{
|
||||
ExecuteCallback((uint)address);
|
||||
}
|
||||
if (ReadCallback != null)
|
||||
{
|
||||
ReadCallback((uint)address);
|
||||
}
|
||||
ExecuteCallback?.Invoke((uint)address);
|
||||
ReadCallback?.Invoke((uint)address);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Read(int address)
|
||||
{
|
||||
int region = PageRegion[address >> 8];
|
||||
var result = ((address & 0xF000) != 0xC000) ? _regionRead[region][address - RegionBaseAddress[region]] : ReadIoRegionC0CF(address);
|
||||
if (ReadCallback != null)
|
||||
{
|
||||
ReadCallback((uint)address);
|
||||
}
|
||||
var result = (address & 0xF000) != 0xC000
|
||||
? _regionRead[region][address - RegionBaseAddress[region]]
|
||||
: ReadIoRegionC0CF(address);
|
||||
ReadCallback?.Invoke((uint)address);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Peek(int address)
|
||||
{
|
||||
int region = PageRegion[address >> 8];
|
||||
return ((address & 0xF000) != 0xC000) ? _regionRead[region][address - RegionBaseAddress[region]] : ReadIoRegionC0CF(address);
|
||||
return (address & 0xF000) != 0xC000
|
||||
? _regionRead[region][address - RegionBaseAddress[region]]
|
||||
: ReadIoRegionC0CF(address);
|
||||
}
|
||||
|
||||
public int ReadZeroPage(int address)
|
||||
{
|
||||
if (ReadCallback != null)
|
||||
{
|
||||
ReadCallback((uint)address);
|
||||
}
|
||||
ReadCallback?.Invoke((uint)address);
|
||||
return _zeroPage[address];
|
||||
}
|
||||
|
||||
|
@ -238,30 +270,22 @@ namespace Jellyfish.Virtu
|
|||
int region = PageRegion[address >> 8];
|
||||
if (_writeRegion[region] == null)
|
||||
{
|
||||
if (WriteCallback != null)
|
||||
{
|
||||
WriteCallback((uint)address);
|
||||
}
|
||||
WriteCallback?.Invoke((uint)address);
|
||||
_regionWrite[region][address - RegionBaseAddress[region]] = (byte)data;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (WriteCallback != null)
|
||||
{
|
||||
WriteCallback((uint)address);
|
||||
}
|
||||
WriteCallback?.Invoke((uint)address);
|
||||
_writeRegion[region](address, (byte)data);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteZeroPage(int address, int data)
|
||||
{
|
||||
if (WriteCallback != null)
|
||||
{
|
||||
WriteCallback((uint)address);
|
||||
}
|
||||
WriteCallback?.Invoke((uint)address);
|
||||
_zeroPage[address] = (byte)data;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read Actions
|
||||
|
@ -300,13 +324,10 @@ namespace Jellyfish.Virtu
|
|||
|
||||
private int ReadIoRegionC0C0(int address)
|
||||
{
|
||||
if ((0xC000 <= address && address <= 0xC00F) || (0xC061 <= address && address <= 0xC067) || (0xC069 <= address && address <= 0xC06F))
|
||||
if (0xC000 <= address && address <= 0xC00F || 0xC061 <= address && address <= 0xC067 || 0xC069 <= address && address <= 0xC06F)
|
||||
{
|
||||
Machine.Lagged = false;
|
||||
if (InputCallback != null)
|
||||
{
|
||||
InputCallback();
|
||||
}
|
||||
Lagged = false;
|
||||
InputCallback?.Invoke();
|
||||
}
|
||||
|
||||
switch (address)
|
||||
|
@ -327,56 +348,56 @@ namespace Jellyfish.Virtu
|
|||
case 0xC00D:
|
||||
case 0xC00E:
|
||||
case 0xC00F:
|
||||
return SetBit7(_keyboard.Latch, _keyboard.Strobe);
|
||||
return SetBit7(Keyboard.Latch, Keyboard.Strobe);
|
||||
|
||||
case 0xC010:
|
||||
_keyboard.ResetStrobe();
|
||||
return SetBit7(_keyboard.Latch, _keyboard.IsAnyKeyDown);
|
||||
Keyboard.ResetStrobe();
|
||||
return SetBit7(Keyboard.Latch, Keyboard.IsAnyKeyDown);
|
||||
|
||||
case 0xC011:
|
||||
return SetBit7(_keyboard.Latch, !IsHighRamBank1); // Bank1' [5-22]
|
||||
return SetBit7(Keyboard.Latch, !IsHighRamBank1); // Bank1' [5-22]
|
||||
|
||||
case 0xC012:
|
||||
return SetBit7(_keyboard.Latch, IsHighRamRead);
|
||||
return SetBit7(Keyboard.Latch, IsHighRamRead);
|
||||
|
||||
case 0xC013:
|
||||
return SetBit7(_keyboard.Latch, IsRamReadAux);
|
||||
return SetBit7(Keyboard.Latch, IsRamReadAux);
|
||||
|
||||
case 0xC014:
|
||||
return SetBit7(_keyboard.Latch, IsRamWriteAux);
|
||||
return SetBit7(Keyboard.Latch, IsRamWriteAux);
|
||||
|
||||
case 0xC015:
|
||||
return SetBit7(_keyboard.Latch, IsRomC1CFInternal);
|
||||
return SetBit7(Keyboard.Latch, IsRomC1CFInternal);
|
||||
|
||||
case 0xC016:
|
||||
return SetBit7(_keyboard.Latch, IsZeroPageAux);
|
||||
return SetBit7(Keyboard.Latch, IsZeroPageAux);
|
||||
|
||||
case 0xC017:
|
||||
return SetBit7(_keyboard.Latch, IsRomC3C3External);
|
||||
return SetBit7(Keyboard.Latch, IsRomC3C3External);
|
||||
|
||||
case 0xC018:
|
||||
return SetBit7(_keyboard.Latch, Is80Store);
|
||||
return SetBit7(Keyboard.Latch, Is80Store);
|
||||
|
||||
case 0xC019:
|
||||
return SetBit7(_keyboard.Latch, !_video.IsVBlank); // Vbl' [7-5]
|
||||
return SetBit7(Keyboard.Latch, !_video.IsVBlank); // Vbl' [7-5]
|
||||
|
||||
case 0xC01A:
|
||||
return SetBit7(_keyboard.Latch, IsText);
|
||||
return SetBit7(Keyboard.Latch, IsText);
|
||||
|
||||
case 0xC01B:
|
||||
return SetBit7(_keyboard.Latch, IsMixed);
|
||||
return SetBit7(Keyboard.Latch, IsMixed);
|
||||
|
||||
case 0xC01C:
|
||||
return SetBit7(_keyboard.Latch, IsPage2);
|
||||
return SetBit7(Keyboard.Latch, IsPage2);
|
||||
|
||||
case 0xC01D:
|
||||
return SetBit7(_keyboard.Latch, IsHires);
|
||||
return SetBit7(Keyboard.Latch, IsHires);
|
||||
|
||||
case 0xC01E:
|
||||
return SetBit7(_keyboard.Latch, IsCharSetAlternate);
|
||||
return SetBit7(Keyboard.Latch, IsCharSetAlternate);
|
||||
|
||||
case 0xC01F:
|
||||
return SetBit7(_keyboard.Latch, Is80Columns);
|
||||
return SetBit7(Keyboard.Latch, Is80Columns);
|
||||
|
||||
case 0xC020:
|
||||
case 0xC021:
|
||||
|
@ -413,7 +434,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC03D:
|
||||
case 0xC03E:
|
||||
case 0xC03F:
|
||||
_speaker.ToggleOutput();
|
||||
Speaker.ToggleOutput();
|
||||
break;
|
||||
|
||||
case 0xC040:
|
||||
|
@ -561,7 +582,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC09D:
|
||||
case 0xC09E:
|
||||
case 0xC09F:
|
||||
return Machine.Slot1.ReadIoRegionC0C0(address);
|
||||
return _slot1.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0A0:
|
||||
case 0xC0A1:
|
||||
|
@ -579,7 +600,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0AD:
|
||||
case 0xC0AE:
|
||||
case 0xC0AF:
|
||||
return Machine.Slot2.ReadIoRegionC0C0(address);
|
||||
return _slot2.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0B0:
|
||||
case 0xC0B1:
|
||||
|
@ -597,7 +618,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0BD:
|
||||
case 0xC0BE:
|
||||
case 0xC0BF:
|
||||
return Machine.Slot3.ReadIoRegionC0C0(address);
|
||||
return _slot3.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0C0:
|
||||
case 0xC0C1:
|
||||
|
@ -615,7 +636,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0CD:
|
||||
case 0xC0CE:
|
||||
case 0xC0CF:
|
||||
return Machine.Slot4.ReadIoRegionC0C0(address);
|
||||
return _slot4.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0D0:
|
||||
case 0xC0D1:
|
||||
|
@ -633,7 +654,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0DD:
|
||||
case 0xC0DE:
|
||||
case 0xC0DF:
|
||||
return Machine.Slot5.ReadIoRegionC0C0(address);
|
||||
return _slot5.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0E0:
|
||||
case 0xC0E1:
|
||||
|
@ -651,7 +672,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0ED:
|
||||
case 0xC0EE:
|
||||
case 0xC0EF:
|
||||
return Machine.Slot6.ReadIoRegionC0C0(address);
|
||||
return DiskIIController.ReadIoRegionC0C0(address);
|
||||
|
||||
case 0xC0F0:
|
||||
case 0xC0F1:
|
||||
|
@ -669,10 +690,10 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0FD:
|
||||
case 0xC0FE:
|
||||
case 0xC0FF:
|
||||
return Machine.Slot7.ReadIoRegionC0C0(address);
|
||||
return _slot7.ReadIoRegionC0C0(address);
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("address");
|
||||
throw new ArgumentOutOfRangeException(nameof(address));
|
||||
}
|
||||
|
||||
return _video.ReadFloatingBus();
|
||||
|
@ -681,7 +702,7 @@ namespace Jellyfish.Virtu
|
|||
private int ReadIoRegionC1C7(int address)
|
||||
{
|
||||
_slotRegionC8CF = (address >> 8) & 0x07;
|
||||
return IsRomC1CFInternal ? _romInternalRegionC1CF[address - 0xC100] : Machine.Slots[_slotRegionC8CF].ReadIoRegionC1C7(address);
|
||||
return IsRomC1CFInternal ? _romInternalRegionC1CF[address - 0xC100] : Slots[_slotRegionC8CF].ReadIoRegionC1C7(address);
|
||||
}
|
||||
|
||||
private int ReadIoRegionC3C3(int address)
|
||||
|
@ -691,7 +712,10 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
SetRomC8CF(true); // $C3XX sets IntC8Rom; inhibits I/O Strobe' [5-28, 7-21]
|
||||
}
|
||||
return (IsRomC1CFInternal || !IsRomC3C3External) ? _noSlotClock.Read(address, _romInternalRegionC1CF[address - 0xC100]) : Machine.Slot3.ReadIoRegionC1C7(address);
|
||||
|
||||
return IsRomC1CFInternal || !IsRomC3C3External
|
||||
? _noSlotClock.Read(address, _romInternalRegionC1CF[address - 0xC100])
|
||||
: _slot3.ReadIoRegionC1C7(address);
|
||||
}
|
||||
|
||||
private int ReadIoRegionC8CF(int address)
|
||||
|
@ -700,7 +724,9 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
SetRomC8CF(false); // $CFFF resets IntC8Rom [5-28, 7-21]
|
||||
}
|
||||
return (IsRomC1CFInternal || IsRomC8CFInternal) ? _noSlotClock.Read(address, _romInternalRegionC1CF[address - 0xC100]) : Machine.Slots[_slotRegionC8CF].ReadIoRegionC8CF(address);
|
||||
return IsRomC1CFInternal || IsRomC8CFInternal
|
||||
? _noSlotClock.Read(address, _romInternalRegionC1CF[address - 0xC100])
|
||||
: Slots[_slotRegionC8CF].ReadIoRegionC8CF(address);
|
||||
}
|
||||
|
||||
public int ReadRamMainRegion02BF(int address)
|
||||
|
@ -782,7 +808,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC01D:
|
||||
case 0xC01E:
|
||||
case 0xC01F:
|
||||
_keyboard.ResetStrobe();
|
||||
Keyboard.ResetStrobe();
|
||||
break;
|
||||
|
||||
case 0xC020:
|
||||
|
@ -820,7 +846,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC03D:
|
||||
case 0xC03E:
|
||||
case 0xC03F:
|
||||
_speaker.ToggleOutput();
|
||||
Speaker.ToggleOutput();
|
||||
break;
|
||||
|
||||
case 0xC040:
|
||||
|
@ -954,7 +980,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC09D:
|
||||
case 0xC09E:
|
||||
case 0xC09F:
|
||||
Machine.Slot1.WriteIoRegionC0C0(address, data);
|
||||
_slot1.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0A0:
|
||||
|
@ -973,7 +999,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0AD:
|
||||
case 0xC0AE:
|
||||
case 0xC0AF:
|
||||
Machine.Slot2.WriteIoRegionC0C0(address, data);
|
||||
_slot2.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0B0:
|
||||
|
@ -992,7 +1018,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0BD:
|
||||
case 0xC0BE:
|
||||
case 0xC0BF:
|
||||
Machine.Slot3.WriteIoRegionC0C0(address, data);
|
||||
_slot3.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0C0:
|
||||
|
@ -1011,7 +1037,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0CD:
|
||||
case 0xC0CE:
|
||||
case 0xC0CF:
|
||||
Machine.Slot4.WriteIoRegionC0C0(address, data);
|
||||
_slot4.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0D0:
|
||||
|
@ -1030,7 +1056,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0DD:
|
||||
case 0xC0DE:
|
||||
case 0xC0DF:
|
||||
Machine.Slot5.WriteIoRegionC0C0(address, data);
|
||||
_slot5.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0E0:
|
||||
|
@ -1049,7 +1075,7 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0ED:
|
||||
case 0xC0EE:
|
||||
case 0xC0EF:
|
||||
Machine.Slot6.WriteIoRegionC0C0(address, data);
|
||||
DiskIIController.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
case 0xC0F0:
|
||||
|
@ -1068,11 +1094,11 @@ namespace Jellyfish.Virtu
|
|||
case 0xC0FD:
|
||||
case 0xC0FE:
|
||||
case 0xC0FF:
|
||||
Machine.Slot7.WriteIoRegionC0C0(address, data);
|
||||
_slot7.WriteIoRegionC0C0(address, data);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("address");
|
||||
throw new ArgumentOutOfRangeException(nameof(address));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1081,7 +1107,7 @@ namespace Jellyfish.Virtu
|
|||
_slotRegionC8CF = (address >> 8) & 0x07;
|
||||
if (!IsRomC1CFInternal)
|
||||
{
|
||||
Machine.Slots[_slotRegionC8CF].WriteIoRegionC1C7(address, data);
|
||||
Slots[_slotRegionC8CF].WriteIoRegionC1C7(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1098,7 +1124,7 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
else
|
||||
{
|
||||
Machine.Slot3.WriteIoRegionC1C7(address, data);
|
||||
_slot3.WriteIoRegionC1C7(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1114,7 +1140,7 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
else
|
||||
{
|
||||
Machine.Slots[_slotRegionC8CF].WriteIoRegionC8CF(address, data);
|
||||
Slots[_slotRegionC8CF].WriteIoRegionC8CF(address, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1993,84 +2019,22 @@ namespace Jellyfish.Virtu
|
|||
MapRegionD0FF();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void Load(Stream stream, int startAddress)
|
||||
{
|
||||
DebugService.WriteMessage("Loading memory ${0:X04}", startAddress);
|
||||
int address = startAddress;
|
||||
if (address < 0x0200)
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegion0001, address, minCount: 0);
|
||||
}
|
||||
if ((0x0200 <= address) && (address < 0xC000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegion02BF, address - 0x0200, minCount: 0);
|
||||
}
|
||||
if ((0xC000 <= address) && (address < 0xD000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainBank1RegionD0DF, address - 0xC000, minCount: 0);
|
||||
}
|
||||
if ((0xD000 <= address) && (address < 0xE000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainBank2RegionD0DF, address - 0xD000, minCount: 0);
|
||||
}
|
||||
if (0xE000 <= address)
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegionE0FF, address - 0xE000, minCount: 0);
|
||||
}
|
||||
if (address > startAddress)
|
||||
{
|
||||
DebugService.WriteMessage("Loaded memory ${0:X04}-${1:X04} (${2:X04})", startAddress, address - 1, address - startAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private void Load(Stream stream, int startAddress, int length)
|
||||
{
|
||||
DebugService.WriteMessage("Loading memory ${0:X04}-${1:X04} (${2:X04})", startAddress, startAddress + length - 1, length);
|
||||
int address = startAddress;
|
||||
if (address < 0x0200)
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegion0001, address, ref length);
|
||||
}
|
||||
if ((0x0200 <= address) && (address < 0xC000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegion02BF, address - 0x0200, ref length);
|
||||
}
|
||||
if ((0xC000 <= address) && (address < 0xD000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainBank1RegionD0DF, address - 0xC000, ref length);
|
||||
}
|
||||
if ((0xD000 <= address) && (address < 0xE000))
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainBank2RegionD0DF, address - 0xD000, ref length);
|
||||
}
|
||||
if (0xE000 <= address)
|
||||
{
|
||||
address += stream.ReadBlock(_ramMainRegionE0FF, address - 0xE000, ref length);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetWarmEntry(int address)
|
||||
{
|
||||
_ramMainRegion02BF[0x03F2 - 0x0200] = (byte)(address & 0xFF);
|
||||
_ramMainRegion02BF[0x03F3 - 0x0200] = (byte)(address >> 8);
|
||||
_ramMainRegion02BF[0x03F4 - 0x0200] = (byte)((address >> 8) ^ 0xA5);
|
||||
}
|
||||
|
||||
private static int SetBit7(int data, bool value)
|
||||
{
|
||||
return value ? (data | 0x80) : (data & 0x7F);
|
||||
return value ? data | 0x80 : data & 0x7F;
|
||||
}
|
||||
|
||||
private static bool TestBit(int data, int bit)
|
||||
{
|
||||
return ((data & (0x1 << bit)) != 0x0);
|
||||
return (data & (0x1 << bit)) != 0x0;
|
||||
}
|
||||
|
||||
private static bool TestMask(int data, int mask, int value)
|
||||
{
|
||||
return ((data & mask) == value);
|
||||
return (data & mask) == value;
|
||||
}
|
||||
|
||||
private void ResetState(int mask)
|
||||
|
@ -2097,49 +2061,44 @@ namespace Jellyfish.Virtu
|
|||
|
||||
private bool TestState(int mask)
|
||||
{
|
||||
return ((_state & mask) != 0x0);
|
||||
return (_state & mask) != 0x0;
|
||||
}
|
||||
|
||||
private bool TestState(int mask, bool value)
|
||||
{
|
||||
return (((_state & mask) != 0x0) == value);
|
||||
return ((_state & mask) != 0x0) == value;
|
||||
}
|
||||
|
||||
private bool TestState(int mask, int value)
|
||||
{
|
||||
return ((_state & mask) == value);
|
||||
return (_state & mask) == value;
|
||||
}
|
||||
|
||||
public bool Is80Columns { get { return TestState(State80Col); } }
|
||||
public bool Is80Store { get { return TestState(State80Store); } }
|
||||
public bool IsAnnunciator0 { get { return TestState(StateAn0); } }
|
||||
public bool IsAnnunciator1 { get { return TestState(StateAn1); } }
|
||||
public bool IsAnnunciator2 { get { return TestState(StateAn2); } }
|
||||
public bool IsAnnunciator3 { get { return TestState(StateAn3); } }
|
||||
public bool IsCharSetAlternate { get { return TestState(StateAltChrSet); } }
|
||||
public bool IsDoubleRes { get { return TestState(StateDRes); } }
|
||||
public bool IsHighRamAux { get { return IsZeroPageAux; } }
|
||||
public bool IsHighRamBank1 { get { return TestState(StateBank1); } }
|
||||
public bool IsHighRamRead { get { return TestState(StateHRamRd); } }
|
||||
public bool IsHighRamWrite { get { return !TestState(StateHRamWrt); } } // HRamWrt' [5-23]
|
||||
public bool IsHires { get { return TestState(StateHires); } }
|
||||
public bool IsMixed { get { return TestState(StateMixed); } }
|
||||
public bool IsPage2 { get { return TestState(StatePage2); } }
|
||||
public bool IsRamReadAux { get { return TestState(StateRamRd); } }
|
||||
public bool IsRamReadAuxRegion0407 { get { return Is80Store ? IsPage2 : IsRamReadAux; } }
|
||||
public bool IsRamReadAuxRegion203F { get { return TestState(State80Store | StateHires, State80Store | StateHires) ? IsPage2 : IsRamReadAux; } }
|
||||
public bool IsRamWriteAux { get { return TestState(StateRamWrt); } }
|
||||
public bool IsRamWriteAuxRegion0407 { get { return Is80Store ? IsPage2 : IsRamWriteAux; } }
|
||||
public bool IsRamWriteAuxRegion203F { get { return TestState(State80Store | StateHires, State80Store | StateHires) ? IsPage2 : IsRamWriteAux; } }
|
||||
public bool IsRomC1CFInternal { get { return TestState(StateIntCXRom); } }
|
||||
public bool IsRomC3C3External { get { return TestState(StateSlotC3Rom); } }
|
||||
public bool IsRomC8CFInternal { get { return TestState(StateIntC8Rom); } }
|
||||
public bool IsText { get { return TestState(StateText); } }
|
||||
public bool IsVideoPage2 { get { return TestState(State80Store | StatePage2, StatePage2); } } // 80Store inhibits video Page2 [5-7, 8-19]
|
||||
public bool IsZeroPageAux { get { return TestState(StateAltZP); } }
|
||||
private bool Is80Columns => TestState(State80Col);
|
||||
private bool Is80Store => TestState(State80Store);
|
||||
public bool IsCharSetAlternate => TestState(StateAltChrSet);
|
||||
private bool IsHighRamAux => IsZeroPageAux;
|
||||
private bool IsHighRamBank1 => TestState(StateBank1);
|
||||
private bool IsHighRamRead => TestState(StateHRamRd);
|
||||
private bool IsHighRamWrite => !TestState(StateHRamWrt); // HRamWrt' [5-23]
|
||||
public bool IsHires => TestState(StateHires);
|
||||
public bool IsMixed => TestState(StateMixed);
|
||||
internal bool IsPage2 => TestState(StatePage2);
|
||||
internal bool IsRamReadAux => TestState(StateRamRd);
|
||||
internal bool IsRamReadAuxRegion0407 => Is80Store ? IsPage2 : IsRamReadAux;
|
||||
internal bool IsRamReadAuxRegion203F => TestState(State80Store | StateHires, State80Store | StateHires) ? IsPage2 : IsRamReadAux;
|
||||
internal bool IsRamWriteAux => TestState(StateRamWrt);
|
||||
internal bool IsRamWriteAuxRegion0407 => Is80Store ? IsPage2 : IsRamWriteAux;
|
||||
internal bool IsRamWriteAuxRegion203F => TestState(State80Store | StateHires, State80Store | StateHires) ? IsPage2 : IsRamWriteAux;
|
||||
internal bool IsRomC1CFInternal => TestState(StateIntCXRom);
|
||||
internal bool IsRomC3C3External => TestState(StateSlotC3Rom);
|
||||
internal bool IsRomC8CFInternal => TestState(StateIntC8Rom);
|
||||
public bool IsText => TestState(StateText);
|
||||
public bool IsVideoPage2 => TestState(State80Store | StatePage2, StatePage2); // 80Store inhibits video Page2 [5-7, 8-19]
|
||||
internal bool IsZeroPageAux => TestState(StateAltZP);
|
||||
|
||||
public MonitorType Monitor { get; private set; }
|
||||
public int VideoMode { get { return StateVideoMode[_state & StateVideo]; } }
|
||||
public int VideoMode => StateVideoMode[_state & StateVideo];
|
||||
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeIoRegionC0C0;
|
||||
|
@ -2164,13 +2123,6 @@ namespace Jellyfish.Virtu
|
|||
[JsonIgnore]
|
||||
public Action InputCallback;
|
||||
|
||||
private Keyboard _keyboard;
|
||||
private GamePort _gamePort;
|
||||
private Cassette _cassette;
|
||||
private Speaker _speaker;
|
||||
private Video _video;
|
||||
private NoSlotClock _noSlotClock;
|
||||
|
||||
private int _state;
|
||||
private int _slotRegionC8CF;
|
||||
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public partial class Memory
|
||||
{
|
||||
private const int BankCount = 2;
|
||||
|
||||
private const int BankMain = 0;
|
||||
private const int BankAux = 1;
|
||||
|
||||
private const int RegionCount = 12;
|
||||
|
||||
private const int Region0001 = 0;
|
||||
private const int Region02BF = 1;
|
||||
private const int Region0407 = 2;
|
||||
private const int Region080B = 3;
|
||||
private const int Region203F = 4;
|
||||
private const int Region405F = 5;
|
||||
private const int RegionC0C0 = 6;
|
||||
private const int RegionC1C7 = 7;
|
||||
private const int RegionC3C3 = 8;
|
||||
private const int RegionC8CF = 9;
|
||||
private const int RegionD0DF = 10;
|
||||
private const int RegionE0FF = 11;
|
||||
|
||||
private static readonly int[] RegionBaseAddress = new int[RegionCount]
|
||||
{
|
||||
0x0000, 0x0200, 0x0200, 0x0200, 0x0200, 0x0200, 0xC000, 0xC100, 0xC100, 0xC100, 0xD000, 0xE000
|
||||
};
|
||||
|
||||
private const int PageCount = 256;
|
||||
|
||||
private static readonly int[] PageRegion = new int[PageCount]
|
||||
{
|
||||
Region0001, Region0001, Region02BF, Region02BF, Region0407, Region0407, Region0407, Region0407,
|
||||
Region080B, Region080B, Region080B, Region080B, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F, Region203F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F, Region405F,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF, Region02BF,
|
||||
RegionC0C0, RegionC1C7, RegionC1C7, RegionC3C3, RegionC1C7, RegionC1C7, RegionC1C7, RegionC1C7,
|
||||
RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF, RegionC8CF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF, RegionD0DF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF,
|
||||
RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF, RegionE0FF
|
||||
};
|
||||
|
||||
private const int State80Col = 0x000001;
|
||||
private const int StateText = 0x000002;
|
||||
private const int StateMixed = 0x000004;
|
||||
private const int StateHires = 0x000008;
|
||||
private const int StateDRes = 0x000010;
|
||||
private const int State80Store = 0x000020;
|
||||
private const int StateAltChrSet = 0x000040;
|
||||
private const int StateAltZP = 0x000080;
|
||||
private const int StateBank1 = 0x000100;
|
||||
private const int StateHRamRd = 0x000200;
|
||||
private const int StateHRamPreWrt = 0x000400;
|
||||
private const int StateHRamWrt = 0x000800;
|
||||
private const int StatePage2 = 0x001000;
|
||||
private const int StateRamRd = 0x002000;
|
||||
private const int StateRamWrt = 0x004000;
|
||||
private const int StateSlotC3Rom = 0x008000;
|
||||
private const int StateIntC8Rom = 0x010000; // [5-28]
|
||||
private const int StateIntCXRom = 0x020000;
|
||||
private const int StateAn0 = 0x040000;
|
||||
private const int StateAn1 = 0x080000;
|
||||
private const int StateAn2 = 0x100000;
|
||||
private const int StateAn3 = 0x200000;
|
||||
private const int StateVideo = State80Col | StateText | StateMixed | StateHires | StateDRes;
|
||||
|
||||
private const int StateVideoModeCount = 32;
|
||||
|
||||
private static readonly int[] StateVideoMode = new int[StateVideoModeCount]
|
||||
{
|
||||
Video.Mode0, Video.Mode0, Video.Mode1, Video.Mode2, Video.Mode3, Video.Mode4, Video.Mode1, Video.Mode2,
|
||||
Video.Mode5, Video.Mode5, Video.Mode1, Video.Mode2, Video.Mode6, Video.Mode7, Video.Mode1, Video.Mode2,
|
||||
Video.Mode8, Video.Mode9, Video.Mode1, Video.Mode2, Video.ModeA, Video.ModeB, Video.Mode1, Video.Mode2,
|
||||
Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
private Action<int, byte>[][][] WriteRamModeBankRegion;
|
||||
}
|
||||
}
|
|
@ -1,201 +1,212 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class NoSlotClock : MachineComponent
|
||||
{
|
||||
public interface ISlotClock
|
||||
{
|
||||
int Read(int address, int value);
|
||||
void Write(int address);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed class NoSlotClock : ISlotClock
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private Video _video;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public NoSlotClock() { }
|
||||
public NoSlotClock(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_clockEnabled = false;
|
||||
_writeEnabled = true;
|
||||
_clockRegister = new RingRegister(0x0, 0x1);
|
||||
_comparisonRegister = new RingRegister(ClockInitSequence, 0x1);
|
||||
}
|
||||
public NoSlotClock(Video video)
|
||||
{
|
||||
_video = video;
|
||||
|
||||
public int Read(int address, int data)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
return ReadClock(data);
|
||||
}
|
||||
_clockEnabled = false;
|
||||
_writeEnabled = true;
|
||||
_clockRegister = new RingRegister(0x0, 0x1);
|
||||
_comparisonRegister = new RingRegister(ClockInitSequence, 0x1);
|
||||
}
|
||||
|
||||
WriteClock(address);
|
||||
return data;
|
||||
}
|
||||
public int Read(int address, int value)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
return ReadClock(value);
|
||||
}
|
||||
|
||||
public void Write(int address)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
ReadClock(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteClock(address);
|
||||
}
|
||||
}
|
||||
WriteClock(address);
|
||||
return value;
|
||||
}
|
||||
|
||||
private int ReadClock(int data)
|
||||
{
|
||||
// for a ROM, A2 high = read, and data out (if any) is on D0
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
_comparisonRegister.Reset();
|
||||
_writeEnabled = true;
|
||||
return data;
|
||||
}
|
||||
public void Write(int address)
|
||||
{
|
||||
// this may read or write the clock
|
||||
if ((address & 0x4) != 0)
|
||||
{
|
||||
ReadClock(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteClock(address);
|
||||
}
|
||||
}
|
||||
|
||||
data = _clockRegister.ReadBit(Machine.Video.ReadFloatingBus());
|
||||
if (_clockRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = false;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private int ReadClock(int data)
|
||||
{
|
||||
// for a ROM, A2 high = read, and data out (if any) is on D0
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
_comparisonRegister.Reset();
|
||||
_writeEnabled = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
private void WriteClock(int address)
|
||||
{
|
||||
// for a ROM, A2 low = write, and data in is on A0
|
||||
if (!_writeEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
data = _clockRegister.ReadBit(_video.ReadFloatingBus());
|
||||
if (_clockRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = false;
|
||||
}
|
||||
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
if ((_comparisonRegister.CompareBit(address)))
|
||||
{
|
||||
if (_comparisonRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = true;
|
||||
PopulateClockRegister();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mismatch ignores further writes
|
||||
_writeEnabled = false;
|
||||
}
|
||||
}
|
||||
else if (_clockRegister.NextBit())
|
||||
{
|
||||
// simulate writes, but our clock register is read-only
|
||||
_clockEnabled = false;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private void PopulateClockRegister()
|
||||
{
|
||||
// all values are in packed BCD format (4 bits per decimal digit)
|
||||
var now = DateTime.Now;
|
||||
private void WriteClock(int address)
|
||||
{
|
||||
// for a ROM, A2 low = write, and data in is on A0
|
||||
if (!_writeEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int centisecond = now.Millisecond / 10; // 00-99
|
||||
_clockRegister.WriteNibble(centisecond % 10);
|
||||
_clockRegister.WriteNibble(centisecond / 10);
|
||||
if (!_clockEnabled)
|
||||
{
|
||||
if ((_comparisonRegister.CompareBit(address)))
|
||||
{
|
||||
if (_comparisonRegister.NextBit())
|
||||
{
|
||||
_clockEnabled = true;
|
||||
PopulateClockRegister();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// mismatch ignores further writes
|
||||
_writeEnabled = false;
|
||||
}
|
||||
}
|
||||
else if (_clockRegister.NextBit())
|
||||
{
|
||||
// simulate writes, but our clock register is read-only
|
||||
_clockEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
int second = now.Second; // 00-59
|
||||
_clockRegister.WriteNibble(second % 10);
|
||||
_clockRegister.WriteNibble(second / 10);
|
||||
private void PopulateClockRegister()
|
||||
{
|
||||
// all values are in packed BCD format (4 bits per decimal digit)
|
||||
var now = DateTime.Now;
|
||||
|
||||
int minute = now.Minute; // 00-59
|
||||
_clockRegister.WriteNibble(minute % 10);
|
||||
_clockRegister.WriteNibble(minute / 10);
|
||||
int centisecond = now.Millisecond / 10; // 00-99
|
||||
_clockRegister.WriteNibble(centisecond % 10);
|
||||
_clockRegister.WriteNibble(centisecond / 10);
|
||||
|
||||
int hour = now.Hour; // 01-23
|
||||
_clockRegister.WriteNibble(hour % 10);
|
||||
_clockRegister.WriteNibble(hour / 10);
|
||||
int second = now.Second; // 00-59
|
||||
_clockRegister.WriteNibble(second % 10);
|
||||
_clockRegister.WriteNibble(second / 10);
|
||||
|
||||
int day = (int)now.DayOfWeek + 1; // 01-07 (1 = Sunday)
|
||||
_clockRegister.WriteNibble(day % 10);
|
||||
_clockRegister.WriteNibble(day / 10);
|
||||
int minute = now.Minute; // 00-59
|
||||
_clockRegister.WriteNibble(minute % 10);
|
||||
_clockRegister.WriteNibble(minute / 10);
|
||||
|
||||
int date = now.Day; // 01-31
|
||||
_clockRegister.WriteNibble(date % 10);
|
||||
_clockRegister.WriteNibble(date / 10);
|
||||
int hour = now.Hour; // 01-23
|
||||
_clockRegister.WriteNibble(hour % 10);
|
||||
_clockRegister.WriteNibble(hour / 10);
|
||||
|
||||
int month = now.Month; // 01-12
|
||||
_clockRegister.WriteNibble(month % 10);
|
||||
_clockRegister.WriteNibble(month / 10);
|
||||
int day = (int)now.DayOfWeek + 1; // 01-07 (1 = Sunday)
|
||||
_clockRegister.WriteNibble(day % 10);
|
||||
_clockRegister.WriteNibble(day / 10);
|
||||
|
||||
int year = now.Year % 100; // 00-99
|
||||
_clockRegister.WriteNibble(year % 10);
|
||||
_clockRegister.WriteNibble(year / 10);
|
||||
}
|
||||
int date = now.Day; // 01-31
|
||||
_clockRegister.WriteNibble(date % 10);
|
||||
_clockRegister.WriteNibble(date / 10);
|
||||
|
||||
private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5;
|
||||
int month = now.Month; // 01-12
|
||||
_clockRegister.WriteNibble(month % 10);
|
||||
_clockRegister.WriteNibble(month / 10);
|
||||
|
||||
private bool _clockEnabled;
|
||||
private bool _writeEnabled;
|
||||
private RingRegister _clockRegister;
|
||||
private RingRegister _comparisonRegister;
|
||||
int year = now.Year % 100; // 00-99
|
||||
_clockRegister.WriteNibble(year % 10);
|
||||
_clockRegister.WriteNibble(year / 10);
|
||||
}
|
||||
|
||||
private struct RingRegister
|
||||
{
|
||||
public RingRegister(ulong data, ulong mask)
|
||||
{
|
||||
_data = data;
|
||||
_mask = mask;
|
||||
}
|
||||
private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_mask = 0x1;
|
||||
}
|
||||
private bool _clockEnabled;
|
||||
private bool _writeEnabled;
|
||||
private RingRegister _clockRegister;
|
||||
private RingRegister _comparisonRegister;
|
||||
|
||||
public void WriteNibble(int data)
|
||||
{
|
||||
WriteBits(data, 4);
|
||||
}
|
||||
private struct RingRegister
|
||||
{
|
||||
public RingRegister(ulong data, ulong mask)
|
||||
{
|
||||
_data = data;
|
||||
_mask = mask;
|
||||
}
|
||||
|
||||
public void WriteBits(int data, int count)
|
||||
{
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
WriteBit(data);
|
||||
NextBit();
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
public void Reset()
|
||||
{
|
||||
_mask = 0x1;
|
||||
}
|
||||
|
||||
public void WriteBit(int data)
|
||||
{
|
||||
_data = ((data & 0x1) != 0) ? (_data | _mask) : (_data & ~_mask);
|
||||
}
|
||||
public void WriteNibble(int data)
|
||||
{
|
||||
WriteBits(data, 4);
|
||||
}
|
||||
|
||||
public int ReadBit(int data)
|
||||
{
|
||||
return ((_data & _mask) != 0) ? (data | 0x1) : (data & ~0x1);
|
||||
}
|
||||
private void WriteBits(int data, int count)
|
||||
{
|
||||
for (int i = 1; i <= count; i++)
|
||||
{
|
||||
WriteBit(data);
|
||||
NextBit();
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompareBit(int data)
|
||||
{
|
||||
return (((_data & _mask) != 0) == ((data & 0x1) != 0));
|
||||
}
|
||||
private void WriteBit(int data)
|
||||
{
|
||||
_data = (data & 0x1) != 0
|
||||
? _data | _mask
|
||||
: _data & ~_mask;
|
||||
}
|
||||
|
||||
public bool NextBit()
|
||||
{
|
||||
if ((_mask <<= 1) == 0)
|
||||
{
|
||||
_mask = 0x1;
|
||||
return true; // wrap
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public int ReadBit(int data)
|
||||
{
|
||||
return (_data & _mask) != 0
|
||||
? data | 0x1
|
||||
: data & ~0x1;
|
||||
}
|
||||
|
||||
public ulong Data { get { return _data; } } // no auto props
|
||||
public ulong Mask { get { return _mask; } }
|
||||
public bool CompareBit(int data)
|
||||
{
|
||||
return (_data & _mask) != 0 == ((data & 0x1) != 0);
|
||||
}
|
||||
|
||||
private ulong _data;
|
||||
private ulong _mask;
|
||||
}
|
||||
}
|
||||
public bool NextBit()
|
||||
{
|
||||
if ((_mask <<= 1) == 0)
|
||||
{
|
||||
_mask = 0x1;
|
||||
return true; // wrap
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ulong _data;
|
||||
private ulong _mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public class PeripheralCard : MachineComponent
|
||||
{
|
||||
public PeripheralCard() { }
|
||||
public PeripheralCard(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual int ReadIoRegionC0C0(int address)
|
||||
{
|
||||
// read Device Select' address $C0nX; n = slot number + 8
|
||||
return ReadFloatingBus();
|
||||
}
|
||||
|
||||
public virtual int ReadIoRegionC1C7(int address)
|
||||
{
|
||||
// read I/O Select' address $CsXX; s = slot number
|
||||
return ReadFloatingBus();
|
||||
}
|
||||
|
||||
public virtual int ReadIoRegionC8CF(int address)
|
||||
{
|
||||
// read I/O Strobe' address $C800-$CFFF
|
||||
return ReadFloatingBus();
|
||||
}
|
||||
|
||||
public virtual void WriteIoRegionC0C0(int address, int data)
|
||||
{
|
||||
// write Device Select' address $C0nX; n = slot number + 8
|
||||
}
|
||||
|
||||
public virtual void WriteIoRegionC1C7(int address, int data)
|
||||
{
|
||||
// write I/O Select' address $CsXX; s = slot number
|
||||
}
|
||||
|
||||
public virtual void WriteIoRegionC8CF(int address, int data)
|
||||
{
|
||||
// write I/O Strobe' address $C800-$CFFF
|
||||
}
|
||||
|
||||
protected int ReadFloatingBus()
|
||||
{
|
||||
return Machine.Video.ReadFloatingBus();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// this isn't really a "service" anymore, just a helper for the speaker class
|
||||
/// </summary>
|
||||
public class AudioService
|
||||
{
|
||||
public AudioService() { }
|
||||
|
||||
public void Output(int data) // machine thread
|
||||
{
|
||||
data = (int)(data * 0.2);
|
||||
if (pos < buff.Length - 2)
|
||||
{
|
||||
buff[pos++] = (short)data;
|
||||
buff[pos++] = (short)data;
|
||||
}
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore] // only relevant if trying to savestate midframe
|
||||
private short[] buff = new short[4096];
|
||||
[Newtonsoft.Json.JsonIgnore] // only relevant if trying to savestate midframe
|
||||
private int pos = 0;
|
||||
|
||||
[System.Runtime.Serialization.OnDeserialized]
|
||||
public void OnDeserialized(System.Runtime.Serialization.StreamingContext context)
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
public void GetSamples(out short[] samples, out int nsamp)
|
||||
{
|
||||
samples = buff;
|
||||
nsamp = pos / 2;
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Jellyfish.Library;
|
||||
|
||||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// this isn't really a "service" anymore
|
||||
/// </summary>
|
||||
public static class DebugService
|
||||
{
|
||||
public static void WriteMessage(string message)
|
||||
{
|
||||
OnWriteMessage(FormatMessage(message));
|
||||
}
|
||||
|
||||
public static void WriteMessage(string format, params object[] args)
|
||||
{
|
||||
OnWriteMessage(FormatMessage(format, args));
|
||||
}
|
||||
|
||||
private static void OnWriteMessage(string message)
|
||||
{
|
||||
#if SILVERLIGHT
|
||||
Debug.WriteLine(message);
|
||||
#else
|
||||
Trace.WriteLine(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string FormatMessage(string format, params object[] args)
|
||||
{
|
||||
var message = new StringBuilder(256);
|
||||
message.AppendFormat(CultureInfo.InvariantCulture, "[{0} T{1:X3} Virtu] ", DateTime.Now.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture), Thread.CurrentThread.ManagedThreadId);
|
||||
if (args.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
message.AppendFormat(CultureInfo.InvariantCulture, format, args);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
WriteMessage("[DebugService.FormatMessage] format: {0}; args: {1}; exception: {2}", format, string.Join(", ", args), ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
message.Append(format);
|
||||
}
|
||||
|
||||
return message.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
namespace Jellyfish.Virtu.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// this isn't really a "service" anymore, just a helper for the video class
|
||||
/// </summary>
|
||||
public class VideoService
|
||||
{
|
||||
public VideoService()
|
||||
{
|
||||
fb = new int[560 * 384];
|
||||
}
|
||||
public VideoService(int[] fb)
|
||||
{
|
||||
this.fb = fb;
|
||||
}
|
||||
|
||||
[Newtonsoft.Json.JsonIgnore] // client can serialize framebuffer if it wants to
|
||||
public int[] fb;
|
||||
|
||||
public void SetPixel(int x, int y, int color)
|
||||
{
|
||||
int i = 560 * y + x;
|
||||
fb[i] = fb[i + 560] = color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +1,115 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Jellyfish.Virtu.Services;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class Speaker : MachineComponent
|
||||
{
|
||||
public interface ISpeaker
|
||||
{
|
||||
void ToggleOutput();
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Clear();
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void GetSamples(out short[] samples, out int nSamp);
|
||||
}
|
||||
|
||||
public sealed class Speaker : ISpeaker
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private MachineEvents _events;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private ICpu _cpu;
|
||||
|
||||
public Speaker() { }
|
||||
public Speaker(Machine machine) :
|
||||
base(machine)
|
||||
{
|
||||
_flushOutputEvent = FlushOutputEvent; // cache delegates; avoids garbage
|
||||
}
|
||||
public Speaker(MachineEvents events, ICpu cpu)
|
||||
{
|
||||
_events = events;
|
||||
_cpu = cpu;
|
||||
_flushOutputEvent = FlushOutputEvent; // cache delegates; avoids garbage
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
AudioService = new Services.AudioService();
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent);
|
||||
|
||||
Machine.Events.AddEvent(CyclesPerFlush * Machine.Cpu.Multiplier, _flushOutputEvent);
|
||||
}
|
||||
_isHigh = false;
|
||||
_highCycles = _totalCycles = 0;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_isHigh = false;
|
||||
_highCycles = _totalCycles = 0;
|
||||
}
|
||||
private const int CyclesPerFlush = 23;
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
UpdateCycles();
|
||||
_isHigh ^= true;
|
||||
}
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private Action _flushOutputEvent;
|
||||
|
||||
private void FlushOutputEvent()
|
||||
{
|
||||
UpdateCycles();
|
||||
private bool _isHigh;
|
||||
private int _highCycles;
|
||||
private int _totalCycles;
|
||||
private long _lastCycles;
|
||||
|
||||
[JsonIgnore] // only relevant if trying to savestate mid-frame
|
||||
private readonly short[] _buffer = new short[4096];
|
||||
|
||||
[JsonIgnore] // only relevant if trying to savestate mid-frame
|
||||
private int _position;
|
||||
|
||||
#region Api
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
public void GetSamples(out short[] samples, out int nSamp)
|
||||
{
|
||||
samples = _buffer;
|
||||
nSamp = _position / 2;
|
||||
_position = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
UpdateCycles();
|
||||
_isHigh ^= true;
|
||||
}
|
||||
|
||||
private void FlushOutputEvent()
|
||||
{
|
||||
UpdateCycles();
|
||||
// TODO: better than simple decimation here!!
|
||||
AudioService.Output(_highCycles * short.MaxValue / _totalCycles);
|
||||
_highCycles = _totalCycles = 0;
|
||||
Output(_highCycles * short.MaxValue / _totalCycles);
|
||||
_highCycles = _totalCycles = 0;
|
||||
|
||||
Machine.Events.AddEvent(CyclesPerFlush * Machine.Cpu.Multiplier, _flushOutputEvent);
|
||||
}
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent);
|
||||
}
|
||||
|
||||
private void UpdateCycles()
|
||||
{
|
||||
int delta = (int)(Machine.Cpu.Cycles - _lastCycles);
|
||||
if (_isHigh)
|
||||
{
|
||||
_highCycles += delta;
|
||||
}
|
||||
_totalCycles += delta;
|
||||
_lastCycles = Machine.Cpu.Cycles;
|
||||
}
|
||||
private void UpdateCycles()
|
||||
{
|
||||
int delta = (int)(_cpu.Cycles - _lastCycles);
|
||||
if (_isHigh)
|
||||
{
|
||||
_highCycles += delta;
|
||||
}
|
||||
_totalCycles += delta;
|
||||
_lastCycles = _cpu.Cycles;
|
||||
}
|
||||
|
||||
private const int CyclesPerFlush = 23;
|
||||
private void Output(int data) // machine thread
|
||||
{
|
||||
data = (int)(data * 0.2);
|
||||
if (_position < _buffer.Length - 2)
|
||||
{
|
||||
_buffer[_position++] = (short)data;
|
||||
_buffer[_position++] = (short)data;
|
||||
}
|
||||
}
|
||||
|
||||
private Action _flushOutputEvent;
|
||||
|
||||
private bool _isHigh;
|
||||
private int _highCycles;
|
||||
private int _totalCycles;
|
||||
private long _lastCycles;
|
||||
|
||||
public AudioService AudioService { get; private set; }
|
||||
}
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
_position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Virtu</RootNamespace>
|
||||
<AssemblyName>Virtu</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
|
@ -45,35 +45,27 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Cassette.cs" />
|
||||
<Compile Include="ICassette.cs" />
|
||||
<Compile Include="Cpu.cs" />
|
||||
<Compile Include="CpuData.cs" />
|
||||
<Compile Include="ExtraConverters.cs" />
|
||||
<Compile Include="Cpu.Data.cs" DependentUpon="Cpu.cs" />
|
||||
<Compile Include="Disk525.cs" />
|
||||
<Compile Include="DiskDsk.cs" />
|
||||
<Compile Include="DiskIIController.cs" />
|
||||
<Compile Include="DiskIIDrive.cs" />
|
||||
<Compile Include="DiskNib.cs" />
|
||||
<Compile Include="GamePort.cs" />
|
||||
<Compile Include="IGamePort.cs" />
|
||||
<Compile Include="Keyboard.cs" />
|
||||
<Compile Include="Library\DisposableBase.cs" />
|
||||
<Compile Include="Library\MathHelpers.cs" />
|
||||
<Compile Include="Library\StreamExtensions.cs" />
|
||||
<Compile Include="Machine.cs" />
|
||||
<Compile Include="MachineComponent.cs" />
|
||||
<Compile Include="MachineEvents.cs" />
|
||||
<Compile Include="Memory.cs" />
|
||||
<Compile Include="MemoryData.cs" />
|
||||
<Compile Include="Memory.Data.cs" DependentUpon="Memory.cs" />
|
||||
<Compile Include="NoSlotClock.cs" />
|
||||
<Compile Include="PeripheralCard.cs" />
|
||||
<Compile Include="IPeripheralCard.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\AudioService.cs" />
|
||||
<Compile Include="Services\DebugService.cs" />
|
||||
<Compile Include="Services\VideoService.cs" />
|
||||
<Compile Include="Speaker.cs" />
|
||||
<Compile Include="Video.cs" />
|
||||
<Compile Include="VideoData.cs" />
|
||||
<Compile Include="Video.Data.cs" DependentUpon="Video.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\references\$(TargetFileName)"</PostBuildEvent>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp80</s:String></wpf:ResourceDictionary>
|
|
@ -0,0 +1,79 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AA/@EntryIndexedValue">AA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AB/@EntryIndexedValue">AB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AC/@EntryIndexedValue">AC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AD/@EntryIndexedValue">AD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AE/@EntryIndexedValue">AE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AF/@EntryIndexedValue">AF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BA/@EntryIndexedValue">BA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BB/@EntryIndexedValue">BB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BC/@EntryIndexedValue">BC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BD/@EntryIndexedValue">BD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BF/@EntryIndexedValue">BF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CA/@EntryIndexedValue">CA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CB/@EntryIndexedValue">CB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CC/@EntryIndexedValue">CC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CD/@EntryIndexedValue">CD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CE/@EntryIndexedValue">CE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CF/@EntryIndexedValue">CF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DA/@EntryIndexedValue">DA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DC/@EntryIndexedValue">DC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DD/@EntryIndexedValue">DD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DE/@EntryIndexedValue">DE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DF/@EntryIndexedValue">DF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EA/@EntryIndexedValue">EA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EB/@EntryIndexedValue">EB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EC/@EntryIndexedValue">EC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ED/@EntryIndexedValue">ED</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EE/@EntryIndexedValue">EE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=EF/@EntryIndexedValue">EF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FA/@EntryIndexedValue">FA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FB/@EntryIndexedValue">FB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FC/@EntryIndexedValue">FC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FD/@EntryIndexedValue">FD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FE/@EntryIndexedValue">FE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FF/@EntryIndexedValue">FF</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=II/@EntryIndexedValue">II</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ND/@EntryIndexedValue">ND</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RA/@EntryIndexedValue">RA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RP/@EntryIndexedValue">RP</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RPC/@EntryIndexedValue">RPC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RS/@EntryIndexedValue">RS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RX/@EntryIndexedValue">RX</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RY/@EntryIndexedValue">RY</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XCC/@EntryIndexedValue">XCC</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=YCC/@EntryIndexedValue">YCC</s:String>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Annunciator/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=backrefs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=BGRA/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=centisecond/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=CFFF/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=datasheet/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=dhires/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=dlores/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=framebuffer/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hblank/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=hcount/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Inversed/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lores/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lores_000D/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mlores/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ndhires/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=nibblize/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Ntsc/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Opcode/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=opcodes/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=SAMESLOT/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Samp/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=savestate/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uninitialize/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uninitializing/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=vblank/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=vcount/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Virtu/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=vline/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=vsync/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=_003B_0020_002F_002F/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net461" />
|
||||
</packages>
|
Binary file not shown.
Loading…
Reference in New Issue