diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs
index 4781963..989f06a 100644
--- a/Contralto/AltoSystem.cs
+++ b/Contralto/AltoSystem.cs
@@ -46,6 +46,7 @@ namespace Contralto
_orbitController = new OrbitController(this);
_audioDAC = new AudioDAC(this);
_organKeyboard = new OrganKeyboard(this);
+ _tridentController = new TridentController(this);
_cpu = new AltoCPU(this);
@@ -73,7 +74,8 @@ namespace Contralto
_mouse.Reset();
_cpu.Reset();
_ethernetController.Reset();
- _orbitController.Reset();
+ _orbitController.Reset();
+ _tridentController.Reset();
UCodeMemory.Reset();
}
@@ -104,6 +106,17 @@ namespace Contralto
// Allow the DAC to flush its output
//
_audioDAC.Shutdown();
+
+ //
+ // Save disk contents
+ //
+ _diskController.CommitDisk(0);
+ _diskController.CommitDisk(1);
+
+ for (int i = 0; i < 8; i++)
+ {
+ _tridentController.CommitDisk(i);
+ }
}
public void SingleStep()
@@ -116,73 +129,131 @@ namespace Contralto
_scheduler.Clock();
}
- public void LoadDrive(int drive, string path)
+ public void LoadDiabloDrive(int drive, string path, bool newImage)
{
if (drive < 0 || drive > 1)
{
throw new InvalidOperationException("drive must be 0 or 1.");
}
- DiabloDiskType type;
+ //
+ // Commit the current disk first
+ //
+ _diskController.CommitDisk(drive);
+
+ DiskGeometry geometry;
//
// We select the disk type based on the file extension. Very elegant.
//
switch(Path.GetExtension(path).ToLowerInvariant())
{
+ case ".dsk":
+ geometry = DiskGeometry.Diablo31;
+ break;
+
case ".dsk44":
- type = DiabloDiskType.Diablo44;
+ geometry = DiskGeometry.Diablo44;
break;
default:
- type = DiabloDiskType.Diablo31;
+ geometry = DiskGeometry.Diablo31;
break;
- }
+ }
- DiabloPack newPack = new DiabloPack(type);
- using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
- {
- newPack.Load(fs, path, false);
- fs.Close();
+ IDiskPack newPack;
+
+ if (newImage)
+ {
+ newPack = InMemoryDiskPack.CreateEmpty(geometry, path);
+ }
+ else
+ {
+ newPack = InMemoryDiskPack.Load(geometry, path);
}
_diskController.Drives[drive].LoadPack(newPack);
}
- public void UnloadDrive(int drive)
+ public void UnloadDiabloDrive(int drive)
{
if (drive < 0 || drive > 1)
{
throw new InvalidOperationException("drive must be 0 or 1.");
}
+ //
+ // Commit the current disk first
+ //
+ _diskController.CommitDisk(drive);
+
_diskController.Drives[drive].UnloadPack();
}
- //
- // Disk handling
- //
- public void CommitDiskPack(int driveId)
+ public void LoadTridentDrive(int drive, string path, bool newImage)
{
- DiabloDrive drive = _diskController.Drives[driveId];
- if (drive.IsLoaded)
+ if (drive < 0 || drive > 8)
{
- using (FileStream fs = new FileStream(drive.Pack.PackName, FileMode.Create, FileAccess.Write))
- {
- try
- {
- drive.Pack.Save(fs);
- }
- catch (Exception e)
- {
- // TODO: this does not really belong here.
- System.Windows.Forms.MessageBox.Show(String.Format("Unable to save disk {0}'s contents. Error {0}. Any changes have been lost.", e.Message), "Disk save error");
- }
- }
+ throw new InvalidOperationException("drive must be between 0 and 7.");
}
+
+ //
+ // Commit the current disk first
+ //
+ _tridentController.CommitDisk(drive);
+
+ DiskGeometry geometry;
+
+ //
+ // We select the disk type based on the file extension. Very elegant.
+ //
+ switch (Path.GetExtension(path).ToLowerInvariant())
+ {
+ case ".t80":
+ geometry = DiskGeometry.TridentT80;
+ break;
+
+ case ".t300":
+ geometry = DiskGeometry.TridentT300;
+ break;
+
+ default:
+ geometry = DiskGeometry.TridentT80;
+ break;
+ }
+
+
+ IDiskPack newPack;
+
+ if (newImage)
+ {
+ newPack = FileBackedDiskPack.CreateEmpty(geometry, path);
+ }
+ else
+ {
+ newPack = FileBackedDiskPack.Load(geometry, path);
+ }
+
+ _tridentController.Drives[drive].LoadPack(newPack);
}
+ public void UnloadTridentDrive(int drive)
+ {
+ if (drive < 0 || drive > 7)
+ {
+ throw new InvalidOperationException("drive must be between 0 and 7.");
+ }
+
+ //
+ // Commit the current disk first
+ //
+ _tridentController.CommitDisk(drive);
+
+ _tridentController.Drives[drive].UnloadPack();
+ }
+
+
public void PressBootKeys(AlternateBootType bootType)
{
switch(bootType)
@@ -237,6 +308,11 @@ namespace Contralto
get { return _orbitController; }
}
+ public TridentController TridentController
+ {
+ get { return _tridentController; }
+ }
+
public Scheduler Scheduler
{
get { return _scheduler; }
@@ -253,6 +329,7 @@ namespace Contralto
private OrbitController _orbitController;
private AudioDAC _audioDAC;
private OrganKeyboard _organKeyboard;
+ private TridentController _tridentController;
private Scheduler _scheduler;
}
diff --git a/Contralto/App.config b/Contralto/App.config
index 9fb50f4..4b0d123 100644
--- a/Contralto/App.config
+++ b/Contralto/App.config
@@ -67,6 +67,22 @@
True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs
index f6e0116..22201f8 100644
--- a/Contralto/CPU/CPU.cs
+++ b/Contralto/CPU/CPU.cs
@@ -26,6 +26,7 @@ namespace Contralto.CPU
Invalid = -1,
Emulator = 0,
Orbit = 1,
+ TridentOutput = 3,
DiskSector = 4,
Ethernet = 7,
MemoryRefresh = 8,
@@ -35,6 +36,7 @@ namespace Contralto.CPU
DisplayVertical = 12,
Parity = 13,
DiskWord = 14,
+ TridentInput = 15,
}
public partial class AltoCPU : IClockable
@@ -54,6 +56,8 @@ namespace Contralto.CPU
_tasks[(int)TaskType.Ethernet] = new EthernetTask(this);
_tasks[(int)TaskType.Parity] = new ParityTask(this);
_tasks[(int)TaskType.Orbit] = new OrbitTask(this);
+ _tasks[(int)TaskType.TridentInput] = new TridentTask(this, true);
+ _tasks[(int)TaskType.TridentOutput] = new TridentTask(this, false);
Reset();
}
@@ -182,7 +186,7 @@ namespace Contralto.CPU
_tasks[i].SoftReset();
}
}
-
+
Log.Write(LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr));
UCodeMemory.LoadBanksFromRMR(_rmr);
@@ -202,6 +206,9 @@ namespace Contralto.CPU
//
WakeupTask(CPU.TaskType.DiskSector);
BlockTask(CPU.TaskType.DiskWord);
+
+ BlockTask(CPU.TaskType.TridentInput);
+ BlockTask(CPU.TaskType.TridentOutput);
}
///
diff --git a/Contralto/CPU/MicroInstruction.cs b/Contralto/CPU/MicroInstruction.cs
index 9151942..c013d84 100644
--- a/Contralto/CPU/MicroInstruction.cs
+++ b/Contralto/CPU/MicroInstruction.cs
@@ -217,6 +217,21 @@ namespace Contralto.CPU
OrbitROSCommand = 14,
}
+ ///
+ /// Trident disk controller, from the microcode.
+ ///
+ enum TridentF2
+ {
+ ReadKDTA = 6,
+ STATUS = 8,
+ KTAG = 10,
+ WriteKDTA = 11,
+ WAIT = 12, // These two are identical in function
+ WAIT2 = 13,
+ RESET = 14,
+ EMPTY = 15,
+ }
+
///
/// MicroInstruction encapsulates the decoding of a microinstruction word.
/// It also caches precomputed metadata related to the microinstruction that
diff --git a/Contralto/CPU/Tasks/EmulatorTask.cs b/Contralto/CPU/Tasks/EmulatorTask.cs
index bb2f4f4..1b05edd 100644
--- a/Contralto/CPU/Tasks/EmulatorTask.cs
+++ b/Contralto/CPU/Tasks/EmulatorTask.cs
@@ -33,6 +33,9 @@ namespace Contralto.CPU
// The Wakeup signal is always true for the Emulator task.
_wakeup = true;
+
+ // The Emulator is a RAM-related task.
+ _ramTask = true;
}
public override void Reset()
@@ -138,20 +141,26 @@ namespace Contralto.CPU
//
switch(_busData)
{
- case 1:
- case 2:
- case 3:
+ case 0x01:
+ case 0x02:
+ case 0x03:
// Ethernet
_cpu._system.EthernetController.STARTF(_busData);
break;
- case 4:
- // Orbit
+ case 0x04:
+ // Orbit
_cpu._system.OrbitController.STARTF(_busData);
break;
+ case 0x10:
+ case 0x20:
+ // Trident start
+ _cpu._system.TridentController.STARTF(_busData);
+ break;
+
default:
- Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for non-Ethernet device (code {0})",
+ Log.Write(Logging.LogType.Warning, Logging.LogComponent.EmulatorTask, "STARTF for unknown device (code {0})",
Conversion.ToOctal(_busData));
break;
}
@@ -163,6 +172,8 @@ namespace Contralto.CPU
break;
case EmulatorF1.RDRAM:
+ // TODO: move RDRAM, WRTRAM and S-register BS stuff into the main Task implementation,
+ // guarded by a check of _ramTask.
_rdRam = true;
break;
diff --git a/Contralto/CPU/Tasks/OrbitTask.cs b/Contralto/CPU/Tasks/OrbitTask.cs
index e192a14..0de6245 100644
--- a/Contralto/CPU/Tasks/OrbitTask.cs
+++ b/Contralto/CPU/Tasks/OrbitTask.cs
@@ -31,6 +31,9 @@ namespace Contralto.CPU
{
_taskType = TaskType.Orbit;
_wakeup = false;
+
+ // The Orbit task is RAM-related.
+ _ramTask = true;
}
protected override void ExecuteBlock()
diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs
index aa9ce98..3b01a14 100644
--- a/Contralto/CPU/Tasks/Task.cs
+++ b/Contralto/CPU/Tasks/Task.cs
@@ -139,13 +139,13 @@ namespace Contralto.CPU
protected virtual InstructionCompletion ExecuteInstruction(MicroInstruction instruction)
{
InstructionCompletion completion = InstructionCompletion.Normal;
- bool swMode = false;
+ bool swMode = false;
ushort aluData;
ushort nextModifier;
bool softReset = _softReset;
_loadR = false;
_loadS = false;
- _rSelect = 0;
+ _rSelect = 0;
_busData = 0;
_softReset = false;
@@ -159,7 +159,7 @@ namespace Contralto.CPU
{
if (!_cpu._system.MemoryBus.Ready(instruction.MemoryOperation))
{
- // Suspend operation for this cycle.
+ // Suspend operation for this cycle.
return InstructionCompletion.MemoryWait;
}
}
@@ -248,6 +248,12 @@ namespace Contralto.CPU
_busData &= instruction.ConstantValue;
}
+ //
+ // Let F2s that need to modify bus data before the ALU runs do their thing.
+ // (This is used by the Trident KDTA special functions)
+ //
+ ExecuteSpecialFunction2PostBusSource(instruction);
+
//
// If there was a RDRAM operation last cycle, we AND in the uCode RAM data here.
//
@@ -487,7 +493,7 @@ namespace Contralto.CPU
// address register... which is loaded from the ALU output whenver T is loaded
// from its source."
//
- UCodeMemory.LoadControlRAMAddress(aluData);
+ UCodeMemory.LoadControlRAMAddress(aluData);
}
// Load L (and M) from ALU outputs.
@@ -496,8 +502,7 @@ namespace Contralto.CPU
_cpu._l = aluData;
// Only RAM-related tasks can modify M.
- if (_taskType == TaskType.Emulator ||
- _taskType == TaskType.Orbit)
+ if (_ramTask)
{
_cpu._m = aluData;
}
@@ -587,6 +592,16 @@ namespace Contralto.CPU
// Nothing by default.
}
+ ///
+ /// Executes after bus sources are selected but before the ALU. Used to allow Task-specific F2s that need to
+ /// modify bus data to do so.
+ ///
+ ///
+ protected virtual void ExecuteSpecialFunction2PostBusSource(MicroInstruction instruction)
+ {
+ // Nothing by default.
+ }
+
///
/// Executes after the ALU has run but before the shifter runs, provides task-specific implementations
/// the opportunity to handle task-specific F2s.
@@ -629,6 +644,14 @@ namespace Contralto.CPU
///
protected SystemType _systemType;
+ //
+ // Task metadata:
+ //
+
+ // Whether this task is RAM-enabled or not
+ // (can access S registers, etc.)
+ protected bool _ramTask;
+
//
// Per uInstruction Task Data:
// Modified by both the base Task implementation and any subclasses
diff --git a/Contralto/CPU/Tasks/TridentTask.cs b/Contralto/CPU/Tasks/TridentTask.cs
new file mode 100644
index 0000000..6a6015c
--- /dev/null
+++ b/Contralto/CPU/Tasks/TridentTask.cs
@@ -0,0 +1,157 @@
+/*
+ This file is part of ContrAlto.
+
+ ContrAlto is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ ContrAlto is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with ContrAlto. If not, see .
+*/
+
+using Contralto.IO;
+using System;
+
+namespace Contralto.CPU
+{
+ public partial class AltoCPU
+ {
+ ///
+ /// TridentTask implements the microcode special functions for the Trident
+ /// disk controller (for both the read and write tasks).
+ ///
+ private sealed class TridentTask : Task
+ {
+ public TridentTask(AltoCPU cpu, bool input) : base(cpu)
+ {
+ _taskType = input ? TaskType.TridentInput : TaskType.TridentOutput;
+ _wakeup = false;
+
+ _tridentController = cpu._system.TridentController;
+
+ // Both Trident tasks are RAM-related
+ _ramTask = true;
+ }
+
+ public override void SoftReset()
+ {
+ //
+ // Stop the controller.
+ //
+ _tridentController.Stop();
+
+ base.SoftReset();
+ }
+
+ protected override ushort GetBusSource(MicroInstruction instruction)
+ {
+ //
+ // The Trident tasks are wired to be RAM-enabled tasks so they can use
+ // S registers.
+ // This code is stolen from the Emulator task; we should REALLY refactor this
+ // since both this and the Orbit Task need it.
+ //
+ EmulatorBusSource ebs = (EmulatorBusSource)instruction.BS;
+
+ switch (ebs)
+ {
+ case EmulatorBusSource.ReadSLocation:
+ if (instruction.RSELECT != 0)
+ {
+ return _cpu._s[_rb][instruction.RSELECT];
+ }
+ else
+ {
+ // "...when reading data from the S registers onto the processor bus,
+ // the RSELECT value 0 causes the current value of the M register to
+ // appear on the bus..."
+ return _cpu._m;
+ }
+
+ case EmulatorBusSource.LoadSLocation:
+ // "When an S register is being loaded from M, the processor bus receives an
+ // undefined value rather than being set to zero."
+ _loadS = true;
+ return 0xffff; // Technically this is an "undefined value," we're defining it as -1.
+
+ default:
+ throw new InvalidOperationException(String.Format("Unhandled bus source {0}", instruction.BS));
+ }
+ }
+
+ protected override void ExecuteSpecialFunction2PostBusSource(MicroInstruction instruction)
+ {
+ TridentF2 tf2 = (TridentF2)instruction.F2;
+
+ switch (tf2)
+ {
+ case TridentF2.ReadKDTA:
+ //
+ // <-KDTA is actually MD<- (SF 6), repurposed to gate disk data onto the bus
+ // iff BS is None. Otherwise it behaves like a normal MD<-. We'll let
+ // the normal Task implementation handle the actual MD<- operation.
+ //
+ if (instruction.BS == BusSource.None)
+ {
+ // _busData at this point should be 0xffff. We could technically
+ // just directly assign the bits...
+ _busData &= _tridentController.KDTA;
+ }
+ break;
+
+ case TridentF2.STATUS:
+ _busData &= _tridentController.STATUS;
+ break;
+
+ case TridentF2.EMPTY:
+ _tridentController.WaitForEmpty();
+ break;
+
+ }
+ }
+
+ protected override void ExecuteSpecialFunction2(MicroInstruction instruction)
+ {
+ TridentF2 tf2 = (TridentF2)instruction.F2;
+
+ switch (tf2)
+ {
+ case TridentF2.KTAG:
+ _tridentController.TagInstruction(_busData);
+ break;
+
+ case TridentF2.WriteKDTA:
+ _tridentController.KDTA = _busData;
+ break;
+
+ case TridentF2.WAIT:
+ case TridentF2.WAIT2:
+ // Identical to BLOCK
+ this.BlockTask();
+ break;
+
+ case TridentF2.RESET:
+ _tridentController.ControllerReset();
+ break;
+
+ case TridentF2.STATUS:
+ case TridentF2.EMPTY:
+ // Handled in PostBusSource override.
+ break;
+
+ default:
+ throw new InvalidOperationException(String.Format("Unhandled trident special function 2 {0}", tf2));
+ }
+ }
+
+ private TridentController _tridentController;
+
+ }
+ }
+}
diff --git a/Contralto/CPU/UCodeDisassembler.cs b/Contralto/CPU/UCodeDisassembler.cs
index a460f54..e082ecf 100644
--- a/Contralto/CPU/UCodeDisassembler.cs
+++ b/Contralto/CPU/UCodeDisassembler.cs
@@ -229,7 +229,13 @@ namespace Contralto.CPU
break;
case SpecialFunction2.StoreMD:
- if (instruction.F1 != SpecialFunction1.LoadMAR)
+ // Special case for Trident
+ if ((task == TaskType.TridentInput || task == TaskType.TridentOutput) &&
+ instruction.BS == BusSource.None)
+ {
+ f2 = "MD← KDTA ";
+ }
+ else if (instruction.F1 != SpecialFunction1.LoadMAR)
{
f2 = "MD← ";
}
@@ -352,6 +358,8 @@ namespace Contralto.CPU
{
case TaskType.Emulator:
case TaskType.Orbit:
+ case TaskType.TridentInput:
+ case TaskType.TridentOutput:
return DisassembleEmulatorBusSource(instruction, out loadS);
default:
@@ -368,7 +376,7 @@ namespace Contralto.CPU
return DisassembleEmulatorSpecialFunction1(instruction);
case TaskType.Orbit:
- return DisassembleOrbitSpecialFunction1(instruction);
+ return DisassembleOrbitSpecialFunction1(instruction);
default:
return String.Format("F1 {0}", Conversion.ToOctal((int)instruction.F1));
@@ -383,7 +391,11 @@ namespace Contralto.CPU
return DisassembleEmulatorSpecialFunction2(instruction);
case TaskType.Orbit:
- return DisassembleOrbitSpecialFunction2(instruction);
+ return DisassembleOrbitSpecialFunction2(instruction);
+
+ case TaskType.TridentInput:
+ case TaskType.TridentOutput:
+ return DisassembleTridentSpecialFunction2(instruction);
default:
return String.Format("F2 {0}", Conversion.ToOctal((int)instruction.F2));
@@ -539,5 +551,38 @@ namespace Contralto.CPU
return String.Format("Orbit F2 {0}", Conversion.ToOctal((int)of2));
}
}
+
+ private static string DisassembleTridentSpecialFunction2(MicroInstruction instruction)
+ {
+ TridentF2 tf2 = (TridentF2)instruction.F2;
+
+ switch (tf2)
+ {
+ case TridentF2.EMPTY:
+ return "EMPTY ";
+
+ case TridentF2.KTAG:
+ return "KTAG← ";
+
+ case TridentF2.ReadKDTA:
+ return "←KDTA ";
+
+ case TridentF2.RESET:
+ return "RESET ";
+
+ case TridentF2.STATUS:
+ return "STATUS ";
+
+ case TridentF2.WAIT:
+ case TridentF2.WAIT2:
+ return "WAIT ";
+
+ case TridentF2.WriteKDTA:
+ return "KDTA← ";
+
+ default:
+ return String.Format("Trident F2 {0}", Conversion.ToOctal((int)tf2));
+ }
+ }
}
}
diff --git a/Contralto/Configuration.cs b/Contralto/Configuration.cs
index bd6be45..7df5fbc 100644
--- a/Contralto/Configuration.cs
+++ b/Contralto/Configuration.cs
@@ -17,6 +17,7 @@
using Contralto.Logging;
using System;
+using System.Collections.Specialized;
using System.IO;
using System.Reflection;
@@ -114,7 +115,14 @@ namespace Contralto
// See if PCap is available.
TestPCap();
- ReadConfiguration();
+ try
+ {
+ ReadConfiguration();
+ }
+ catch
+ {
+ Console.WriteLine("Warning: unable to load configuration. Assuming default settings.");
+ }
// Special case: On first startup, AlternateBoot will come back as "None" which
// is an invalid UI setting; default to Ethernet in this case.
@@ -122,6 +130,15 @@ namespace Contralto
{
AlternateBootType = AlternateBootType.Ethernet;
}
+
+ //
+ // If configuration specifies fewer than 8 Trident disks, we need to pad the list out to 8 with empty entries.
+ //
+ int start = TridentImages.Count;
+ for (int i = start; i < 8; i++)
+ {
+ TridentImages.Add(String.Empty);
+ }
}
///
@@ -144,6 +161,11 @@ namespace Contralto
///
public static string Drive1Image;
+ ///
+ /// The currently loaded images for the Trident controller.
+ ///
+ public static StringCollection TridentImages;
+
///
/// The Ethernet host address for this Alto
///
@@ -294,6 +316,7 @@ namespace Contralto
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
Drive0Image = Properties.Settings.Default.Drive0Image;
Drive1Image = Properties.Settings.Default.Drive1Image;
+ TridentImages = Properties.Settings.Default.TridentImages;
SystemType = (SystemType)Properties.Settings.Default.SystemType;
HostAddress = Properties.Settings.Default.HostAddress;
HostPacketInterfaceName = Properties.Settings.Default.HostPacketInterfaceName;
@@ -308,13 +331,14 @@ namespace Contralto
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
EnablePrinting = Properties.Settings.Default.EnablePrinting;
PrintOutputPath = Properties.Settings.Default.PrintOutputPath;
- ReversePageOrder = Properties.Settings.Default.ReversePageOrder;
+ ReversePageOrder = Properties.Settings.Default.ReversePageOrder;
}
private static void WriteConfigurationWindows()
{
Properties.Settings.Default.Drive0Image = Drive0Image;
Properties.Settings.Default.Drive1Image = Drive1Image;
+ Properties.Settings.Default.TridentImages = TridentImages;
Properties.Settings.Default.SystemType = (int)SystemType;
Properties.Settings.Default.HostAddress = HostAddress;
Properties.Settings.Default.HostPacketInterfaceName = HostPacketInterfaceName;
@@ -478,6 +502,21 @@ namespace Contralto
field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true));
}
break;
+
+ case "StringCollection":
+ {
+ // value is expected to be a comma-delimited set.
+ StringCollection sc = new StringCollection();
+ string[] strings = value.Split(',');
+
+ foreach(string s in strings)
+ {
+ sc.Add(s);
+ }
+
+ field.SetValue(null, sc);
+ }
+ break;
}
}
catch
diff --git a/Contralto/Contralto.cfg b/Contralto/Contralto.cfg
index e0e177a..3217608 100644
--- a/Contralto/Contralto.cfg
+++ b/Contralto/Contralto.cfg
@@ -23,3 +23,15 @@ AlternateBootType = Ethernet
EnablePrinting = true
PrintOutputPath = .
ReversePageOrder = true
+
+# Disk options
+#
+
+# Diablo images: These specify a single image file for drive 0 and drive 1
+#
+# Drive0Image =
+# Drive1Image =
+
+# Trident images: This specifies up to eight images for Trident drives 0 through 8
+# in a comma-delimited list. Empty entries are allowed, as below:
+# TridentImages = image0.dsk80, , image2.dsk300, image3.disk80
diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj
index 3798567..d02af50 100644
--- a/Contralto/Contralto.csproj
+++ b/Contralto/Contralto.csproj
@@ -125,7 +125,9 @@
+
+
@@ -133,6 +135,7 @@
+
@@ -199,7 +202,7 @@
-
+
diff --git a/Contralto/IO/DiabloDrive.cs b/Contralto/IO/DiabloDrive.cs
index f0b32ea..e737faf 100644
--- a/Contralto/IO/DiabloDrive.cs
+++ b/Contralto/IO/DiabloDrive.cs
@@ -75,7 +75,7 @@ namespace Contralto.IO
public void Reset()
{
_sector = 0;
- _cylinder = 0;
+ _cylinder = 0;
_head = 0;
_sectorModified = false;
@@ -91,14 +91,24 @@ namespace Contralto.IO
// Total "words" (really timeslices) per sector.
public static readonly int SectorWordCount = 269 + HeaderOffset + 34;
- public void LoadPack(DiabloPack pack)
+ public void LoadPack(IDiskPack pack)
{
+ if (_pack != null)
+ {
+ _pack.Dispose();
+ }
+
_pack = pack;
Reset();
}
public void UnloadPack()
{
+ if (_pack != null)
+ {
+ _pack.Dispose();
+ }
+
_pack = null;
Reset();
}
@@ -108,7 +118,7 @@ namespace Contralto.IO
get { return _pack != null; }
}
- public DiabloPack Pack
+ public IDiskPack Pack
{
get { return _pack; }
}
@@ -218,13 +228,13 @@ namespace Contralto.IO
// Note that this data is packed in in REVERSE ORDER because that's
// how it gets written out and it's how the Alto expects it to be read back in.
//
- DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector);
+ DiskSector sector = _pack.GetSector(_cylinder, _head, _sector);
// Header (2 words data, 1 word cksum)
for (int i = HeaderOffset + 1, j = 1; i < HeaderOffset + 3; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- _sectorData[i] = new DataCell(sector.Header[j], CellType.Data);
+ _sectorData[i] = new DataCell(sector.ReadHeader(j), CellType.Data);
}
ushort checksum = CalculateChecksum(_sectorData, HeaderOffset + 1, 2);
@@ -235,7 +245,7 @@ namespace Contralto.IO
for (int i = LabelOffset + 1, j = 7; i < LabelOffset + 9; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- _sectorData[i] = new DataCell(sector.Label[j], CellType.Data);
+ _sectorData[i] = new DataCell(sector.ReadLabel(j), CellType.Data);
}
checksum = CalculateChecksum(_sectorData, LabelOffset + 1, 8);
@@ -246,7 +256,7 @@ namespace Contralto.IO
for (int i = DataOffset + 1, j = 255; i < DataOffset + 257; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- _sectorData[i] = new DataCell(sector.Data[j], CellType.Data);
+ _sectorData[i] = new DataCell(sector.ReadData(j), CellType.Data);
}
checksum = CalculateChecksum(_sectorData, DataOffset + 1, 256);
@@ -257,7 +267,7 @@ namespace Contralto.IO
///
/// Commits modified sector data back to the emulated disk.
- /// Intended to be called at the end of the sector / beginning of the next.
+ /// Intended to be called at the end of the sector / beginning of the next.
///
private void CommitSector()
{
@@ -266,33 +276,38 @@ namespace Contralto.IO
return;
}
- DiabloDiskSector sector = _pack.GetSector(_cylinder, _head, _sector);
+ DiskSector sector = _pack.GetSector(_cylinder, _head, _sector);
// Header (2 words data, 1 word cksum)
for (int i = HeaderOffset + 1, j = 1; i < HeaderOffset + 3; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- sector.Header[j] = _sectorData[i].Data;
+ sector.WriteHeader(j, _sectorData[i].Data);
}
// Label (8 words data, 1 word cksum)
for (int i = LabelOffset + 1, j = 7; i < LabelOffset + 9; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- sector.Label[j] = _sectorData[i].Data;
+ sector.WriteLabel(j, _sectorData[i].Data);
}
// sector data (256 words data, 1 word cksum)
for (int i = DataOffset + 1, j = 255; i < DataOffset + 257; i++, j--)
{
// actual data to be loaded from disk / cksum calculated
- sector.Data[j] = _sectorData[i].Data;
+ sector.WriteData(j, _sectorData[i].Data);
}
+
+ //
+ // Commit it back to the pack.
+ //
+ _pack.CommitSector(sector);
}
private void InitSector()
{
- // Fill in sector with default data (basically, fill in non-data areas).
+ // Fill in sector with default data (basically, fill in non-data areas).
//
// header delay, 22 words
@@ -362,6 +377,6 @@ namespace Contralto.IO
// The pack loaded into the drive
- DiabloPack _pack;
+ IDiskPack _pack;
}
}
diff --git a/Contralto/IO/DiabloPack.cs b/Contralto/IO/DiabloPack.cs
deleted file mode 100644
index 1fa7bc9..0000000
--- a/Contralto/IO/DiabloPack.cs
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- This file is part of ContrAlto.
-
- ContrAlto is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- ContrAlto is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with ContrAlto. If not, see .
-*/
-
-using System;
-using System.IO;
-
-namespace Contralto.IO
-{
-
- ///
- /// DiskGeometry encapsulates the geometry of a disk.
- ///
- public struct DiskGeometry
- {
- public DiskGeometry(int cylinders, int tracks, int sectors)
- {
- Cylinders = cylinders;
- Tracks = tracks;
- Sectors = sectors;
- }
-
- public int Cylinders;
- public int Tracks;
- public int Sectors;
- }
-
- public enum DiabloDiskType
- {
- Diablo31,
- Diablo44
- }
-
- ///
- /// DiabloDiskSector encapsulates the records contained in a single Alto disk sector
- /// on a Diablo disk. This includes the header, label, and data records.
- ///
- public class DiabloDiskSector
- {
- public DiabloDiskSector(byte[] header, byte[] label, byte[] data)
- {
- if (header.Length != 4 ||
- label.Length != 16 ||
- data.Length != 512)
- {
- throw new InvalidOperationException("Invalid sector header/label/data length.");
- }
-
- Header = GetUShortArray(header);
- Label = GetUShortArray(label);
- Data = GetUShortArray(data);
- }
-
- private ushort[] GetUShortArray(byte[] data)
- {
- if ((data.Length % 2) != 0)
- {
- throw new InvalidOperationException("Array length must be even.");
- }
-
- ushort[] array = new ushort[data.Length / 2];
-
- int offset = 0;
- for(int i=0;i
- /// Encapsulates disk image data for all disk packs used with the
- /// standard Alto Disk Controller (i.e. the 31 and 44, which differ
- /// only in the number of cylinders)
- ///
- public class DiabloPack
- {
- public DiabloPack(DiabloDiskType type)
- {
- _diskType = type;
- _packName = null;
- _geometry = new DiskGeometry(type == DiabloDiskType.Diablo31 ? 203 : 406, 2, 12);
- _sectors = new DiabloDiskSector[_geometry.Cylinders, _geometry.Tracks, _geometry.Sectors];
- }
-
- public DiskGeometry Geometry
- {
- get { return _geometry; }
- }
-
- public string PackName
- {
- get { return _packName; }
- }
-
- public void Load(Stream imageStream, string path, bool reverseByteOrder)
- {
- _packName = path;
- for(int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
- {
- for(int track = 0; track < _geometry.Tracks; track++)
- {
- for(int sector = 0; sector < _geometry.Sectors; sector++)
- {
- byte[] header = new byte[4]; // 2 words
- byte[] label = new byte[16]; // 8 words
- byte[] data = new byte[512]; // 256 words
-
- //
- // Bitsavers images have an extra word in the header for some reason.
- // ignore it.
- // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
- //
- imageStream.Seek(2, SeekOrigin.Current);
-
-
- if (imageStream.Read(header, 0, header.Length) != header.Length)
- {
- throw new InvalidOperationException("Short read while reading sector header.");
- }
-
- if (imageStream.Read(label, 0, label.Length) != label.Length)
- {
- throw new InvalidOperationException("Short read while reading sector label.");
- }
-
- if (imageStream.Read(data, 0, data.Length) != data.Length)
- {
- throw new InvalidOperationException("Short read while reading sector data.");
- }
-
- if (reverseByteOrder)
- {
- SwapBytes(header);
- SwapBytes(label);
- SwapBytes(data);
- }
-
- _sectors[cylinder, track, sector] = new DiabloDiskSector(header, label, data);
- }
- }
- }
-
- if (imageStream.Position != imageStream.Length)
- {
- throw new InvalidOperationException("Extra data at end of image file.");
- }
- }
-
- public void Save(Stream imageStream)
- {
- for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
- {
- for (int track = 0; track < _geometry.Tracks; track++)
- {
- for (int sector = 0; sector < _geometry.Sectors; sector++)
- {
- byte[] header = new byte[4]; // 2 words
- byte[] label = new byte[16]; // 8 words
- byte[] data = new byte[512]; // 256 words
-
- //
- // Bitsavers images have an extra word in the header for some reason.
- // We will follow this 'standard' when writing out.
- // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
- //
- byte[] dummy = new byte[2];
- imageStream.Write(dummy, 0, 2);
-
- DiabloDiskSector s = _sectors[cylinder, track, sector];
-
- WriteWordBuffer(imageStream, s.Header);
- WriteWordBuffer(imageStream, s.Label);
- WriteWordBuffer(imageStream, s.Data);
- }
- }
- }
- }
-
- public DiabloDiskSector GetSector(int cylinder, int track, int sector)
- {
- return _sectors[cylinder, track, sector];
- }
-
- private void WriteWordBuffer(Stream imageStream, ushort[] buffer)
- {
- // TODO: this is beyond inefficient
- for(int i=0;i> 8));
- }
- }
-
- private void SwapBytes(byte[] data)
- {
- for(int i=0;i.
+*/
+
+using System;
+using System.IO;
+
+namespace Contralto.IO
+{
+
+ ///
+ /// DiskGeometry encapsulates the geometry of a disk.
+ ///
+ public struct DiskGeometry
+ {
+ public DiskGeometry(int cylinders, int heads, int sectors, SectorGeometry sectorGeometry)
+ {
+ Cylinders = cylinders;
+ Heads = heads;
+ Sectors = sectors;
+ SectorGeometry = sectorGeometry;
+ }
+
+ public int Cylinders;
+ public int Heads;
+ public int Sectors;
+ public SectorGeometry SectorGeometry;
+
+ ///
+ /// Returns the total size (in bytes) of a disk with this geometry.
+ /// This includes the extra word-per-sector of the Bitsavers images.
+ ///
+ ///
+ public int GetDiskSizeBytes()
+ {
+ return SectorGeometry.GetSectorSizeBytes() * (Sectors * Heads * Cylinders);
+ }
+
+ //
+ // Standard Diablo geometries
+ //
+ public static readonly DiskGeometry Diablo31 = new DiskGeometry(203, 2, 12, SectorGeometry.Diablo);
+ public static readonly DiskGeometry Diablo44 = new DiskGeometry(406, 2, 12, SectorGeometry.Diablo);
+
+ //
+ // Standard Trident geometries
+ //
+ public static readonly DiskGeometry TridentT80 = new DiskGeometry(815, 5, 9, SectorGeometry.Trident);
+ public static readonly DiskGeometry TridentT300 = new DiskGeometry(815, 19, 9, SectorGeometry.Trident);
+ public static readonly DiskGeometry Shugart4004 = new DiskGeometry(202, 4, 8, SectorGeometry.Trident);
+ public static readonly DiskGeometry Shugart4008 = new DiskGeometry(202, 8, 8, SectorGeometry.Trident);
+ }
+
+ ///
+ /// Describes the geometry of an Alto disk sector in terms of the
+ /// size of the header, label, and data blocks.
+ ///
+ public struct SectorGeometry
+ {
+ ///
+ /// Specifies the layout of a sector on an Alto pack. Sizes are in bytes.
+ ///
+ public SectorGeometry(int headerSizeBytes, int labelSizeBytes, int dataSizeBytes)
+ {
+ HeaderSizeBytes = headerSizeBytes;
+ LabelSizeBytes = labelSizeBytes;
+ DataSizeBytes = dataSizeBytes;
+
+ HeaderSize = HeaderSizeBytes / 2;
+ LabelSize = LabelSizeBytes / 2;
+ DataSize = DataSizeBytes / 2;
+ }
+
+ public int HeaderSize;
+ public int LabelSize;
+ public int DataSize;
+
+ public int HeaderSizeBytes;
+ public int LabelSizeBytes;
+ public int DataSizeBytes;
+
+ ///
+ /// Returns the total size (in bytes) of a sector with this geometry.
+ /// This includes the extra word-per-sector of the Bitsavers images.
+ ///
+ ///
+ public int GetSectorSizeBytes()
+ {
+ return DataSizeBytes +
+ LabelSizeBytes +
+ HeaderSizeBytes +
+ 2; // Extra dummy word
+ }
+
+ public static readonly SectorGeometry Diablo = new SectorGeometry(4, 16, 512);
+ public static readonly SectorGeometry Trident = new SectorGeometry(4, 20, 2048);
+ }
+
+ ///
+ /// DiskSector encapsulates the records contained in a single Alto disk sector
+ /// on a disk. This includes the header, label, and data records.
+ ///
+ public class DiskSector
+ {
+ ///
+ /// Create a new DiskSector populated from the specified stream.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public DiskSector(SectorGeometry geometry, Stream inputStream, int cylinder, int head, int sector)
+ {
+ //
+ // Read in the sector from the input stream.
+ //
+ byte[] header = new byte[geometry.HeaderSizeBytes];
+ byte[] label = new byte[geometry.LabelSizeBytes];
+ byte[] data = new byte[geometry.DataSizeBytes];
+
+ //
+ // Bitsavers images have an extra word in the header for some reason.
+ // ignore it.
+ // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
+ //
+ inputStream.Seek(2, SeekOrigin.Current);
+
+ if (inputStream.Read(header, 0, header.Length) != header.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector header.");
+ }
+
+ if (inputStream.Read(label, 0, label.Length) != label.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector label.");
+ }
+
+ if (inputStream.Read(data, 0, data.Length) != data.Length)
+ {
+ throw new InvalidOperationException("Short read while reading sector data.");
+ }
+
+ _header = GetUShortArray(header);
+ _label = GetUShortArray(label);
+ _data = GetUShortArray(data);
+
+ _cylinder = cylinder;
+ _head = head;
+ _sector = sector;
+
+ _modified = false;
+ }
+
+ ///
+ /// Create a new, empty sector.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public DiskSector(SectorGeometry geometry, int cylinder, int head, int sector)
+ {
+ _header = new ushort[geometry.HeaderSize];
+ _label = new ushort[geometry.LabelSize];
+ _data = new ushort[geometry.DataSize];
+
+ _cylinder = cylinder;
+ _head = head;
+ _sector = sector;
+
+ _modified = true;
+ }
+
+ public int Cylinder
+ {
+ get { return _cylinder; }
+ }
+
+ public int Head
+ {
+ get { return _head; }
+ }
+
+ public int Sector
+ {
+ get { return _sector; }
+ }
+
+ public bool Modified
+ {
+ get { return _modified; }
+ }
+
+ public ushort ReadHeader(int offset)
+ {
+ return _header[offset];
+ }
+
+ public void WriteHeader(int offset, ushort value)
+ {
+ _header[offset] = value;
+ _modified = true;
+ }
+
+ public ushort ReadLabel(int offset)
+ {
+ return _label[offset];
+ }
+
+ public void WriteLabel(int offset, ushort value)
+ {
+ _label[offset] = value;
+ _modified = true;
+ }
+
+ public ushort ReadData(int offset)
+ {
+ return _data[offset];
+ }
+
+ public void WriteData(int offset, ushort value)
+ {
+ _data[offset] = value;
+ _modified = true;
+ }
+
+ public void WriteToStream(Stream s)
+ {
+ //
+ // Bitsavers images have an extra word in the header for some reason.
+ // We will follow this standard when writing out.
+ // TODO: should support different formats ("correct" raw, Alto CopyDisk format, etc.)
+ //
+ byte[] dummy = new byte[2];
+ s.Write(dummy, 0, 2);
+
+ WriteWordBuffer(s, _header);
+ WriteWordBuffer(s, _label);
+ WriteWordBuffer(s, _data);
+
+ _modified = false;
+ }
+
+ private ushort[] GetUShortArray(byte[] data)
+ {
+ if ((data.Length % 2) != 0)
+ {
+ throw new InvalidOperationException("Array length must be even.");
+ }
+
+ ushort[] array = new ushort[data.Length / 2];
+
+ int offset = 0;
+ for(int i=0;i> 8));
+ }
+ }
+
+ private ushort[] _header;
+ private ushort[] _label;
+ private ushort[] _data;
+
+ private int _cylinder;
+ private int _head;
+ private int _sector;
+
+ private bool _modified;
+ }
+
+ ///
+ /// The IDiskPack interface defines a generic mechanism for creating, loading, storing,
+ /// and accessing the sectors of a disk pack.
+ ///
+ public interface IDiskPack : IDisposable
+ {
+ ///
+ /// The geometry of this pack.
+ ///
+ DiskGeometry Geometry { get; }
+
+ ///
+ /// The filename of this pack.
+ ///
+ string PackName { get; }
+
+ ///
+ /// Commits the current in-memory image back to the file it came from.
+ ///
+ void Save();
+
+ ///
+ /// Retrieves the specified sector from storage.
+ ///
+ ///
+ ///
+ ///
+ ///
+ DiskSector GetSector(int cylinder, int head, int sector);
+
+ ///
+ /// Commits this sector back to storage.
+ ///
+ ///
+ void CommitSector(DiskSector sector);
+ }
+
+
+ ///
+ /// In-memory implementation of IDiskPack. Useful for small disks (e.g. Diablo).
+ /// Changes to the in-memory copy are only committed back to the disk image
+ /// when Save is invoked.
+ ///
+ public class InMemoryDiskPack : IDiskPack
+ {
+ ///
+ /// Creates a new, empty disk pack with the specified geometry.
+ ///
+ ///
+ ///
+ public static InMemoryDiskPack CreateEmpty(DiskGeometry geometry, string path)
+ {
+ return new InMemoryDiskPack(geometry, path, false);
+ }
+
+ ///
+ /// Loads an existing disk pack image.
+ ///
+ ///
+ ///
+ ///
+ public static InMemoryDiskPack Load(DiskGeometry geometry, string path)
+ {
+ return new InMemoryDiskPack(geometry, path, true);
+ }
+
+ public void Dispose()
+ {
+ //
+ // Nothing to do here.
+ //
+ }
+
+ private InMemoryDiskPack(DiskGeometry geometry, string path, bool load)
+ {
+ _packName = path;
+ _geometry = geometry;
+ _sectors = new DiskSector[_geometry.Cylinders, _geometry.Heads, _geometry.Sectors];
+
+ if (load)
+ {
+ //
+ // Attempt to load in the specified image file.
+ //
+ using (FileStream imageStream = new FileStream(_packName, FileMode.Open, FileAccess.Read))
+ {
+ try
+ {
+ for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
+ {
+ for (int head = 0; head < _geometry.Heads; head++)
+ {
+ for (int sector = 0; sector < _geometry.Sectors; sector++)
+ {
+ _sectors[cylinder, head, sector] = new DiskSector(_geometry.SectorGeometry, imageStream, cylinder, head, sector);
+ }
+ }
+ }
+
+ if (imageStream.Position != imageStream.Length)
+ {
+ throw new InvalidOperationException("Extra data at end of image file.");
+ }
+ }
+ finally
+ {
+ imageStream.Close();
+ }
+ }
+ }
+ else
+ {
+ //
+ // Just initialize a new, empty disk.
+ //
+ for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
+ {
+ for (int head = 0; head < _geometry.Heads; head++)
+ {
+ for (int sector = 0; sector < _geometry.Sectors; sector++)
+ {
+ _sectors[cylinder, head, sector] = new DiskSector(_geometry.SectorGeometry, cylinder, head, sector);
+ }
+ }
+ }
+ }
+ }
+
+ public DiskGeometry Geometry
+ {
+ get { return _geometry; }
+ }
+
+ public string PackName
+ {
+ get { return _packName; }
+ }
+
+ ///
+ /// Commits the current in-memory image back to the file from which it was loaded.
+ ///
+ public void Save()
+ {
+ using (FileStream imageStream = new FileStream(_packName, FileMode.Create, FileAccess.Write))
+ {
+ try
+ {
+ for (int cylinder = 0; cylinder < _geometry.Cylinders; cylinder++)
+ {
+ for (int head = 0; head < _geometry.Heads; head++)
+ {
+ for (int sector = 0; sector < _geometry.Sectors; sector++)
+ {
+ _sectors[cylinder, head, sector].WriteToStream(imageStream);
+ }
+ }
+ }
+ }
+ finally
+ {
+ imageStream.Close();
+ }
+ }
+ }
+
+ public DiskSector GetSector(int cylinder, int head, int sector)
+ {
+ //
+ // Return the in-memory sector reference.
+ //
+ return _sectors[cylinder, head, sector];
+ }
+
+ public void CommitSector(DiskSector sector)
+ {
+ //
+ // Update the in-memory sector reference to point to this (possibly new) sector object.
+ //
+ if (sector.Modified)
+ {
+ _sectors[sector.Cylinder, sector.Head, sector.Sector] = sector;
+ }
+ }
+
+ private string _packName; // The file from whence the data came
+ private DiskSector[,,] _sectors; // All of the sectors on disk
+ private DiskGeometry _geometry; // The geometry of this disk.
+ }
+
+ ///
+ /// FileBackedDiskPack provides an implementation of IDiskPack where sectors are read into memory
+ /// only when requested, and changes are flushed to disk when use of the sector is complete.
+ ///
+ public class FileBackedDiskPack : IDiskPack
+ {
+ ///
+ /// Creates a new, empty disk pack with the specified geometry.
+ ///
+ ///
+ ///
+ public static FileBackedDiskPack CreateEmpty(DiskGeometry geometry, string path)
+ {
+ return new FileBackedDiskPack(geometry, path, false);
+ }
+
+ ///
+ /// Loads an existing image.
+ ///
+ ///
+ ///
+ ///
+ public static FileBackedDiskPack Load(DiskGeometry geometry, string path)
+ {
+ return new FileBackedDiskPack(geometry, path, true);
+ }
+
+ public void Dispose()
+ {
+ if (_diskStream != null)
+ {
+ _diskStream.Close();
+ }
+ }
+
+ private FileBackedDiskPack(DiskGeometry geometry, string path, bool load)
+ {
+ _packName = path;
+ _geometry = geometry;
+
+ if (load)
+ {
+ //
+ // Attempt to open an existing stream for read/write access.
+ //
+ _diskStream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
+
+ //
+ // Quick sanity check that the disk image is the right size.
+ //
+ if (_diskStream.Length != geometry.GetDiskSizeBytes())
+ {
+ _diskStream.Close();
+ _diskStream = null;
+ throw new InvalidOperationException("Image size is invalid.");
+ }
+ }
+ else
+ {
+ //
+ // Attempt to initialize a new stream with read/write access.
+ //
+ _diskStream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite);
+
+ //
+ // And set the size of the stream.
+ //
+ _diskStream.SetLength(geometry.GetDiskSizeBytes());
+ }
+ }
+
+ public DiskGeometry Geometry
+ {
+ get { return _geometry; }
+ }
+
+ public string PackName
+ {
+ get { return _packName; }
+ }
+
+ ///
+ /// Commits pending changes back to disc.
+ ///
+ ///
+ public void Save()
+ {
+ // Nothing here, we expect CommitSector
+ // to be called by whoever has a sector checked out before shutdown.
+ }
+
+ public DiskSector GetSector(int cylinder, int head, int sector)
+ {
+ //
+ // Retrieve the appropriate sector from disk.
+ // Seek to the appropriate position and read.
+ //
+ _diskStream.Position = GetOffsetForSector(cylinder, head, sector);
+
+ return new DiskSector(_geometry.SectorGeometry, _diskStream, cylinder, head, sector);
+ }
+
+ public void CommitSector(DiskSector sector)
+ {
+ if (sector.Modified)
+ {
+ //
+ // Commit this data back to disk.
+ // Seek to the appropriate position and flush.
+ //
+ _diskStream.Position = GetOffsetForSector(sector.Cylinder, sector.Head, sector.Sector);
+ sector.WriteToStream(_diskStream);
+ }
+ }
+
+ private long GetOffsetForSector(int cylinder, int head, int sector)
+ {
+ int sectorNumber = (cylinder * _geometry.Heads * _geometry.Sectors) +
+ (head * _geometry.Sectors) +
+ sector;
+
+ return sectorNumber * _geometry.SectorGeometry.GetSectorSizeBytes();
+ }
+
+ private string _packName; // The file from whence the data came
+ private FileStream _diskStream; // The disk image stream containing this disk's contents.
+ private DiskGeometry _geometry; // The geometry of this disk.
+ }
+}
diff --git a/Contralto/IO/TridentController.cs b/Contralto/IO/TridentController.cs
new file mode 100644
index 0000000..f2f2504
--- /dev/null
+++ b/Contralto/IO/TridentController.cs
@@ -0,0 +1,1123 @@
+using Contralto.CPU;
+using Contralto.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Contralto.IO
+{
+ ///
+ /// Implements the Alto's Trident disk controller ("Tricon").
+ /// This can talk to up to 8 (technically 16, but the software doesn't
+ /// appear to support more than 8) T-80 (80mb) or T-300 (300mb) drives.
+ ///
+ /// TODO: This implementation is preliminary and still experimental.
+ /// There are a few major issues:
+ /// - The state machine for the Output FIFO is a tangled mess.
+ /// - TFU refuses to talk to more than drive 0. Unsure if this is
+ /// a bug in the Trident emulation, a bug elsewhere in Contralto
+ /// or user error. TriEx seems to be fine.
+ /// - TriEx gets many errors of status "00000" (meaning that TriEx
+ /// didn't get a response from the controller when it expected to.)
+ /// TFU works fine and can certify, erase, and exercise packs all day.
+ /// The TriEx issue seems to be related in some way to the timing of
+ /// the output FIFO. Probably a subtle issue with microcode wakeups.
+ /// Untangle the output FIFO state machine and revisit this.
+ /// - There is at this time no way to toggle the Read Only switch
+ /// on the emulated drive. (All drives are read/write).
+ /// - The Trident sector wakeup signal needs to go away when the TriCon
+ /// is put to sleep (for performance reasons, no sense keeping the
+ /// scheduler busy for no reason.)
+ ///
+ public class TridentController
+ {
+ public TridentController(AltoSystem system)
+ {
+ _system = system;
+
+ _drives = new TridentDrive[16];
+
+ for(int i=0;i<_drives.Length;i++)
+ {
+ _drives[i] = new TridentDrive(system);
+ }
+
+ Reset();
+ }
+
+ public void Reset()
+ {
+ _runEnable = false;
+
+ _seekIncomplete = false;
+ _headOverflow = false;
+ _deviceCheck = false;
+ _notSelected = false;
+ _notOnline = false;
+ _notReady = false;
+ _sectorOverflow = false;
+ _outputLate = false;
+ _inputLate = false;
+ _compareError = false;
+ _readOnly = false;
+ _offset = false;
+
+ _selectedDrive = 0;
+
+ _sector = 0;
+ _checkDone = false;
+ _empty = false;
+ _pauseOutputProcessing = false;
+ _readState = ReadState.Idle;
+ _writeState = WriteState.Idle;
+
+ _outputFifo = new Queue();
+ _inputFifo = new Queue();
+
+ _sectorEvent = new Event(0, null, SectorCallback);
+ _outputFifoEvent = new Event(0, null, OutputFifoCallback);
+ _seekEvent = new Event(0, null, SeekCallback);
+ _readWordEvent = new Event(0, null, ReadWordCallback);
+
+ // And schedule the first sector pulse.
+ _system.Scheduler.Schedule(_sectorEvent);
+ }
+
+ ///
+ /// "RESET INPUT FIFO AND CLEAR ERRORS"
+ ///
+ public void ControllerReset()
+ {
+ _seekIncomplete = false;
+ _headOverflow = false;
+ _deviceCheck = false;
+ _notSelected = false;
+ _notOnline = false;
+ _notReady = false;
+ _sectorOverflow = false;
+ _outputLate = false;
+ _inputLate = false;
+ _compareError = false;
+ _readOnly = false;
+ _offset = false;
+
+ _writeState = WriteState.Idle;
+ _readState = ReadState.Idle;
+
+ _empty = false;
+ //_pauseOutputProcessing = false;
+
+ ClearInputFIFO();
+ }
+
+ public void CommitDisk(int drive)
+ {
+ TridentDrive d = _drives[drive];
+ if (d.IsLoaded)
+ {
+ try
+ {
+ d.Pack.Save();
+ }
+ catch (Exception e)
+ {
+ // TODO: this does not really belong here.
+ System.Windows.Forms.MessageBox.Show(String.Format("Unable to save Trident disk {0}'s contents. Error {1}. Any changes have been lost.", drive, e.Message), "Disk save error");
+ }
+ }
+
+ }
+
+ public TridentDrive[] Drives
+ {
+ get { return _drives; }
+ }
+
+ public void STARTF(ushort value)
+ {
+ //
+ // "An SIO with bit 10 set will cause Run-Enable to be set.
+ // An SIO with bit 11 set to one will cause Run-Enable to be
+ // reset."
+ //
+ if ((value & 0x10) != 0)
+ {
+ _runEnable = false;
+ _system.CPU.BlockTask(TaskType.TridentInput);
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+
+ //ClearOutputFIFO();
+ //ClearInputFIFO();
+ }
+
+ if ((value & 0x20) != 0)
+ {
+ _runEnable = true;
+
+ // "Issuing an SIO with bit 10 set will wake up the microcode
+ // once and thus may report status in the absence of sector
+ // pulses from the disk."
+ // So do that now.
+ // Based on a reading of the microcode and schematics it looks like the
+ // Write (Output) task is woken up here.
+
+ //
+ // Clear error flags.
+ //
+ _inputLate = false;
+ _outputLate = false;
+ _sectorOverflow = false;
+
+ // _empty = false;
+ // _pauseOutputProcessing = false;
+
+ //
+ // Clear the output FIFO
+ ClearOutputFIFO();
+ ClearInputFIFO();
+
+ //_system.CPU.WakeupTask(TaskType.TridentInput);
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+
+ //_system.CPU.BlockTask(TaskType.TridentInput);
+ //_system.CPU.BlockTask(TaskType.TridentOutput);
+
+ }
+
+ Log.Write(LogComponent.TridentController, "Trident STARTF {0}", Conversion.ToOctal(value));
+ }
+
+ public void Stop()
+ {
+ _runEnable = false;
+ _system.CPU.BlockTask(TaskType.TridentInput);
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+ }
+
+ public ushort KDTA
+ {
+ get
+ {
+ Log.Write(LogComponent.TridentController, "Trident KDTA read");
+ return DequeueInputFIFO();
+ }
+
+ set
+ {
+ Log.Write(LogComponent.TridentController, "------> Trident KDTA queued {0}", Conversion.ToOctal(value));
+ WriteOutputFifo(value);
+ }
+ }
+
+ public ushort STATUS
+ {
+ get
+ {
+ //
+ // The status bits from the controller are:
+ // 0 - Seek Incomplete : drive was unable to correctly position the heads, a Rezero
+ // must be issued to clear this error.
+ // 1 - Head Overflow : head address given to the drive is invalid.
+ // 2 - Device check : One of the following errors occurred
+ // a) Head select or Cylinder select or Write commands and disk not ready
+ // b) An illegal cylinder address.
+ // c) Offset active and cylinder select command.
+ // d) Read-Only and write
+ // e) Certain errors during writing -- multiple heads selected, various read errors)
+ // 3 - Not selected : The selected drive is off-line or not powered up.
+ // 4 - Not Online : The drive is in test mode or the heads are not loaded.
+ // 5 - Not Ready : There is a cylinder seek in progress or the heads are not loaded
+ // 6 - Sector Overflow : The controller detected a write command was active when the next
+ // sector pulse occurred.
+ // 7 - Output Late : The 16 word output buffer became empty while a read or write command
+ // was in progress.
+ // 8 - Input Late : The 16 word input buffer became full.
+ // 9 - Compare Error : The data read during a "Read and Compare" operation did not match
+ // the data read off the disk.
+ // 10 - Read Only : The "Read-Only" switch on the disk drive is on.
+ // 11 - Offset : The cylinder position is currently offset. This is a mode used for
+ // recovery of bad data. (NOTE: Not emulated.)
+ // 12-15 Sector count : A value from 0 to count-1, where count is the number of sectors
+ // implemented in the disk drive. The value returned here is the sector
+ // count for the *next sector* on the disk.
+
+ ushort status = (ushort)(
+ (_seekIncomplete ? 0x8000 : 0) |
+ (_headOverflow ? 0x4000 : 0) |
+ (_deviceCheck ? 0x2000 : 0) |
+ (_notSelected ? 0x1000 : 0) |
+ (_notOnline ? 0x0800 : 0) |
+ (NotReady() ? 0x0400 : 0) |
+ (_sectorOverflow ? 0x0200 : 0) |
+ (_outputLate ? 0x0100 : 0) |
+ (_inputLate ? 0x0080 : 0) |
+ (_compareError ? 0x0040 : 0) |
+ (SelectedDrive.ReadOnly ? 0x0020 : 0) |
+ (_offset ? 0x0010 : 0) |
+ ((_sector) & 0x000f));
+
+ Log.Write(LogComponent.TridentController, "STATUS word is {0}", Conversion.ToOctal(status));
+ return status;
+ }
+ }
+
+ public void TagInstruction(ushort tag)
+ {
+ Log.Write(LogComponent.TridentController, "------> Trident tag instruction queued {0}", Conversion.ToOctal(tag));
+
+ //
+ // Add tag to the output FIFO, with bit 16 set, identifying it as a tag.
+ //
+ WriteOutputFifo(0x10000 | tag);
+ }
+
+ public void WaitForEmpty()
+ {
+ // Block the Output task; it will be awoken when the output FIFO is emptied.
+ if (_outputFifo.Count > 0)
+ {
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+ _empty = true;
+ Log.Write(LogComponent.TridentController, "Trident output task blocked until Output FIFO emptied.");
+ }
+ }
+
+ private bool NotReady()
+ {
+ return _notReady | !SelectedDrive.IsLoaded;
+ }
+
+ private void WriteOutputFifo(int value)
+ {
+ if (_outputFifo.Count > 16)
+ {
+ Log.Write(LogType.Error, LogComponent.TridentController, "Output FIFO full, dropping word.");
+ return;
+ }
+
+ EnqueueOutputFIFO(value);
+
+ //
+ // If this is the first word written to an empty FIFO, and we aren't waiting for the next
+ // sector pulse, queue up the fifo callback to pull words and process them.
+ //
+ if (!_pauseOutputProcessing && _outputFifo.Count == 1)
+ {
+ _outputFifoEvent.TimestampNsec = _outputFifoDuration;
+ _system.Scheduler.Schedule(_outputFifoEvent);
+ }
+ }
+
+ private void OutputFifoCallback(ulong timeNsec, ulong skewNsec, object context)
+ {
+ ProcessCommandOutput();
+ }
+
+ private void ProcessCommandOutput()
+ {
+ if (_outputFifo.Count == 0)
+ {
+ if (_writeState == WriteState.Writing)
+ {
+ //
+ // We have run out of data on a write.
+ //
+ _outputLate = true;
+
+ Log.Write(LogComponent.TridentController, "Output FIFO underflowed.");
+ }
+
+ return;
+ }
+
+ if (_readState != ReadState.Reading)
+ {
+ int fifoWord = DequeueOutputFIFO();
+
+ if ((fifoWord & 0x10000) != 0)
+ {
+ //
+ // This is a Tag word; process it accordingly.
+ //
+ Log.Write(LogComponent.TridentController, "Tag word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord));
+
+ //
+ // From the TRICON schematic (page 16):
+ // Bit 3 is the Enable bit, which enables selection of one of four commands
+ // to the drive, specified in bits 1 and 2:
+ // (and as usual, these are in the backwards Nova/Alto bit ordering scheme)
+ // Bits 1 and 2 decode to:
+ // 0 0 - Control
+ // 0 1 - Set Head
+ // 1 0 - Set Cylinder
+ // 1 1 - Set Drive
+ //
+ // Head, Cylinder, Drive are straightforward -- the lower bits of the tag
+ // word contain the data for the command.
+ //
+ // The Control bits are described in the Trident T300 Theory of Operations manual,
+ // Page 3-13 and are the lower 10 bits of the tag command:
+ //
+ // 0 - Strobe Late : Skews read data detection 4ns late for attempted read-error recovery.
+ // 1 - Strobe Early : Same as above, but early.
+ // 2 - Write : Turns on circuits to write data,
+ // 3 - Read : Turns on read circuits an resets Attention interrupts.
+ // 4 - Address Mark : Commands an address mark to be generated, if writing; or
+ // enables the address mark detector, if reading.
+ // 5 - Reset Head Register : Resets HAR to 0
+ // 6 - Device Check Reset : Resets most types of Device Check errors unless an error
+ // condition is still present.
+ // 7 - Head Select : Tuns on the head-selection circuits. Head select must be active 5
+ // or 15 microseconds before Write or Read is commanded, respectively.
+ // 8 - Rezero : Repositions the heads to cylinder 000, selects Head Address 0, and resets
+ // some types of Device Checks.
+ // 9 - Head Advance : Increases Head Address count by one.
+ //
+ //
+ // Bit 0 of the Tag word, if set, tells the controller to hold off Output FIFO processing
+ // until the next sector pulse.
+ //
+ if ((fifoWord & 0x8000) != 0)
+ {
+ _pauseOutputProcessing = true;
+ Log.Write(LogComponent.TridentController, "Output FIFO processing paused until next sector pulse.");
+ }
+ // See if the enable bit (3) is set, in which case this is a command to the drive
+ //
+ if ((fifoWord & 0x1000) != 0)
+ {
+ //
+ // Switch on the specific command
+ switch ((fifoWord & 0x6000) >> 13)
+ {
+ case 0: // Control
+ Log.Write(LogComponent.TridentController, "Control word.");
+
+ if ((fifoWord & 0x0001) != 0) // 9 - Head advance
+ {
+ if (!SelectedDrive.IsLoaded)
+ {
+ _deviceCheck = true;
+ }
+ else
+ {
+ if (SelectedDrive.Head + 1 >= SelectedDrive.Pack.Geometry.Heads)
+ {
+ _headOverflow = true;
+ _deviceCheck = true;
+
+ Log.Write(LogComponent.TridentController, "Head {0} is out of range on Head Advance.", SelectedDrive.Head + 1);
+ }
+ else
+ {
+ SelectedDrive.Head++;
+ Log.Write(LogComponent.TridentController, "Control: Head Advance. Head is now {0}", SelectedDrive.Head);
+ }
+ }
+ }
+
+ if ((fifoWord & 0x0002) != 0) // 8 - Rezero
+ {
+ _deviceCheck = false;
+ SelectedDrive.Head = 0;
+
+ InitSeek(0);
+
+ Log.Write(LogComponent.TridentController, "Control: Rezero.");
+ }
+
+ if ((fifoWord & 0x0004) != 0) // 7 - Head select
+ {
+ Log.Write(LogComponent.TridentController, "Control: Head Select.");
+
+ if (!SelectedDrive.IsLoaded)
+ {
+ _deviceCheck = true;
+ }
+
+ // TODO: technically this needs to be active before a write or read is selected. Do I care?
+ }
+
+ if ((fifoWord & 0x0008) != 0) // 6 - Device Check Reset
+ {
+ Log.Write(LogComponent.TridentController, "Control: Device Check Reset.");
+ _deviceCheck = false;
+ }
+
+ if ((fifoWord & 0x0010) != 0) // 5 - Reset Head Register
+ {
+ Log.Write(LogComponent.TridentController, "Control: Reset Head Register.");
+ SelectedDrive.Head = 0;
+ }
+
+ if ((fifoWord & 0x0020) != 0) // 4 - Address Mark
+ {
+ Log.Write(LogComponent.TridentController, "Control: Address mark.");
+
+ // Not much to do here, emulation-wise.
+ }
+
+ if ((fifoWord & 0x0040) != 0) // 3 - Read
+ {
+ Log.Write(LogComponent.TridentController, "Control: Read.");
+ //
+ // Commence reading -- start reading a word at a time into the input FIFO,
+ // Waking up the Input task as necessary.
+ //
+ if (NotReady())
+ {
+ _deviceCheck = true;
+ }
+ else
+ {
+ InitRead();
+ }
+ }
+
+ if ((fifoWord & 0x0080) != 0) // 2 - Write
+ {
+ Log.Write(LogComponent.TridentController, "Control: Write.");
+
+ //
+ // Commence writing -- start pulling a word at a time out of the output FIFO,
+ // Waking up the Output task as necessary.
+ //
+ if (NotReady())
+ {
+ _deviceCheck = true;
+ }
+ else
+ {
+ InitWrite();
+ }
+ }
+
+ if ((fifoWord & 0x0100) != 0) // 1 - Strobe Early
+ {
+ Log.Write(LogComponent.TridentController, "Control: Strobe Early.");
+
+ // Not going to emulate this, as fun as it sounds.
+ }
+
+ if ((fifoWord & 0x0200) != 0) // 0 - Strobe Late
+ {
+ Log.Write(LogComponent.TridentController, "Control: Strobe Late.");
+
+ // Not going to emulate this either.
+ }
+
+ break;
+
+ case 1: // Set Head
+ int head = fifoWord & 0x1f; // low 5 bits
+ Log.Write(LogComponent.TridentController, "Command is Set Head {0}", head);
+
+ if (!SelectedDrive.IsLoaded)
+ {
+ _deviceCheck = true;
+ }
+ else
+ {
+ if (head >= SelectedDrive.Pack.Geometry.Heads)
+ {
+ _headOverflow = true;
+ _deviceCheck = true;
+
+ Log.Write(LogComponent.TridentController, "Head {0} is out of range.", head);
+ }
+ else
+ {
+ SelectedDrive.Head = head;
+ }
+ }
+ break;
+
+ case 2: // Set Cylinder
+ int cyl = fifoWord & 0x3ff; // low 10 bits
+ Log.Write(LogComponent.TridentController, "Command is Set Cylinder {0}.", cyl);
+
+ if (NotReady())
+ {
+ _deviceCheck = true;
+ }
+ else
+ {
+ InitSeek(cyl);
+ }
+ break;
+
+ case 3: // Set Drive
+ _selectedDrive = fifoWord & 0xf;
+ Log.Write(LogComponent.TridentController, "Command is Set Drive {0}", _selectedDrive);
+ break;
+ }
+ }
+ else
+ {
+ //
+ // There are two values we expect to see with the enable bit off:
+ // 0 is sent to reset the tag bus (to the drive) to zeros, we simply ignore it.
+ // 2 is sent because the drive expects the Rezero signal to be held for a time
+ // after the Control signal is dropped. We will commence a Rezero operation
+ // if we get one here.
+ //
+ if (fifoWord == 2)
+ {
+ Log.Write(LogComponent.TridentController, "Rezero started.");
+ }
+ else if (fifoWord != 0)
+ {
+ Log.Write(LogComponent.TridentController, "Unexpected Tag word without enable bit set: {0}", Conversion.ToOctal(fifoWord));
+ }
+ }
+ }
+ else
+ {
+ //
+ // This is a data word.
+ //
+ Log.Write(LogComponent.TridentController, "Data word {0} pulled from Output FIFO.", Conversion.ToOctal((ushort)fifoWord));
+
+ if (_writeState != WriteState.Idle)
+ {
+ switch (_writeState)
+ {
+ case WriteState.Initialized:
+ // This word should be the MAX number of words to check.
+ _writeWordCount = fifoWord - 1;
+ _writeState = WriteState.WaitingForStartBit;
+ Log.Write(LogComponent.TridentController, "Write initializing {0} words (max) to write.", _writeWordCount);
+ break;
+
+ case WriteState.WaitingForStartBit:
+ if (fifoWord == 1)
+ {
+ Log.Write(LogComponent.TridentController, "Start bit recognized, commencing write operation.");
+ _writeState = WriteState.Writing;
+ }
+ break;
+
+ case WriteState.Writing:
+ ProcessDiskWrite(fifoWord);
+ break;
+ }
+ }
+
+ if (_readState != ReadState.Idle)
+ {
+ switch (_readState)
+ {
+ case ReadState.Initialized:
+ _readWordCount = fifoWord - 1;
+ _readState = ReadState.Reading;
+
+ Log.Write(LogType.Verbose, LogComponent.TridentController, "Read initializing {0} words (max) to read.", _readWordCount);
+ break;
+
+ default:
+ throw new InvalidOperationException("Unexpected ReadState in ProcessCommandOutput.");
+
+ }
+ }
+ }
+ }
+
+ if (!_pauseOutputProcessing && _outputFifo.Count > 0)
+ {
+ //
+ // Schedule next FIFO wakeup.
+ _outputFifoEvent.TimestampNsec = _outputFifoDuration;
+ _system.Scheduler.Schedule(_outputFifoEvent);
+ }
+ }
+
+ private void InitSeek(int destCylinder)
+ {
+ if (destCylinder > SelectedDrive.Pack.Geometry.Cylinders - 1)
+ {
+ Log.Write(LogType.Error, LogComponent.TridentController, "Specified cylinder {0} is out of range. Seek aborted.", destCylinder);
+ _deviceCheck = true;
+ return;
+ }
+
+ int currentCylinder = SelectedDrive.Cylinder;
+ if (destCylinder != currentCylinder)
+ {
+ // Do a seek.
+ _notReady = true;
+
+ _destCylinder = destCylinder;
+
+ //
+ // I can't find a specific formula for seek timing; the Century manual says:
+ // "Positioning time for seeking to the next cylinder is normally 6ms, and
+ // for full seeks (814 cylinder differerence) it is 55ms."
+ //
+ // I'm just going to fudge this for now and assume a linear ramp; this is not
+ // accurate but it's not all that important.
+ //
+ _seekDuration = (ulong)((6.0 + 0.602 * Math.Abs(currentCylinder - destCylinder)) * Conversion.MsecToNsec);
+
+ Log.Write(LogComponent.TridentController, "Commencing seek from {0} to {1}. Seek time is {2}ns", destCylinder, currentCylinder, _seekDuration);
+
+ _seekEvent.TimestampNsec = _seekDuration;
+ _system.Scheduler.Schedule(_seekEvent);
+ }
+ else
+ {
+ Log.Write(LogComponent.TridentController, "Seek is a no-op.");
+ }
+ }
+
+ private void SeekCallback(ulong timeNsec, ulong skewNsec, object context)
+ {
+ Log.Write(LogComponent.TridentDisk, "Seek to {0} complete.", _destCylinder);
+
+ SelectedDrive.Cylinder = _destCylinder;
+ _notReady = false;
+ }
+
+ private void InitRead()
+ {
+ if (_readState == ReadState.Idle)
+ {
+ _readState = ReadState.Initialized;
+ _checkedWordCount = 0;
+ _readIndex = 0;
+ _readWordCount = 0;
+ _checkDone = false;
+
+ // Clear the FIFO: TODO check schematics.
+ // ClearInputFIFO();
+
+ _readWordEvent.TimestampNsec = _readWordDuration * 10;
+ _system.Scheduler.Schedule(_readWordEvent);
+ }
+ else
+ {
+ // Unexpected, throw for now.
+ throw new InvalidOperationException("Unexpected Read command while read active.");
+ }
+ }
+
+ private void ReadWordCallback(ulong timeNsec, ulong skewNsec, object context)
+ {
+ if (_readWordCount > 0)
+ {
+ // Enqueue data from disk.
+ ushort dataWord;
+
+ switch (_sectorBlock)
+ {
+ case SectorBlock.Header:
+ dataWord = SelectedDrive.ReadHeader(_readIndex);
+ break;
+
+ case SectorBlock.Label:
+ dataWord = SelectedDrive.ReadLabel(_readIndex);
+ break;
+
+ case SectorBlock.Data:
+ dataWord = SelectedDrive.ReadData(_readIndex);
+ break;
+
+ default:
+ throw new InvalidOperationException(String.Format("Unexpected Sector Block of {0} on read.", _sectorBlock));
+ }
+
+ Log.Write(LogType.Verbose, LogComponent.TridentController, "Read word {0}:{1}", _readIndex, Conversion.ToOctal(dataWord));
+
+ EnqueueInputFIFO(dataWord);
+
+ //
+ // Compare data with check data in output fifo, if any.
+ // From the microcode comments:
+ // "Note that the first two words of a block are always checked,
+ // followed by additional words until a zero word or the end of the block."
+ //
+ // If we hit a tag word, the check is also completed.
+ // TODO: verify this w/schematic & microcode.
+
+ if (_outputFifo.Count > 0)
+ {
+ int checkWord = _outputFifo.Peek();
+ if ((checkWord & 0x10000) == 0 && (!_checkDone || _checkedWordCount < 2))
+ {
+ // Actually pull the word off
+ checkWord = (ushort)DequeueOutputFIFO();
+
+ Log.Write(LogType.Verbose, LogComponent.TridentController, "Pulled checkword {0} from output FIFO", Conversion.ToOctal(checkWord));
+
+ // A zero word indicates the check is complete (we will
+ // still read the minimum two words in)
+ if (checkWord == 0)
+ {
+ _checkDone = true;
+ }
+ // Compare didn't.
+ else if (checkWord != dataWord)
+ {
+ _compareError = true;
+ }
+ _checkedWordCount++;
+ }
+ }
+ }
+ else
+ {
+ // Last four words (0 through -3) are checksum and ECC words.
+ // The checksum words are ignored by the microcode, and
+ // since we're not simulating faulty disks, these are always zero.
+ EnqueueInputFIFO(0);
+ Log.Write(LogComponent.TridentController, "Read ECC/checksum word 0");
+ }
+
+ _readIndex++;
+ _readWordCount--;
+
+ if (_readWordCount > -4)
+ {
+ // More words to read, queue up the next.
+ _readWordEvent.TimestampNsec = _readWordDuration;
+ _system.Scheduler.Schedule(_readWordEvent);
+ }
+ else
+ {
+ Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} read complete.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock);
+ _readState = ReadState.Idle;
+
+ _sectorBlock++;
+ }
+ }
+
+ private void InitWrite()
+ {
+ if (_writeState == WriteState.Idle)
+ {
+ Log.Write(LogComponent.TridentController, "Write primed, waiting for start bit.");
+ _writeState = WriteState.Initialized;
+ _writeIndex = 0;
+ }
+ else
+ {
+ // Unexpected, throw for now.
+ throw new InvalidOperationException("Unexpected Write command while write active.");
+ }
+ }
+
+ private void ProcessDiskWrite(int dataWord)
+ {
+ //
+ // Sanity check: if there's a tag bit set, we've picked up some
+ // invalid data...
+ //
+ if ((dataWord & 0x10000) != 0)
+ {
+ Log.Write(LogType.Error, LogComponent.TridentController, "Tag bit set in data word during Write ({0}).", Conversion.ToOctal(dataWord));
+ }
+
+ Log.Write(LogComponent.TridentController, "Write word {0}:{1}", _writeIndex, Conversion.ToOctal(dataWord));
+
+ //
+ // Commit to the proper block in the sector:
+ //
+ switch(_sectorBlock)
+ {
+ case SectorBlock.Header:
+ SelectedDrive.WriteHeader(_writeIndex, (ushort)dataWord);
+ break;
+
+ case SectorBlock.Label:
+ SelectedDrive.WriteLabel(_writeIndex, (ushort)dataWord);
+ break;
+
+ case SectorBlock.Data:
+ SelectedDrive.WriteData(_writeIndex, (ushort)dataWord);
+ break;
+
+ default:
+ throw new InvalidOperationException(String.Format("Unexpected Sector Block of {0} on write.", _sectorBlock));
+ }
+
+ _writeIndex++;
+
+ _writeWordCount--;
+ if (_writeWordCount <= 0)
+ {
+ Log.Write(LogComponent.TridentController, "CHS {0}/{1}/{2} {3} write complete. {4} words written.", SelectedDrive.Cylinder, SelectedDrive.Head, _sector, _sectorBlock, _writeIndex);
+ _writeState = WriteState.Idle;
+ _writeIndex = 0;
+
+ // Move to the next block
+ _sectorBlock++;
+ }
+ }
+
+ private void SectorCallback(ulong timeNsec, ulong skewNsec, object context)
+ {
+ // Move to the next sector.
+ _sector = (_sector + 1) % 9;
+ SelectedDrive.Sector = _sector;
+
+ // Reset to the first block (header) in the sector.
+ _sectorBlock = SectorBlock.Header;
+
+ // Wake up the Output task if RunEnable is true and the drive is ready.
+ if (_runEnable && !NotReady())
+ {
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+ }
+
+ //
+ // Schedule the next sector pulse
+ //
+ _sectorEvent.TimestampNsec = _sectorDuration - skewNsec;
+ _system.Scheduler.Schedule(_sectorEvent);
+
+ //
+ // If output fifo processing was paused and there's data in the FIFO
+ // to be dealt with, wake up the callback now.
+ //
+ if (_pauseOutputProcessing && _outputFifo.Count > 0)
+ {
+ _pauseOutputProcessing = false;
+
+ _outputFifoEvent.TimestampNsec = _outputFifoDuration;
+ _system.Scheduler.Schedule(_outputFifoEvent);
+ }
+ }
+
+ private TridentDrive SelectedDrive
+ {
+ get { return _drives[_selectedDrive]; }
+ }
+
+ //
+ // Input FIFO semantics
+ //
+ private void ClearInputFIFO()
+ {
+ _inputFifo.Clear();
+ _system.CPU.BlockTask(TaskType.TridentInput);
+ }
+
+ private void EnqueueInputFIFO(ushort word)
+ {
+ if (_inputFifo.Count == 16)
+ {
+ // We have overflowed the input FIFO. Set the requisite error
+ // flags and shut this thing down.
+ _inputLate = true;
+
+ Log.Write(LogComponent.TridentController, "Input FIFO overflowed.");
+ }
+ else
+ {
+ _inputFifo.Enqueue(word);
+
+ if (_inputFifo.Count >= 4)
+ {
+ _system.CPU.WakeupTask(TaskType.TridentInput);
+ }
+ else
+ {
+ _system.CPU.BlockTask(TaskType.TridentInput);
+ }
+ }
+ }
+
+ private ushort DequeueInputFIFO()
+ {
+ ushort word = 0;
+ if (_inputFifo.Count == 0)
+ {
+ Log.Write(LogType.Warning, LogComponent.TridentController, "Input FIFO underflowed, returning 0.");
+ }
+ else
+ {
+ word = _inputFifo.Dequeue();
+ }
+
+ if (_inputFifo.Count < 4)
+ {
+ _system.CPU.BlockTask(TaskType.TridentInput);
+ }
+ else
+ {
+ _system.CPU.WakeupTask(TaskType.TridentInput);
+ }
+ return word;
+ }
+
+ //
+ // Output FIFO semantics
+ //
+ private void ClearOutputFIFO()
+ {
+ _outputFifo.Clear();
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+ }
+
+ private void EnqueueOutputFIFO(int word)
+ {
+ if (_outputFifo.Count == 16)
+ {
+ Log.Write(LogComponent.TridentController, "Output FIFO overflowed, dropping word.");
+ }
+ else
+ {
+ _outputFifo.Enqueue(word);
+
+ Log.Write(LogComponent.TridentController, "Output FIFO enqueued, queue depth is now {0}", _outputFifo.Count);
+ }
+
+ if (_outputFifo.Count <= 12)
+ {
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+ }
+ else
+ {
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+ }
+
+ }
+
+ private int DequeueOutputFIFO()
+ {
+ int word = 0;
+ if (_outputFifo.Count == 0)
+ {
+ Log.Write(LogType.Warning, LogComponent.TridentController, "Output FIFO underflowed, returning 0.");
+ }
+ else
+ {
+ word = _outputFifo.Dequeue();
+ Log.Write(LogComponent.TridentController, "Output FIFO dequeued, queue depth is now {0}", _outputFifo.Count);
+ }
+
+ if (_empty)
+ {
+ // Only wake up the Output task when the output FIFO goes completely empty.
+ if (_outputFifo.Count == 0)
+ {
+ _empty = false;
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+ Log.Write(LogComponent.TridentController, "Output FIFO emptied, waking Output task.");
+ }
+ else
+ {
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+ }
+ }
+ else
+ {
+ if (_outputFifo.Count <= 12)
+ {
+ _system.CPU.WakeupTask(TaskType.TridentOutput);
+ }
+ else
+ {
+ _system.CPU.BlockTask(TaskType.TridentOutput);
+ }
+ }
+ return word;
+ }
+
+ // Wakeup signals
+ private bool _runEnable;
+
+ // Status bits
+ private bool _seekIncomplete;
+ private bool _headOverflow;
+ private bool _deviceCheck;
+ private bool _notSelected;
+ private bool _notOnline;
+ private bool _notReady;
+ private bool _sectorOverflow;
+ private bool _outputLate;
+ private bool _inputLate;
+ private bool _compareError;
+ private bool _readOnly;
+ private bool _offset;
+
+ // Current sector
+ private int _sector;
+
+ ///
+ /// Drive timings
+ ///
+ private static ulong _sectorDuration = (ulong)((16.66666 / 9.0) * Conversion.MsecToNsec); // time in nsec for one sector -- 9 sectors, 16.66ms per rotation
+ private Event _sectorEvent;
+
+ //
+ // Seek timings
+ //
+ private static ulong _seekDuration = (6 * Conversion.MsecToNsec);
+ private Event _seekEvent;
+ private int _destCylinder;
+
+ // Output FIFO. This is actually 17-bits wide (the 17th bit is the Tag bit).
+ private Queue _outputFifo;
+ private Event _outputFifoEvent;
+ private bool _pauseOutputProcessing;
+ private bool _empty;
+ private static ulong _outputFifoDuration = (ulong)(_sectorDuration / 2048.0); // time in nsec for one word -- 1120 words per sector.
+
+ // Input FIFO.
+ private Queue _inputFifo;
+
+ //
+ // Read timings
+ //
+ private static ulong _readWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector.
+ private Event _readWordEvent;
+ private int _readIndex;
+ private int _readWordCount;
+ private bool _checkDone;
+ private int _checkedWordCount;
+
+ //
+ // Write timings
+ //
+ private static ulong _writeWordDuration = (ulong)(_sectorDuration / 1120.0); // time in nsec for one word -- 1120 words per sector.
+ private int _writeIndex;
+ private int _writeWordCount;
+
+ private enum WriteState
+ {
+ Idle,
+ Initialized,
+ WaitingForStartBit,
+ Writing,
+ }
+ private WriteState _writeState;
+
+ private enum ReadState
+ {
+ Idle,
+ Initialized,
+ WaitingForStartBit,
+ Reading,
+ }
+ private ReadState _readState;
+
+ private enum SectorBlock
+ {
+ Header,
+ Label,
+ Data,
+ }
+ private SectorBlock _sectorBlock;
+
+ //
+ // Attached drives
+ //
+ private TridentDrive[] _drives;
+ private int _selectedDrive;
+
+ private AltoSystem _system;
+ }
+}
diff --git a/Contralto/IO/TridentDrive.cs b/Contralto/IO/TridentDrive.cs
new file mode 100644
index 0000000..d256cac
--- /dev/null
+++ b/Contralto/IO/TridentDrive.cs
@@ -0,0 +1,284 @@
+/*
+ This file is part of ContrAlto.
+
+ ContrAlto is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ ContrAlto is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with ContrAlto. If not, see .
+*/
+
+using Contralto.Logging;
+using System;
+
+namespace Contralto.IO
+{
+ ///
+ /// Encapsulates logic that belongs to a Trident drive, including loading/saving packs,
+ /// seeking, and parceling out sector data.
+ ///
+ public class TridentDrive
+ {
+ public TridentDrive(AltoSystem system)
+ {
+ _system = system;
+ Reset();
+ }
+
+ public void Reset()
+ {
+ _sector = 0;
+ _cylinder = 0;
+ _head = 0;
+
+ UpdateTrackCache();
+ }
+
+ public void NewPack(string path, DiskGeometry geometry)
+ {
+ if (_pack != null)
+ {
+ UpdateTrackCache();
+ _pack.Dispose();
+ }
+
+ _pack = FileBackedDiskPack.CreateEmpty(geometry, path);
+ Reset();
+ }
+
+ public void LoadPack(IDiskPack pack)
+ {
+ if (_pack != null)
+ {
+ UpdateTrackCache();
+ _pack.Dispose();
+ }
+
+ _pack = pack;
+ Reset();
+ }
+
+ public void UnloadPack()
+ {
+ if (_pack != null)
+ {
+ UpdateTrackCache();
+ _pack.Dispose();
+ }
+
+ _pack = null;
+ Reset();
+ }
+
+ public bool IsLoaded
+ {
+ get { return _pack != null; }
+ }
+
+ public IDiskPack Pack
+ {
+ get { return _pack; }
+ }
+
+ public int Sector
+ {
+ get { return _sector; }
+ set { _sector = value; }
+ }
+
+ public int Head
+ {
+ get { return _head; }
+ set
+ {
+ if (_head != value)
+ {
+ _head = value;
+ UpdateTrackCache();
+ }
+ }
+ }
+
+ public int Cylinder
+ {
+ get { return _cylinder; }
+ set
+ {
+ if (_cylinder != value)
+ {
+ _cylinder = value;
+ UpdateTrackCache();
+ }
+ }
+ }
+
+ public bool ReadOnly
+ {
+ get { return false; }
+ }
+
+ public ushort ReadHeader(int wordOffset)
+ {
+ if (wordOffset >= SectorGeometry.Trident.HeaderSize)
+ {
+ //
+ // We just ignore this; the microcode may read extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra header word read, offset {0}", wordOffset);
+ return 0;
+ }
+ else
+ {
+ return CurrentSector.ReadHeader(wordOffset);
+ }
+ }
+
+ public ushort ReadLabel(int wordOffset)
+ {
+ if (wordOffset >= SectorGeometry.Trident.LabelSize)
+ {
+ //
+ // We just ignore this; the microcode may read extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra label word read, offset {0}", wordOffset);
+ return 0;
+ }
+ else
+ {
+ return CurrentSector.ReadLabel(wordOffset);
+ }
+ }
+
+ public ushort ReadData(int wordOffset)
+ {
+ if (wordOffset >= SectorGeometry.Trident.DataSize)
+ {
+ //
+ // We just ignore this; the microcode may read extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra data word read, offset {0 }", wordOffset);
+ return 0;
+ }
+ else
+ {
+ return CurrentSector.ReadData(wordOffset);
+ }
+ }
+
+ public void WriteHeader(int wordOffset, ushort word)
+ {
+ if (wordOffset >= SectorGeometry.Trident.HeaderSize)
+ {
+ //
+ // We just ignore this; the microcode may send extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra header word ({0}) written, offset {1}", Conversion.ToOctal(word), wordOffset);
+ }
+ else
+ {
+ CurrentSector.WriteHeader(wordOffset, word);
+ }
+ }
+
+ public void WriteLabel(int wordOffset, ushort word)
+ {
+ if (wordOffset >= SectorGeometry.Trident.LabelSize)
+ {
+ //
+ // We just ignore this; the microcode may send extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra label word ({0}) written, offset {1}", Conversion.ToOctal(word), wordOffset);
+ }
+ else
+ {
+ CurrentSector.WriteLabel(wordOffset, word);
+ }
+ }
+
+ public void WriteData(int wordOffset, ushort word)
+ {
+ if (wordOffset >= SectorGeometry.Trident.DataSize)
+ {
+ //
+ // We just ignore this; the microcode may send extra words
+ // and the controller was expected to ignore them.
+ //
+ Log.Write(LogType.Warning, LogComponent.TridentDisk, "Extra data word ({0}) written, offset {1}", Conversion.ToOctal(word), wordOffset);
+ }
+ else
+ {
+ CurrentSector.WriteData(wordOffset, word);
+ }
+ }
+
+ //
+ // Sector management. We load in an entire track's worth of sectors at a time.
+ // When the head or cylinder changes, UpdateTrackCache must be called.
+ //
+ private void UpdateTrackCache()
+ {
+ if (_pack != null)
+ {
+ if (_trackCache == null)
+ {
+ //
+ // First time through, initialize the cache.
+ //
+ _trackCache = new DiskSector[_pack.Geometry.Sectors];
+ }
+ else
+ {
+ //
+ // Commit the sectors back to disk before loading in the new ones.
+ //
+ for (int i = 0; i < _trackCache.Length; i++)
+ {
+ _pack.CommitSector(_trackCache[i]);
+ }
+ }
+
+ //
+ // Load the new sectors for this track.
+ //
+ for (int i = 0; i < _trackCache.Length; i++)
+ {
+ _trackCache[i] = _pack.GetSector(_cylinder, _head, i);
+ }
+ }
+ }
+
+ private DiskSector CurrentSector
+ {
+ get { return _trackCache[_sector]; }
+ }
+
+ private AltoSystem _system;
+
+ //
+ // Current disk position
+ //
+ private int _cylinder;
+ private int _head;
+ private int _sector;
+
+ // The pack loaded into the drive
+ IDiskPack _pack;
+
+ //
+ // The track cache
+ //
+ private DiskSector[] _trackCache;
+ }
+}
diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs
index db43779..744d050 100644
--- a/Contralto/Logging/Log.cs
+++ b/Contralto/Logging/Log.cs
@@ -49,6 +49,9 @@ namespace Contralto.Logging
Organ = 0x20000,
Orbit = 0x40000,
DoverROS = 0x80000,
+ TridentTask = 0x100000,
+ TridentController = 0x200000,
+ TridentDisk = 0x400000,
Debug = 0x40000000,
All = 0x7fffffff
@@ -77,7 +80,7 @@ namespace Contralto.Logging
{
_components = Configuration.LogComponents;
_type = Configuration.LogTypes;
-
+ _logIndex = 0;
}
public static LogComponent LogComponents
@@ -107,7 +110,8 @@ namespace Contralto.Logging
//
// My log has something to tell you...
// TODO: color based on type, etc.
- Console.WriteLine(component.ToString() + ": " + message, args);
+ Console.WriteLine(_logIndex.ToString() + ": " + component.ToString() + ": " + message, args);
+ _logIndex++;
}
}
#else
@@ -125,5 +129,6 @@ namespace Contralto.Logging
private static LogComponent _components;
private static LogType _type;
+ private static long _logIndex;
}
}
diff --git a/Contralto/Program.cs b/Contralto/Program.cs
index ca7d4d7..1e35f04 100644
--- a/Contralto/Program.cs
+++ b/Contralto/Program.cs
@@ -42,16 +42,17 @@ namespace Contralto
_system = new AltoSystem();
+ // Load disks specified by configuration
if (!String.IsNullOrEmpty(Configuration.Drive0Image))
{
try
{
- _system.LoadDrive(0, Configuration.Drive0Image);
+ _system.LoadDiabloDrive(0, Configuration.Drive0Image, false);
}
catch(Exception e)
{
- Console.WriteLine("Could not load image '{0}' for drive 0. Error '{1}'.", Configuration.Drive0Image, e.Message);
- _system.UnloadDrive(0);
+ Console.WriteLine("Could not load image '{0}' for Diablo drive 0. Error '{1}'.", Configuration.Drive0Image, e.Message);
+ _system.UnloadDiabloDrive(0);
}
}
@@ -59,12 +60,32 @@ namespace Contralto
{
try
{
- _system.LoadDrive(1, Configuration.Drive1Image);
+ _system.LoadDiabloDrive(1, Configuration.Drive1Image, false);
}
catch (Exception e)
{
- Console.WriteLine("Could not load image '{0}' for drive 1. Error '{1}'.", Configuration.Drive1Image, e.Message);
- _system.UnloadDrive(1);
+ Console.WriteLine("Could not load image '{0}' for Diablo drive 1. Error '{1}'.", Configuration.Drive1Image, e.Message);
+ _system.UnloadDiabloDrive(1);
+ }
+ }
+
+
+ if (Configuration.TridentImages != null)
+ {
+ for (int i = 0; i < Math.Min(8, Configuration.TridentImages.Count); i++)
+ {
+ try
+ {
+ if (!String.IsNullOrWhiteSpace(Configuration.TridentImages[i]))
+ {
+ _system.LoadTridentDrive(i, Configuration.TridentImages[i], false);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Could not load image '{0}' for Trident drive {1}. Error '{2}'.", Configuration.TridentImages[i], i, e.Message);
+ _system.UnloadTridentDrive(i);
+ }
}
}
@@ -106,13 +127,7 @@ namespace Contralto
private static void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("Exiting...");
-
- //
- // Save disk contents
- //
- _system.CommitDiskPack(0);
- _system.CommitDiskPack(1);
-
+
_system.Shutdown();
//
diff --git a/Contralto/Properties/Settings.Designer.cs b/Contralto/Properties/Settings.Designer.cs
index f0c06fd..79da709 100644
--- a/Contralto/Properties/Settings.Designer.cs
+++ b/Contralto/Properties/Settings.Designer.cs
@@ -238,5 +238,28 @@ namespace Contralto.Properties {
this["ReversePageOrder"] = value;
}
}
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute(@"
+
+
+
+
+
+
+
+
+
+
+")]
+ public global::System.Collections.Specialized.StringCollection TridentImages {
+ get {
+ return ((global::System.Collections.Specialized.StringCollection)(this["TridentImages"]));
+ }
+ set {
+ this["TridentImages"] = value;
+ }
+ }
}
}
diff --git a/Contralto/Properties/Settings.settings b/Contralto/Properties/Settings.settings
index 6ce2097..642c663 100644
--- a/Contralto/Properties/Settings.settings
+++ b/Contralto/Properties/Settings.settings
@@ -56,5 +56,19 @@
True
+
+ <?xml version="1.0" encoding="utf-16"?>
+<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <string />
+ <string />
+ <string />
+ <string />
+ <string />
+ <string />
+ <string />
+ <string />
+ <string />
+</ArrayOfString>
+
\ No newline at end of file
diff --git a/Contralto/Scheduler.cs b/Contralto/Scheduler.cs
index 568ede9..86f26e2 100644
--- a/Contralto/Scheduler.cs
+++ b/Contralto/Scheduler.cs
@@ -15,6 +15,7 @@
along with ContrAlto. If not, see .
*/
+using System;
using System.Collections.Generic;
@@ -120,6 +121,13 @@ namespace Contralto
///
public Event Schedule(Event e)
{
+#if DEBUG
+ if (_schedule.Contains(e))
+ {
+ throw new InvalidOperationException("Can't do that, time will bend.");
+ }
+#endif
+
e.TimestampNsec += _currentTimeNsec;
_schedule.Push(e);
@@ -163,6 +171,11 @@ namespace Contralto
}
}
+ public bool Contains(Event e)
+ {
+ return _queue.Contains(e);
+ }
+
public void Push(Event e)
{
// Degenerate case: list is empty or new entry is earlier than the head of the list.
diff --git a/Contralto/SdlUI/SdlConsole.cs b/Contralto/SdlUI/SdlConsole.cs
index 3afc195..f1f7d85 100644
--- a/Contralto/SdlUI/SdlConsole.cs
+++ b/Contralto/SdlUI/SdlConsole.cs
@@ -187,11 +187,8 @@ namespace Contralto.SdlUI
throw new InvalidOperationException("Drive specification out of range.");
}
- // Save current drive contents.
- _system.CommitDiskPack(drive);
-
// Load the new pack.
- _system.LoadDrive(drive, path);
+ _system.LoadDiabloDrive(drive, path, false);
Console.WriteLine("Drive {0} loaded.", drive);
return CommandResult.Normal;
@@ -205,11 +202,8 @@ namespace Contralto.SdlUI
throw new InvalidOperationException("Drive specification out of range.");
}
- // Save current drive contents.
- _system.CommitDiskPack(drive);
-
// Unload the current pack.
- _system.UnloadDrive(drive);
+ _system.UnloadDiabloDrive(drive);
Console.WriteLine("Drive {0} unloaded.", drive);
return CommandResult.Normal;
@@ -236,7 +230,60 @@ namespace Contralto.SdlUI
}
return CommandResult.Normal;
- }
+ }
+
+ [DebuggerFunction("load trident", "Loads the specified trident drive with the requested disk image.", " ")]
+ private CommandResult LoadTrident(ushort drive, string path)
+ {
+ if (drive > 7)
+ {
+ throw new InvalidOperationException("Drive specification out of range.");
+ }
+
+ // Load the new pack.
+ _system.LoadTridentDrive(drive, path, false);
+ Console.WriteLine("Trident {0} loaded.", drive);
+
+ return CommandResult.Normal;
+ }
+
+ [DebuggerFunction("unload trident", "Unloads the specified trident drive.", "")]
+ private CommandResult UnloadTrident(ushort drive)
+ {
+ if (drive > 7)
+ {
+ throw new InvalidOperationException("Drive specification out of range.");
+ }
+
+ // Unload the current pack.
+ _system.UnloadTridentDrive(drive);
+ Console.WriteLine("Trident {0} unloaded.", drive);
+
+ return CommandResult.Normal;
+ }
+
+ [DebuggerFunction("show trident", "Displays the contents of the specified trident drive.", "")]
+ private CommandResult ShowTrident(ushort drive)
+ {
+ if (drive > 7)
+ {
+ throw new InvalidOperationException("Drive specification out of range.");
+ }
+
+ // Save current drive contents.
+ if (_system.TridentController.Drives[drive].IsLoaded)
+ {
+ Console.WriteLine("Trident {0} contains image {1}",
+ drive,
+ _system.TridentController.Drives[drive].Pack.PackName);
+ }
+ else
+ {
+ Console.WriteLine("Trident {0} is not loaded.", drive);
+ }
+
+ return CommandResult.Normal;
+ }
[DebuggerFunction("show system type", "Displays the Alto system type.")]
private CommandResult ShowSystemType()
diff --git a/Contralto/UI/AltoWindow.Designer.cs b/Contralto/UI/AltoWindow.Designer.cs
index fae007b..32078f5 100644
--- a/Contralto/UI/AltoWindow.Designer.cs
+++ b/Contralto/UI/AltoWindow.Designer.cs
@@ -41,13 +41,14 @@
this.unloadToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
this.Drive0ImageName = new System.Windows.Forms.ToolStripMenuItem();
this.drive1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.unloadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.loadToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
+ this.unloadToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
this.Drive1ImageName = new System.Windows.Forms.ToolStripMenuItem();
this.AlternateBootToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SystemEthernetBootMenu = new System.Windows.Forms.ToolStripMenuItem();
this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.SystemShowDebuggerMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.fullScreenToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.StatusLine = new System.Windows.Forms.StatusStrip();
@@ -56,7 +57,9 @@
this.CaptureStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.SystemStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.DisplayBox = new System.Windows.Forms.PictureBox();
- this.fullScreenToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.newToolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem();
+ this.newToolStripMenuItem2 = new System.Windows.Forms.ToolStripMenuItem();
+ this.TridentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this._mainMenu.SuspendLayout();
this.StatusLine.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.DisplayBox)).BeginInit();
@@ -106,6 +109,7 @@
this.SystemResetMenuItem,
this.drive0ToolStripMenuItem,
this.drive1ToolStripMenuItem,
+ this.TridentToolStripMenuItem,
this.AlternateBootToolStripMenuItem,
this.SystemEthernetBootMenu,
this.optionsToolStripMenuItem,
@@ -139,6 +143,7 @@
this.drive0ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.loadToolStripMenuItem1,
this.unloadToolStripMenuItem1,
+ this.newToolStripMenuItem1,
this.Drive0ImageName});
this.drive0ToolStripMenuItem.Name = "drive0ToolStripMenuItem";
this.drive0ToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
@@ -157,7 +162,7 @@
//
this.unloadToolStripMenuItem1.Name = "unloadToolStripMenuItem1";
this.unloadToolStripMenuItem1.Size = new System.Drawing.Size(172, 22);
- this.unloadToolStripMenuItem1.Text = "Unload...";
+ this.unloadToolStripMenuItem1.Text = "Unload";
this.unloadToolStripMenuItem1.Click += new System.EventHandler(this.OnSystemDrive0UnloadClick);
//
// Drive0ImageName
@@ -170,32 +175,33 @@
// drive1ToolStripMenuItem
//
this.drive1ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.loadToolStripMenuItem,
- this.unloadToolStripMenuItem,
+ this.loadToolStripMenuItem2,
+ this.unloadToolStripMenuItem2,
+ this.newToolStripMenuItem2,
this.Drive1ImageName});
this.drive1ToolStripMenuItem.Name = "drive1ToolStripMenuItem";
this.drive1ToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
this.drive1ToolStripMenuItem.Text = "Drive 1";
//
- // loadToolStripMenuItem
+ // loadToolStripMenuItem2
//
- this.loadToolStripMenuItem.Name = "loadToolStripMenuItem";
- this.loadToolStripMenuItem.Size = new System.Drawing.Size(142, 22);
- this.loadToolStripMenuItem.Text = "Load...";
- this.loadToolStripMenuItem.Click += new System.EventHandler(this.OnSystemDrive1LoadClick);
+ this.loadToolStripMenuItem2.Name = "loadToolStripMenuItem2";
+ this.loadToolStripMenuItem2.Size = new System.Drawing.Size(152, 22);
+ this.loadToolStripMenuItem2.Text = "Load...";
+ this.loadToolStripMenuItem2.Click += new System.EventHandler(this.OnSystemDrive1LoadClick);
//
- // unloadToolStripMenuItem
+ // unloadToolStripMenuItem2
//
- this.unloadToolStripMenuItem.Name = "unloadToolStripMenuItem";
- this.unloadToolStripMenuItem.Size = new System.Drawing.Size(142, 22);
- this.unloadToolStripMenuItem.Text = "Unload...";
- this.unloadToolStripMenuItem.Click += new System.EventHandler(this.OnSystemDrive1UnloadClick);
+ this.unloadToolStripMenuItem2.Name = "unloadToolStripMenuItem2";
+ this.unloadToolStripMenuItem2.Size = new System.Drawing.Size(152, 22);
+ this.unloadToolStripMenuItem2.Text = "Unload";
+ this.unloadToolStripMenuItem2.Click += new System.EventHandler(this.OnSystemDrive1UnloadClick);
//
// Drive1ImageName
//
this.Drive1ImageName.Enabled = false;
this.Drive1ImageName.Name = "Drive1ImageName";
- this.Drive1ImageName.Size = new System.Drawing.Size(142, 22);
+ this.Drive1ImageName.Size = new System.Drawing.Size(152, 22);
this.Drive1ImageName.Text = "Image Name";
//
// AlternateBootToolStripMenuItem
@@ -228,6 +234,15 @@
this.SystemShowDebuggerMenuItem.Text = "Show Debugger";
this.SystemShowDebuggerMenuItem.Click += new System.EventHandler(this.OnDebuggerShowClick);
//
+ // fullScreenToolStripMenuItem
+ //
+ this.fullScreenToolStripMenuItem.Name = "fullScreenToolStripMenuItem";
+ this.fullScreenToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt)
+ | System.Windows.Forms.Keys.F)));
+ this.fullScreenToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
+ this.fullScreenToolStripMenuItem.Text = "Full Screen";
+ this.fullScreenToolStripMenuItem.Click += new System.EventHandler(this.OnFullScreenMenuClick);
+ //
// helpToolStripMenuItem
//
this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
@@ -307,14 +322,25 @@
this.DisplayBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnDisplayMouseMove);
this.DisplayBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnDisplayMouseUp);
//
- // fullScreenToolStripMenuItem
+ // newToolStripMenuItem1
//
- this.fullScreenToolStripMenuItem.Name = "fullScreenToolStripMenuItem";
- this.fullScreenToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt)
- | System.Windows.Forms.Keys.F)));
- this.fullScreenToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
- this.fullScreenToolStripMenuItem.Text = "Full Screen";
- this.fullScreenToolStripMenuItem.Click += new System.EventHandler(this.OnFullScreenMenuClick);
+ this.newToolStripMenuItem1.Name = "newToolStripMenuItem1";
+ this.newToolStripMenuItem1.Size = new System.Drawing.Size(172, 22);
+ this.newToolStripMenuItem1.Text = "New...";
+ this.newToolStripMenuItem1.Click += new System.EventHandler(this.OnSystemDrive0NewClick);
+ //
+ // newToolStripMenuItem2
+ //
+ this.newToolStripMenuItem2.Name = "newToolStripMenuItem2";
+ this.newToolStripMenuItem2.Size = new System.Drawing.Size(152, 22);
+ this.newToolStripMenuItem2.Text = "New...";
+ this.newToolStripMenuItem2.Click += new System.EventHandler(this.OnSystemDrive1NewClick);
+ //
+ // TridentToolStripMenuItem
+ //
+ this.TridentToolStripMenuItem.Name = "TridentToolStripMenuItem";
+ this.TridentToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
+ this.TridentToolStripMenuItem.Text = "Trident Drives";
//
// AltoWindow
//
@@ -363,8 +389,8 @@
private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem unloadToolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem drive1ToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem;
- private System.Windows.Forms.ToolStripMenuItem unloadToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem loadToolStripMenuItem2;
+ private System.Windows.Forms.ToolStripMenuItem unloadToolStripMenuItem2;
private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem;
private System.Windows.Forms.StatusStrip StatusLine;
private System.Windows.Forms.ToolStripStatusLabel CaptureStatusLabel;
@@ -378,5 +404,8 @@
private System.Windows.Forms.ToolStripMenuItem saveScreenshotToolStripMenuItem;
private System.Windows.Forms.ToolStripStatusLabel FPSLabel;
private System.Windows.Forms.ToolStripMenuItem fullScreenToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem1;
+ private System.Windows.Forms.ToolStripMenuItem newToolStripMenuItem2;
+ private System.Windows.Forms.ToolStripMenuItem TridentToolStripMenuItem;
}
}
\ No newline at end of file
diff --git a/Contralto/UI/AltoWindow.cs b/Contralto/UI/AltoWindow.cs
index 31dfe56..0a6b702 100644
--- a/Contralto/UI/AltoWindow.cs
+++ b/Contralto/UI/AltoWindow.cs
@@ -62,6 +62,8 @@ namespace Contralto
ReleaseMouse();
+ CreateTridentMenu();
+
SystemStatusLabel.Text = _systemStoppedText;
DiskStatusLabel.Text = String.Empty;
@@ -95,8 +97,16 @@ namespace Contralto
_controller.ErrorCallback += OnExecutionError;
// Update disk image UI info
+ // Diablo disks:
Drive0ImageName.Text = _system.DiskController.Drives[0].IsLoaded ? Path.GetFileName(_system.DiskController.Drives[0].Pack.PackName) : _noImageLoadedText;
Drive1ImageName.Text = _system.DiskController.Drives[1].IsLoaded ? Path.GetFileName(_system.DiskController.Drives[1].Pack.PackName) : _noImageLoadedText;
+
+ // Trident disks
+ for (int i = 0; i < _tridentImageNames.Count; i++)
+ {
+ TridentDrive drive = _system.TridentController.Drives[i];
+ _tridentImageNames[i].Text = drive.IsLoaded ? Path.GetFileName(drive.Pack.PackName) : _noImageLoadedText;
+ }
}
///
@@ -129,7 +139,7 @@ namespace Contralto
private void OnSystemDrive0LoadClick(object sender, EventArgs e)
{
- string path = ShowImageLoadDialog(0);
+ string path = ShowImageLoadDialog(0, false);
if (String.IsNullOrEmpty(path))
{
@@ -138,9 +148,7 @@ namespace Contralto
try
{
- // Commit loaded pack back to disk
- _system.CommitDiskPack(0);
- _system.LoadDrive(0, path);
+ _system.LoadDiabloDrive(0, path, false);
Drive0ImageName.Text = System.IO.Path.GetFileName(path);
Configuration.Drive0Image = path;
}
@@ -154,15 +162,14 @@ namespace Contralto
private void OnSystemDrive0UnloadClick(object sender, EventArgs e)
{
- _system.CommitDiskPack(0);
- _system.UnloadDrive(0);
+ _system.UnloadDiabloDrive(0);
Drive0ImageName.Text = _noImageLoadedText;
Configuration.Drive0Image = String.Empty;
}
- private void OnSystemDrive1LoadClick(object sender, EventArgs e)
+ private void OnSystemDrive0NewClick(object sender, EventArgs e)
{
- string path = ShowImageLoadDialog(1);
+ string path = ShowImageNewDialog(0, false);
if (String.IsNullOrEmpty(path))
{
@@ -171,9 +178,30 @@ namespace Contralto
try
{
- // Commit loaded pack back to disk
- _system.CommitDiskPack(1);
- _system.LoadDrive(1, path);
+ _system.LoadDiabloDrive(0, path, true);
+ Drive0ImageName.Text = System.IO.Path.GetFileName(path);
+ Configuration.Drive0Image = path;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ String.Format("An error occurred while creating new disk image: {0}", ex.Message),
+ "Image creation error", MessageBoxButtons.OK);
+ }
+ }
+
+ private void OnSystemDrive1LoadClick(object sender, EventArgs e)
+ {
+ string path = ShowImageLoadDialog(1, false);
+
+ if (String.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ try
+ {
+ _system.LoadDiabloDrive(1, path, false);
Drive1ImageName.Text = System.IO.Path.GetFileName(path);
Configuration.Drive1Image = path;
}
@@ -186,13 +214,92 @@ namespace Contralto
}
private void OnSystemDrive1UnloadClick(object sender, EventArgs e)
- {
- _system.CommitDiskPack(1);
- _system.UnloadDrive(1);
+ {
+ _system.UnloadDiabloDrive(1);
Drive1ImageName.Text = _noImageLoadedText;
Configuration.Drive1Image = String.Empty;
}
+
+ private void OnSystemDrive1NewClick(object sender, EventArgs e)
+ {
+ string path = ShowImageNewDialog(1, false);
+
+ if (String.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ try
+ {
+ _system.LoadDiabloDrive(1, path, true);
+ Drive0ImageName.Text = System.IO.Path.GetFileName(path);
+ Configuration.Drive0Image = path;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ String.Format("An error occurred while creating new disk image: {0}", ex.Message),
+ "Image creation error", MessageBoxButtons.OK);
+ }
+ }
+
+ private void OnTridentLoadClick(object sender, EventArgs e)
+ {
+ int drive = (int)((ToolStripDropDownItem)sender).Tag;
+ string path = ShowImageLoadDialog(drive, true);
+
+ if (String.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ try
+ {
+ _system.LoadTridentDrive(drive, path, false);
+ _tridentImageNames[drive].Text = System.IO.Path.GetFileName(path);
+ Configuration.TridentImages[drive] = path;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ String.Format("An error occurred while loading Trident image: {0}", ex.Message),
+ "Image load error", MessageBoxButtons.OK);
+ }
+ }
+
+ private void OnTridentUnloadClick(object sender, EventArgs e)
+ {
+ int drive = (int)((ToolStripDropDownItem)sender).Tag;
+ _system.UnloadTridentDrive(drive);
+ _tridentImageNames[drive].Text = _noImageLoadedText;
+ Configuration.TridentImages[drive] = String.Empty;
+ }
+
+ private void OnTridentNewClick(object sender, EventArgs e)
+ {
+ int drive = (int)((ToolStripDropDownItem)sender).Tag;
+ string path = ShowImageNewDialog(drive, true);
+
+ if (String.IsNullOrEmpty(path))
+ {
+ return;
+ }
+
+ try
+ {
+ _system.LoadTridentDrive(drive, path, true);
+ _tridentImageNames[drive].Text = System.IO.Path.GetFileName(path);
+ Configuration.TridentImages[drive] = path;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(
+ String.Format("An error occurred while creating new Trident image: {0}", ex.Message),
+ "Image creation error", MessageBoxButtons.OK);
+ }
+ }
+
private void OnAlternateBootOptionsClicked(object sender, EventArgs e)
{
AlternateBootOptions bootWindow = new AlternateBootOptions();
@@ -311,16 +418,40 @@ namespace Contralto
DialogResult = DialogResult.OK;
}
- private string ShowImageLoadDialog(int drive)
+ private string ShowImageLoadDialog(int drive, bool trident)
{
OpenFileDialog fileDialog = new OpenFileDialog();
- fileDialog.DefaultExt = "dsk";
- fileDialog.Filter = "Alto Disk Images (*.dsk, *.dsk44)|*.dsk;*.dsk44|Diablo 31 Disk Images (*.dsk)|*.dsk|Diablo 44 Disk Images (*.dsk44)|*.dsk44|All Files (*.*)|*.*";
+ fileDialog.DefaultExt = trident ? "dsk80" : "dsk";
+ fileDialog.Filter = trident ? _tridentFilter : _diabloFilter;
fileDialog.Multiselect = false;
fileDialog.CheckFileExists = true;
fileDialog.CheckPathExists = true;
- fileDialog.Title = String.Format("Select image to load into drive {0}", drive);
+ fileDialog.Title = String.Format("Select image to load into {0} drive {1}", trident ? "Trident" : "Diablo", drive);
+
+ DialogResult res = fileDialog.ShowDialog();
+
+ if (res == DialogResult.OK)
+ {
+ return fileDialog.FileName;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private string ShowImageNewDialog(int drive, bool trident)
+ {
+ SaveFileDialog fileDialog = new SaveFileDialog();
+
+ fileDialog.DefaultExt = trident ? "dsk80" : "dsk";
+ fileDialog.Filter = trident ? _tridentFilter : _diabloFilter;
+ fileDialog.CheckFileExists = false;
+ fileDialog.CheckPathExists = true;
+ fileDialog.OverwritePrompt = true;
+ fileDialog.ValidateNames = true;
+ fileDialog.Title = String.Format("Select path for new {0} image for drive {1}", trident ? "Trident" : "Diablo", drive);
DialogResult res = fileDialog.ShowDialog();
@@ -965,6 +1096,47 @@ namespace Contralto
return null;
}
+ private void CreateTridentMenu()
+ {
+ //
+ // Add eight sub-menus, one per drive.
+ //
+ _tridentImageNames = new List(8);
+
+ for (int i=0;i<8;i++)
+ {
+ // Parent menu item
+ ToolStripMenuItem tridentMenu = new ToolStripMenuItem(String.Format("Drive {0}", i));
+
+ // Children:
+ // - Load
+ // - Unload
+ // - New
+ // - Pack Name (disabled)
+ //
+ ToolStripMenuItem loadMenu = new ToolStripMenuItem("Load...", null, OnTridentLoadClick);
+ loadMenu.Tag = i;
+
+ ToolStripMenuItem unloadMenu = new ToolStripMenuItem("Unload", null, OnTridentUnloadClick);
+ unloadMenu.Tag = i;
+
+ ToolStripMenuItem newMenu = new ToolStripMenuItem("New...", null, OnTridentNewClick);
+ newMenu.Tag = i;
+
+ ToolStripMenuItem imageMenu = new ToolStripMenuItem(_noImageLoadedText);
+ imageMenu.Tag = i;
+ imageMenu.Enabled = false;
+ _tridentImageNames.Add(imageMenu);
+
+ tridentMenu.DropDownItems.Add(loadMenu);
+ tridentMenu.DropDownItems.Add(unloadMenu);
+ tridentMenu.DropDownItems.Add(newMenu);
+ tridentMenu.DropDownItems.Add(imageMenu);
+
+ TridentToolStripMenuItem.DropDownItems.Add(tridentMenu);
+ }
+ }
+
// Display related data.
// Note: display is actually 606 pixels wide, but that's not an even multiple of 8, so we round up.
@@ -1015,10 +1187,17 @@ namespace Contralto
private Image _diskWriteImage;
private Image _diskSeekImage;
+ // Trident menu items for disk names
+ private List _tridentImageNames;
+
// strings. TODO: move to resource
private const string _noImageLoadedText = "";
private const string _systemStoppedText = "Alto Stopped.";
private const string _systemRunningText = "Alto Running.";
- private const string _systemErrorText = "Alto Stopped due to error. See Debugger.";
+ private const string _systemErrorText = "Alto Stopped due to error. See Debugger.";
+ private const string _diabloFilter = "Alto Diablo Disk Images (*.dsk, *.dsk44)|*.dsk;*.dsk44|Diablo 31 Disk Images (*.dsk)|*.dsk|Diablo 44 Disk Images (*.dsk44)|*.dsk44|All Files (*.*)|*.*";
+ private const string _tridentFilter = "Alto Trident Disk Images (*.dsk80, *.dsk300)|*.dsk80;*.dsk300|Trident T80 Disk Images (*.dsk80)|*.dsk80|Trident T300 Disk Images (*.dsk300)|*.dsk300|All Files (*.*)|*.*";
+
+
}
}
diff --git a/Contralto/UI/Debugger.cs b/Contralto/UI/Debugger.cs
index 68baf69..4821d56 100644
--- a/Contralto/UI/Debugger.cs
+++ b/Contralto/UI/Debugger.cs
@@ -598,7 +598,7 @@ namespace Contralto
"EM", // 0 - emulator
"OR", // 1 - orbit
String.Empty,
- String.Empty,
+ "TO", // 3 - trident output
"KS", // 4 - disk sector
String.Empty,
String.Empty,
@@ -610,7 +610,7 @@ namespace Contralto
"DV", // 12 - display vertical
"PA", // 13 - parity
"KW", // 14 - disk word
- String.Empty,
+ "TI", // 15 - trident input
};
if (task == TaskType.Invalid)
@@ -630,7 +630,7 @@ namespace Contralto
Color.LightBlue, // 0 - emulator
Color.LightGoldenrodYellow, // 1 - orbit
Color.LightGray, // 2 - unused
- Color.LightGray, // 3 - unused
+ Color.LightCoral, // 3 - trident output
Color.LightGreen, // 4 - disk sector
Color.LightGray, // 5 - unused
Color.LightGray, // 6 - unused
@@ -638,11 +638,11 @@ namespace Contralto
Color.LightSeaGreen,// 8 - memory refresh
Color.LightYellow, // 9 - display word
Color.LightPink, // 10 - cursor
- Color.Chartreuse, // 11 - display horizontal
+ Color.Chartreuse, // 11 - display horizontal
Color.LightCoral, // 12 - display vertical
Color.LightSteelBlue, // 13 - parity
Color.Gray, // 14 - disk word
- Color.LightGray, // 15 - unused
+ Color.LightSteelBlue, // 15 - trident output
};
if (task == TaskType.Invalid)
diff --git a/Contralto/UI/SystemOptions.Designer.cs b/Contralto/UI/SystemOptions.Designer.cs
index 014156c..661c07a 100644
--- a/Contralto/UI/SystemOptions.Designer.cs
+++ b/Contralto/UI/SystemOptions.Designer.cs
@@ -55,15 +55,15 @@
this.label2 = new System.Windows.Forms.Label();
this.EnableDACCaptureCheckBox = new System.Windows.Forms.CheckBox();
this.EnableDACCheckBox = new System.Windows.Forms.CheckBox();
- this.DialogOKButton = new System.Windows.Forms.Button();
- this.DialogCancelButton = new System.Windows.Forms.Button();
this.PrintingTab = new System.Windows.Forms.TabPage();
this.PrintingOptionsGroupBox = new System.Windows.Forms.GroupBox();
+ this.ReversePageOrderCheckBox = new System.Windows.Forms.CheckBox();
this.button1 = new System.Windows.Forms.Button();
this.PrintOutputPathTextBox = new System.Windows.Forms.TextBox();
this.label5 = new System.Windows.Forms.Label();
this.EnablePrintingCheckBox = new System.Windows.Forms.CheckBox();
- this.ReversePageOrderCheckBox = new System.Windows.Forms.CheckBox();
+ this.DialogOKButton = new System.Windows.Forms.Button();
+ this.DialogCancelButton = new System.Windows.Forms.Button();
this.OptionsTabs.SuspendLayout();
this.tabPage1.SuspendLayout();
this.tabPage2.SuspendLayout();
@@ -368,26 +368,6 @@
this.EnableDACCheckBox.UseVisualStyleBackColor = true;
this.EnableDACCheckBox.CheckedChanged += new System.EventHandler(this.OnEnableDACCheckboxChanged);
//
- // DialogOKButton
- //
- this.DialogOKButton.Location = new System.Drawing.Point(211, 239);
- this.DialogOKButton.Name = "DialogOKButton";
- this.DialogOKButton.Size = new System.Drawing.Size(75, 23);
- this.DialogOKButton.TabIndex = 1;
- this.DialogOKButton.Text = "OK";
- this.DialogOKButton.UseVisualStyleBackColor = true;
- this.DialogOKButton.Click += new System.EventHandler(this.OKButton_Click);
- //
- // DialogCancelButton
- //
- this.DialogCancelButton.Location = new System.Drawing.Point(292, 239);
- this.DialogCancelButton.Name = "DialogCancelButton";
- this.DialogCancelButton.Size = new System.Drawing.Size(75, 23);
- this.DialogCancelButton.TabIndex = 2;
- this.DialogCancelButton.Text = "Cancel";
- this.DialogCancelButton.UseVisualStyleBackColor = true;
- this.DialogCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
- //
// PrintingTab
//
this.PrintingTab.Controls.Add(this.PrintingOptionsGroupBox);
@@ -413,6 +393,16 @@
this.PrintingOptionsGroupBox.TabStop = false;
this.PrintingOptionsGroupBox.Text = "Printing options";
//
+ // ReversePageOrderCheckBox
+ //
+ this.ReversePageOrderCheckBox.AutoSize = true;
+ this.ReversePageOrderCheckBox.Location = new System.Drawing.Point(22, 51);
+ this.ReversePageOrderCheckBox.Name = "ReversePageOrderCheckBox";
+ this.ReversePageOrderCheckBox.Size = new System.Drawing.Size(158, 17);
+ this.ReversePageOrderCheckBox.TabIndex = 4;
+ this.ReversePageOrderCheckBox.Text = "Reverse Output Page Order";
+ this.ReversePageOrderCheckBox.UseVisualStyleBackColor = true;
+ //
// button1
//
this.button1.Location = new System.Drawing.Point(254, 24);
@@ -421,6 +411,7 @@
this.button1.TabIndex = 3;
this.button1.Text = "Browse...";
this.button1.UseVisualStyleBackColor = true;
+ this.button1.Click += new System.EventHandler(this.OnPrintOutputBrowseButtonClicked);
//
// PrintOutputPathTextBox
//
@@ -449,15 +440,25 @@
this.EnablePrintingCheckBox.UseVisualStyleBackColor = true;
this.EnablePrintingCheckBox.CheckedChanged += new System.EventHandler(this.EnablePrintingCheckBox_CheckedChanged);
//
- // ReversePageOrderCheckBox
+ // DialogOKButton
//
- this.ReversePageOrderCheckBox.AutoSize = true;
- this.ReversePageOrderCheckBox.Location = new System.Drawing.Point(22, 51);
- this.ReversePageOrderCheckBox.Name = "ReversePageOrderCheckBox";
- this.ReversePageOrderCheckBox.Size = new System.Drawing.Size(158, 17);
- this.ReversePageOrderCheckBox.TabIndex = 4;
- this.ReversePageOrderCheckBox.Text = "Reverse Output Page Order";
- this.ReversePageOrderCheckBox.UseVisualStyleBackColor = true;
+ this.DialogOKButton.Location = new System.Drawing.Point(211, 239);
+ this.DialogOKButton.Name = "DialogOKButton";
+ this.DialogOKButton.Size = new System.Drawing.Size(75, 23);
+ this.DialogOKButton.TabIndex = 1;
+ this.DialogOKButton.Text = "OK";
+ this.DialogOKButton.UseVisualStyleBackColor = true;
+ this.DialogOKButton.Click += new System.EventHandler(this.OKButton_Click);
+ //
+ // DialogCancelButton
+ //
+ this.DialogCancelButton.Location = new System.Drawing.Point(292, 239);
+ this.DialogCancelButton.Name = "DialogCancelButton";
+ this.DialogCancelButton.Size = new System.Drawing.Size(75, 23);
+ this.DialogCancelButton.TabIndex = 2;
+ this.DialogCancelButton.Text = "Cancel";
+ this.DialogCancelButton.UseVisualStyleBackColor = true;
+ this.DialogCancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
// SystemOptions
//
diff --git a/Contralto/UI/SystemOptions.cs b/Contralto/UI/SystemOptions.cs
index 23a599b..a1fe523 100644
--- a/Contralto/UI/SystemOptions.cs
+++ b/Contralto/UI/SystemOptions.cs
@@ -360,10 +360,15 @@ namespace Contralto.UI
}
else
{
- EnablePrintingCheckBox.Checked = false;
+ EnablePrintingCheckBox.Checked = string.IsNullOrEmpty(PrintOutputPathTextBox.Text) ? false : true;
}
}
+ private void OnPrintOutputBrowseButtonClicked(object sender, EventArgs e)
+ {
+ BrowseForPrintOutputFolder();
+ }
+
private void EnablePrintingCheckBox_CheckedChanged(object sender, EventArgs e)
{
PrintingOptionsGroupBox.Enabled = EnablePrintingCheckBox.Checked;
@@ -374,6 +379,6 @@ namespace Contralto.UI
//
BrowseForPrintOutputFolder();
}
- }
+ }
}
}
diff --git a/ContraltoSetup/Product.wxs b/ContraltoSetup/Product.wxs
index db96d77..ba787cc 100644
--- a/ContraltoSetup/Product.wxs
+++ b/ContraltoSetup/Product.wxs
@@ -194,10 +194,10 @@
-
-
-
-
+
+
+
+