Apple II - refactor states to use Serializer (#1859)
* apple II - poc to redo binary states to use the Serializer and pass it into virtu * virtu - savestate logic for Video * AppleII - serialize more components with AppleSerializer * cleanup * refactor MachineEvents to store delegates to call and have the calling code interact with an enum, prep work for serializing this class * apple II - first attempt at serializing Machine events * cleanup * add isvblank to savestates * put more things in savestates, cleanup * cleanup * cleanup, save more things * cleanup, fixes * cleanup, save more things * virtu - savestate fixes * cleanup * save CurrentDisk in savestates
This commit is contained in:
parent
9831ca59bd
commit
33ad336b6a
|
@ -8,7 +8,8 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
public ControllerDefinition ControllerDefinition => AppleIIController;
|
||||
|
||||
public int Frame { get; private set; }
|
||||
private int _frame;
|
||||
public int Frame { get => _frame; set => _frame = value; }
|
||||
|
||||
public string SystemId => "AppleII";
|
||||
|
||||
|
|
|
@ -4,7 +4,12 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
{
|
||||
public partial class AppleII : IInputPollable
|
||||
{
|
||||
public int LagCount { get; set; }
|
||||
private int _lagcount;
|
||||
public int LagCount
|
||||
{
|
||||
get => _lagcount;
|
||||
set => _lagcount = value;
|
||||
}
|
||||
|
||||
public bool IsLagFrame
|
||||
{
|
||||
|
|
|
@ -1,181 +1,97 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using System.IO;
|
||||
using BizHawk.Common;
|
||||
using BizHawk.Emulation.Common;
|
||||
using Jellyfish.Virtu;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
public partial class AppleII : ITextStatable
|
||||
{
|
||||
private class CoreConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(Components);
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
return CreateSerializer().Deserialize<Components>(reader);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void SerializeEverything(JsonWriter w)
|
||||
{
|
||||
// this is much faster than other possibilities for serialization
|
||||
w.WriteStartObject();
|
||||
w.WritePropertyName(nameof(Frame));
|
||||
w.WriteValue(Frame);
|
||||
w.WritePropertyName(nameof(LagCount));
|
||||
w.WriteValue(LagCount);
|
||||
w.WritePropertyName(nameof(IsLagFrame));
|
||||
w.WriteValue(IsLagFrame);
|
||||
w.WritePropertyName(nameof(CurrentDisk));
|
||||
w.WriteValue(CurrentDisk);
|
||||
w.WritePropertyName("PreviousDiskPressed");
|
||||
w.WriteValue(_prevPressed);
|
||||
w.WritePropertyName("NextDiskPressed");
|
||||
w.WriteValue(_nextPressed);
|
||||
w.WritePropertyName("Core");
|
||||
CreateSerializer().Serialize(w, _machine);
|
||||
w.WriteEndObject();
|
||||
}
|
||||
|
||||
private void DeserializeEverything(JsonReader r)
|
||||
{
|
||||
var o = (OtherData)_ser.Deserialize(r, typeof(OtherData));
|
||||
Frame = o.Frame;
|
||||
LagCount = o.LagCount;
|
||||
IsLagFrame = o.IsLagFrame;
|
||||
CurrentDisk = o.CurrentDisk;
|
||||
_machine = o.Core;
|
||||
_prevPressed = o.PreviousDiskPressed;
|
||||
_nextPressed = o.NextDiskPressed;
|
||||
|
||||
// since _machine was replaced, we need to reload settings from frontend
|
||||
PutSettings(_settings);
|
||||
}
|
||||
|
||||
public class OtherData
|
||||
{
|
||||
public int Frame;
|
||||
public int LagCount;
|
||||
public bool IsLagFrame;
|
||||
public int CurrentDisk;
|
||||
public bool PreviousDiskPressed;
|
||||
public bool NextDiskPressed;
|
||||
public Components Core;
|
||||
}
|
||||
|
||||
private void InitSaveStates()
|
||||
{
|
||||
_ser.Converters.Add(new CoreConverter());
|
||||
}
|
||||
|
||||
private readonly JsonSerializer _ser = new JsonSerializer();
|
||||
|
||||
public void SaveStateText(TextWriter writer)
|
||||
{
|
||||
SerializeEverything(new JsonTextWriter(writer) { Formatting = Formatting.None });
|
||||
SyncState(new AppleSerializer(writer));
|
||||
}
|
||||
|
||||
public void LoadStateText(TextReader reader)
|
||||
{
|
||||
DeserializeEverything(new JsonTextReader(reader));
|
||||
SyncState(new AppleSerializer(reader));
|
||||
}
|
||||
|
||||
/*
|
||||
* These are horrible; the LoadStateBinary() takes over 10x as long as LoadStateText()
|
||||
* Until we figure out why JSON.NET's BSONwriter sucks and how to fix it, stick with text-as-binary
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
SerializeEverything(new BsonWriter(writer));
|
||||
SyncState(new AppleSerializer(writer));
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
DeserializeEverything(new BsonReader(reader));
|
||||
}
|
||||
*/
|
||||
/*
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
var tw = new StreamWriter(writer.BaseStream, new System.Text.UTF8Encoding(false));
|
||||
SaveStateText(tw);
|
||||
tw.Flush();
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
var tr = new StreamReader(reader.BaseStream, System.Text.Encoding.UTF8);
|
||||
LoadStateText(tr);
|
||||
}*/
|
||||
|
||||
// these homemade classes edge out the stock ones slightly, but need BufferedStream to not be bad
|
||||
public void SaveStateBinary(BinaryWriter writer)
|
||||
{
|
||||
var buffer = new BufferedStream(writer.BaseStream, 16384);
|
||||
var bw2 = new BinaryWriter(buffer);
|
||||
SerializeEverything(new LBW(bw2));
|
||||
bw2.Flush();
|
||||
buffer.Flush();
|
||||
}
|
||||
|
||||
public void LoadStateBinary(BinaryReader reader)
|
||||
{
|
||||
var buffer = new BufferedStream(reader.BaseStream, 16384);
|
||||
var br2 = new BinaryReader(buffer);
|
||||
DeserializeEverything(new LBR(br2));
|
||||
SyncState(new AppleSerializer(reader));
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
// our savestate array can be of varying sizes, so this can't be too clever
|
||||
var stream = new MemoryStream();
|
||||
var writer = new BinaryWriter(stream);
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream);
|
||||
SaveStateBinary(writer);
|
||||
writer.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static JsonSerializer CreateSerializer()
|
||||
private void SyncState(AppleSerializer ser)
|
||||
{
|
||||
// TODO: converters could be cached for speedup
|
||||
int version = 2;
|
||||
ser.BeginSection(nameof(AppleII));
|
||||
ser.Sync(nameof(version), ref version);
|
||||
ser.Sync("Frame", ref _frame);
|
||||
ser.Sync("Lag", ref _lagcount);
|
||||
ser.Sync("PrevDiskPressed", ref _prevPressed);
|
||||
ser.Sync("NextDiskPressed", ref _nextPressed);
|
||||
ser.Sync("CurrentDisk", ref _currentDisk);
|
||||
|
||||
var ser = new JsonSerializer
|
||||
ser.BeginSection("Events");
|
||||
_machine.Events.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.BeginSection("Cpu");
|
||||
_machine.Cpu.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.BeginSection("Video");
|
||||
_machine.Video.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.BeginSection("Memory");
|
||||
_machine.Memory.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.BeginSection("NoSlotClock");
|
||||
_machine.NoSlotClock.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.BeginSection("DiskIIController");
|
||||
_machine.DiskIIController.Sync(ser);
|
||||
ser.EndSection();
|
||||
|
||||
ser.EndSection();
|
||||
}
|
||||
|
||||
public class AppleSerializer : Serializer, IComponentSerializer
|
||||
{
|
||||
public AppleSerializer(BinaryReader br) : base(br)
|
||||
{
|
||||
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[]
|
||||
public AppleSerializer(BinaryWriter bw) : base(bw)
|
||||
{
|
||||
// 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());
|
||||
public AppleSerializer(TextReader tr) : base(tr)
|
||||
{
|
||||
}
|
||||
|
||||
var cr = new DefaultContractResolver();
|
||||
cr.DefaultMembersSearchFlags |= System.Reflection.BindingFlags.NonPublic;
|
||||
ser.ContractResolver = cr;
|
||||
|
||||
return ser;
|
||||
public AppleSerializer(TextWriter tw) : base(tw)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
|
||||
SetCallbacks();
|
||||
|
||||
InitSaveStates();
|
||||
SetupMemoryDomains();
|
||||
PutSettings(settings ?? new Settings());
|
||||
}
|
||||
|
@ -72,7 +71,14 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
private readonly byte[] _appleIIRom;
|
||||
private readonly byte[] _diskIIRom;
|
||||
|
||||
public int CurrentDisk { get; private set; }
|
||||
private int _currentDisk;
|
||||
|
||||
public int CurrentDisk
|
||||
{
|
||||
get => _currentDisk;
|
||||
set => _currentDisk = value;
|
||||
}
|
||||
|
||||
public int DiskCount => _romSet.Count;
|
||||
|
||||
public void SetDisk(int discNum)
|
||||
|
|
|
@ -7,11 +7,6 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
/// </summary>
|
||||
public sealed class Components
|
||||
{
|
||||
/// <summary>
|
||||
/// for deserialization only!!
|
||||
/// </summary>
|
||||
public Components() { }
|
||||
|
||||
public Components(byte[] appleIIe, byte[] diskIIRom)
|
||||
{
|
||||
Events = new MachineEvents();
|
||||
|
@ -19,6 +14,9 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
Cpu = new Cpu(Memory);
|
||||
Video = new Video(Events, Memory);
|
||||
|
||||
NoSlotClock = new NoSlotClock(Video);
|
||||
DiskIIController = new DiskIIController(Video, diskIIRom);
|
||||
|
||||
var emptySlot = new EmptyPeripheralCard(Video);
|
||||
|
||||
// Necessary because of tangling dependencies between memory and video classes
|
||||
|
@ -28,7 +26,7 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
new EmptyCassetteComponent(),
|
||||
new Speaker(Events, Cpu),
|
||||
Video,
|
||||
new NoSlotClock(Video),
|
||||
NoSlotClock,
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
emptySlot,
|
||||
|
@ -42,9 +40,13 @@ namespace BizHawk.Emulation.Cores.Computers.AppleII
|
|||
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; }
|
||||
public MachineEvents Events { get; }
|
||||
public Memory Memory { get; }
|
||||
public Cpu Cpu { get; }
|
||||
public Video Video { get; }
|
||||
|
||||
// Only needed for convenience of savestate syncing, else the memory component needs to do it
|
||||
public NoSlotClock NoSlotClock { get; }
|
||||
public DiskIIController DiskIIController { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,334 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,238 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Computers.AppleII
|
||||
{
|
||||
// barebones classes for writing and reading a simple bson-like format, used to gain a bit of speed in Apple II savestates
|
||||
internal enum LBTOK : byte
|
||||
{
|
||||
Null,
|
||||
Undefined,
|
||||
StartArray,
|
||||
EndArray,
|
||||
StartObject,
|
||||
EndObject,
|
||||
Property,
|
||||
S8,
|
||||
U8,
|
||||
S16,
|
||||
U16,
|
||||
S32,
|
||||
U32,
|
||||
S64,
|
||||
U64,
|
||||
False,
|
||||
True,
|
||||
String,
|
||||
F32,
|
||||
F64,
|
||||
ByteArray,
|
||||
}
|
||||
|
||||
public class LBR : JsonReader
|
||||
{
|
||||
private readonly BinaryReader _r;
|
||||
private object _v;
|
||||
private JsonToken _t;
|
||||
|
||||
public LBR(BinaryReader reader)
|
||||
{
|
||||
_r = reader;
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
}
|
||||
|
||||
// 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 => throw new NotImplementedException();
|
||||
|
||||
public override Type ValueType => _v?.GetType();
|
||||
|
||||
public override JsonToken TokenType => _t;
|
||||
|
||||
public override object Value => _v;
|
||||
|
||||
public override bool Read()
|
||||
{
|
||||
LBTOK l = (LBTOK)_r.ReadByte();
|
||||
switch (l)
|
||||
{
|
||||
case LBTOK.StartArray: _t = JsonToken.StartArray; _v = null; break;
|
||||
case LBTOK.EndArray: _t = JsonToken.EndArray; _v = null; break;
|
||||
case LBTOK.StartObject: _t = JsonToken.StartObject; _v = null; break;
|
||||
case LBTOK.EndObject: _t = JsonToken.EndObject; _v = null; break;
|
||||
case LBTOK.Null: _t = JsonToken.Null; _v = null; break;
|
||||
case LBTOK.False: _t = JsonToken.Boolean; _v = false; break;
|
||||
case LBTOK.True: _t = JsonToken.Boolean; _v = true; break;
|
||||
case LBTOK.Property: _t = JsonToken.PropertyName; _v = _r.ReadString(); break;
|
||||
case LBTOK.Undefined: _t = JsonToken.Undefined; _v = null; break;
|
||||
case LBTOK.S8: _t = JsonToken.Integer; _v = _r.ReadSByte(); break;
|
||||
case LBTOK.U8: _t = JsonToken.Integer; _v = _r.ReadByte(); break;
|
||||
case LBTOK.S16: _t = JsonToken.Integer; _v = _r.ReadInt16(); break;
|
||||
case LBTOK.U16: _t = JsonToken.Integer; _v = _r.ReadUInt16(); break;
|
||||
case LBTOK.S32: _t = JsonToken.Integer; _v = _r.ReadInt32(); break;
|
||||
case LBTOK.U32: _t = JsonToken.Integer; _v = _r.ReadUInt32(); break;
|
||||
case LBTOK.S64: _t = JsonToken.Integer; _v = _r.ReadInt64(); break;
|
||||
case LBTOK.U64: _t = JsonToken.Integer; _v = _r.ReadUInt64(); break;
|
||||
case LBTOK.String: _t = JsonToken.String; _v = _r.ReadString(); break;
|
||||
case LBTOK.F32: _t = JsonToken.Float; _v = _r.ReadSingle(); break;
|
||||
case LBTOK.F64: _t = JsonToken.Float; _v = _r.ReadDouble(); break;
|
||||
case LBTOK.ByteArray: _t = JsonToken.Bytes; _v = _r.ReadBytes(_r.ReadInt32()); break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override byte[] ReadAsBytes()
|
||||
{
|
||||
if (!Read() || _t != JsonToken.Bytes)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (byte[])_v;
|
||||
}
|
||||
|
||||
public override DateTime? ReadAsDateTime()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override DateTimeOffset? ReadAsDateTimeOffset()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override decimal? ReadAsDecimal()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int? ReadAsInt32()
|
||||
{
|
||||
// TODO: speed this up if needed
|
||||
if (!Read())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (_t)
|
||||
{
|
||||
case JsonToken.Null:
|
||||
return null;
|
||||
case JsonToken.Integer:
|
||||
case JsonToken.Float:
|
||||
return Convert.ToInt32(_v);
|
||||
case JsonToken.String:
|
||||
if (int.TryParse(_v.ToString(), out var i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ReadAsString()
|
||||
{
|
||||
if (!Read())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (_t)
|
||||
{
|
||||
case JsonToken.Null:
|
||||
return null;
|
||||
case JsonToken.Float:
|
||||
case JsonToken.Integer:
|
||||
case JsonToken.Boolean:
|
||||
case JsonToken.String:
|
||||
return _v.ToString();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class LBW : JsonWriter
|
||||
{
|
||||
private readonly BinaryWriter w;
|
||||
|
||||
private void WT(LBTOK t)
|
||||
{
|
||||
w.Write((byte)t);
|
||||
}
|
||||
|
||||
public LBW(BinaryWriter w)
|
||||
{
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
w.Flush();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
}
|
||||
|
||||
public override void WriteValue(bool value) { WT(value ? LBTOK.True : LBTOK.False); }
|
||||
|
||||
public override void WriteValue(sbyte value) { WT(LBTOK.S8); w.Write(value); }
|
||||
public override void WriteValue(byte value) { WT(LBTOK.U8); w.Write(value); }
|
||||
public override void WriteValue(short value) { WT(LBTOK.S16); w.Write(value); }
|
||||
public override void WriteValue(ushort value) { WT(LBTOK.U16); w.Write(value); }
|
||||
public override void WriteValue(int value) { WT(LBTOK.S32); w.Write(value); }
|
||||
public override void WriteValue(uint value) { WT(LBTOK.U32); w.Write(value); }
|
||||
public override void WriteValue(long value) { WT(LBTOK.S64); w.Write(value); }
|
||||
public override void WriteValue(ulong value) { WT(LBTOK.U64); w.Write(value); }
|
||||
|
||||
public override void WriteStartArray() { WT(LBTOK.StartArray); }
|
||||
public override void WriteEndArray() { WT(LBTOK.EndArray); }
|
||||
public override void WriteStartObject() { WT(LBTOK.StartObject); }
|
||||
public override void WriteEndObject() { WT(LBTOK.EndObject); }
|
||||
public override void WriteNull() { WT(LBTOK.Null); }
|
||||
public override void WriteUndefined() { WT(LBTOK.Undefined); }
|
||||
|
||||
public override void WriteValue(float value) { WT(LBTOK.F32); w.Write(value); }
|
||||
public override void WriteValue(double value) { WT(LBTOK.F64); w.Write(value); }
|
||||
|
||||
public override void WriteValue(byte[] value) { WT(LBTOK.ByteArray); w.Write(value.Length); w.Write(value); }
|
||||
|
||||
public override void WriteComment(string text) { throw new NotImplementedException(); }
|
||||
public override void WriteWhitespace(string ws) { throw new NotImplementedException(); }
|
||||
protected override void WriteIndent() { throw new NotImplementedException(); }
|
||||
protected override void WriteIndentSpace() { throw new NotImplementedException(); }
|
||||
public override void WriteEnd() { throw new NotImplementedException(); }
|
||||
protected override void WriteEnd(JsonToken token) { throw new NotImplementedException(); }
|
||||
public override void WriteRaw(string json) { throw new NotImplementedException(); }
|
||||
public override void WriteRawValue(string json) { throw new NotImplementedException(); }
|
||||
public override void WriteStartConstructor(string name) { throw new NotImplementedException(); }
|
||||
public override void WriteEndConstructor() { throw new NotImplementedException(); }
|
||||
protected override void WriteValueDelimiter() { throw new NotImplementedException(); }
|
||||
|
||||
public override void WritePropertyName(string name) { WT(LBTOK.Property); w.Write(name); }
|
||||
public override void WriteValue(string value) { WT(LBTOK.String); w.Write(value); }
|
||||
public override void WritePropertyName(string name, bool escape) { WT(LBTOK.Property); w.Write(name); } // no escaping required
|
||||
|
||||
public override void WriteValue(char value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(DateTime value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(DateTimeOffset value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(decimal value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(Guid value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(TimeSpan value) { throw new NotImplementedException(); }
|
||||
public override void WriteValue(Uri value) { throw new NotImplementedException(); }
|
||||
}
|
||||
}
|
|
@ -1,15 +1,50 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public partial class Cpu
|
||||
{
|
||||
[JsonIgnore]
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_is65C02), ref _is65C02);
|
||||
ser.Sync(nameof(_multiplier), ref _multiplier);
|
||||
|
||||
ser.Sync(nameof(_ra), ref _ra);
|
||||
ser.Sync(nameof(_rx), ref _rx);
|
||||
ser.Sync(nameof(_ry), ref _ry);
|
||||
ser.Sync(nameof(_rs), ref _rs);
|
||||
ser.Sync(nameof(_rp), ref _rp);
|
||||
ser.Sync(nameof(_rpc), ref _rpc);
|
||||
ser.Sync(nameof(_ea), ref _ea);
|
||||
ser.Sync(nameof(_cc), ref _cc);
|
||||
ser.Sync(nameof(_opCode), ref _opCode);
|
||||
ser.Sync(nameof(_cycles), ref _cycles);
|
||||
|
||||
if (!ser.IsReader)
|
||||
{
|
||||
// A way to set the action callback
|
||||
Is65C02 = _is65C02;
|
||||
}
|
||||
}
|
||||
|
||||
private Action[] _executeOpCode65N02;
|
||||
[JsonIgnore]
|
||||
private Action[] _executeOpCode65C02;
|
||||
private Action[] _executeOpCode;
|
||||
|
||||
private bool _is65C02;
|
||||
private int _multiplier;
|
||||
|
||||
private int _ra;
|
||||
private int _rx;
|
||||
private int _ry;
|
||||
private int _rs;
|
||||
private int _rp;
|
||||
private int _rpc;
|
||||
private int _ea;
|
||||
private int _cc;
|
||||
private int _opCode;
|
||||
private long _cycles;
|
||||
|
||||
private const int Pc = 0x01;
|
||||
private const int Pz = 0x02;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
|
@ -13,19 +12,15 @@ namespace Jellyfish.Virtu
|
|||
int Execute();
|
||||
long Cycles { get; }
|
||||
int Multiplier { get; }
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed partial class Cpu : ICpu
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IMemoryBus _memory;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Cpu()
|
||||
{
|
||||
InitializeOpCodeDelegates();
|
||||
}
|
||||
private readonly IMemoryBus _memory;
|
||||
|
||||
public Cpu(IMemoryBus memory)
|
||||
{
|
||||
|
@ -3432,29 +3427,22 @@ namespace Jellyfish.Virtu
|
|||
set { _is65C02 = value; _executeOpCode = _is65C02 ? _executeOpCode65C02 : _executeOpCode65N02; }
|
||||
}
|
||||
|
||||
public int Multiplier { get; set; }
|
||||
public int Multiplier { get => _multiplier; private set => _multiplier = value; }
|
||||
|
||||
public int RA { get; set; }
|
||||
public int RX { get; set; }
|
||||
public int RY { get; set; }
|
||||
public int RS { get; set; }
|
||||
public int RP { get; set; }
|
||||
public int RPC { get; set; }
|
||||
public int EA { get; private set; }
|
||||
public int CC { get; private set; }
|
||||
public int OpCode { get; private set; }
|
||||
public long Cycles { get; private set; }
|
||||
public int RA { get => _ra; set => _ra = value; }
|
||||
public int RX { get => _rx; set => _rx = value; }
|
||||
public int RY { get => _ry; set => _ry = value; }
|
||||
public int RS { get => _rs; set => _rs = value; }
|
||||
public int RP { get => _rp; set => _rp = value; }
|
||||
public int RPC { get => _rpc; set => _rpc = value; }
|
||||
public int EA { get => _ea; private set => _ea = value; }
|
||||
public int CC { get => _cc; private set => _cc = value; }
|
||||
public int OpCode { get => _opCode; private set => _opCode = value; }
|
||||
public long Cycles { get => _cycles; private set => _cycles = value; }
|
||||
|
||||
[JsonIgnore]
|
||||
private bool _is65C02;
|
||||
[JsonIgnore]
|
||||
private Action[] _executeOpCode;
|
||||
|
||||
[JsonIgnore]
|
||||
public Action<string[]> TraceCallback;
|
||||
|
||||
/// <summary>Carry Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagC
|
||||
{
|
||||
get => (RP & 0x01) != 0;
|
||||
|
@ -3462,7 +3450,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Zero Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagZ
|
||||
{
|
||||
get => (RP & 0x02) != 0;
|
||||
|
@ -3470,7 +3457,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Interrupt Disable Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagI
|
||||
{
|
||||
get => (RP & 0x04) != 0;
|
||||
|
@ -3478,7 +3464,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Decimal Mode Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagD
|
||||
{
|
||||
get => (RP & 0x08) != 0;
|
||||
|
@ -3486,7 +3471,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Break Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagB
|
||||
{
|
||||
get => (RP & 0x10) != 0;
|
||||
|
@ -3494,7 +3478,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>T... Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagT
|
||||
{
|
||||
get => (RP & 0x20) != 0;
|
||||
|
@ -3502,7 +3485,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Overflow Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagV
|
||||
{
|
||||
get => (RP & 0x40) != 0;
|
||||
|
@ -3510,7 +3492,6 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
|
||||
/// <summary>Negative Flag</summary>
|
||||
[JsonIgnore]
|
||||
public bool FlagN
|
||||
{
|
||||
get => (RP & 0x80) != 0;
|
||||
|
|
|
@ -4,9 +4,8 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
internal abstract class Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
// ReSharper disable once PublicConstructorInAbstractClass
|
||||
public Disk525() { }
|
||||
protected byte[] Data;
|
||||
public bool IsWriteProtected;
|
||||
|
||||
protected Disk525(byte[] data, bool isWriteProtected)
|
||||
{
|
||||
|
@ -14,6 +13,12 @@ namespace Jellyfish.Virtu
|
|||
IsWriteProtected = isWriteProtected;
|
||||
}
|
||||
|
||||
public virtual void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(Data), ref Data, false);
|
||||
ser.Sync(nameof(IsWriteProtected), ref IsWriteProtected);
|
||||
}
|
||||
|
||||
public static Disk525 CreateDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
if (name == null)
|
||||
|
@ -43,11 +48,6 @@ namespace Jellyfish.Virtu
|
|||
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;
|
||||
|
|
|
@ -2,12 +2,19 @@
|
|||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
internal enum SectorSkew { None = 0, Dos, ProDos };
|
||||
|
||||
internal sealed class DiskDsk : Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskDsk() { }
|
||||
private const int SecondaryBufferLength = 0x56;
|
||||
private const int Volume = 0xFE;
|
||||
|
||||
private byte[] _trackBuffer;
|
||||
private int _trackOffset;
|
||||
private byte[] _primaryBuffer = new byte[0x100];
|
||||
private byte[] _secondaryBuffer = new byte[SecondaryBufferLength + 1];
|
||||
private int[] _sectorSkew;
|
||||
|
||||
public DiskDsk(byte[] data, bool isWriteProtected, SectorSkew sectorSkew)
|
||||
: base(data, isWriteProtected)
|
||||
|
@ -15,6 +22,16 @@ namespace Jellyfish.Virtu
|
|||
_sectorSkew = SectorSkewMode[(int)sectorSkew];
|
||||
}
|
||||
|
||||
public override void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_trackBuffer), ref _trackBuffer, false);
|
||||
ser.Sync(nameof(_trackOffset), ref _trackOffset);
|
||||
ser.Sync(nameof(_primaryBuffer), ref _primaryBuffer, false);
|
||||
ser.Sync(nameof(_secondaryBuffer), ref _secondaryBuffer, false);
|
||||
ser.Sync(nameof(_sectorSkew), ref _sectorSkew, false);
|
||||
base.Sync(ser);
|
||||
}
|
||||
|
||||
public override void ReadTrack(int number, int fraction, byte[] buffer)
|
||||
{
|
||||
int track = number / 2;
|
||||
|
@ -165,13 +182,12 @@ namespace Jellyfish.Virtu
|
|||
|
||||
private int ReadNibble44()
|
||||
{
|
||||
return (((ReadNibble() << 1) | 0x1) & ReadNibble());
|
||||
return ((ReadNibble() << 1) | 0x1) & ReadNibble();
|
||||
}
|
||||
|
||||
private byte ReadTranslatedNibble()
|
||||
{
|
||||
byte data = NibbleToByte[ReadNibble()];
|
||||
return data;
|
||||
return NibbleToByte[ReadNibble()];
|
||||
}
|
||||
|
||||
private bool ReadDataNibbles(int sectorOffset)
|
||||
|
@ -268,14 +284,6 @@ namespace Jellyfish.Virtu
|
|||
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 static readonly byte[] SwapBits = { 0, 2, 1, 3 };
|
||||
|
||||
private static readonly int[] SectorSkewNone =
|
||||
|
|
|
@ -8,16 +8,26 @@ namespace Jellyfish.Virtu
|
|||
|
||||
// ReSharper disable once UnusedMemberInSuper.Global
|
||||
DiskIIDrive Drive1 { get; }
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed class DiskIIController : IDiskIIController
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IVideo _video;
|
||||
private const int Phase1On = 1 << 1;
|
||||
private readonly IVideo _video;
|
||||
private readonly byte[] _romRegionC1C7;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskIIController() { }
|
||||
private bool _driveLight;
|
||||
private int _latch;
|
||||
private int _phaseStates;
|
||||
private bool _motorOn;
|
||||
private int _driveNumber;
|
||||
private bool _loadMode;
|
||||
private bool _writeMode;
|
||||
private bool _driveSpin;
|
||||
|
||||
public DiskIIController(IVideo video, byte[] diskIIRom)
|
||||
{
|
||||
|
@ -32,8 +42,30 @@ namespace Jellyfish.Virtu
|
|||
_writeMode = false;
|
||||
}
|
||||
|
||||
public bool DriveLight { get; set; }
|
||||
public DiskIIDrive Drive1 { get; }
|
||||
public DiskIIDrive Drive2 { get; }
|
||||
|
||||
public bool DriveLight
|
||||
{
|
||||
get => _driveLight;
|
||||
set => _driveLight = value;
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_driveLight), ref _driveLight);
|
||||
|
||||
ser.Sync(nameof(_latch), ref _latch);
|
||||
ser.Sync(nameof(_phaseStates), ref _phaseStates);
|
||||
ser.Sync(nameof(_motorOn), ref _motorOn);
|
||||
ser.Sync(nameof(_driveNumber), ref _driveNumber);
|
||||
ser.Sync(nameof(_loadMode), ref _loadMode);
|
||||
ser.Sync(nameof(_writeMode), ref _writeMode);
|
||||
ser.Sync(nameof(_driveSpin), ref _driveSpin);
|
||||
|
||||
Drive1.Sync(ser);
|
||||
Drive2.Sync(ser);
|
||||
}
|
||||
public IList<DiskIIDrive> Drives => new List<DiskIIDrive> { Drive1, Drive2 };
|
||||
|
||||
public void WriteIoRegionC8CF(int address, int data) => _video.ReadFloatingBus();
|
||||
|
@ -236,24 +268,5 @@ namespace Jellyfish.Virtu
|
|||
Drives[_driveNumber].ApplyPhaseChange(_phaseStates);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
public DiskIIDrive Drive1 { get; private set; }
|
||||
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
public DiskIIDrive Drive2 { get; private set; }
|
||||
|
||||
private const int Phase1On = 1 << 1;
|
||||
|
||||
private int _latch;
|
||||
private int _phaseStates;
|
||||
private bool _motorOn;
|
||||
private int _driveNumber;
|
||||
private bool _loadMode;
|
||||
private bool _writeMode;
|
||||
private bool _driveSpin;
|
||||
|
||||
private byte[] _romRegionC1C7 = new byte[0x0100];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public sealed class DiskIIDrive
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IDiskIIController _diskController;
|
||||
private readonly IDiskIIController _diskController;
|
||||
private readonly int[][] _driveArmStepDelta = new int[PhaseCount][];
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskIIDrive() { }
|
||||
private bool _trackLoaded;
|
||||
private bool _trackChanged;
|
||||
private int _trackNumber;
|
||||
private int _trackOffset;
|
||||
|
||||
private byte[] _trackData = new byte[Disk525.TrackSize];
|
||||
private Disk525 _disk;
|
||||
|
||||
public DiskIIDrive(IDiskIIController diskController)
|
||||
{
|
||||
|
@ -19,6 +22,18 @@ namespace Jellyfish.Virtu
|
|||
_driveArmStepDelta[3] = new[] { 0, 1, 0, 1, -1, 0, -1, 0, 0, 1, 0, 1, -1, 0, -1, 0 }; // phase 3
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_trackLoaded), ref _trackLoaded);
|
||||
ser.Sync(nameof(_trackChanged), ref _trackChanged);
|
||||
ser.Sync(nameof(_trackNumber), ref _trackNumber);
|
||||
ser.Sync(nameof(_trackOffset), ref _trackOffset);
|
||||
ser.Sync(nameof(_trackData), ref _trackData, false);
|
||||
|
||||
// TODO: save the delta, this is saving the rom into save states
|
||||
_disk?.Sync(ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void InsertDisk(string name, byte[] data, bool isWriteProtected)
|
||||
{
|
||||
|
@ -101,24 +116,9 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsWriteProtected => _disk.IsWriteProtected;
|
||||
public bool IsWriteProtected => _disk?.IsWriteProtected ?? false;
|
||||
|
||||
private const int TrackNumberMax = 0x44;
|
||||
|
||||
private const int PhaseCount = 4;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private int[][] _driveArmStepDelta = new int[PhaseCount][];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
internal sealed class DiskNib : Disk525
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public DiskNib() { }
|
||||
|
||||
public DiskNib(byte[] data, bool isWriteProtected)
|
||||
: base(data, isWriteProtected)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
namespace Jellyfish.Virtu
|
||||
{
|
||||
// Serves as a generalized interface to the BizHawk serializer
|
||||
public interface IComponentSerializer
|
||||
{
|
||||
bool IsReader { get; }
|
||||
void Sync(string name, ref bool val);
|
||||
void Sync(string name, ref int val);
|
||||
void Sync(string name, ref long val);
|
||||
void Sync(string name, ref ulong val);
|
||||
|
||||
void Sync(string name, ref bool[] val, bool useNull);
|
||||
void Sync(string name, ref byte[] val, bool useNull);
|
||||
void Sync(string name, ref ushort[] val, bool useNull);
|
||||
void Sync(string name, ref int[] val, bool useNull);
|
||||
}
|
||||
}
|
|
@ -143,9 +143,6 @@ namespace Jellyfish.Virtu
|
|||
|
||||
public sealed class Keyboard
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Keyboard() { }
|
||||
|
||||
static Keyboard()
|
||||
{
|
||||
for (int i = 0; i < 62; i++)
|
||||
|
@ -226,15 +223,15 @@ namespace Jellyfish.Virtu
|
|||
0x7a1a5a1a, // z
|
||||
};
|
||||
|
||||
/// <param name="key">0 - 55</param>
|
||||
// key: 0 - 55
|
||||
private static int KeyToAscii(int key, bool control, bool shift)
|
||||
{
|
||||
int s = control ? (shift ? 0 : 16) : (shift ? 8 : 24);
|
||||
int s = control ? shift ? 0 : 16 : shift ? 8 : 24;
|
||||
return (int)(KeyAsciiData[key] >> s & 0x7f);
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>();
|
||||
private static readonly Dictionary<string, Keys> DescriptionsToKeys = new Dictionary<string, Keys>();
|
||||
|
||||
private static Keys FromStrings(IEnumerable<string> keynames)
|
||||
{
|
||||
|
|
|
@ -1,29 +1,87 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public enum EventCallbacks
|
||||
{
|
||||
FlushOutput,
|
||||
FlushRow,
|
||||
LeaveVBlank,
|
||||
ResetVsync,
|
||||
InverseText
|
||||
}
|
||||
|
||||
internal sealed class MachineEvent
|
||||
{
|
||||
public MachineEvent(int delta, Action action)
|
||||
public MachineEvent(int delta, EventCallbacks type)
|
||||
{
|
||||
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);
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public int Delta { get; set; }
|
||||
public Action Action { get; set; }
|
||||
public EventCallbacks Type { get; set; }
|
||||
}
|
||||
|
||||
public sealed class MachineEvents
|
||||
{
|
||||
public void AddEvent(int delta, Action action)
|
||||
private readonly Dictionary<EventCallbacks, Action> _eventDelegates = new Dictionary<EventCallbacks, Action>();
|
||||
|
||||
private readonly LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
private readonly LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
if (ser.IsReader)
|
||||
{
|
||||
int[] usedDelta = new int[0];
|
||||
int[] usedType = new int[0];
|
||||
int[] freeDelta = new int[0];
|
||||
int[] freeType = new int[0];
|
||||
|
||||
ser.Sync("UsedDelta", ref usedDelta, false);
|
||||
ser.Sync("UsedType", ref usedType, false);
|
||||
ser.Sync("FreeDelta", ref freeDelta, false);
|
||||
ser.Sync("FreeType", ref freeType, false);
|
||||
|
||||
_used.Clear();
|
||||
for (int i = 0; i < usedDelta.Length; i++)
|
||||
{
|
||||
var e = new MachineEvent(usedDelta[i], (EventCallbacks)usedType[i]);
|
||||
_used.AddLast(new LinkedListNode<MachineEvent>(e));
|
||||
}
|
||||
|
||||
_free.Clear();
|
||||
for (int i = 0; i < freeDelta.Length; i++)
|
||||
{
|
||||
var e = new MachineEvent(freeDelta[i], (EventCallbacks)freeType[i]);
|
||||
_free.AddLast(new LinkedListNode<MachineEvent>(e));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var usedDelta = _used.Select(u => u.Delta).ToArray();
|
||||
var usedType = _used.Select(u => (int)u.Type).ToArray();
|
||||
var freeDelta = _free.Select(f => f.Delta).ToArray();
|
||||
var freeType = _free.Select(f => (int)f.Type).ToArray();
|
||||
|
||||
ser.Sync("UsedDelta", ref usedDelta, false);
|
||||
ser.Sync("UsedType", ref usedType, false);
|
||||
ser.Sync("FreeDelta", ref freeDelta, false);
|
||||
ser.Sync("FreeType", ref freeType, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddEventDelegate(EventCallbacks type, Action action)
|
||||
|
||||
{
|
||||
_eventDelegates[type] = action;
|
||||
}
|
||||
|
||||
public void AddEvent(int delta, EventCallbacks type)
|
||||
{
|
||||
var node = _used.First;
|
||||
for (; node != null; node = node.Next)
|
||||
|
@ -44,11 +102,11 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
_free.RemoveFirst();
|
||||
newNode.Value.Delta = delta;
|
||||
newNode.Value.Action = action;
|
||||
newNode.Value.Type = type;
|
||||
}
|
||||
else
|
||||
{
|
||||
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, action));
|
||||
newNode = new LinkedListNode<MachineEvent>(new MachineEvent(delta, type));
|
||||
}
|
||||
|
||||
if (node != null)
|
||||
|
@ -61,7 +119,7 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
}
|
||||
|
||||
public int FindEvent(Action action)
|
||||
public int FindEvent(EventCallbacks type)
|
||||
{
|
||||
int delta = 0;
|
||||
|
||||
|
@ -69,9 +127,9 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
delta += node.Value.Delta;
|
||||
|
||||
var other = node.Value.Action;
|
||||
var other = node.Value.Type;
|
||||
|
||||
if (other.Method == action.Method && other.Target == action.Target)
|
||||
if (other == type)
|
||||
{
|
||||
return delta;
|
||||
}
|
||||
|
@ -88,7 +146,7 @@ namespace Jellyfish.Virtu
|
|||
|
||||
while (node.Value.Delta <= 0)
|
||||
{
|
||||
node.Value.Action();
|
||||
_eventDelegates[node.Value.Type]();
|
||||
RemoveEvent(node);
|
||||
node = _used.First;
|
||||
}
|
||||
|
@ -104,11 +162,5 @@ namespace Jellyfish.Virtu
|
|||
_used.Remove(node);
|
||||
_free.AddFirst(node); // cache node; avoids garbage
|
||||
}
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private LinkedList<MachineEvent> _used = new LinkedList<MachineEvent>();
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private LinkedList<MachineEvent> _free = new LinkedList<MachineEvent>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
|
@ -96,7 +95,6 @@ namespace Jellyfish.Virtu
|
|||
Video.ModeC, Video.ModeD, Video.Mode1, Video.Mode2, Video.ModeE, Video.ModeF, Video.Mode1, Video.Mode2
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
private Action<int, byte>[][][] WriteRamModeBankRegion;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public enum MonitorType { Unknown, Standard, Enhanced }
|
||||
|
||||
public interface IMemoryBus
|
||||
|
@ -31,8 +31,12 @@ namespace Jellyfish.Virtu
|
|||
|
||||
int VideoMode { get; }
|
||||
MonitorType Monitor { get; }
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed partial class Memory : IMemoryBus
|
||||
{
|
||||
private IGamePort _gamePort;
|
||||
|
@ -47,15 +51,30 @@ namespace Jellyfish.Virtu
|
|||
private IPeripheralCard _slot5;
|
||||
private IPeripheralCard _slot7;
|
||||
|
||||
// TODO: this shouldn't be in savestates!
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private byte[] _appleIIe;
|
||||
private readonly byte[] _appleIIe;
|
||||
private readonly byte[][] _regionRead = new byte[RegionCount][];
|
||||
private readonly byte[][] _regionWrite = new byte[RegionCount][];
|
||||
private readonly Action<int, byte>[] _writeRegion = new Action<int, byte>[RegionCount];
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Memory()
|
||||
{
|
||||
InitializeWriteDelegates();
|
||||
}
|
||||
private bool _lagged;
|
||||
private int _state;
|
||||
private int _slotRegionC8CF;
|
||||
private byte[] _zeroPage;
|
||||
private byte[] _ramMainRegion0001 = new byte[0x0200];
|
||||
private byte[] _ramMainRegion02BF = new byte[0xBE00];
|
||||
private byte[] _ramMainBank1RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramMainBank2RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramMainRegionE0FF = new byte[0x2000];
|
||||
private byte[] _ramAuxRegion0001 = new byte[0x0200];
|
||||
private byte[] _ramAuxRegion02BF = new byte[0xBE00];
|
||||
private byte[] _ramAuxBank1RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramAuxBank2RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramAuxRegionE0FF = new byte[0x2000];
|
||||
|
||||
private byte[] _romExternalRegionC1CF = new byte[0x0F00];
|
||||
private byte[] _romInternalRegionC1CF = new byte[0x0F00];
|
||||
private byte[] _romRegionD0DF = new byte[0x1000];
|
||||
private byte[] _romRegionE0FF = new byte[0x2000];
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public Memory(byte[] appleIIe)
|
||||
|
@ -64,6 +83,37 @@ namespace Jellyfish.Virtu
|
|||
InitializeWriteDelegates();
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_lagged), ref _lagged);
|
||||
ser.Sync(nameof(_state), ref _state);
|
||||
ser.Sync(nameof(_slotRegionC8CF), ref _slotRegionC8CF);
|
||||
ser.Sync(nameof(_zeroPage), ref _zeroPage, false);
|
||||
|
||||
ser.Sync(nameof(_ramMainRegion0001), ref _ramMainRegion0001, false);
|
||||
ser.Sync(nameof(_ramMainRegion02BF), ref _ramMainRegion02BF, false);
|
||||
ser.Sync(nameof(_ramMainBank1RegionD0DF), ref _ramMainBank1RegionD0DF, false);
|
||||
ser.Sync(nameof(_ramMainBank2RegionD0DF), ref _ramMainBank2RegionD0DF, false);
|
||||
ser.Sync(nameof(_ramMainRegionE0FF), ref _ramMainRegionE0FF, false);
|
||||
ser.Sync(nameof(_ramAuxRegion0001), ref _ramAuxRegion0001, false);
|
||||
ser.Sync(nameof(_ramAuxRegion02BF), ref _ramAuxRegion02BF, false);
|
||||
ser.Sync(nameof(_ramAuxBank1RegionD0DF), ref _ramAuxBank1RegionD0DF, false);
|
||||
ser.Sync(nameof(_ramAuxBank2RegionD0DF), ref _ramAuxBank2RegionD0DF, false);
|
||||
ser.Sync(nameof(_ramAuxRegionE0FF), ref _ramAuxRegionE0FF, false);
|
||||
ser.Sync(nameof(_romExternalRegionC1CF), ref _romExternalRegionC1CF, false);
|
||||
ser.Sync(nameof(_romInternalRegionC1CF), ref _romInternalRegionC1CF, false);
|
||||
ser.Sync(nameof(_romRegionD0DF), ref _romRegionD0DF, false);
|
||||
ser.Sync(nameof(_romRegionE0FF), ref _romRegionE0FF, false);
|
||||
|
||||
if (ser.IsReader)
|
||||
{
|
||||
MapRegion0001();
|
||||
MapRegion02BF();
|
||||
MapRegionC0CF();
|
||||
MapRegionD0FF();
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void Initialize(
|
||||
Keyboard keyboard,
|
||||
|
@ -123,7 +173,7 @@ namespace Jellyfish.Virtu
|
|||
}
|
||||
}
|
||||
|
||||
public bool Lagged { get; set; }
|
||||
public bool Lagged { get => _lagged; set => _lagged = value; }
|
||||
|
||||
private IList<IPeripheralCard> Slots => new List<IPeripheralCard>
|
||||
{
|
||||
|
@ -2100,51 +2150,15 @@ namespace Jellyfish.Virtu
|
|||
public MonitorType Monitor { get; private set; }
|
||||
public int VideoMode => StateVideoMode[_state & StateVideo];
|
||||
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeIoRegionC0C0;
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeIoRegionC1C7;
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeIoRegionC3C3;
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeIoRegionC8CF;
|
||||
[JsonIgnore]
|
||||
private Action<int, byte> _writeRomRegionD0FF;
|
||||
|
||||
[JsonIgnore]
|
||||
|
||||
public Action<uint> ReadCallback;
|
||||
|
||||
[JsonIgnore]
|
||||
public Action<uint> WriteCallback;
|
||||
|
||||
[JsonIgnore]
|
||||
public Action<uint> ExecuteCallback;
|
||||
|
||||
[JsonIgnore]
|
||||
public Action InputCallback;
|
||||
|
||||
private int _state;
|
||||
private int _slotRegionC8CF;
|
||||
|
||||
private byte[] _zeroPage;
|
||||
private byte[][] _regionRead = new byte[RegionCount][];
|
||||
private byte[][] _regionWrite = new byte[RegionCount][];
|
||||
private Action<int, byte>[] _writeRegion = new Action<int, byte>[RegionCount];
|
||||
|
||||
private byte[] _ramMainRegion0001 = new byte[0x0200];
|
||||
private byte[] _ramMainRegion02BF = new byte[0xBE00];
|
||||
private byte[] _ramMainBank1RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramMainBank2RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramMainRegionE0FF = new byte[0x2000];
|
||||
private byte[] _ramAuxRegion0001 = new byte[0x0200];
|
||||
private byte[] _ramAuxRegion02BF = new byte[0xBE00];
|
||||
private byte[] _ramAuxBank1RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramAuxBank2RegionD0DF = new byte[0x1000];
|
||||
private byte[] _ramAuxRegionE0FF = new byte[0x2000];
|
||||
|
||||
private byte[] _romExternalRegionC1CF = new byte[0x0F00];
|
||||
private byte[] _romInternalRegionC1CF = new byte[0x0F00];
|
||||
private byte[] _romRegionD0DF = new byte[0x1000];
|
||||
private byte[] _romRegionE0FF = new byte[0x2000];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,20 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
int Read(int address, int value);
|
||||
void Write(int address);
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public sealed class NoSlotClock : ISlotClock
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private Video _video;
|
||||
private readonly Video _video;
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public NoSlotClock() { }
|
||||
private bool _clockEnabled;
|
||||
private bool _writeEnabled;
|
||||
private RingRegister _clockRegister;
|
||||
private RingRegister _comparisonRegister;
|
||||
|
||||
public NoSlotClock(Video video)
|
||||
{
|
||||
|
@ -27,6 +31,14 @@ namespace Jellyfish.Virtu
|
|||
_comparisonRegister = new RingRegister(ClockInitSequence, 0x1);
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_clockEnabled), ref _clockEnabled);
|
||||
ser.Sync(nameof(_writeEnabled), ref _writeEnabled);
|
||||
_clockRegister.Sync(ser);
|
||||
_comparisonRegister.Sync(ser);
|
||||
}
|
||||
|
||||
public int Read(int address, int value)
|
||||
{
|
||||
// this may read or write the clock
|
||||
|
@ -142,11 +154,6 @@ namespace Jellyfish.Virtu
|
|||
|
||||
private const ulong ClockInitSequence = 0x5CA33AC55CA33AC5;
|
||||
|
||||
private bool _clockEnabled;
|
||||
private bool _writeEnabled;
|
||||
private RingRegister _clockRegister;
|
||||
private RingRegister _comparisonRegister;
|
||||
|
||||
private struct RingRegister
|
||||
{
|
||||
public RingRegister(ulong data, ulong mask)
|
||||
|
@ -155,6 +162,12 @@ namespace Jellyfish.Virtu
|
|||
_mask = mask;
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_data), ref _data);
|
||||
ser.Sync(nameof(_mask), ref _mask);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_mask = 0x1;
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
public interface ISpeaker
|
||||
{
|
||||
|
@ -13,46 +9,46 @@ namespace Jellyfish.Virtu
|
|||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void GetSamples(out short[] samples, out int nSamp);
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
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(MachineEvents events, ICpu cpu)
|
||||
{
|
||||
_events = events;
|
||||
_cpu = cpu;
|
||||
_flushOutputEvent = FlushOutputEvent; // cache delegates; avoids garbage
|
||||
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent);
|
||||
|
||||
_isHigh = false;
|
||||
_highCycles = _totalCycles = 0;
|
||||
}
|
||||
|
||||
private const int CyclesPerFlush = 23;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private Action _flushOutputEvent;
|
||||
private readonly MachineEvents _events;
|
||||
private readonly ICpu _cpu;
|
||||
|
||||
private bool _isHigh;
|
||||
private int _highCycles;
|
||||
private int _totalCycles;
|
||||
private long _lastCycles;
|
||||
|
||||
[JsonIgnore] // only relevant if trying to savestate mid-frame
|
||||
// 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 Speaker(MachineEvents events, ICpu cpu)
|
||||
{
|
||||
_events = events;
|
||||
_cpu = cpu;
|
||||
_events.AddEventDelegate(EventCallbacks.FlushOutput, FlushOutputEvent);
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput);
|
||||
|
||||
_isHigh = false;
|
||||
_highCycles = _totalCycles = 0;
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
ser.Sync(nameof(_isHigh), ref _isHigh);
|
||||
ser.Sync(nameof(_highCycles), ref _highCycles);
|
||||
ser.Sync(nameof(_totalCycles), ref _totalCycles);
|
||||
ser.Sync(nameof(_lastCycles), ref _lastCycles);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
|
@ -66,8 +62,6 @@ namespace Jellyfish.Virtu
|
|||
_position = 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ToggleOutput()
|
||||
{
|
||||
UpdateCycles();
|
||||
|
@ -81,7 +75,7 @@ namespace Jellyfish.Virtu
|
|||
Output(_highCycles * short.MaxValue / _totalCycles);
|
||||
_highCycles = _totalCycles = 0;
|
||||
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, _flushOutputEvent);
|
||||
_events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput);
|
||||
}
|
||||
|
||||
private void UpdateCycles()
|
||||
|
@ -104,12 +98,5 @@ namespace Jellyfish.Virtu
|
|||
_buffer[_position++] = (short)data;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
_position = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1615,8 +1615,7 @@ namespace Jellyfish.Virtu
|
|||
public const int ModeE = 0xE;
|
||||
public const int ModeF = 0xF;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private Action<int>[] _flushRowMode;
|
||||
private readonly Action<int>[] _flushRowMode;
|
||||
|
||||
private const int Width = 560;
|
||||
private const int Height = VLineEnterVBlank;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jellyfish.Virtu
|
||||
{
|
||||
|
@ -9,7 +7,9 @@ namespace Jellyfish.Virtu
|
|||
|
||||
public interface IVideo
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
int[] GetVideoBuffer();
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Reset();
|
||||
|
||||
void DirtyCell(int addressOffset);
|
||||
|
@ -20,26 +20,42 @@ namespace Jellyfish.Virtu
|
|||
void SetCharSet();
|
||||
|
||||
bool IsVBlank { get; }
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
void Sync(IComponentSerializer ser);
|
||||
}
|
||||
|
||||
public sealed partial class Video : IVideo
|
||||
{
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private MachineEvents _events;
|
||||
private readonly MachineEvents _events;
|
||||
private readonly IMemoryBus _memory;
|
||||
|
||||
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
||||
private IMemoryBus _memory;
|
||||
private readonly int[] _colorPalette = new int[ColorPaletteCount];
|
||||
|
||||
private bool _isVBlank;
|
||||
private int[] _framebuffer = new int[560 * 384];
|
||||
private bool _isTextInversed;
|
||||
private ScannerOptions _scannerOptions;
|
||||
private int _cyclesPerVBlank;
|
||||
private int _cyclesPerVBlankPreset;
|
||||
private int _cyclesPerVSync;
|
||||
private int _cyclesPerFlash;
|
||||
private int _vCountPreset;
|
||||
private int _vLineLeaveVBlank;
|
||||
private ushort[] _charSet;
|
||||
private bool _isMonochrome;
|
||||
|
||||
private bool[] _isCellDirty = new bool[Height * CellColumns + 1]; // includes sentinel
|
||||
|
||||
public Video() { }
|
||||
public Video(MachineEvents events, IMemoryBus memory)
|
||||
{
|
||||
_events = events;
|
||||
_memory = memory;
|
||||
|
||||
_flushRowEvent = FlushRowEvent; // cache delegates; avoids garbage
|
||||
_inverseTextEvent = InverseTextEvent;
|
||||
_leaveVBlankEvent = LeaveVBlankEvent;
|
||||
_resetVSyncEvent = ResetVSyncEvent;
|
||||
_events.AddEventDelegate(EventCallbacks.FlushRow, FlushRowEvent);
|
||||
_events.AddEventDelegate(EventCallbacks.InverseText, InverseTextEvent);
|
||||
_events.AddEventDelegate(EventCallbacks.LeaveVBlank, LeaveVBlankEvent);
|
||||
_events.AddEventDelegate(EventCallbacks.ResetVsync, ResetVSyncEvent);
|
||||
|
||||
_flushRowMode = new Action<int>[]
|
||||
{
|
||||
|
@ -73,22 +89,43 @@ namespace Jellyfish.Virtu
|
|||
IsMonochrome = false;
|
||||
ScannerOptions = ScannerOptions.None;
|
||||
|
||||
IsVBlank = true;
|
||||
_isVBlank = true;
|
||||
|
||||
_events.AddEvent(_cyclesPerVBlankPreset, _leaveVBlankEvent); // align flush events with scanner; assumes vcount preset at start of frame [3-15, 3-16]
|
||||
_events.AddEvent(_cyclesPerVSync, _resetVSyncEvent);
|
||||
_events.AddEvent(_cyclesPerFlash, _inverseTextEvent);
|
||||
_events.AddEvent(_cyclesPerVBlankPreset, EventCallbacks.LeaveVBlank); // align flush events with scanner; assumes vcount preset at start of frame [3-15, 3-16]
|
||||
_events.AddEvent(_cyclesPerVSync, EventCallbacks.ResetVsync);
|
||||
_events.AddEvent(_cyclesPerFlash, EventCallbacks.InverseText);
|
||||
}
|
||||
|
||||
public void Sync(IComponentSerializer ser)
|
||||
{
|
||||
if (ser.IsReader)
|
||||
{
|
||||
int option = 0;
|
||||
ser.Sync(nameof(_scannerOptions), ref option);
|
||||
_scannerOptions = (ScannerOptions)option;
|
||||
}
|
||||
else
|
||||
{
|
||||
int option = (int)_scannerOptions;
|
||||
ser.Sync(nameof(_scannerOptions), ref option);
|
||||
}
|
||||
|
||||
ser.Sync(nameof(_isVBlank), ref _isVBlank);
|
||||
ser.Sync(nameof(_framebuffer), ref _framebuffer, false);
|
||||
ser.Sync(nameof(_isTextInversed), ref _isTextInversed);
|
||||
ser.Sync(nameof(_cyclesPerVBlank), ref _cyclesPerVBlank);
|
||||
ser.Sync(nameof(_cyclesPerVBlankPreset), ref _cyclesPerVBlankPreset);
|
||||
ser.Sync(nameof(_cyclesPerVSync), ref _cyclesPerVSync);
|
||||
ser.Sync(nameof(_cyclesPerFlash), ref _cyclesPerFlash);
|
||||
ser.Sync(nameof(_vCountPreset), ref _vCountPreset);
|
||||
ser.Sync(nameof(_vLineLeaveVBlank), ref _vLineLeaveVBlank);
|
||||
ser.Sync(nameof(_charSet), ref _charSet, false);
|
||||
ser.Sync(nameof(_isCellDirty), ref _isCellDirty, false);
|
||||
ser.Sync(nameof(_isMonochrome), ref _isMonochrome);
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public int[] GetVideoBuffer() => Framebuffer;
|
||||
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
// the VideoService forgets all of its information on LoadState
|
||||
DirtyScreen();
|
||||
}
|
||||
public int[] GetVideoBuffer() => _framebuffer;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
|
@ -149,7 +186,7 @@ namespace Jellyfish.Virtu
|
|||
public int ReadFloatingBus() // [5-40]
|
||||
{
|
||||
// derive scanner counters from current cycles into frame; assumes hcount and vcount preset at start of frame [3-13, 3-15, 3-16]
|
||||
int cycles = _cyclesPerVSync - _events.FindEvent(_resetVSyncEvent);
|
||||
int cycles = _cyclesPerVSync - _events.FindEvent(EventCallbacks.ResetVsync);
|
||||
int hClock = cycles % CyclesPerHSync;
|
||||
int hCount = (hClock != 0) ? HCountPreset + hClock - 1 : 0;
|
||||
int vLine = cycles / CyclesPerHSync;
|
||||
|
@ -918,18 +955,18 @@ namespace Jellyfish.Virtu
|
|||
|
||||
private void FlushRowEvent()
|
||||
{
|
||||
int y = (_cyclesPerVSync - _cyclesPerVBlankPreset - _events.FindEvent(_resetVSyncEvent)) / CyclesPerHSync;
|
||||
int y = (_cyclesPerVSync - _cyclesPerVBlankPreset - _events.FindEvent(EventCallbacks.ResetVsync)) / CyclesPerHSync;
|
||||
|
||||
_flushRowMode[_memory.VideoMode](y - CellHeight); // in arrears
|
||||
|
||||
if (y < Height)
|
||||
{
|
||||
_events.AddEvent(CyclesPerFlush, _flushRowEvent);
|
||||
_events.AddEvent(CyclesPerFlush, EventCallbacks.FlushRow);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsVBlank = true;
|
||||
_events.AddEvent(_cyclesPerVBlank, _leaveVBlankEvent);
|
||||
_isVBlank = true;
|
||||
_events.AddEvent(_cyclesPerVBlank, EventCallbacks.LeaveVBlank);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -946,18 +983,18 @@ namespace Jellyfish.Virtu
|
|||
{
|
||||
_isTextInversed = !_isTextInversed;
|
||||
DirtyScreenText();
|
||||
_events.AddEvent(_cyclesPerFlash, _inverseTextEvent);
|
||||
_events.AddEvent(_cyclesPerFlash, EventCallbacks.InverseText);
|
||||
}
|
||||
|
||||
private void LeaveVBlankEvent()
|
||||
{
|
||||
IsVBlank = false;
|
||||
_events.AddEvent(CyclesPerFlush, _flushRowEvent);
|
||||
_isVBlank = false;
|
||||
_events.AddEvent(CyclesPerFlush, EventCallbacks.FlushRow);
|
||||
}
|
||||
|
||||
private void ResetVSyncEvent()
|
||||
{
|
||||
_events.AddEvent(_cyclesPerVSync, _resetVSyncEvent);
|
||||
_events.AddEvent(_cyclesPerVSync, EventCallbacks.ResetVsync);
|
||||
}
|
||||
|
||||
private void SetPalette()
|
||||
|
@ -1002,13 +1039,10 @@ namespace Jellyfish.Virtu
|
|||
DirtyScreen();
|
||||
}
|
||||
|
||||
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Local
|
||||
private int[] Framebuffer { get; set; } = new int[560 * 384];
|
||||
|
||||
private void SetPixel(int x, int y, int color)
|
||||
{
|
||||
int i = 560 * (2 * y) + x;
|
||||
Framebuffer[i] = Framebuffer[i + 560] = _colorPalette[color];
|
||||
_framebuffer[i] = _framebuffer[i + 560] = _colorPalette[color];
|
||||
}
|
||||
|
||||
private void SetScanner()
|
||||
|
@ -1030,53 +1064,36 @@ namespace Jellyfish.Virtu
|
|||
_cyclesPerFlash = VSyncsPerFlash * _cyclesPerVSync;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsMonochrome { get => _isMonochrome; set { _isMonochrome = value; DirtyScreen(); } }
|
||||
public bool IsMonochrome
|
||||
{
|
||||
get => _isMonochrome;
|
||||
set { _isMonochrome = value; DirtyScreen(); }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
internal ScannerOptions ScannerOptions { get => _scannerOptions; set { _scannerOptions = value; SetScanner(); } }
|
||||
internal ScannerOptions ScannerOptions
|
||||
{
|
||||
get => _scannerOptions;
|
||||
set { _scannerOptions = value; SetScanner(); }
|
||||
}
|
||||
|
||||
public bool IsVBlank { get; private set; }
|
||||
public bool IsVBlank => _isVBlank;
|
||||
|
||||
private Action _flushRowEvent;
|
||||
private Action _inverseTextEvent;
|
||||
private Action _leaveVBlankEvent;
|
||||
private Action _resetVSyncEvent;
|
||||
|
||||
private int _colorBlack;
|
||||
private int _colorDarkBlue;
|
||||
private int _colorDarkGreen;
|
||||
private int _colorMediumBlue;
|
||||
private int _colorBrown;
|
||||
private int _colorLightGrey;
|
||||
private int _colorGreen;
|
||||
private int _colorAquamarine;
|
||||
private int _colorDeepRed;
|
||||
private int _colorPurple;
|
||||
private int _colorDarkGrey;
|
||||
private int _colorLightBlue;
|
||||
private int _colorOrange;
|
||||
private int _colorPink;
|
||||
private int _colorYellow;
|
||||
private int _colorWhite;
|
||||
private int _colorMonochrome;
|
||||
|
||||
[JsonIgnore] // not sync relevant
|
||||
private bool _isMonochrome;
|
||||
|
||||
private bool _isTextInversed;
|
||||
private ScannerOptions _scannerOptions;
|
||||
private int _cyclesPerVBlank;
|
||||
private int _cyclesPerVBlankPreset;
|
||||
private int _cyclesPerVSync;
|
||||
private int _cyclesPerFlash;
|
||||
private int _vCountPreset;
|
||||
private int _vLineLeaveVBlank;
|
||||
|
||||
private ushort[] _charSet;
|
||||
private int[] _colorPalette = new int[ColorPaletteCount];
|
||||
|
||||
[JsonIgnore] // everything is automatically dirtied on load, so no need to save
|
||||
private bool[] _isCellDirty = new bool[Height * CellColumns + 1]; // includes sentinel
|
||||
private readonly int _colorBlack;
|
||||
private readonly int _colorDarkBlue;
|
||||
private readonly int _colorDarkGreen;
|
||||
private readonly int _colorMediumBlue;
|
||||
private readonly int _colorBrown;
|
||||
private readonly int _colorLightGrey;
|
||||
private readonly int _colorGreen;
|
||||
private readonly int _colorAquamarine;
|
||||
private readonly int _colorDeepRed;
|
||||
private readonly int _colorPurple;
|
||||
private readonly int _colorDarkGrey;
|
||||
private readonly int _colorLightBlue;
|
||||
private readonly int _colorOrange;
|
||||
private readonly int _colorPink;
|
||||
private readonly int _colorYellow;
|
||||
private readonly int _colorWhite;
|
||||
private readonly int _colorMonochrome;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,6 @@
|
|||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\..\References\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
|
@ -53,6 +50,7 @@
|
|||
<Compile Include="DiskIIController.cs" />
|
||||
<Compile Include="DiskIIDrive.cs" />
|
||||
<Compile Include="DiskNib.cs" />
|
||||
<Compile Include="IComponentSerializer.cs" />
|
||||
<Compile Include="IGamePort.cs" />
|
||||
<Compile Include="Keyboard.cs" />
|
||||
<Compile Include="MachineEvents.cs" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<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/=0DF/@EntryIndexedValue">0DF</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>
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue