1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-02-01 22:33:05 +00:00

Initial implementation of Trident controller and drives (supporting T-80 and T-300 packs). TFU works and can certify, erase, exercise and manipulate files on Trident packs. TriEx doesn't quite work properly yet. Still some issues to iron out.

Added file-backed disk image implementation for use with Trident disk images, did some basic refactoring of disk load/unload logic, added support for creating new (empty) disk images for both Trident and Diablo disks.

Added UI for loading/unloading/creating up to 8 trident packs; added blank Diablo pack creation UI.  (Both Windows and *nix interfaces.)

Added configuration support for same (both Windows and *nix.)

Small correction to Print output path browsing logic.

Fixed Windows installer, now places the right ROMs for Alto I configurations in the right place.

Fixed issue when starting up with corrupted configuration.  Corrupted configuration is ignored and ContrAlto will run with default config.
This commit is contained in:
Josh Dersch
2017-08-22 13:18:31 -07:00
parent bfcce44a8f
commit 523a4bb27f
30 changed files with 2969 additions and 406 deletions

View File

@@ -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;
}

View File

@@ -67,6 +67,22 @@
<setting name="ReversePageOrder" serializeAs="String">
<value>True</value>
</setting>
<setting name="TridentImages" serializeAs="Xml">
<value>
<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>
</value>
</setting>
</Contralto.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -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);
}
/// <summary>

View File

@@ -217,6 +217,21 @@ namespace Contralto.CPU
OrbitROSCommand = 14,
}
/// <summary>
/// Trident disk controller, from the microcode.
/// </summary>
enum TridentF2
{
ReadKDTA = 6,
STATUS = 8,
KTAG = 10,
WriteKDTA = 11,
WAIT = 12, // These two are identical in function
WAIT2 = 13,
RESET = 14,
EMPTY = 15,
}
/// <summary>
/// MicroInstruction encapsulates the decoding of a microinstruction word.
/// It also caches precomputed metadata related to the microinstruction that

View File

@@ -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;

View File

@@ -31,6 +31,9 @@ namespace Contralto.CPU
{
_taskType = TaskType.Orbit;
_wakeup = false;
// The Orbit task is RAM-related.
_ramTask = true;
}
protected override void ExecuteBlock()

View File

@@ -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.
}
/// <summary>
/// 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.
/// </summary>
/// <param name="f2"></param>
protected virtual void ExecuteSpecialFunction2PostBusSource(MicroInstruction instruction)
{
// Nothing by default.
}
/// <summary>
/// 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
/// </summary>
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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
using Contralto.IO;
using System;
namespace Contralto.CPU
{
public partial class AltoCPU
{
/// <summary>
/// TridentTask implements the microcode special functions for the Trident
/// disk controller (for both the read and write tasks).
/// </summary>
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;
}
}
}

View File

@@ -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));
}
}
}
}

View File

@@ -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);
}
}
/// <summary>
@@ -144,6 +161,11 @@ namespace Contralto
/// </summary>
public static string Drive1Image;
/// <summary>
/// The currently loaded images for the Trident controller.
/// </summary>
public static StringCollection TridentImages;
/// <summary>
/// The Ethernet host address for this Alto
/// </summary>
@@ -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

View File

@@ -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

View File

@@ -125,7 +125,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="CPU\Tasks\OrbitTask.cs" />
<Compile Include="CPU\Tasks\TridentTask.cs" />
<Compile Include="HighResTimer.cs" />
<Compile Include="IO\TridentDrive.cs" />
<Compile Include="IO\DoverROS.cs" />
<Compile Include="IO\AudioDAC.cs" />
<Compile Include="IO\OrbitController.cs" />
@@ -133,6 +135,7 @@
<Compile Include="IO\Printing\IPageSink.cs" />
<Compile Include="IO\Printing\NullPageSink.cs" />
<Compile Include="IO\Printing\PdfPageSink.cs" />
<Compile Include="IO\TridentController.cs" />
<Compile Include="IO\UDPEncapsulation.cs" />
<Compile Include="IO\IPacketEncapsulation.cs" />
<Compile Include="Properties\Resources.Designer.cs">
@@ -199,7 +202,7 @@
<Compile Include="IClockable.cs" />
<Compile Include="IO\DiabloDrive.cs" />
<Compile Include="IO\DiskController.cs" />
<Compile Include="IO\DiabloPack.cs" />
<Compile Include="IO\DiskPack.cs" />
<Compile Include="IO\EthernetController.cs" />
<Compile Include="IO\HostEthernetEncapsulation.cs" />
<Compile Include="IO\Keyboard.cs" />

