using System; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; using Jellyfish.Virtu.Services; namespace Jellyfish.Virtu { public enum MachineState { Stopped = 0, Starting, Running, Pausing, Paused, Stopping } public sealed class Machine : IDisposable { public Machine() { Events = new MachineEvents(); Services = new MachineServices(); Cpu = new Cpu(this); Memory = new Memory(this); 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); Slot7 = emptySlot; Slots = new Collection { null, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 }; Components = new Collection { Cpu, Memory, Keyboard, GamePort, Cassette, Speaker, Video, NoSlotClock, Slot1, Slot2, Slot3, Slot4, Slot5, Slot6, Slot7 }; BootDiskII = Slots.OfType().Last(); Thread = new Thread(Run) { Name = "Machine" }; } public void Dispose() { _pauseEvent.Close(); _unpauseEvent.Close(); } 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); } } [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")] public void Start() { _debugService = Services.GetService(); _storageService = Services.GetService(); _debugService.WriteMessage("Starting machine"); State = MachineState.Starting; Thread.Start(); } [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")] public void Pause() { _debugService.WriteMessage("Pausing machine"); State = MachineState.Pausing; _pauseEvent.WaitOne(); State = MachineState.Paused; _debugService.WriteMessage("Paused machine"); } [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")] public void Unpause() { _debugService.WriteMessage("Running machine"); State = MachineState.Running; _unpauseEvent.Set(); } [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")] public void Stop() { _debugService.WriteMessage("Stopping machine"); State = MachineState.Stopping; _unpauseEvent.Set(); if (Thread.IsAlive) { Thread.Join(); } State = MachineState.Stopped; _debugService.WriteMessage("Stopped machine"); } 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 LoadState() { #if WINDOWS var args = Environment.GetCommandLineArgs(); if (args.Length > 1) { string name = args[1]; Func, bool> loader = StorageService.LoadFile; if (name.StartsWith("res://", StringComparison.OrdinalIgnoreCase)) { name = name.Substring(6); loader = StorageService.LoadResource; } if (name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase)) { loader(name, stream => LoadState(stream)); } else if (name.EndsWith(".prg", StringComparison.OrdinalIgnoreCase)) { loader(name, stream => Memory.LoadPrg(stream)); } else if (name.EndsWith(".xex", StringComparison.OrdinalIgnoreCase)) { loader(name, stream => Memory.LoadXex(stream)); } else { loader(name, stream => BootDiskII.BootDrive.InsertDisk(name, stream, false)); } } else #endif if (!_storageService.Load(Machine.StateFileName, stream => LoadState(stream))) { StorageService.LoadResource("Disks/Default.dsk", stream => BootDiskII.BootDrive.InsertDisk("Default.dsk", stream, false)); } } private void LoadState(Stream stream) { using (var reader = new BinaryReader(stream)) { string signature = reader.ReadString(); var version = new Version(reader.ReadString()); if ((signature != StateSignature) || (version != new Version(Machine.Version))) // avoid state version mismatch (for now) { throw new InvalidOperationException(); } foreach (var component in Components) { _debugService.WriteMessage("Loading machine '{0}'", component.GetType().Name); component.LoadState(reader, version); //_debugService.WriteMessage("Loaded machine '{0}'", component.GetType().Name); } } } private void SaveState() { _storageService.Save(Machine.StateFileName, stream => SaveState(stream)); } private void SaveState(Stream stream) { using (var writer = new BinaryWriter(stream)) { writer.Write(StateSignature); writer.Write(Machine.Version); foreach (var component in Components) { _debugService.WriteMessage("Saving machine '{0}'", component.GetType().Name); component.SaveState(writer); //_debugService.WriteMessage("Saved 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); } } [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "Jellyfish.Virtu.Services.DebugService.WriteMessage(System.String)")] private void Run() // machine thread { Initialize(); Reset(); LoadState(); _debugService.WriteMessage("Running machine"); State = MachineState.Running; do { do { Events.HandleEvents(Cpu.Execute()); } while (State == MachineState.Running); if (State == MachineState.Pausing) { _pauseEvent.Set(); _unpauseEvent.WaitOne(); } } while (State != MachineState.Stopping); SaveState(); Uninitialize(); } public const string Version = "0.9.4.0"; public MachineEvents Events { get; private set; } public MachineServices Services { get; private set; } public MachineState State { get { return _state; } private set { _state = value; } } 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 Collection Slots { get; private set; } public Collection Components { get; private set; } public DiskIIController BootDiskII { get; private set; } public Thread Thread { get; private set; } private const string StateFileName = "State.bin"; private const string StateSignature = "Virtu"; private DebugService _debugService; private StorageService _storageService; private volatile MachineState _state; private AutoResetEvent _pauseEvent = new AutoResetEvent(false); private AutoResetEvent _unpauseEvent = new AutoResetEvent(false); } }