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

View File

@ -27,12 +27,14 @@ namespace Jellyfish.Virtu
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);
}
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);
}
@ -49,7 +51,7 @@ namespace Jellyfish.Virtu
string name = reader.ReadString();
bool isWriteProtected = reader.ReadBoolean();
byte[] data = reader.ReadBytes(reader.ReadInt32());
var data = reader.ReadBytes(reader.ReadInt32());
if (name.EndsWith(".dsk", StringComparison.OrdinalIgnoreCase))
{

View File

@ -16,7 +16,6 @@ namespace Jellyfish.Virtu
public override void Initialize()
{
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()

View File

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

View File

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Jellyfish.Library;
using Jellyfish.Virtu.Services;
namespace Jellyfish.Virtu
@ -48,6 +48,11 @@ namespace Jellyfish.Virtu
_unpauseEvent.Close();
}
public IEnumerable<T> GetCards<T>() where T : PeripheralCard
{
return Slots.Where(card => card is T).Cast<T>();
}
public void Reset()
{
foreach (var component in Components)
@ -88,20 +93,6 @@ namespace Jellyfish.Virtu
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()
{
foreach (var component in Components)
@ -112,36 +103,59 @@ namespace Jellyfish.Virtu
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
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))
{
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);
}
}
}
name = name.Substring(6);
loader = StorageService.LoadResource;
}
catch (Exception)
if (name.EndsWith(".bin", StringComparison.OrdinalIgnoreCase))
{
if (Debugger.IsAttached)
{
Debugger.Break();
}
loader(name, stream => LoadState(stream));
}
});
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()
@ -156,26 +170,29 @@ namespace Jellyfish.Virtu
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);
writer.Write(Machine.Version);
foreach (var component in Components)
{
//_debugService.WriteLine("Saving component '{0}' state", component.GetType().Name);
component.SaveState(writer);
//_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
{
_debugService = Services.GetService<DebugService>();
//_debugService = Services.GetService<DebugService>();
_storageService = Services.GetService<StorageService>();
_bootDiskII = GetCards<DiskIIController>().Last();
Initialize();
Reset();
@ -225,17 +242,20 @@ namespace Jellyfish.Virtu
public PeripheralCard Slot6 { get; private set; }
public PeripheralCard Slot7 { get; private set; }
public DiskIIController BootDiskII { get { return _bootDiskII; } }
public Collection<PeripheralCard> Slots { get; private set; }
public Collection<MachineComponent> Components { 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 DebugService _debugService;
//private DebugService _debugService;
private StorageService _storageService;
private volatile MachineState _state;
private DiskIIController _bootDiskII;
private AutoResetEvent _pauseEvent = new AutoResetEvent(false);
private AutoResetEvent _unpauseEvent = new AutoResetEvent(false);

View File

@ -124,6 +124,45 @@ namespace Jellyfish.Virtu
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)
{
if (reader == null)
@ -1507,6 +1546,13 @@ namespace Jellyfish.Virtu
}
#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)
{
return value ? (data | 0x80) : (data & 0x7F);

View File

@ -2,7 +2,6 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.IO.IsolatedStorage;
using Jellyfish.Library;
namespace Jellyfish.Virtu.Services
{
@ -13,51 +12,36 @@ namespace Jellyfish.Virtu.Services
{
}
public override void Load(string fileName, Action<Stream> reader)
protected override void OnLoad(string fileName, Action<Stream> reader)
{
if (reader == null)
{
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)
{
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")]

View File

@ -1,9 +1,9 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Resources;
using System.Security;
using Jellyfish.Library;
using Jellyfish.Virtu.Properties;
namespace Jellyfish.Virtu.Services
@ -15,12 +15,27 @@ namespace Jellyfish.Virtu.Services
{
}
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
[SecuritySafeCritical]
#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)
{
@ -34,15 +49,20 @@ namespace Jellyfish.Virtu.Services
reader(stream);
}
}
catch (FileNotFoundException)
catch (Exception e)
{
Debug.WriteLine(e.ToString());
return false;
}
return true;
}
#if !WINDOWS
[SecuritySafeCritical]
#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)
{
@ -60,12 +80,17 @@ namespace Jellyfish.Virtu.Services
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)
{
@ -79,33 +104,63 @@ namespace Jellyfish.Virtu.Services
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
[SecuritySafeCritical]
#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)
{
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
[SecuritySafeCritical]
#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)
{
@ -116,12 +171,26 @@ namespace Jellyfish.Virtu.Services
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)
{
resourceName = "Jellyfish.Virtu." + resourceName.Replace('/', '.');

View File

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

View File

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

View File

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

View File

@ -18,29 +18,23 @@ namespace Jellyfish.Virtu.Services
_game = game;
}
public override void Load(string fileName, Action<Stream> reader)
protected override void OnLoad(string fileName, Action<Stream> reader)
{
if (reader == null)
{
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)
{