View File

@@ -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
/// <summary>
/// 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.
/// </summary>
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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
namespace Contralto.IO
{
/// <summary>
/// DiskGeometry encapsulates the geometry of a disk.
/// </summary>
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
}
/// <summary>
/// DiabloDiskSector encapsulates the records contained in a single Alto disk sector
/// on a Diablo disk. This includes the header, label, and data records.
/// </summary>
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<array.Length;i++)
{
array[i] = (ushort)((data[offset]) | (data[offset + 1] << 8));
offset += 2;
}
return array;
}
public ushort[] Header;
public ushort[] Label;
public ushort[] Data;
}
/// <summary>
/// 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)
/// </summary>
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<buffer.Length;i++)
{
imageStream.WriteByte((byte)buffer[i]);
imageStream.WriteByte((byte)(buffer[i] >> 8));
}
}
private void SwapBytes(byte[] data)
{
for(int i=0;i<data.Length;i+=2)
{
byte t = data[i];
data[i] = data[i + 1];
data[i + 1] = t;
}
}
private string _packName; // The file from whence the data came
private DiabloDiskSector[,,] _sectors;
private DiabloDiskType _diskType;
private DiskGeometry _geometry;
}
}

View File

@@ -230,7 +230,24 @@ namespace Contralto.IO
public DiskActivityType LastDiskActivity
{
get { return _lastDiskActivity; }
}
}
public void CommitDisk(int driveId)
{
DiabloDrive drive = _drives[driveId];
if (drive.IsLoaded)
{
try
{
drive.Pack.Save();
}
catch (Exception e)
{
// TODO: this does not really belong here.
System.Windows.Forms.MessageBox.Show(String.Format("Unable to save Diablo disk {0}'s contents. Error {0}. Any changes have been lost.", e.Message), "Disk save error");
}
}
}
public void Reset()
{
@@ -322,7 +339,7 @@ namespace Contralto.IO
Log.Write(LogType.Verbose, LogComponent.DiskController, "KDATA is {0}", Conversion.ToOctal(_kDataWrite));
_system.CPU.WakeupTask(CPU.TaskType.DiskSector);
// Reset SECLATE
// Reset SECLATE
_seclate = false;
_seclateEnable = true;
_kStat &= (ushort)~SECLATE;

615
Contralto/IO/DiskPack.cs Normal file
View File

@@ -0,0 +1,615 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
namespace Contralto.IO
{
/// <summary>
/// DiskGeometry encapsulates the geometry of a disk.
/// </summary>
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;
/// <summary>
/// Returns the total size (in bytes) of a disk with this geometry.
/// This includes the extra word-per-sector of the Bitsavers images.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Describes the geometry of an Alto disk sector in terms of the
/// size of the header, label, and data blocks.
/// </summary>
public struct SectorGeometry
{
/// <summary>
/// Specifies the layout of a sector on an Alto pack. Sizes are in bytes.
/// </summary>
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;
/// <summary>
/// Returns the total size (in bytes) of a sector with this geometry.
/// This includes the extra word-per-sector of the Bitsavers images.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// DiskSector encapsulates the records contained in a single Alto disk sector
/// on a disk. This includes the header, label, and data records.
/// </summary>
public class DiskSector
{
/// <summary>
/// Create a new DiskSector populated from the specified stream.
/// </summary>
/// <param name="geometry"></param>
/// <param name="inputStream"></param>
/// <param name="cylinder"></param>
/// <param name="head"></param>
/// <param name="sector"></param>
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;
}
/// <summary>
/// Create a new, empty sector.
/// </summary>
/// <param name="geometry"></param>
/// <param name="cylinder"></param>
/// <param name="head"></param>
/// <param name="sector"></param>
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<array.Length;i++)
{
array[i] = (ushort)((data[offset]) | (data[offset + 1] << 8));
offset += 2;
}
return array;
}
private void WriteWordBuffer(Stream imageStream, ushort[] buffer)
{
// TODO: this is beyond inefficient
for (int i = 0; i < buffer.Length; i++)
{
imageStream.WriteByte((byte)buffer[i]);
imageStream.WriteByte((byte)(buffer[i] >> 8));
}
}
private ushort[] _header;
private ushort[] _label;
private ushort[] _data;
private int _cylinder;
private int _head;
private int _sector;
private bool _modified;
}
/// <summary>
/// The IDiskPack interface defines a generic mechanism for creating, loading, storing,
/// and accessing the sectors of a disk pack.
/// </summary>
public interface IDiskPack : IDisposable
{
/// <summary>
/// The geometry of this pack.
/// </summary>
DiskGeometry Geometry { get; }
/// <summary>
/// The filename of this pack.
/// </summary>
string PackName { get; }
/// <summary>
/// Commits the current in-memory image back to the file it came from.
/// </summary>
void Save();
/// <summary>
/// Retrieves the specified sector from storage.
/// </summary>
/// <param name="cylinder"></param>
/// <param name="head"></param>
/// <param name="sector"></param>
/// <returns></returns>
DiskSector GetSector(int cylinder, int head, int sector);
/// <summary>
/// Commits this sector back to storage.
/// </summary>
/// <param name="sector"></param>
void CommitSector(DiskSector sector);
}
/// <summary>
/// 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.
/// </summary>
public class InMemoryDiskPack : IDiskPack
{
/// <summary>
/// Creates a new, empty disk pack with the specified geometry.
/// </summary>
/// <param name="geometry"></param>
/// <param name="path"></param>
public static InMemoryDiskPack CreateEmpty(DiskGeometry geometry, string path)
{
return new InMemoryDiskPack(geometry, path, false);
}
/// <summary>
/// Loads an existing disk pack image.
/// </summary>
/// <param name="geometry"></param>
/// <param name="path"></param>
/// <returns></returns>
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; }
}
/// <summary>
/// Commits the current in-memory image back to the file from which it was loaded.
/// </summary>
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.
}
/// <summary>
/// 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.
/// </summary>
public class FileBackedDiskPack : IDiskPack
{
/// <summary>
/// Creates a new, empty disk pack with the specified geometry.
/// </summary>
/// <param name="geometry"></param>
/// <param name="path"></param>
public static FileBackedDiskPack CreateEmpty(DiskGeometry geometry, string path)
{
return new FileBackedDiskPack(geometry, path, false);
}
/// <summary>
/// Loads an existing image.
/// </summary>
/// <param name="geometry"></param>
/// <param name="path"></param>
/// <returns></returns>
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; }
}
/// <summary>
/// Commits pending changes back to disc.
/// </summary>
/// <param name="imageStream"></param>
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.
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
using Contralto.Logging;
using System;
namespace Contralto.IO
{
/// <summary>
/// Encapsulates logic that belongs to a Trident drive, including loading/saving packs,
/// seeking, and parceling out sector data.
/// </summary>
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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
//

View File

@@ -238,5 +238,28 @@ namespace Contralto.Properties {
this["ReversePageOrder"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute(@"<?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>")]
public global::System.Collections.Specialized.StringCollection TridentImages {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["TridentImages"]));
}
set {
this["TridentImages"] = value;
}
}
}
}

