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:
adelikat 2020-02-14 09:54:35 -06:00 committed by GitHub
parent 758a3e0f15
commit b3a80965af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 5099 additions and 5731 deletions

View File

@ -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" />

View File

@ -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;
}
}

View File

@ -32,7 +32,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
public void Dispose()
{
_machine.Dispose();
}
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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:

View File

@ -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) =>

View File

@ -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; }
}
}

View File

@ -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()
{
}
}
}

View File

@ -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) { }
}
}

View File

@ -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() { }
}
}

View File

@ -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;
}
}
}
}

View File

@ -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();

View File

@ -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()
{
}
}
}

View File

@ -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

View File

@ -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
};
}
}

View File

@ -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;
}
}

View File

@ -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
};
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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;*/
}
}

View File

@ -0,0 +1,8 @@
namespace Jellyfish.Virtu
{
public interface ICassette
{
bool ReadInput();
void ToggleOutput();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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?
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
}
}
}

View File

@ -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];
}
}

View File

@ -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 }
};
}
}
}

View File

@ -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; }
}
}
}

View File

@ -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>();
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.