Added simple commandline support on Windows, i.e.

Jellyfish.Virtu.exe <FileName>
Includes new support for 'program' (.prg) files.
This commit is contained in:
Sean Fausett 2012-05-20 13:00:39 +12:00
parent 7e09b214de
commit 6aa86024d3
12 changed files with 241 additions and 147 deletions

View File

@ -1,31 +1,30 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace Jellyfish.Library namespace Jellyfish.Library
{ {
public static class StreamExtensions public static class StreamExtensions
{ {
public static byte[] ReadBlock(this Stream stream, int count) [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")]
public static int ReadBlock(this Stream stream, byte[] buffer, int offset = 0, int minCount = int.MaxValue)
{ {
return ReadBlock(stream, new byte[count], 0, count); return ReadBlock(stream, buffer, offset, int.MaxValue, minCount);
} }
public static byte[] ReadBlock(this Stream stream, byte[] buffer) public static int ReadBlock(this Stream stream, byte[] buffer, int offset, int count, int minCount)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
return ReadBlock(stream, buffer, 0, buffer.Length);
}
public static byte[] ReadBlock(this Stream stream, byte[] buffer, int offset, int count)
{ {
if (stream == null) if (stream == null)
{ {
throw new ArgumentNullException("stream"); 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 total = 0;
int read; int read;
@ -35,12 +34,12 @@ public static byte[] ReadBlock(this Stream stream, byte[] buffer, int offset, in
} }
while ((read > 0) && (total < count)); while ((read > 0) && (total < count));
if (total < count) if (total < minCount)
{ {
throw new EndOfStreamException(); throw new EndOfStreamException();
} }
return buffer; return total;
} }
public static void SkipBlock(this Stream stream, int count) public static void SkipBlock(this Stream stream, int count)
@ -57,8 +56,7 @@ public static void SkipBlock(this Stream stream, int count)
else else
{ {
const int BufferSize = 1024; const int BufferSize = 1024;
byte[] buffer = new byte[BufferSize]; var buffer = new byte[BufferSize];
int total = 0; int total = 0;
int read; int read;
do do

View File

@ -27,12 +27,14 @@ public static Disk525 CreateDisk(string name, Stream stream, bool isWriteProtect
if (name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) if (name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase))
{ {
byte[] data = stream.ReadBlock(TrackCount * SectorCount * SectorSize); var data = new byte[TrackCount * SectorCount * SectorSize];
stream.ReadBlock(data);
return new DiskDsk(name, data, isWriteProtected); return new DiskDsk(name, data, isWriteProtected);
} }
else if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase)) else if (name.EndsWith(".nib", StringComparison.OrdinalIgnoreCase))
{ {
byte[] data = stream.ReadBlock(TrackCount * TrackSize); var data = new byte[TrackCount * TrackSize];
stream.ReadBlock(data);
return new DiskNib(name, data, isWriteProtected); return new DiskNib(name, data, isWriteProtected);
} }
@ -49,7 +51,7 @@ public static Disk525 LoadState(BinaryReader reader, Version version)
string name = reader.ReadString(); string name = reader.ReadString();
bool isWriteProtected = reader.ReadBoolean(); bool isWriteProtected = reader.ReadBoolean();
byte[] data = reader.ReadBytes(reader.ReadInt32()); var data = reader.ReadBytes(reader.ReadInt32());
if (name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase)) if (name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -16,7 +16,6 @@ public DiskIIController(Machine machine) :
public override void Initialize() public override void Initialize()
{ {
StorageService.LoadResource("Roms/DiskII.rom", stream => stream.ReadBlock(_romRegionC1C7)); StorageService.LoadResource("Roms/DiskII.rom", stream => stream.ReadBlock(_romRegionC1C7));
StorageService.LoadResource("Disks/Default.dsk", stream => _drives[0].InsertDisk("Default.dsk", stream, false));
} }
public override void Reset() public override void Reset()

View File

@ -55,11 +55,6 @@ public void SaveState(BinaryWriter writer)
} }
} }
public void InsertDisk(string fileName, bool isWriteProtected)
{
StorageService.LoadFile(fileName, stream => InsertDisk(fileName, stream, isWriteProtected));
}
public void InsertDisk(string name, Stream stream, bool isWriteProtected) public void InsertDisk(string name, Stream stream, bool isWriteProtected)
{ {
FlushTrack(); FlushTrack();

View File

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Jellyfish.Library;
using Jellyfish.Virtu.Services; using Jellyfish.Virtu.Services;
namespace Jellyfish.Virtu namespace Jellyfish.Virtu
@ -48,6 +48,11 @@ public void Dispose()
_unpauseEvent.Close(); _unpauseEvent.Close();
} }
public IEnumerable<T> GetCards<T>() where T : PeripheralCard
{
return Slots.Where(card => card is T).Cast<T>();
}
public void Reset() public void Reset()
{ {
foreach (var component in Components) foreach (var component in Components)
@ -88,20 +93,6 @@ public void Stop()
State = MachineState.Stopped; State = MachineState.Stopped;
} }
public DiskIIController FindDiskIIController()
{
for (int i = 7; i >= 1; i--)
{
var diskII = Slots[i] as DiskIIController;
if (diskII != null)
{
return diskII;
}
}
return null;
}
private void Initialize() private void Initialize()
{ {
foreach (var component in Components) foreach (var component in Components)
@ -112,36 +103,59 @@ private void Initialize()
} }
} }
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
private void LoadState() private void LoadState()
{ {
_storageService.Load(Machine.LastStateFileName, stream => #if WINDOWS
var args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{ {
try string name = args[1];
Func<string, Action<Stream>, bool> loader = StorageService.LoadFile;
if (name.StartsWith("res://"))
{ {
using (var reader = new BinaryReader(stream)) name = name.Substring(6);
{ loader = StorageService.LoadResource;
string stateSignature = reader.ReadString();
var stateVersion = new Version(reader.ReadString());
if ((stateSignature == StateSignature) && (stateVersion == new Version(Machine.Version))) // avoid state version mismatch (for now)
{
foreach (var component in Components)
{
//_debugService.WriteLine("Loading component '{0}' state", component.GetType().Name);
component.LoadState(reader, stateVersion);
//_debugService.WriteLine("Loaded component '{0}' state", component.GetType().Name);
}
}
}
} }
catch (Exception)
if (name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
{ {
if (Debugger.IsAttached) loader(name, stream => LoadState(stream));
{
Debugger.Break();
}
} }
}); else if (name.EndsWith(".prg", StringComparison.OrdinalIgnoreCase))
{
loader(name, stream => Memory.LoadProgram(stream));
}
else if (Regex.IsMatch(name, @"\.(dsk|nib)$", RegexOptions.IgnoreCase))
{
loader(name, stream => BootDiskII.Drives[0].InsertDisk(name, stream, false));
}
}
else
#endif
if (!_storageService.Load(Machine.StateFileName, stream => LoadState(stream)))
{
StorageService.LoadResource("Disks/Default.dsk", stream => BootDiskII.Drives[0].InsertDisk("Default.dsk", stream, false));
}
}
private void LoadState(Stream stream)
{
using (var reader = new BinaryReader(stream))
{
string stateSignature = reader.ReadString();
var stateVersion = new Version(reader.ReadString());
if ((stateSignature != StateSignature) || (stateVersion != new Version(Machine.Version))) // avoid state version mismatch (for now)
{
throw new InvalidOperationException();
}
foreach (var component in Components)
{
//_debugService.WriteLine("Loading component '{0}' state", component.GetType().Name);
component.LoadState(reader, stateVersion);
//_debugService.WriteLine("Loaded component '{0}' state", component.GetType().Name);
}
}
} }
private void Uninitialize() private void Uninitialize()
@ -156,26 +170,29 @@ private void Uninitialize()
private void SaveState() private void SaveState()
{ {
_storageService.Save(Machine.LastStateFileName, stream => _storageService.Save(Machine.StateFileName, stream => SaveState(stream));
}
private void SaveState(Stream stream)
{
using (var writer = new BinaryWriter(stream))
{ {
using (var writer = new BinaryWriter(stream)) writer.Write(StateSignature);
writer.Write(Machine.Version);
foreach (var component in Components)
{ {
writer.Write(StateSignature); //_debugService.WriteLine("Saving component '{0}' state", component.GetType().Name);
writer.Write(Machine.Version); component.SaveState(writer);
foreach (var component in Components) //_debugService.WriteLine("Saved component '{0}' state", component.GetType().Name);
{
//_debugService.WriteLine("Saving component '{0}' state", component.GetType().Name);
component.SaveState(writer);
//_debugService.WriteLine("Saved component '{0}' state", component.GetType().Name);
}
} }
}); }
} }
private void Run() // machine thread private void Run() // machine thread
{ {
_debugService = Services.GetService<DebugService>(); //_debugService = Services.GetService<DebugService>();
_storageService = Services.GetService<StorageService>(); _storageService = Services.GetService<StorageService>();
_bootDiskII = GetCards<DiskIIController>().Last();
Initialize(); Initialize();
Reset(); Reset();
@ -225,17 +242,20 @@ private void Run() // machine thread
public PeripheralCard Slot6 { get; private set; } public PeripheralCard Slot6 { get; private set; }
public PeripheralCard Slot7 { get; private set; } public PeripheralCard Slot7 { get; private set; }
public DiskIIController BootDiskII { get { return _bootDiskII; } }
public Collection<PeripheralCard> Slots { get; private set; } public Collection<PeripheralCard> Slots { get; private set; }
public Collection<MachineComponent> Components { get; private set; } public Collection<MachineComponent> Components { get; private set; }
public Thread Thread { get; private set; } public Thread Thread { get; private set; }
private const string LastStateFileName = "LastState.bin"; private const string StateFileName = "State.bin";
private const string StateSignature = "Virtu"; private const string StateSignature = "Virtu";
private DebugService _debugService; //private DebugService _debugService;
private StorageService _storageService; private StorageService _storageService;
private volatile MachineState _state; private volatile MachineState _state;
private DiskIIController _bootDiskII;
private AutoResetEvent _pauseEvent = new AutoResetEvent(false); private AutoResetEvent _pauseEvent = new AutoResetEvent(false);
private AutoResetEvent _unpauseEvent = new AutoResetEvent(false); private AutoResetEvent _unpauseEvent = new AutoResetEvent(false);

View File

@ -124,6 +124,45 @@ public override void Reset() // [7-3]
MapRegionD0FF(); MapRegionD0FF();
} }
public void LoadProgram(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
int address = stream.ReadByte();
address |= stream.ReadByte() << 8;
if (address < 0)
{
throw new EndOfStreamException();
}
int entry = address;
if (address < 0x0200)
{
address += stream.ReadBlock(_ramMainRegion0001, address, 0);
}
if ((0x0200 <= address) && (address < 0xC000))
{
address += stream.ReadBlock(_ramMainRegion02BF, address - 0x0200, 0);
}
if ((0xC000 <= address) && (address < 0xD000))
{
address += stream.ReadBlock(_ramMainBank1RegionD0DF, address - 0xC000, 0);
}
if ((0xD000 <= address) && (address < 0xE000))
{
address += stream.ReadBlock(_ramMainBank2RegionD0DF, address - 0xD000, 0);
}
if (0xE000 <= address)
{
address += stream.ReadBlock(_ramMainRegionE0FF, address - 0xE000, 0);
}
SetWarmEntry(entry); // assumes autostart monitor
}
public override void LoadState(BinaryReader reader, Version version) public override void LoadState(BinaryReader reader, Version version)
{ {
if (reader == null) if (reader == null)
@ -1507,6 +1546,13 @@ private void SetZeroPage(bool value)
} }
#endregion #endregion
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) private static int SetBit7(int data, bool value)
{ {
return value ? (data | 0x80) : (data & 0x7F); return value ? (data | 0x80) : (data & 0x7F);

View File

@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.IO.IsolatedStorage; using System.IO.IsolatedStorage;
using Jellyfish.Library;
namespace Jellyfish.Virtu.Services namespace Jellyfish.Virtu.Services
{ {
@ -13,51 +12,36 @@ public IsolatedStorageService(Machine machine) :
{ {
} }
public override void Load(string fileName, Action<Stream> reader) protected override void OnLoad(string fileName, Action<Stream> reader)
{ {
if (reader == null) if (reader == null)
{ {
throw new ArgumentNullException("reader"); throw new ArgumentNullException("reader");
} }
try using (var store = GetStore())
{ {
using (var store = GetStore()) using (var stream = store.OpenFile(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
using (var stream = store.OpenFile(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) reader(stream);
{
reader(stream);
}
} }
} }
catch (FileNotFoundException)
{
}
catch (IsolatedStorageException)
{
}
} }
public override void Save(string fileName, Action<Stream> writer) protected override void OnSave(string fileName, Action<Stream> writer)
{ {
if (writer == null) if (writer == null)
{ {
throw new ArgumentNullException("writer"); throw new ArgumentNullException("writer");
} }
try using (var store = GetStore())
{ {
using (var store = GetStore()) using (var stream = store.OpenFile(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{ {
using (var stream = store.OpenFile(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) writer(stream);
{
writer(stream);
}
} }
} }
catch (IsolatedStorageException)
{
}
} }
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]

View File

@ -1,9 +1,9 @@
using System; using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Resources;
using System.Security; using System.Security;
using Jellyfish.Library;
using Jellyfish.Virtu.Properties; using Jellyfish.Virtu.Properties;
namespace Jellyfish.Virtu.Services namespace Jellyfish.Virtu.Services
@ -15,12 +15,27 @@ protected StorageService(Machine machine) :
{ {
} }
public abstract void Load(string fileName, Action<Stream> reader); [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public bool Load(string fileName, Action<Stream> reader)
{
try
{
OnLoad(fileName, reader);
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return false;
}
return true;
}
#if !WINDOWS #if !WINDOWS
[SecuritySafeCritical] [SecuritySafeCritical]
#endif #endif
public static void LoadFile(string fileName, Action<Stream> reader) [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static bool LoadFile(string fileName, Action<Stream> reader)
{ {
if (reader == null) if (reader == null)
{ {
@ -34,15 +49,20 @@ public static void LoadFile(string fileName, Action<Stream> reader)
reader(stream); reader(stream);
} }
} }
catch (FileNotFoundException) catch (Exception e)
{ {
Debug.WriteLine(e.ToString());
return false;
} }
return true;
} }
#if !WINDOWS #if !WINDOWS
[SecuritySafeCritical] [SecuritySafeCritical]
#endif #endif
public static void LoadFile(FileInfo fileInfo, Action<Stream> reader) [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static bool LoadFile(FileInfo fileInfo, Action<Stream> reader)
{ {
if (fileInfo == null) if (fileInfo == null)
{ {
@ -60,12 +80,17 @@ public static void LoadFile(FileInfo fileInfo, Action<Stream> reader)
reader(stream); reader(stream);
} }
} }
catch (SecurityException) catch (Exception e)
{ {
Debug.WriteLine(e.ToString());
return false;
} }
return true;
} }
public static void LoadResource(string resourceName, Action<Stream> reader) [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static bool LoadResource(string resourceName, Action<Stream> reader)
{ {
if (reader == null) if (reader == null)
{ {
@ -79,33 +104,63 @@ public static void LoadResource(string resourceName, Action<Stream> reader)
reader(stream); reader(stream);
} }
} }
catch (FileNotFoundException) catch (Exception e)
{ {
Debug.WriteLine(e.ToString());
return false;
} }
return true;
} }
public abstract void Save(string fileName, Action<Stream> writer); [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public bool Save(string fileName, Action<Stream> writer)
{
try
{
OnSave(fileName, writer);
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return false;
}
return true;
}
#if !WINDOWS #if !WINDOWS
[SecuritySafeCritical] [SecuritySafeCritical]
#endif #endif
public static void SaveFile(string fileName, Action<Stream> writer) [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static bool SaveFile(string fileName, Action<Stream> writer)
{ {
if (writer == null) if (writer == null)
{ {
throw new ArgumentNullException("writer"); throw new ArgumentNullException("writer");
} }
using (var stream = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) try
{ {
writer(stream); using (var stream = File.Open(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
{
writer(stream);
}
} }
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return false;
}
return true;
} }
#if !WINDOWS #if !WINDOWS
[SecuritySafeCritical] [SecuritySafeCritical]
#endif #endif
public static void SaveFile(FileInfo fileInfo, Action<Stream> writer) [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
public static bool SaveFile(FileInfo fileInfo, Action<Stream> writer)
{ {
if (fileInfo == null) if (fileInfo == null)
{ {
@ -116,12 +171,26 @@ public static void SaveFile(FileInfo fileInfo, Action<Stream> writer)
throw new ArgumentNullException("writer"); throw new ArgumentNullException("writer");
} }
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.None)) try
{ {
writer(stream); using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write, FileShare.None))
{
writer(stream);
}
} }
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return false;
}
return true;
} }
protected abstract void OnLoad(string fileName, Action<Stream> reader);
protected abstract void OnSave(string fileName, Action<Stream> writer);
private static Stream GetResourceStream(string resourceName) private static Stream GetResourceStream(string resourceName)
{ {
resourceName = "Jellyfish.Virtu." + resourceName.Replace('/', '.'); resourceName = "Jellyfish.Virtu." + resourceName.Replace('/', '.');

View File

@ -70,11 +70,7 @@ private void OnDiskButtonClick(int drive)
if (result.HasValue && result.Value) if (result.HasValue && result.Value)
{ {
_machine.Pause(); _machine.Pause();
var diskII = _machine.FindDiskIIController(); StorageService.LoadFile(dialog.File, stream => _machine.BootDiskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false));
if (diskII != null)
{
StorageService.LoadFile(dialog.File, stream => diskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false));
}
_machine.Unpause(); _machine.Unpause();
} }
} }

View File

@ -70,11 +70,7 @@ private void OnCompositionTargetRendering(object sender, EventArgs e)
// if (result.HasValue && result.Value) // if (result.HasValue && result.Value)
// { // {
// _machine.Pause(); // _machine.Pause();
// var diskII = _machine.FindDiskIIController(); // StorageService.LoadFile(dialog.File, stream => _machine.BootDiskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false));
// if (diskII != null)
// {
// StorageService.LoadFile(dialog.File, stream => diskII.Drives[drive].InsertDisk(dialog.File.Name, stream, false));
// }
// _machine.Unpause(); // _machine.Unpause();
// } // }
//} //}

View File

@ -1,6 +1,5 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.IO;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@ -72,11 +71,7 @@ private void OnDiskButtonClick(int drive)
if (result.HasValue && result.Value) if (result.HasValue && result.Value)
{ {
_machine.Pause(); _machine.Pause();
var diskII = _machine.FindDiskIIController(); StorageService.LoadFile(dialog.FileName, stream => _machine.BootDiskII.Drives[drive].InsertDisk(dialog.FileName, stream, false));
if (diskII != null)
{
StorageService.LoadFile(dialog.FileName, stream => diskII.Drives[drive].InsertDisk(dialog.FileName, stream, false));
}
_machine.Unpause(); _machine.Unpause();
} }
} }

View File

@ -18,29 +18,23 @@ public XnaStorageService(Machine machine, GameBase game) :
_game = game; _game = game;
} }
public override void Load(string fileName, Action<Stream> reader) protected override void OnLoad(string fileName, Action<Stream> reader)
{ {
if (reader == null) if (reader == null)
{ {
throw new ArgumentNullException("reader"); throw new ArgumentNullException("reader");
} }
try using (var storageContainer = OpenContainer())
{ {
using (var storageContainer = OpenContainer()) using (var stream = storageContainer.OpenFile(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{ {
using (var stream = storageContainer.OpenFile(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) reader(stream);
{
reader(stream);
}
} }
} }
catch (FileNotFoundException)
{
}
} }
public override void Save(string fileName, Action<Stream> writer) protected override void OnSave(string fileName, Action<Stream> writer)
{ {
if (writer == null) if (writer == null)
{ {