View File

@@ -56,5 +56,19 @@
<Setting Name="ReversePageOrder" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="TridentImages" Type="System.Collections.Specialized.StringCollection" Scope="User">
<Value Profile="(Default)">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;string /&gt;
&lt;/ArrayOfString&gt;</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -15,6 +15,7 @@
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
@@ -120,6 +121,13 @@ namespace Contralto
/// <returns></returns>
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.

View File

@@ -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.", "<drive> <path>")]
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.", "<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.", "<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()

View File

@@ -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;
}
}

View File

@@ -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;
}
}
/// <summary>
@@ -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<ToolStripMenuItem>(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<ToolStripMenuItem> _tridentImageNames;
// strings. TODO: move to resource
private const string _noImageLoadedText = "<no image loaded>";
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 (*.*)|*.*";
}
}

View File

@@ -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)

View File

@@ -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
//

View File

@@ -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();
}
}
}
}
}

View File

@@ -194,10 +194,10 @@
<File Id="u36_23" Name="36_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\36_23.BIN"/>
<File Id="u37_23" Name="37_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\37_23.BIN"/>
<File Id="C0_23" Name="C1_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoII\C0"/>
<File Id="C1_23" Name="C2_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoII\C1"/>
<File Id="C2_23" Name="C3_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoII\C2"/>
<File Id="C3_23" Name="C4_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoII\C3"/>
<File Id="C0_23" Name="C0_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\C0_23.BIN"/>
<File Id="C1_23" Name="C1_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\C1_23.BIN"/>
<File Id="C2_23" Name="C2_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\C2_23.BIN"/>
<File Id="C3_23" Name="C3_23.BIN" Source="$(var.Contralto.TargetDir)\ROM\AltoI\C3_23.BIN"/>
</Component>
</ComponentGroup>