diff --git a/Contralto/AltoSystem.cs b/Contralto/AltoSystem.cs
index b2f0c85..fd6eb10 100644
--- a/Contralto/AltoSystem.cs
+++ b/Contralto/AltoSystem.cs
@@ -47,7 +47,7 @@ namespace Contralto
t.AutoReset = true;
t.Interval = 1000;
t.Elapsed += T_Elapsed;
- t.Start();
+ //t.Start();
}
public void Reset()
@@ -80,6 +80,11 @@ namespace Contralto
_displayController.AttachDisplay(d);
}
+ public void DetachDisplay()
+ {
+ _displayController.DetachDisplay();
+ }
+
public void SingleStep()
{
// Run every device that needs attention for a single clock cycle.
@@ -128,7 +133,7 @@ namespace Contralto
//
if (Configuration.EthernetBootEnabled)
{
- _keyboard.PressBootKeys(Configuration.EthernetBootFile, true);
+ _keyboard.PressBootKeys(Configuration.BootAddress, true);
}
}
@@ -172,20 +177,10 @@ namespace Contralto
{
get { return _scheduler; }
}
-
- ///
- /// Time (in msec) for one system clock
- ///
- ///
- public static double ClockInterval
- {
- get { return 0.00017; } // appx 170nsec, TODO: more accurate value?
- }
-
private void T_Elapsed(object sender, ElapsedEventArgs e)
{
- //System.Console.WriteLine("{0} CPU clocks/sec %{1}. {2} fields/sec", _clocks, ((double)_clocks / 5882353.0) * 100.0, _displayController.Fields);
+ System.Console.WriteLine("{0} CPU clocks/sec %{1}. {2} fields/sec", _clocks, ((double)_clocks / 5882353.0) * 100.0, _displayController.Fields);
_clocks = 0;
_displayController.Fields = 0;
}
diff --git a/Contralto/CPU/CPU.cs b/Contralto/CPU/CPU.cs
index 13fdeb7..1710b30 100644
--- a/Contralto/CPU/CPU.cs
+++ b/Contralto/CPU/CPU.cs
@@ -172,7 +172,7 @@ namespace Contralto.CPU
//
WakeupTask(CPU.TaskType.DiskSector);
- Logging.Log.Write(Logging.LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr));
+ Log.Write(Logging.LogComponent.CPU, "Silent Boot; microcode banks initialized to {0}", Conversion.ToOctal(_rmr));
}
///
@@ -232,13 +232,15 @@ namespace Contralto.CPU
if (_tasks[i] != null && _tasks[i].Wakeup)
{
_nextTask = _tasks[i];
+ _nextTask.FirstInstructionAfterSwitch = true;
+ /*
if (_nextTask != _currentTask && _currentTask != null)
{
Log.Write(LogComponent.TaskSwitch, "TASK: Next task will be {0} (pri {1}); current task {2} (pri {3})",
(TaskType)_nextTask.Priority, _nextTask.Priority,
(TaskType)_currentTask.Priority, _currentTask.Priority);
- }
+ } */
break;
}
}
diff --git a/Contralto/CPU/Tasks/Task.cs b/Contralto/CPU/Tasks/Task.cs
index d4932a2..649c663 100644
--- a/Contralto/CPU/Tasks/Task.cs
+++ b/Contralto/CPU/Tasks/Task.cs
@@ -42,6 +42,17 @@ namespace Contralto.CPU
get { return _mpc; }
}
+ ///
+ /// Indicates whether a task switch just happened. TASK instructions behave differently on the
+ /// first instruction after a switch. This is not documented, but observed on the real hardware.
+ /// (See the implementation of the Task SF for more details.)
+ ///
+ public bool FirstInstructionAfterSwitch
+ {
+ get { return _firstInstructionAfterSwitch; }
+ set { _firstInstructionAfterSwitch = value; }
+ }
+
///
/// Indicates whether the current uInstruction asserts BLOCK.
/// Used by hardware for various tasks.
@@ -59,6 +70,7 @@ namespace Contralto.CPU
_mpc = (ushort)_taskType;
_rdRam = false;
_rb = 0;
+ _firstInstructionAfterSwitch = false;
}
public virtual void SoftReset()
@@ -273,7 +285,16 @@ namespace Contralto.CPU
break;
case SpecialFunction1.Task:
- nextTask = true; // Yield to other more important tasks
+ //
+ // If the first uOp executed after a task switch contains a TASK F1, it does not take effect.
+ // This is observed on the real hardware, and does not appear to be documented.
+ // It also doensn't appear to affect the execution of the standard Alto uCode in any significant
+ // way, but is included here for correctness.
+ //
+ //if (!_firstInstructionAfterSwitch)
+ {
+ nextTask = true; // Yield to other more important tasks
+ }
break;
case SpecialFunction1.Block:
@@ -430,7 +451,7 @@ namespace Contralto.CPU
if (swMode)
{
UCodeMemory.SwitchMode(instruction.NEXT, _taskType);
- Logging.Log.Write(Logging.LogComponent.Microcode, "SWMODE: uPC {0}, next uPC {1}", Conversion.ToOctal(_mpc), Conversion.ToOctal(instruction.NEXT | nextModifier));
+ Logging.Log.Write(Logging.LogComponent.Microcode, "SWMODE: uPC {0}, next uPC {1}", Conversion.ToOctal(_mpc), Conversion.ToOctal(instruction.NEXT));
}
//
@@ -450,6 +471,7 @@ namespace Contralto.CPU
_mpc = (ushort)(instruction.NEXT | nextModifier);
}
+ _firstInstructionAfterSwitch = false;
return nextTask;
}
@@ -523,6 +545,7 @@ namespace Contralto.CPU
protected ushort _rb; // S register bank select
protected TaskType _taskType;
protected bool _wakeup;
+ protected bool _firstInstructionAfterSwitch;
protected bool _block;
diff --git a/Contralto/CPU/UCodeMemory.cs b/Contralto/CPU/UCodeMemory.cs
index 744f9ef..84f4c23 100644
--- a/Contralto/CPU/UCodeMemory.cs
+++ b/Contralto/CPU/UCodeMemory.cs
@@ -40,17 +40,14 @@ namespace Contralto.CPU
private static void Init()
{
- //
- // TODO: this is currently configured for a 2K ROM machine
- // (1K RAM, 2K ROM). This should be configurable.
- //
- // 1 bank of microcode RAM
- _uCodeRam = new UInt32[1024];
+ //
+ // Max 3 banks of microcode RAM
+ _uCodeRam = new UInt32[1024 * 3];
LoadMicrocode(_uCodeRoms);
//
- // Cache 3k of instructions: 2K ROM, 1K RAM.
- _decodeCache = new MicroInstruction[1024 * 3];
+ // Cache 5k of instructions: max 2K ROM, 3K RAM.
+ _decodeCache = new MicroInstruction[1024 * 5];
// Precache ROM
CacheMicrocodeROM();
@@ -103,12 +100,22 @@ namespace Contralto.CPU
_ramBank = (address & 0x3000) >> 12;
_ramSelect = (address & 0x0800) == 0;
_lowHalfsel = (address & 0x0400) == 0;
- _ramAddr = (address & 0x3ff);
-
- if (_ramBank != 0)
+ _ramAddr = (address & 0x3ff);
+
+ // Clip RAM bank into range, it's always 0 unless we have a 3K uCode RAM system
+ switch (Configuration.SystemType)
{
- //throw new NotImplementedException(String.Format("Unexpected RAM BANK select of {0}", _ramBank));
- _ramBank = 0;
+ case SystemType.OneKRom:
+ case SystemType.TwoKRom:
+ _ramBank = 0;
+ break;
+ case SystemType.ThreeKRam:
+ if (_ramBank > 3)
+ {
+ // TODO: clip or not.
+ throw new InvalidOperationException(String.Format("Unexpected RAM bank value of {0}.", _ramBank));
+ }
+ break;
}
}
@@ -121,26 +128,35 @@ namespace Contralto.CPU
public static void SwitchMode(ushort nextAddress, TaskType task)
{
Logging.Log.Write(Logging.LogComponent.Microcode, "SWMODE: Current Bank {0}", _microcodeBank[(int)task]);
-
- // 2K ROM
- switch(_microcodeBank[(int)task])
+
+ switch (Configuration.SystemType)
{
- case MicrocodeBank.ROM0:
- _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.RAM0 : MicrocodeBank.ROM1;
+ case SystemType.OneKRom:
+ _microcodeBank[(int)task] = _microcodeBank[(int)task] == MicrocodeBank.ROM0 ? MicrocodeBank.RAM0 : MicrocodeBank.ROM0;
break;
- case MicrocodeBank.ROM1:
- _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.ROM0 : MicrocodeBank.RAM0;
+ case SystemType.TwoKRom:
+ switch (_microcodeBank[(int)task])
+ {
+ case MicrocodeBank.ROM0:
+ _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.RAM0 : MicrocodeBank.ROM1;
+ break;
+
+ case MicrocodeBank.ROM1:
+ _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.ROM0 : MicrocodeBank.RAM0;
+ break;
+
+ case MicrocodeBank.RAM0:
+ _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.ROM0 : MicrocodeBank.ROM1;
+ break;
+ }
break;
- case MicrocodeBank.RAM0:
- _microcodeBank[(int)task] = (nextAddress & 0x100) == 0 ? MicrocodeBank.ROM0 : MicrocodeBank.ROM1;
+ case SystemType.ThreeKRam:
+ throw new NotImplementedException("3K uCode RAM not yet implemented.");
break;
}
- // for 1K ROM -- todo: make configurable
- //_microcodeBank[(int)task] = _microcodeBank[(int)task] == MicrocodeBank.ROM0 ? MicrocodeBank.RAM0 : MicrocodeBank.ROM0;
-
Logging.Log.Write(Logging.LogComponent.Microcode, "SWMODE: New Bank {0} for Task {1}", _microcodeBank[(int)task], task);
}
diff --git a/Contralto/Configuration.cs b/Contralto/Configuration.cs
index 616dd23..ac99029 100644
--- a/Contralto/Configuration.cs
+++ b/Contralto/Configuration.cs
@@ -1,11 +1,48 @@
-using System;
+using Contralto.Logging;
+using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto
{
+ ///
+ /// The configuration of an Alto II to emulate
+ ///
+ public enum SystemType
+ {
+ ///
+ /// System with the standard 1K ROM, 1K RAM
+ ///
+ OneKRom,
+
+ ///
+ /// System with 2K ROM, 1K RAM
+ ///
+ TwoKRom,
+
+ ///
+ /// System with 3K RAM
+ ///
+ ThreeKRam,
+ }
+
+ public enum PacketInterfaceType
+ {
+ ///
+ /// Encapsulate frames inside raw ethernet frames on the host interface.
+ /// Requires PCAP.
+ ///
+ EthernetEncapsulation,
+
+ ///
+ /// Encapsulate frames inside UDP datagrams on the host interface.
+ ///
+ UDPEncapsulation,
+ }
+
///
/// Encapsulates user-configurable settings. To be enhanced.
///
@@ -13,22 +50,218 @@ namespace Contralto
{
static Configuration()
{
- // Initialize things to defaults.
- // TODO: Load from config file.
+ // Initialize things to defaults.
HostAddress = 0x22;
EthernetBootEnabled = false;
- EthernetBootFile = 0;
+ BootAddress = 0;
+
+ SystemType = SystemType.TwoKRom;
+
+ InterlaceDisplay = false;
+
+ ThrottleSpeed = true;
+
+ ReadConfiguration();
}
- public static string Drive0Image;
- public static string Drive1Image;
- public static byte HostAddress;
- public static string HostEthernetInterfaceName;
- public static bool HostEthernetAvailable;
+ ///
+ /// The type of Alto II to emulate
+ ///
+ public static SystemType SystemType;
+ ///
+ /// The currently loaded image for Drive 0
+ ///
+ public static string Drive0Image;
+
+ ///
+ /// The currently loaded image for Drive 1
+ ///
+ public static string Drive1Image;
+
+ ///
+ /// The Ethernet host address for this Alto
+ ///
+ public static byte HostAddress;
+
+ ///
+ /// The name of the Ethernet adaptor on the emulator host to use for Ethernet emulation
+ ///
+ public static string HostPacketInterfaceName;
+
+ ///
+ /// Whether any packet interfaces are available on the host
+ ///
+ public static bool HostRawEthernetInterfacesAvailable;
+
+ ///
+ /// The type of interface to use to host networking.
+ ///
+ public static PacketInterfaceType HostPacketInterfaceType;
+
+ ///
+ /// Whether to enable Ethernet boot at reset
+ ///
public static bool EthernetBootEnabled;
- public static ushort EthernetBootFile;
+
+ ///
+ /// The address/file to boot at reset
+ ///
+ public static ushort BootAddress;
+
+ ///
+ /// Whether to render the display "interlaced" or not.
+ ///
+ public static bool InterlaceDisplay;
+
+ ///
+ /// Whether to cap execution speed at native execution speed or not.
+ ///
+ public static bool ThrottleSpeed;
+
+ ///
+ /// Reads the current configuration file from disk.
+ ///
+ /// TODO: use reflection to do this.
+ ///
+ public static void ReadConfiguration()
+ {
+ try
+ {
+ using (StreamReader configStream = new StreamReader("contralto.cfg"))
+ {
+ //
+ // Config file consists of text lines containing name / value pairs:
+ //
+ // Whitespace is ignored
+ //
+ int lineNumber = 0;
+ while (!configStream.EndOfStream)
+ {
+ lineNumber++;
+ string line = configStream.ReadLine().ToLowerInvariant().Trim();
+
+ if (string.IsNullOrEmpty(line))
+ {
+ continue;
+ }
+
+ // Find whitespace separating tokens
+ int ws = line.IndexOfAny(new char[] { ' ', '\t' });
+
+ if (ws < 1)
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration, "Syntax error on line {0}. Ignoring.", lineNumber);
+ continue;
+ }
+
+ string parameter = line.Substring(0, ws);
+ string value = line.Substring(ws + 1, line.Length - ws - 1);
+
+ try
+ {
+ switch (parameter)
+ {
+ case "drive0image":
+ Drive0Image = value;
+ break;
+
+ case "drive1image":
+ Drive1Image = value;
+ break;
+
+ case "systemtype":
+ SystemType = (SystemType)Enum.Parse(typeof(SystemType), value, true);
+ break;
+
+ case "hostaddress":
+ HostAddress = Convert.ToByte(value, 8);
+ break;
+
+ case "hostpacketinterfacename":
+ HostPacketInterfaceName = value;
+ break;
+
+ case "hostpacketinterfacetype":
+ HostPacketInterfaceType = (PacketInterfaceType)Enum.Parse(typeof(PacketInterfaceType), value, true);
+ break;
+
+ case "ethernetbootenabled":
+ EthernetBootEnabled = bool.Parse(value);
+ break;
+
+ case "bootaddress":
+ BootAddress = Convert.ToUInt16(value, 8);
+ break;
+
+ case "interlacedisplay":
+ InterlaceDisplay = bool.Parse(value);
+ break;
+
+ case "throttlespeed":
+ ThrottleSpeed = bool.Parse(value);
+ break;
+
+ default:
+ Log.Write(LogType.Warning, LogComponent.Configuration, "Invalid parameter on line {0}. Ignoring.", lineNumber);
+ break;
+ }
+ }
+ catch
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration, "Invalid value on line {0}. Ignoring.", lineNumber);
+ continue;
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration, "Configuration file 'contralto.cfg' could not be read; assuming default settings.");
+ WriteConfiguration();
+ }
+ }
+
+ ///
+ /// Commits the current configuration to disk.
+ ///
+ public static void WriteConfiguration()
+ {
+ try
+ {
+ using (StreamWriter configStream = new StreamWriter("contralto.cfg"))
+ {
+ if (!string.IsNullOrEmpty(Drive0Image))
+ {
+ configStream.WriteLine("Drive0Image {0}", Drive0Image);
+ }
+
+ if (!string.IsNullOrEmpty(Drive1Image))
+ {
+ configStream.WriteLine("Drive1Image {0}", Drive1Image);
+ }
+
+ configStream.WriteLine("SystemType {0}", SystemType);
+ configStream.WriteLine("HostAddress {0}", Conversion.ToOctal(HostAddress));
+
+ if (!string.IsNullOrEmpty(HostPacketInterfaceName))
+ {
+ configStream.WriteLine("HostPacketInterfaceName {0}", HostPacketInterfaceName);
+ }
+
+ configStream.WriteLine("HostPacketInterfaceType {0}", HostPacketInterfaceType);
+ configStream.WriteLine("EthernetBootEnabled {0}", EthernetBootEnabled);
+ configStream.WriteLine("BootAddress {0}", Conversion.ToOctal(BootAddress));
+ configStream.WriteLine("InterlaceDisplay {0}", InterlaceDisplay);
+ configStream.WriteLine("ThrottleSpeed {0}", ThrottleSpeed);
+ }
+ }
+ catch (Exception)
+ {
+ Log.Write(LogType.Warning, LogComponent.Configuration, "Configuration file 'contralto.cfg' could not be opened for writing.");
+ }
+ }
}
}
diff --git a/Contralto/Contralto.csproj b/Contralto/Contralto.csproj
index d47c7c7..ca432d1 100644
--- a/Contralto/Contralto.csproj
+++ b/Contralto/Contralto.csproj
@@ -99,6 +99,14 @@
+
+
+
+
+ True
+ True
+ Resources.resx
+
Form
@@ -140,18 +148,18 @@
-
+
Form
-
- EthernetBootWindow.cs
+
+ AlternateBootWindow.cs
-
+
@@ -163,6 +171,12 @@
+
+ Form
+
+
+ SystemOptions.cs
+
@@ -334,6 +348,10 @@
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
AboutBox.cs
@@ -343,8 +361,11 @@
Debugger.cs
-
- EthernetBootWindow.cs
+
+ AlternateBootWindow.cs
+
+
+ SystemOptions.cs
diff --git a/Contralto/Disassembly/altoIIcode3.mu b/Contralto/Disassembly/altoIIcode3.mu
index 9b8d381..0d45362 100644
--- a/Contralto/Disassembly/altoIIcode3.mu
+++ b/Contralto/Disassembly/altoIIcode3.mu
@@ -1102,7 +1102,7 @@ EM1036> AC1<- L, :LOADX;
;SIO - 61004 - Put AC0 on the bus, issue STARTF to get device attention,
;Read Host address from Ethernet interface into AC0.
-EM104> SIO: L<- AC0, STARTF;
+EM0104> SIO: L<- AC0, STARTF;
EM1037> T<- 77777; ***X21 sets AC0[0] to 0
EM1040> L<- RSNF AND T;
EM1041> LTOAC0: AC0<- L, TASK, :TOSTART;
diff --git a/Contralto/Disk/Josh.dsk b/Contralto/Disk/Josh.dsk
index cd6f8db..6d808bc 100644
Binary files a/Contralto/Disk/Josh.dsk and b/Contralto/Disk/Josh.dsk differ
diff --git a/Contralto/Disk/allgames.dsk b/Contralto/Disk/allgames.dsk
index 2412dce..b33f0be 100644
Binary files a/Contralto/Disk/allgames.dsk and b/Contralto/Disk/allgames.dsk differ
diff --git a/Contralto/Disk/diag.dsk b/Contralto/Disk/diag.dsk
index 0f0cbdb..a2750d3 100644
Binary files a/Contralto/Disk/diag.dsk and b/Contralto/Disk/diag.dsk differ
diff --git a/Contralto/Disk/st76boot.dsk b/Contralto/Disk/st76boot.dsk
index 335cbd3..390aa00 100644
Binary files a/Contralto/Disk/st76boot.dsk and b/Contralto/Disk/st76boot.dsk differ
diff --git a/Contralto/Disk/tdisk8.dsk b/Contralto/Disk/tdisk8.dsk
index 3b67268..f2b5132 100644
Binary files a/Contralto/Disk/tdisk8.dsk and b/Contralto/Disk/tdisk8.dsk differ
diff --git a/Contralto/Display/DisplayController.cs b/Contralto/Display/DisplayController.cs
index 803bffa..115d25e 100644
--- a/Contralto/Display/DisplayController.cs
+++ b/Contralto/Display/DisplayController.cs
@@ -21,6 +21,11 @@ namespace Contralto.Display
_display = display;
}
+ public void DetachDisplay()
+ {
+ _display = null;
+ }
+
public int Fields
{
get { return _fields; }
@@ -160,6 +165,11 @@ namespace Contralto.Display
private void WordCallback(ulong timeNsec, ulong skewNsec, object context)
{
+ if (_display == null)
+ {
+ return;
+ }
+
// Dequeue a word (if available) and draw it to the screen.
ushort displayWord = (ushort)(_whiteOnBlack ? 0 : 0xffff);
if (_dataBuffer.Count > 0)
diff --git a/Contralto/HighResTimer.cs b/Contralto/HighResTimer.cs
new file mode 100644
index 0000000..12ca5b5
--- /dev/null
+++ b/Contralto/HighResTimer.cs
@@ -0,0 +1,191 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Contralto
+{
+ ///
+ /// HighResTimer gives us access to NT's very-high-resolution PerformanceCounters.
+ /// This gives us the precision we need to sync emulation to any speed we desire.
+ ///
+ public sealed class HighResTimer
+ {
+ [DllImport("Kernel32.dll")]
+ private static extern bool QueryPerformanceCounter(
+ out long lpPerformanceCount);
+
+ [DllImport("Kernel32.dll")]
+ private static extern bool QueryPerformanceFrequency(
+ out long lpFrequency);
+
+ public HighResTimer()
+ {
+ // What's the frequency, Kenneth?
+ if (QueryPerformanceFrequency(out _frequency) == false)
+ {
+ // high-performance counter not supported
+ throw new Win32Exception();
+ }
+ }
+
+ ///
+ /// Returns the current time in seconds.
+ ///
+ ///
+ public double GetCurrentTime()
+ {
+ long currentTime;
+ QueryPerformanceCounter(out currentTime);
+
+ return (double)(currentTime) / (double)_frequency;
+ }
+
+ private long _frequency;
+ }
+
+ public sealed class FrameTimer
+ {
+ [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)]
+ static extern UInt32 TimeGetDevCaps(ref TimeCaps timeCaps, UInt32 sizeTimeCaps);
+
+ [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")]
+ static extern UInt32 TimeBeginPeriod(UInt32 uPeriod);
+
+ [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")]
+ public static extern uint TimeEndPeriod(uint uMilliseconds);
+
+ [DllImport("kernel32.dll", EntryPoint = "CreateTimerQueue")]
+ public static extern IntPtr CreateTimerQueue();
+
+ [DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueEx")]
+ public static extern bool DeleteTimerQueue(IntPtr hTimerQueue, IntPtr hCompletionEvent);
+
+ [DllImport("kernel32.dll", EntryPoint = "CreateTimerQueueTimer")]
+ public static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer, IntPtr hTimerQueue, IntPtr Callback, IntPtr Parameter, UInt32 DueTime, UInt32 Period, ulong Flags);
+
+ [DllImport("kernel32.dll", EntryPoint = "ChangeTimerQueueTimer")]
+ public static extern bool ChangeTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, UInt32 DueTime, UInt32 Period);
+
+
+ [DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueTimer")]
+ public static extern bool DeleteTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, IntPtr hCompletionEvent);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TimeCaps
+ {
+ public UInt32 wPeriodMin;
+ public UInt32 wPeriodMax;
+ };
+
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ public delegate void UnmanagedTimerCallback(IntPtr param, bool timerOrWait);
+
+ ///
+ /// FrameTimer provides a simple method to synchronize execution to a given framerate.
+ /// Calling WaitForFrame() blocks until the start of the next frame.
+ ///
+ /// NOTE: This code uses the Win32 TimerQueue APIs instead of the System.Threading.Timer
+ /// APIs because the .NET APIs do not allow execution of the callback on the timer's thread --
+ /// it queues up a new worker thread. This lowers the accuracy of the timer, and since we
+ /// need all the precision we can get they're not suitable here.
+ ///
+ /// The frame rate to sync to.
+ public FrameTimer(double framesPerSecond)
+ {
+ //
+ // Set the timer to the minimum value (1ms). This should be supported on any modern x86 system.
+ // If not, too bad...
+ //
+ UInt32 res = TimeBeginPeriod(1);
+
+ if (res != 0)
+ {
+ throw new InvalidOperationException("Unable to set timer period.");
+ }
+
+ //
+ // Create a new timer queue
+ //
+ _hTimerQueue = CreateTimerQueue();
+
+ if (_hTimerQueue == IntPtr.Zero)
+ {
+ throw new InvalidOperationException("Unable to create timer queue.");
+ }
+
+ //
+ // Since we only have a resolution of 1ms, we have to do some hackery to get a slightly more accurate framerate.
+ // (60 fields/sec requires 16 2/3s ms frame delay.)
+ // We alternate between two rates at varying intervals and this gets us fairly close to the desired frame rate.
+ //
+ _callback = new UnmanagedTimerCallback(TimerCallbackFn);
+
+ _highPeriod = (uint)Math.Ceiling(1000.0 * (1.0 / framesPerSecond));
+ _lowPeriod = (uint)Math.Floor(1000.0 * (1.0 / framesPerSecond));
+ _periodTenths = _periodSwitch = (uint)((1000.0 * (1.0 / framesPerSecond) - Math.Floor(1000.0 * (1.0 / framesPerSecond))) * 10.0);
+
+ if (!CreateTimerQueueTimer(out _hTimer, _hTimerQueue, Marshal.GetFunctionPointerForDelegate(_callback), IntPtr.Zero, _lowPeriod, _lowPeriod, 0x00000020 /* execute in timer thread */))
+ {
+ throw new InvalidOperationException("Unable to create timer queue timer.");
+ }
+
+ _event = new AutoResetEvent(false);
+
+ _lowTimer = 0;
+ }
+
+ ~FrameTimer()
+ {
+ //
+ // Clean stuff up
+ //
+ DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero);
+ DeleteTimerQueue(_hTimerQueue, IntPtr.Zero);
+
+ //
+ // Fire off a final event to release any call that's waiting...
+ //
+ _event.Set();
+ }
+
+ ///
+ /// Waits for the timer to fire.
+ ///
+ public void WaitForFrame()
+ {
+ _event.WaitOne();
+ }
+
+ ///
+ /// Callback from timer queue. Work done here is executed on the timer's thread, so must be quick.
+ ///
+ ///
+ ///
+ private void TimerCallbackFn(IntPtr lpParameter, bool TimerOrWaitFired)
+ {
+ _event.Set();
+ _lowTimer++;
+
+ if (_lowTimer >= _periodSwitch)
+ {
+ _lowTimer = 0;
+ _period = !_period;
+ ChangeTimerQueueTimer(_hTimerQueue, _hTimer, _period ? _lowPeriod : _highPeriod, _period ? _lowPeriod : _highPeriod);
+
+ _periodSwitch = !_period ? _periodTenths : 10 - _periodTenths;
+ }
+ }
+
+ private IntPtr _hTimerQueue;
+ private IntPtr _hTimer;
+ private AutoResetEvent _event;
+ private UnmanagedTimerCallback _callback;
+ private uint _lowPeriod;
+ private uint _highPeriod;
+ private uint _periodSwitch;
+ private uint _periodTenths;
+ private int _lowTimer;
+ private bool _period;
+ }
+}
diff --git a/Contralto/IO/EthernetController.cs b/Contralto/IO/EthernetController.cs
index 5aa8310..a697cc9 100644
--- a/Contralto/IO/EthernetController.cs
+++ b/Contralto/IO/EthernetController.cs
@@ -25,10 +25,21 @@ namespace Contralto.IO
// Attach real Ethernet device if user has specified one, otherwise leave unattached; output data
// will go into a bit-bucket.
- if (!String.IsNullOrEmpty(Configuration.HostEthernetInterfaceName))
+ switch(Configuration.HostPacketInterfaceType)
{
- _hostEthernet = new HostEthernet(Configuration.HostEthernetInterfaceName);
- _hostEthernet.RegisterReceiveCallback(OnHostPacketReceived);
+ case PacketInterfaceType.UDPEncapsulation:
+ _hostInterface = new UDPEncapsulation(Configuration.HostPacketInterfaceName);
+ _hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
+ break;
+
+ case PacketInterfaceType.EthernetEncapsulation:
+ _hostInterface = new HostEthernetEncapsulation(Configuration.HostPacketInterfaceName);
+ _hostInterface.RegisterReceiveCallback(OnHostPacketReceived);
+ break;
+
+ default:
+ _hostInterface = null;
+ break;
}
// More words than the Alto will ever send.
@@ -285,9 +296,9 @@ namespace Contralto.IO
// And actually tell the host ethernet interface to send the data.
// NOTE: We do not append a checksum to the outgoing 3mbit packet. See comments on the
// receiving end for an explanation.
- if (_hostEthernet != null)
+ if (_hostInterface != null)
{
- _hostEthernet.Send(_outputData, _outputIndex);
+ _hostInterface.Send(_outputData, _outputIndex);
}
_outputIndex = 0;
@@ -526,8 +537,8 @@ namespace Contralto.IO
private const int _maxQueuedPackets = 32;
- // The actual connection to a real Ethernet device on the host
- HostEthernet _hostEthernet;
+ // The actual connection to a real network device of some sort on the host
+ IPacketEncapsulation _hostInterface;
// Buffer to hold outgoing data to the host ethernet
ushort[] _outputData;
diff --git a/Contralto/IO/HostEthernet.cs b/Contralto/IO/HostEthernetEncapsulation.cs
similarity index 88%
rename from Contralto/IO/HostEthernet.cs
rename to Contralto/IO/HostEthernetEncapsulation.cs
index e169930..f045381 100644
--- a/Contralto/IO/HostEthernet.cs
+++ b/Contralto/IO/HostEthernetEncapsulation.cs
@@ -18,11 +18,10 @@ namespace Contralto.IO
public struct EthernetInterface
{
- public EthernetInterface(string name, string description, MacAddress macAddress)
+ public EthernetInterface(string name, string description)
{
Name = name;
- Description = description;
- MacAddress = macAddress;
+ Description = description;
}
public static List EnumerateDevices()
@@ -31,33 +30,35 @@ namespace Contralto.IO
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
{
- interfaces.Add(new EthernetInterface(device.Name, device.Description, device.GetMacAddress()));
+ interfaces.Add(new EthernetInterface(device.Name, device.Description));
}
return interfaces;
}
- public string Name;
- public string Description;
- public MacAddress MacAddress;
- }
+ public override string ToString()
+ {
+ return Description;
+ }
- public delegate void ReceivePacketDelegate(MemoryStream data);
+ public string Name;
+ public string Description;
+ }
///
/// Implements the logic for encapsulating a 3mbit ethernet packet into a 10mb packet and sending it over an actual
- /// interface controlled by the host operating system.
+ /// ethernet interface controlled by the host operating system.
///
/// This uses PCap.NET to do the dirty work.
///
- public class HostEthernet
+ public class HostEthernetEncapsulation : IPacketEncapsulation
{
- public HostEthernet(EthernetInterface iface)
+ public HostEthernetEncapsulation(EthernetInterface iface)
{
AttachInterface(iface);
}
- public HostEthernet(string name)
+ public HostEthernetEncapsulation(string name)
{
// Find the specified device by name
List interfaces = EthernetInterface.EnumerateDevices();
@@ -71,7 +72,7 @@ namespace Contralto.IO
}
}
- throw new InvalidOperationException("Specified ethernet interface does not exist.");
+ throw new InvalidOperationException("Specified ethernet interface does not exist or is not compatible with WinPCAP.");
}
public void RegisterReceiveCallback(ReceivePacketDelegate callback)
@@ -127,7 +128,7 @@ namespace Contralto.IO
byte destinationHost = packetBytes[3];
byte sourceHost = packetBytes[2];
- Log.Write(LogComponent.HostEthernet, "Sending packet; source {0} destination {1}, length {2} words.",
+ Log.Write(LogComponent.HostNetworkInterface, "Sending packet; source {0} destination {1}, length {2} words.",
Conversion.ToOctal(sourceHost),
Conversion.ToOctal(destinationHost),
length);
@@ -153,7 +154,7 @@ namespace Contralto.IO
// Send it over the 'net!
_communicator.SendPacket(builder.Build(DateTime.Now));
- Log.Write(LogComponent.HostEthernet, "Encapsulated 3mbit packet sent.");
+ Log.Write(LogComponent.HostNetworkInterface, "Encapsulated 3mbit packet sent.");
}
private void ReceiveCallback(Packet p)
@@ -166,7 +167,7 @@ namespace Contralto.IO
p.Ethernet.Destination.ToValue() == _10mbitBroadcast) && // broadcast
(p.Ethernet.Source.ToValue() != (UInt48)(_10mbitMACPrefix | Configuration.HostAddress))) // and not sent by this emulator
{
- Log.Write(LogComponent.HostEthernet, "Received encapsulated 3mbit packet.");
+ Log.Write(LogComponent.HostNetworkInterface, "Received encapsulated 3mbit packet.");
_callback(p.Ethernet.Payload.ToMemoryStream());
}
else
@@ -182,7 +183,7 @@ namespace Contralto.IO
// Find the specified device by name
foreach (LivePacketDevice device in LivePacketDevice.AllLocalMachine)
{
- if (device.Name == iface.Name && device.GetMacAddress() == iface.MacAddress)
+ if (device.Description == iface.Name)
{
_interface = device;
break;
@@ -194,7 +195,7 @@ namespace Contralto.IO
throw new InvalidOperationException("Requested interface not found.");
}
- Log.Write(LogComponent.HostEthernet, "Attached to host interface {0}", iface.Name);
+ Log.Write(LogComponent.HostNetworkInterface, "Attached to host interface {0}", iface.Name);
}
private void Open(bool promiscuous, int timeout)
@@ -204,7 +205,7 @@ namespace Contralto.IO
// Set this to 1 so we'll get packets as soon as they arrive, no buffering.
_communicator.SetKernelMinimumBytesToCopy(1);
- Log.Write(LogComponent.HostEthernet, "Host interface opened and receiving packets.");
+ Log.Write(LogComponent.HostNetworkInterface, "Host interface opened and receiving packets.");
}
///
@@ -222,7 +223,7 @@ namespace Contralto.IO
// Just call ReceivePackets, that's it. This will never return.
// (probably need to make this more elegant so we can tear down the thread
// properly.)
- Log.Write(LogComponent.HostEthernet, "Receiver thread started.");
+ Log.Write(LogComponent.HostNetworkInterface, "Receiver thread started.");
_communicator.ReceivePackets(-1, ReceiveCallback);
}
diff --git a/Contralto/IO/IPacketEncapsulation.cs b/Contralto/IO/IPacketEncapsulation.cs
new file mode 100644
index 0000000..8e862c6
--- /dev/null
+++ b/Contralto/IO/IPacketEncapsulation.cs
@@ -0,0 +1,23 @@
+using System.IO;
+
+
+namespace Contralto.IO
+{
+ public delegate void ReceivePacketDelegate(MemoryStream data);
+
+ public interface IPacketEncapsulation
+ {
+ ///
+ /// Registers a callback delegate to handle packets that are received.
+ ///
+ ///
+ void RegisterReceiveCallback(ReceivePacketDelegate callback);
+
+ ///
+ /// Sends the specified word array
+ ///
+ ///
+ ///
+ void Send(ushort[] packet, int length);
+ }
+}
diff --git a/Contralto/IO/UDPEncapsulation.cs b/Contralto/IO/UDPEncapsulation.cs
new file mode 100644
index 0000000..fc571e1
--- /dev/null
+++ b/Contralto/IO/UDPEncapsulation.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+using Contralto.Logging;
+using System.Threading;
+using System.Net.NetworkInformation;
+
+namespace Contralto.IO
+{
+ ///
+ /// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams.
+ /// Sent packets are broadcast to the subnet.
+ ///
+ public class UDPEncapsulation : IPacketEncapsulation
+ {
+ public UDPEncapsulation(string interfaceName)
+ {
+ // Try to set up UDP client.
+ try
+ {
+ _udpClient = new UdpClient(_udpPort, AddressFamily.InterNetwork);
+ _udpClient.EnableBroadcast = true;
+
+ //
+ // Grab the broadcast address for the interface so that we know what broadcast address to use
+ // for our UDP datagrams.
+ //
+ NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
+
+ IPInterfaceProperties props = null;
+ foreach(NetworkInterface nic in nics)
+ {
+ if (nic.Description.ToLowerInvariant() == interfaceName.ToLowerInvariant())
+ {
+ props = nic.GetIPProperties();
+ break;
+ }
+ }
+
+ if (props == null)
+ {
+ throw new InvalidOperationException(String.Format("No interface matching description '{0}' was found.", interfaceName));
+ }
+
+ foreach(UnicastIPAddressInformation unicast in props.UnicastAddresses)
+ {
+ // Find the first InterNetwork address for this interface and
+ // go with it.
+ if (unicast.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ _broadcastEndpoint = new IPEndPoint(GetBroadcastAddress(unicast.Address, unicast.IPv4Mask), _udpPort);
+ break;
+ }
+ }
+
+ if (_broadcastEndpoint == null)
+ {
+ throw new InvalidOperationException(String.Format("No IPV4 network information was found for interface '{0}'.", interfaceName));
+ }
+
+ }
+ catch(Exception e)
+ {
+ Log.Write(LogType.Error, LogComponent.EthernetPacket,
+ "Error configuring UDP socket {0} for use with ContrAlto on interface {1}. Ensure that the selected network interface is valid, configured properly, and that nothing else is using this port.",
+ _udpPort,
+ interfaceName);
+
+ Log.Write(LogType.Error, LogComponent.EthernetPacket,
+ "Error was '{0}'.",
+ e.Message);
+
+ _udpClient = null;
+ }
+ }
+
+ public void RegisterReceiveCallback(ReceivePacketDelegate callback)
+ {
+ // UDP connection could not be configured, can't receive anything.
+ if (_udpClient == null)
+ {
+ return;
+ }
+
+ // Set up input
+ _callback = callback;
+ BeginReceive();
+ }
+
+
+ ///
+ /// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet.
+ ///
+ ///
+ ///
+ public void Send(ushort[] packet, int length)
+ {
+ // UDP could not be configured, drop the packet.
+ if (_udpClient == null)
+ {
+ return;
+ }
+
+ // Sanity check.
+ if (length < 1)
+ {
+ throw new InvalidOperationException("Raw packet data must contain at least two bytes for addressing.");
+ }
+
+ //
+ // Outgoing packet contains 1 extra word (2 bytes) containing
+ // the prepended packet length (one word)
+ byte[] packetBytes = new byte[length * 2 + 2];
+
+ //
+ // First two bytes include the length of the 3mbit packet; since 10mbit packets have a minimum length of 46
+ // bytes, and 3mbit packets have no minimum length this is necessary so the receiver can pull out the
+ // correct amount of data.
+ //
+ packetBytes[0] = (byte)(length);
+ packetBytes[1] = (byte)((length) >> 8);
+
+ //
+ // Do this annoying dance to stuff the ushorts into bytes because this is C#.
+ //
+ for (int i = 0; i < length; i++)
+ {
+ packetBytes[i * 2 + 2] = (byte)(packet[i]);
+ packetBytes[i * 2 + 3] = (byte)(packet[i] >> 8);
+ }
+
+ //
+ // Grab the source and destination host addresses from the packet we're sending
+ // and build 10mbit versions.
+ //
+ byte destinationHost = packetBytes[3];
+ byte sourceHost = packetBytes[2];
+
+ Log.Write(LogComponent.HostNetworkInterface, "Sending packet; source {0} destination {1}, length {2} words.",
+ Conversion.ToOctal(sourceHost),
+ Conversion.ToOctal(destinationHost),
+ length);
+
+ _udpClient.Send(packetBytes, packetBytes.Length, _broadcastEndpoint);
+
+ Log.Write(LogComponent.HostNetworkInterface, "Encapsulated 3mbit packet sent via UDP.");
+ }
+
+ ///
+ /// Begin receiving packets, forever.
+ ///
+ private void BeginReceive()
+ {
+ // Kick off receive thread.
+ _receiveThread = new Thread(ReceiveThread);
+ _receiveThread.Start();
+ }
+
+ private void ReceiveThread()
+ {
+ // Just call ReceivePackets, that's it. This will never return.
+ // (probably need to make this more elegant so we can tear down the thread
+ // properly.)
+ Log.Write(LogComponent.HostNetworkInterface, "UDP Receiver thread started.");
+
+ IPEndPoint groupEndPoint = new IPEndPoint(IPAddress.Any, _udpPort);
+ //return;
+
+ while (true)
+ {
+ byte[] data = _udpClient.Receive(ref groupEndPoint);
+
+ // TODO: sanitize data before handing it off.
+
+ Log.Write(LogComponent.HostNetworkInterface, "Received UDP-encapsulated 3mbit packet.");
+ _callback(new System.IO.MemoryStream(data));
+ }
+ }
+
+
+ private IPAddress GetBroadcastAddress(IPAddress address, IPAddress subnetMask)
+ {
+ byte[] ipAdressBytes = address.GetAddressBytes();
+ byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
+
+ byte[] broadcastAddress = new byte[ipAdressBytes.Length];
+ for (int i = 0; i < broadcastAddress.Length; i++)
+ {
+ broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
+ }
+
+ return new IPAddress(broadcastAddress);
+ }
+
+ // Callback delegate for received data.
+ private ReceivePacketDelegate _callback;
+
+ // Thread used for receive
+ private Thread _receiveThread;
+
+ // UDP port (TODO: make configurable?)
+ private const int _udpPort = 42424;
+ private UdpClient _udpClient;
+ private IPEndPoint _broadcastEndpoint;
+
+ }
+}
diff --git a/Contralto/Logging/Log.cs b/Contralto/Logging/Log.cs
index c7e2012..16ef657 100644
--- a/Contralto/Logging/Log.cs
+++ b/Contralto/Logging/Log.cs
@@ -25,8 +25,9 @@ namespace Contralto.Logging
EthernetController = 0x400,
EthernetTask = 0x800,
TaskSwitch = 0x1000,
- HostEthernet = 0x2000,
+ HostNetworkInterface = 0x2000,
EthernetPacket = 0x4000,
+ Configuration = 0x8000,
Debug = 0x40000000,
All = 0x7fffffff
diff --git a/Contralto/Program.cs b/Contralto/Program.cs
index 76c3a85..b52c998 100644
--- a/Contralto/Program.cs
+++ b/Contralto/Program.cs
@@ -1,6 +1,7 @@
using Contralto.CPU;
using Contralto.IO;
using System;
+using System.Net;
using System.Collections.Generic;
namespace Contralto
@@ -12,11 +13,9 @@ namespace Contralto
{
// Handle command-line args
PrintHerald();
- // See if WinPCap is installed and working
- TestPCap();
- ParseCommandLine(args);
-
+ // See if WinPCap is installed and working
+ TestPCap();
AltoSystem system = new AltoSystem();
@@ -44,99 +43,7 @@ namespace Contralto
Console.WriteLine("ContrAlto v0.1 (c) 2015, 2016 Living Computer Museum.");
Console.WriteLine("Bug reports to joshd@livingcomputermuseum.org");
Console.WriteLine();
- }
-
- private static void ParseCommandLine(string[] args)
- {
- // At the moment, options start with a "-" and are one of:
- // "-hostaddress " : specifies ethernet host address in octal (1-377)
- // "-hostinterface " : specifies the name of the host ethernet interface to use
- // "-listinterfaces" : lists ethernet interfaces known by pcap
- // "-drive0 " : attaches disk image to drive 0
- // "-drive1 " : attaches disk image to drive 1
-
- int index = 0;
-
- // TODO: this parsing needs to be made not terrible.
- while(index < args.Length)
- {
- switch (args[index++].ToLower())
- {
- case "-hostaddress":
- if (index < args.Length)
- {
- Configuration.HostAddress = Convert.ToByte(args[index++], 8);
- }
- else
- {
- PrintUsage();
- }
- break;
-
- case "-hostinterface":
- if (index < args.Length)
- {
- if (!Configuration.HostEthernetAvailable)
- {
- Console.WriteLine("Ethernet functionality is disabled, host interface cannot be specified.");
- }
- else
- {
- Configuration.HostEthernetInterfaceName = args[index++];
- }
- }
- else
- {
- PrintUsage();
- }
- break;
-
- case "-listinterfaces":
- if (!Configuration.HostEthernetAvailable)
- {
- Console.WriteLine("Ethernet functionality is disabled, interfaces cannot be enumerated.");
- }
- else
- {
- List interfaces = EthernetInterface.EnumerateDevices();
-
- foreach (EthernetInterface i in interfaces)
- {
- Console.WriteLine("Name: '{0}'\n Description: '{1}'\n MAC '{2}'", i.Name, i.Description, i.MacAddress);
- }
- }
- break;
-
- case "-drive0":
- if (index < args.Length)
- {
- Configuration.Drive0Image = args[index++];
- }
- else
- {
- PrintUsage();
- }
- break;
-
- case "-drive1":
- if (index < args.Length)
- {
- Configuration.Drive1Image = args[index++];
- }
- else
- {
- PrintUsage();
- }
- break;
- }
- }
- }
-
- private static void PrintUsage()
- {
- // TODO: make more useful.
- Console.WriteLine("Something is wrong, try again.");
- }
+ }
private static void TestPCap()
{
@@ -145,7 +52,7 @@ namespace Contralto
try
{
List interfaces = EthernetInterface.EnumerateDevices();
- Configuration.HostEthernetAvailable = true;
+ Configuration.HostRawEthernetInterfacesAvailable = true;
}
catch
{
@@ -153,7 +60,7 @@ namespace Contralto
Console.WriteLine(" Ethernet functionality is disabled.");
Console.WriteLine(" Please install WinPCAP from: http://www.winpcap.org/");
- Configuration.HostEthernetAvailable = false;
+ Configuration.HostRawEthernetInterfacesAvailable = false;
}
}
}
diff --git a/Contralto/Properties/Resources.Designer.cs b/Contralto/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..0235d95
--- /dev/null
+++ b/Contralto/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Contralto.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Contralto.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Contralto/UI/EthernetBootWindow.resx b/Contralto/Properties/Resources.resx
similarity index 100%
rename from Contralto/UI/EthernetBootWindow.resx
rename to Contralto/Properties/Resources.resx
diff --git a/Contralto/UI/EthernetBootWindow.Designer.cs b/Contralto/UI/AlternateBootWindow.Designer.cs
similarity index 96%
rename from Contralto/UI/EthernetBootWindow.Designer.cs
rename to Contralto/UI/AlternateBootWindow.Designer.cs
index 3850640..d4bf01e 100644
--- a/Contralto/UI/EthernetBootWindow.Designer.cs
+++ b/Contralto/UI/AlternateBootWindow.Designer.cs
@@ -1,6 +1,6 @@
namespace Contralto
{
- partial class EthernetBootWindow
+ partial class AlternateBootOptions
{
///
/// Required designer variable.
@@ -99,16 +99,16 @@
this.CancelButton.UseVisualStyleBackColor = true;
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
//
- // EthernetBootWindow
+ // AlternateBootOptions
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.ClientSize = new System.Drawing.Size(413, 130);
+ this.ClientSize = new System.Drawing.Size(403, 128);
this.Controls.Add(this.CancelButton);
this.Controls.Add(this.OKButton);
this.Controls.Add(this.BootFileGroup);
this.Controls.Add(this.EthernetBootEnabled);
- this.Name = "EthernetBootWindow";
+ this.Name = "AlternateBootOptions";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
diff --git a/Contralto/UI/EthernetBootWindow.cs b/Contralto/UI/AlternateBootWindow.cs
similarity index 95%
rename from Contralto/UI/EthernetBootWindow.cs
rename to Contralto/UI/AlternateBootWindow.cs
index c7964a8..cf5bbc8 100644
--- a/Contralto/UI/EthernetBootWindow.cs
+++ b/Contralto/UI/AlternateBootWindow.cs
@@ -10,9 +10,9 @@ using System.Windows.Forms;
namespace Contralto
{
- public partial class EthernetBootWindow : Form
+ public partial class AlternateBootOptions : Form
{
- public EthernetBootWindow()
+ public AlternateBootOptions()
{
InitializeComponent();
@@ -20,7 +20,7 @@ namespace Contralto
BootFileGroup.Enabled = EthernetBootEnabled.Checked = Configuration.EthernetBootEnabled;
- SelectBootFile(Configuration.EthernetBootFile);
+ SelectBootFile(Configuration.BootAddress);
}
@@ -79,7 +79,7 @@ namespace Contralto
_selectedBoot = ((BootFileEntry)BootFileComboBox.SelectedItem).FileNumber;
}
- Configuration.EthernetBootFile = _selectedBoot;
+ Configuration.BootAddress = _selectedBoot;
Configuration.EthernetBootEnabled = _bootEnabled;
this.Close();
diff --git a/Contralto/UI/AlternateBootWindow.resx b/Contralto/UI/AlternateBootWindow.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/Contralto/UI/AlternateBootWindow.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Contralto/UI/AltoWindow.Designer.cs b/Contralto/UI/AltoWindow.Designer.cs
index d60d8e0..1777900 100644
--- a/Contralto/UI/AltoWindow.Designer.cs
+++ b/Contralto/UI/AltoWindow.Designer.cs
@@ -44,6 +44,8 @@
this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.unloadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.Drive1ImageName = new System.Windows.Forms.ToolStripMenuItem();
+ this.enableAlternateBootToolStripMenuItem = 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.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -52,7 +54,6 @@
this.CaptureStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.SystemStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
this.DiskStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
- this.SystemEthernetBootMenu = new System.Windows.Forms.ToolStripMenuItem();
((System.ComponentModel.ISupportInitialize)(this.DisplayBox)).BeginInit();
this.menuStrip1.SuspendLayout();
this.StatusLine.SuspendLayout();
@@ -93,7 +94,7 @@
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
- this.exitToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+ this.exitToolStripMenuItem.Size = new System.Drawing.Size(92, 22);
this.exitToolStripMenuItem.Text = "Exit";
this.exitToolStripMenuItem.Click += new System.EventHandler(this.OnFileExitClick);
//
@@ -104,6 +105,7 @@
this.SystemResetMenuItem,
this.drive0ToolStripMenuItem,
this.drive1ToolStripMenuItem,
+ this.enableAlternateBootToolStripMenuItem,
this.SystemEthernetBootMenu,
this.optionsToolStripMenuItem,
this.SystemShowDebuggerMenuItem});
@@ -176,14 +178,14 @@
// loadToolStripMenuItem
//
this.loadToolStripMenuItem.Name = "loadToolStripMenuItem";
- this.loadToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+ this.loadToolStripMenuItem.Size = new System.Drawing.Size(134, 22);
this.loadToolStripMenuItem.Text = "Load...";
this.loadToolStripMenuItem.Click += new System.EventHandler(this.OnSystemDrive1LoadClick);
//
// unloadToolStripMenuItem
//
this.unloadToolStripMenuItem.Name = "unloadToolStripMenuItem";
- this.unloadToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+ this.unloadToolStripMenuItem.Size = new System.Drawing.Size(134, 22);
this.unloadToolStripMenuItem.Text = "Unload...";
this.unloadToolStripMenuItem.Click += new System.EventHandler(this.OnSystemDrive1UnloadClick);
//
@@ -191,15 +193,28 @@
//
this.Drive1ImageName.Enabled = false;
this.Drive1ImageName.Name = "Drive1ImageName";
- this.Drive1ImageName.Size = new System.Drawing.Size(152, 22);
+ this.Drive1ImageName.Size = new System.Drawing.Size(134, 22);
this.Drive1ImageName.Text = "Image Name";
//
+ // enableAlternateBootToolStripMenuItem
+ //
+ this.enableAlternateBootToolStripMenuItem.Name = "enableAlternateBootToolStripMenuItem";
+ this.enableAlternateBootToolStripMenuItem.Size = new System.Drawing.Size(210, 22);
+ this.enableAlternateBootToolStripMenuItem.Text = "Enable Alternate Boot";
+ //
+ // SystemEthernetBootMenu
+ //
+ this.SystemEthernetBootMenu.Name = "SystemEthernetBootMenu";
+ this.SystemEthernetBootMenu.Size = new System.Drawing.Size(210, 22);
+ this.SystemEthernetBootMenu.Text = "Alternate Boot Options...";
+ this.SystemEthernetBootMenu.Click += new System.EventHandler(this.OnAlternateBootOptionsClicked);
+ //
// optionsToolStripMenuItem
//
- this.optionsToolStripMenuItem.Enabled = false;
this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem";
this.optionsToolStripMenuItem.Size = new System.Drawing.Size(210, 22);
this.optionsToolStripMenuItem.Text = "Options...";
+ this.optionsToolStripMenuItem.Click += new System.EventHandler(this.OnSystemOptionsClick);
//
// SystemShowDebuggerMenuItem
//
@@ -221,7 +236,7 @@
// aboutToolStripMenuItem
//
this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem";
- this.aboutToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
+ this.aboutToolStripMenuItem.Size = new System.Drawing.Size(103, 22);
this.aboutToolStripMenuItem.Text = "About";
this.aboutToolStripMenuItem.Click += new System.EventHandler(this.OnHelpAboutClick);
//
@@ -256,13 +271,6 @@
this.DiskStatusLabel.Size = new System.Drawing.Size(82, 17);
this.DiskStatusLabel.Text = "DiskStatusLabel";
//
- // SystemEthernetBootMenu
- //
- this.SystemEthernetBootMenu.Name = "SystemEthernetBootMenu";
- this.SystemEthernetBootMenu.Size = new System.Drawing.Size(210, 22);
- this.SystemEthernetBootMenu.Text = "Ethernet Boot...";
- this.SystemEthernetBootMenu.Click += new System.EventHandler(this.OnEthernetBootClicked);
- //
// AltoWindow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@@ -320,5 +328,6 @@
private System.Windows.Forms.ToolStripStatusLabel SystemStatusLabel;
private System.Windows.Forms.ToolStripStatusLabel DiskStatusLabel;
private System.Windows.Forms.ToolStripMenuItem SystemEthernetBootMenu;
+ private System.Windows.Forms.ToolStripMenuItem enableAlternateBootToolStripMenuItem;
}
}
\ No newline at end of file
diff --git a/Contralto/UI/AltoWindow.cs b/Contralto/UI/AltoWindow.cs
index 72bd622..41e5597 100644
--- a/Contralto/UI/AltoWindow.cs
+++ b/Contralto/UI/AltoWindow.cs
@@ -1,17 +1,12 @@
using Contralto.CPU;
using Contralto.Display;
using Contralto.IO;
+using Contralto.UI;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
using System.Windows.Forms;
namespace Contralto
@@ -29,10 +24,17 @@ namespace Contralto
_displayBuffer = new Bitmap(608, 808, PixelFormat.Format1bppIndexed);
DisplayBox.Image = _displayBuffer;
+ _lastBuffer = _currentBuffer = _displayData0;
+ _frame = 0;
+
+ _frameTimer = new FrameTimer(60.0);
+
ReleaseMouse();
SystemStatusLabel.Text = _systemStoppedText;
DiskStatusLabel.Text = String.Empty;
+
+ this.DoubleBuffered = true;
}
public void AttachSystem(AltoSystem system)
@@ -135,9 +137,9 @@ namespace Contralto
Drive1ImageName.Text = _noImageLoadedText;
}
- private void OnEthernetBootClicked(object sender, EventArgs e)
+ private void OnAlternateBootOptionsClicked(object sender, EventArgs e)
{
- EthernetBootWindow bootWindow = new EthernetBootWindow();
+ AlternateBootOptions bootWindow = new AlternateBootOptions();
bootWindow.ShowDialog();
//
@@ -146,6 +148,12 @@ namespace Contralto
_system.PressBootKeys();
}
+ private void OnSystemOptionsClick(object sender, EventArgs e)
+ {
+ SystemOptions optionsWindow = new SystemOptions();
+ optionsWindow.ShowDialog();
+ }
+
private void OnHelpAboutClick(object sender, EventArgs e)
{
AboutBox about = new AboutBox();
@@ -185,14 +193,21 @@ namespace Contralto
private void OnFileExitClick(object sender, EventArgs e)
{
_controller.StopExecution();
- this.Close();
+ this.Close();
}
private void OnAltoWindowClosed(object sender, FormClosedEventArgs e)
{
+ // Halt the system and detach our display
+ _controller.StopExecution();
+ _system.DetachDisplay();
+
// Commit loaded packs back to disk
CommitDiskPack(0);
- CommitDiskPack(1);
+ CommitDiskPack(1);
+
+ this.Dispose();
+ Application.Exit();
}
private string ShowImageLoadDialog(int drive)
@@ -259,7 +274,28 @@ namespace Contralto
public void Render()
{
- BeginInvoke(new DisplayDelegate(RefreshDisplayBox));
+ _frame++;
+
+ // Wait for the next frame
+ _frameTimer.WaitForFrame();
+
+ if (Configuration.InterlaceDisplay)
+ {
+ // Flip the back-buffer
+ if ((_frame % 2) == 0)
+ {
+ _currentBuffer = _displayData0;
+ _lastBuffer = _displayData1;
+ }
+ else
+ {
+ _currentBuffer = _displayData1;
+ _lastBuffer = _displayData0;
+ }
+ }
+
+ // Asynchronously render this frame.
+ BeginInvoke(new DisplayDelegate(RefreshDisplayBox));
}
private void RefreshDisplayBox()
@@ -268,13 +304,16 @@ namespace Contralto
BitmapData data = _displayBuffer.LockBits(_displayRect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
IntPtr ptr = data.Scan0;
- System.Runtime.InteropServices.Marshal.Copy(_displayData, 0, ptr, _displayData.Length - 4);
+ System.Runtime.InteropServices.Marshal.Copy(_lastBuffer, 0, ptr, _lastBuffer.Length - 4);
_displayBuffer.UnlockBits(data);
- DisplayBox.Refresh();
+ DisplayBox.Refresh();
- // If you want interlacing to be more visible:
- //Array.Clear(_displayData, 0, _displayData.Length);
+ // Clear the buffer if we're displaying in fakey-"interlaced" mode.
+ if (Configuration.InterlaceDisplay)
+ {
+ Array.Clear(_lastBuffer, 0, _lastBuffer.Length);
+ }
}
///
@@ -292,29 +331,29 @@ namespace Contralto
// Low resolution; double up pixels.
int address = scanline * 76 + wordOffset * 4;
- if (address > _displayData.Length)
+ if (address > _currentBuffer.Length)
{
throw new InvalidOperationException("Display word address is out of bounds.");
}
UInt32 stretched = StretchWord(word);
- _displayData[address] = (byte)(stretched >> 24);
- _displayData[address + 1] = (byte)(stretched >> 16);
- _displayData[address + 2] = (byte)(stretched >> 8);
- _displayData[address + 3] = (byte)(stretched);
+ _currentBuffer[address] = (byte)(stretched >> 24);
+ _currentBuffer[address + 1] = (byte)(stretched >> 16);
+ _currentBuffer[address + 2] = (byte)(stretched >> 8);
+ _currentBuffer[address + 3] = (byte)(stretched);
}
else
{
int address = scanline * 76 + wordOffset * 2;
- if (address > _displayData.Length)
+ if (address > _currentBuffer.Length)
{
throw new InvalidOperationException("Display word address is out of bounds.");
}
- _displayData[address] = (byte)(word >> 8);
- _displayData[address + 1] = (byte)(word);
+ _currentBuffer[address] = (byte)(word >> 8);
+ _currentBuffer[address + 1] = (byte)(word);
}
}
@@ -333,19 +372,19 @@ namespace Contralto
// Grab the 32 bits straddling the cursor from the display buffer
// so we can merge the 16 cursor bits in.
//
- UInt32 displayWord = (UInt32)((_displayData[address] << 24) |
- (_displayData[address + 1] << 16) |
- (_displayData[address + 2] << 8) |
- _displayData[address + 3]);
+ UInt32 displayWord = (UInt32)((_currentBuffer[address] << 24) |
+ (_currentBuffer[address + 1] << 16) |
+ (_currentBuffer[address + 2] << 8) |
+ _currentBuffer[address + 3]);
UInt32 longcursorWord = (UInt32)(cursorWord << 16);
displayWord ^= (longcursorWord >> (xOffset % 8));
- _displayData[address] = (byte)(displayWord >> 24);
- _displayData[address + 1] = (byte)(displayWord >> 16);
- _displayData[address + 2] = (byte)(displayWord >> 8);
- _displayData[address + 3] = (byte)(displayWord);
+ _currentBuffer[address] = (byte)(displayWord >> 24);
+ _currentBuffer[address + 1] = (byte)(displayWord >> 16);
+ _currentBuffer[address + 2] = (byte)(displayWord >> 8);
+ _currentBuffer[address + 3] = (byte)(displayWord);
}
///
@@ -696,11 +735,19 @@ namespace Contralto
// Display related data.
// Note: display is actually 606 pixels wide, but that's not an even multiple of 8, so we round up.
- private byte[] _displayData = new byte[808 * 76 + 4]; // + 4 to make cursor display logic simpler.
+ // Two backbuffers and references to the current / last buffer for rendering
+ private byte[] _displayData0 = new byte[808 * 76 + 4]; // + 4 to make cursor display logic simpler.
+ private byte[] _displayData1 = new byte[808 * 76 + 4]; // + 4 to make cursor display logic simpler.
+ private byte[] _currentBuffer;
+ private byte[] _lastBuffer;
+ private int _frame;
private Bitmap _displayBuffer;
private Rectangle _displayRect = new Rectangle(0, 0, 608, 808);
private delegate void DisplayDelegate();
+ // Speed throttling
+ FrameTimer _frameTimer;
+
// Input related data
// Keyboard mapping from windows vkeys to Alto keys
@@ -725,6 +772,5 @@ namespace Contralto
private const string _systemRunningText = "Alto Running.";
private const string _systemErrorText = "Alto Stopped due to error. See Debugger.";
-
}
}
diff --git a/Contralto/UI/SystemOptions.Designer.cs b/Contralto/UI/SystemOptions.Designer.cs
new file mode 100644
index 0000000..7df345f
--- /dev/null
+++ b/Contralto/UI/SystemOptions.Designer.cs
@@ -0,0 +1,324 @@
+namespace Contralto.UI
+{
+ partial class SystemOptions
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.tabControl1 = new System.Windows.Forms.TabControl();
+ this.tabPage1 = new System.Windows.Forms.TabPage();
+ this.AltoII3KRAMRadioButton = new System.Windows.Forms.RadioButton();
+ this.AltoII2KROMRadioButton = new System.Windows.Forms.RadioButton();
+ this.label1 = new System.Windows.Forms.Label();
+ this.AltoII1KROMRadioButton = new System.Windows.Forms.RadioButton();
+ this.tabPage2 = new System.Windows.Forms.TabPage();
+ this.groupBox1 = new System.Windows.Forms.GroupBox();
+ this.RawEthernetRadioButton = new System.Windows.Forms.RadioButton();
+ this.UDPRadioButton = new System.Windows.Forms.RadioButton();
+ this.HostInterfaceGroupBox = new System.Windows.Forms.GroupBox();
+ this.EthernetInterfaceListBox = new System.Windows.Forms.ListBox();
+ this.label4 = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.AltoEthernetAddressTextBox = new System.Windows.Forms.TextBox();
+ this.tabPage3 = new System.Windows.Forms.TabPage();
+ this.ThrottleSpeedCheckBox = new System.Windows.Forms.CheckBox();
+ this.InterlaceDisplayCheckBox = new System.Windows.Forms.CheckBox();
+ this.OKButton = new System.Windows.Forms.Button();
+ this.CancelButton = new System.Windows.Forms.Button();
+ this.tabControl1.SuspendLayout();
+ this.tabPage1.SuspendLayout();
+ this.tabPage2.SuspendLayout();
+ this.groupBox1.SuspendLayout();
+ this.HostInterfaceGroupBox.SuspendLayout();
+ this.tabPage3.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // tabControl1
+ //
+ this.tabControl1.Controls.Add(this.tabPage1);
+ this.tabControl1.Controls.Add(this.tabPage2);
+ this.tabControl1.Controls.Add(this.tabPage3);
+ this.tabControl1.Location = new System.Drawing.Point(3, 5);
+ this.tabControl1.Name = "tabControl1";
+ this.tabControl1.SelectedIndex = 0;
+ this.tabControl1.Size = new System.Drawing.Size(368, 227);
+ this.tabControl1.TabIndex = 0;
+ //
+ // tabPage1
+ //
+ this.tabPage1.Controls.Add(this.AltoII3KRAMRadioButton);
+ this.tabPage1.Controls.Add(this.AltoII2KROMRadioButton);
+ this.tabPage1.Controls.Add(this.label1);
+ this.tabPage1.Controls.Add(this.AltoII1KROMRadioButton);
+ this.tabPage1.Location = new System.Drawing.Point(4, 22);
+ this.tabPage1.Name = "tabPage1";
+ this.tabPage1.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage1.Size = new System.Drawing.Size(360, 201);
+ this.tabPage1.TabIndex = 0;
+ this.tabPage1.Text = "CPU";
+ this.tabPage1.UseVisualStyleBackColor = true;
+ //
+ // AltoII3KRAMRadioButton
+ //
+ this.AltoII3KRAMRadioButton.AutoSize = true;
+ this.AltoII3KRAMRadioButton.Location = new System.Drawing.Point(14, 75);
+ this.AltoII3KRAMRadioButton.Name = "AltoII3KRAMRadioButton";
+ this.AltoII3KRAMRadioButton.Size = new System.Drawing.Size(217, 17);
+ this.AltoII3KRAMRadioButton.TabIndex = 3;
+ this.AltoII3KRAMRadioButton.TabStop = true;
+ this.AltoII3KRAMRadioButton.Text = "Alto II, 1K Control ROM, 3K Control RAM";
+ this.AltoII3KRAMRadioButton.UseVisualStyleBackColor = true;
+ this.AltoII3KRAMRadioButton.CheckedChanged += new System.EventHandler(this.OnSystemTypeCheckChanged);
+ //
+ // AltoII2KROMRadioButton
+ //
+ this.AltoII2KROMRadioButton.AutoSize = true;
+ this.AltoII2KROMRadioButton.Location = new System.Drawing.Point(14, 52);
+ this.AltoII2KROMRadioButton.Name = "AltoII2KROMRadioButton";
+ this.AltoII2KROMRadioButton.Size = new System.Drawing.Size(217, 17);
+ this.AltoII2KROMRadioButton.TabIndex = 2;
+ this.AltoII2KROMRadioButton.TabStop = true;
+ this.AltoII2KROMRadioButton.Text = "Alto II, 2K Control ROM, 1K Control RAM";
+ this.AltoII2KROMRadioButton.UseVisualStyleBackColor = true;
+ this.AltoII2KROMRadioButton.CheckedChanged += new System.EventHandler(this.OnSystemTypeCheckChanged);
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(11, 13);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(108, 13);
+ this.label1.TabIndex = 1;
+ this.label1.Text = "System configuration:";
+ //
+ // AltoII1KROMRadioButton
+ //
+ this.AltoII1KROMRadioButton.AutoSize = true;
+ this.AltoII1KROMRadioButton.Location = new System.Drawing.Point(14, 29);
+ this.AltoII1KROMRadioButton.Name = "AltoII1KROMRadioButton";
+ this.AltoII1KROMRadioButton.Size = new System.Drawing.Size(217, 17);
+ this.AltoII1KROMRadioButton.TabIndex = 0;
+ this.AltoII1KROMRadioButton.TabStop = true;
+ this.AltoII1KROMRadioButton.Text = "Alto II, 1K Control ROM, 1K Control RAM";
+ this.AltoII1KROMRadioButton.UseVisualStyleBackColor = true;
+ this.AltoII1KROMRadioButton.CheckedChanged += new System.EventHandler(this.OnSystemTypeCheckChanged);
+ //
+ // tabPage2
+ //
+ this.tabPage2.Controls.Add(this.groupBox1);
+ this.tabPage2.Controls.Add(this.HostInterfaceGroupBox);
+ this.tabPage2.Controls.Add(this.label3);
+ this.tabPage2.Controls.Add(this.AltoEthernetAddressTextBox);
+ this.tabPage2.Location = new System.Drawing.Point(4, 22);
+ this.tabPage2.Name = "tabPage2";
+ this.tabPage2.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage2.Size = new System.Drawing.Size(360, 201);
+ this.tabPage2.TabIndex = 1;
+ this.tabPage2.Text = "Ethernet";
+ this.tabPage2.UseVisualStyleBackColor = true;
+ //
+ // groupBox1
+ //
+ this.groupBox1.Controls.Add(this.RawEthernetRadioButton);
+ this.groupBox1.Controls.Add(this.UDPRadioButton);
+ this.groupBox1.Location = new System.Drawing.Point(10, 30);
+ this.groupBox1.Name = "groupBox1";
+ this.groupBox1.Size = new System.Drawing.Size(335, 39);
+ this.groupBox1.TabIndex = 3;
+ this.groupBox1.TabStop = false;
+ this.groupBox1.Text = "Ethernet Encapsulation";
+ //
+ // RawEthernetRadioButton
+ //
+ this.RawEthernetRadioButton.AutoSize = true;
+ this.RawEthernetRadioButton.Location = new System.Drawing.Point(77, 16);
+ this.RawEthernetRadioButton.Name = "RawEthernetRadioButton";
+ this.RawEthernetRadioButton.Size = new System.Drawing.Size(186, 17);
+ this.RawEthernetRadioButton.TabIndex = 1;
+ this.RawEthernetRadioButton.TabStop = true;
+ this.RawEthernetRadioButton.Text = "Raw Ethernet (requires WinPCAP)";
+ this.RawEthernetRadioButton.UseVisualStyleBackColor = true;
+ this.RawEthernetRadioButton.CheckedChanged += new System.EventHandler(this.OnEthernetTypeCheckedChanged);
+ //
+ // UDPRadioButton
+ //
+ this.UDPRadioButton.AutoSize = true;
+ this.UDPRadioButton.Location = new System.Drawing.Point(9, 16);
+ this.UDPRadioButton.Name = "UDPRadioButton";
+ this.UDPRadioButton.Size = new System.Drawing.Size(48, 17);
+ this.UDPRadioButton.TabIndex = 0;
+ this.UDPRadioButton.TabStop = true;
+ this.UDPRadioButton.Text = "UDP";
+ this.UDPRadioButton.UseVisualStyleBackColor = true;
+ this.UDPRadioButton.CheckedChanged += new System.EventHandler(this.OnEthernetTypeCheckedChanged);
+ //
+ // HostInterfaceGroupBox
+ //
+ this.HostInterfaceGroupBox.Controls.Add(this.EthernetInterfaceListBox);
+ this.HostInterfaceGroupBox.Controls.Add(this.label4);
+ this.HostInterfaceGroupBox.Location = new System.Drawing.Point(10, 75);
+ this.HostInterfaceGroupBox.Name = "HostInterfaceGroupBox";
+ this.HostInterfaceGroupBox.Size = new System.Drawing.Size(335, 120);
+ this.HostInterfaceGroupBox.TabIndex = 2;
+ this.HostInterfaceGroupBox.TabStop = false;
+ this.HostInterfaceGroupBox.Text = "Host Interface";
+ //
+ // EthernetInterfaceListBox
+ //
+ this.EthernetInterfaceListBox.FormattingEnabled = true;
+ this.EthernetInterfaceListBox.Location = new System.Drawing.Point(9, 41);
+ this.EthernetInterfaceListBox.Name = "EthernetInterfaceListBox";
+ this.EthernetInterfaceListBox.Size = new System.Drawing.Size(326, 69);
+ this.EthernetInterfaceListBox.TabIndex = 1;
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.Location = new System.Drawing.Point(9, 20);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(245, 13);
+ this.label4.TabIndex = 0;
+ this.label4.Text = "Select the Network interface to use with ContrAlto:";
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.Location = new System.Drawing.Point(7, 13);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(101, 13);
+ this.label3.TabIndex = 1;
+ this.label3.Text = "Alto Address (octal):";
+ //
+ // AltoEthernetAddressTextBox
+ //
+ this.AltoEthernetAddressTextBox.Location = new System.Drawing.Point(114, 10);
+ this.AltoEthernetAddressTextBox.Name = "AltoEthernetAddressTextBox";
+ this.AltoEthernetAddressTextBox.Size = new System.Drawing.Size(49, 20);
+ this.AltoEthernetAddressTextBox.TabIndex = 0;
+ //
+ // tabPage3
+ //
+ this.tabPage3.Controls.Add(this.ThrottleSpeedCheckBox);
+ this.tabPage3.Controls.Add(this.InterlaceDisplayCheckBox);
+ this.tabPage3.Location = new System.Drawing.Point(4, 22);
+ this.tabPage3.Name = "tabPage3";
+ this.tabPage3.Padding = new System.Windows.Forms.Padding(3);
+ this.tabPage3.Size = new System.Drawing.Size(360, 201);
+ this.tabPage3.TabIndex = 2;
+ this.tabPage3.Text = "Display";
+ this.tabPage3.UseVisualStyleBackColor = true;
+ //
+ // ThrottleSpeedCheckBox
+ //
+ this.ThrottleSpeedCheckBox.AutoSize = true;
+ this.ThrottleSpeedCheckBox.Location = new System.Drawing.Point(19, 22);
+ this.ThrottleSpeedCheckBox.Name = "ThrottleSpeedCheckBox";
+ this.ThrottleSpeedCheckBox.Size = new System.Drawing.Size(188, 17);
+ this.ThrottleSpeedCheckBox.TabIndex = 1;
+ this.ThrottleSpeedCheckBox.Text = "Throttle Framerate at 60 fields/sec";
+ this.ThrottleSpeedCheckBox.UseVisualStyleBackColor = true;
+ //
+ // InterlaceDisplayCheckBox
+ //
+ this.InterlaceDisplayCheckBox.AutoSize = true;
+ this.InterlaceDisplayCheckBox.Location = new System.Drawing.Point(19, 45);
+ this.InterlaceDisplayCheckBox.Name = "InterlaceDisplayCheckBox";
+ this.InterlaceDisplayCheckBox.Size = new System.Drawing.Size(196, 17);
+ this.InterlaceDisplayCheckBox.TabIndex = 0;
+ this.InterlaceDisplayCheckBox.Text = "Interlaced Display (headache mode)";
+ this.InterlaceDisplayCheckBox.UseVisualStyleBackColor = true;
+ //
+ // OKButton
+ //
+ this.OKButton.Location = new System.Drawing.Point(211, 239);
+ this.OKButton.Name = "OKButton";
+ this.OKButton.Size = new System.Drawing.Size(75, 23);
+ this.OKButton.TabIndex = 1;
+ this.OKButton.Text = "OK";
+ this.OKButton.UseVisualStyleBackColor = true;
+ this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
+ //
+ // CancelButton
+ //
+ this.CancelButton.Location = new System.Drawing.Point(292, 239);
+ this.CancelButton.Name = "CancelButton";
+ this.CancelButton.Size = new System.Drawing.Size(75, 23);
+ this.CancelButton.TabIndex = 2;
+ this.CancelButton.Text = "Cancel";
+ this.CancelButton.UseVisualStyleBackColor = true;
+ this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click);
+ //
+ // SystemOptions
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(371, 271);
+ this.Controls.Add(this.CancelButton);
+ this.Controls.Add(this.OKButton);
+ this.Controls.Add(this.tabControl1);
+ this.Name = "SystemOptions";
+ this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+ this.Text = "System Options";
+ this.tabControl1.ResumeLayout(false);
+ this.tabPage1.ResumeLayout(false);
+ this.tabPage1.PerformLayout();
+ this.tabPage2.ResumeLayout(false);
+ this.tabPage2.PerformLayout();
+ this.groupBox1.ResumeLayout(false);
+ this.groupBox1.PerformLayout();
+ this.HostInterfaceGroupBox.ResumeLayout(false);
+ this.HostInterfaceGroupBox.PerformLayout();
+ this.tabPage3.ResumeLayout(false);
+ this.tabPage3.PerformLayout();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.TabControl tabControl1;
+ private System.Windows.Forms.TabPage tabPage1;
+ private System.Windows.Forms.RadioButton AltoII3KRAMRadioButton;
+ private System.Windows.Forms.RadioButton AltoII2KROMRadioButton;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.RadioButton AltoII1KROMRadioButton;
+ private System.Windows.Forms.TabPage tabPage2;
+ private System.Windows.Forms.GroupBox HostInterfaceGroupBox;
+ private System.Windows.Forms.ListBox EthernetInterfaceListBox;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.TextBox AltoEthernetAddressTextBox;
+ private System.Windows.Forms.Button OKButton;
+ private System.Windows.Forms.Button CancelButton;
+ private System.Windows.Forms.TabPage tabPage3;
+ private System.Windows.Forms.CheckBox ThrottleSpeedCheckBox;
+ private System.Windows.Forms.CheckBox InterlaceDisplayCheckBox;
+ private System.Windows.Forms.GroupBox groupBox1;
+ private System.Windows.Forms.RadioButton RawEthernetRadioButton;
+ private System.Windows.Forms.RadioButton UDPRadioButton;
+ }
+}
\ No newline at end of file
diff --git a/Contralto/UI/SystemOptions.cs b/Contralto/UI/SystemOptions.cs
new file mode 100644
index 0000000..2288b80
--- /dev/null
+++ b/Contralto/UI/SystemOptions.cs
@@ -0,0 +1,218 @@
+using Contralto.IO;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Contralto.UI
+{
+ public partial class SystemOptions : Form
+ {
+ public SystemOptions()
+ {
+ InitializeComponent();
+
+ PopulateUI();
+ }
+
+
+ ///
+ /// Populates the UI fields with data from the current configuration.
+ ///
+ private void PopulateUI()
+ {
+ _selectedSystemType = Configuration.SystemType;
+ _selectedInterfaceType = Configuration.HostPacketInterfaceType;
+
+ switch(Configuration.SystemType)
+ {
+ case SystemType.OneKRom:
+ AltoII1KROMRadioButton.Checked = true;
+ break;
+
+ case SystemType.TwoKRom:
+ AltoII2KROMRadioButton.Checked = true;
+ break;
+
+ case SystemType.ThreeKRam:
+ AltoII3KRAMRadioButton.Checked = true;
+ break;
+ }
+
+ InterlaceDisplayCheckBox.Checked = Configuration.InterlaceDisplay;
+ ThrottleSpeedCheckBox.Checked = Configuration.ThrottleSpeed;
+
+ AltoEthernetAddressTextBox.Text = Conversion.ToOctal(Configuration.HostAddress);
+
+ if (!Configuration.HostRawEthernetInterfacesAvailable)
+ {
+ // If PCAP isn't installed, the RAW Ethernet option is not available.
+ RawEthernetRadioButton.Enabled = false;
+ UDPRadioButton.Checked = true;
+ }
+ else
+ {
+ if (Configuration.HostPacketInterfaceType == PacketInterfaceType.UDPEncapsulation)
+ {
+ UDPRadioButton.Checked = true;
+ }
+ else
+ {
+ RawEthernetRadioButton.Checked = true;
+ }
+ }
+
+ PopulateNetworkAdapterList(UDPRadioButton.Checked);
+ }
+
+ private void PopulateNetworkAdapterList(bool udpEncapsulation)
+ {
+ //
+ // Populate the list with the interfaces available on the machine, for the
+ // type of encapsulation being used.
+ //
+ EthernetInterfaceListBox.Items.Clear();
+
+ NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
+
+ // Add the "Use no interface" option
+ EthernetInterfaceListBox.Items.Add(
+ new EthernetInterface("None", "No network adapter"));
+
+ foreach (NetworkInterface iface in interfaces)
+ {
+ // For UDP we show all interfaces that support IPV4, for Raw Ethernet we show only Ethernet interfaces.
+ if (udpEncapsulation)
+ {
+ if (iface.Supports(NetworkInterfaceComponent.IPv4))
+ {
+ EthernetInterfaceListBox.Items.Add(new EthernetInterface(iface.Name, iface.Description));
+ }
+ }
+ else
+ {
+ if (iface.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
+ {
+ EthernetInterfaceListBox.Items.Add(new EthernetInterface(iface.Name, iface.Description));
+ }
+ }
+ }
+
+ //
+ // Select the one that is already selected (if any)
+ //
+ EthernetInterfaceListBox.SelectedIndex = 0;
+
+ if (!string.IsNullOrEmpty(Configuration.HostPacketInterfaceName))
+ {
+ for (int i = 0; i < EthernetInterfaceListBox.Items.Count; i++)
+ {
+ EthernetInterface iface = (EthernetInterface)EthernetInterfaceListBox.Items[i];
+
+ if (iface.Description.ToLowerInvariant() == Configuration.HostPacketInterfaceName.ToLowerInvariant())
+ {
+ EthernetInterfaceListBox.SelectedIndex = i;
+ break;
+ }
+ }
+ }
+ }
+
+ private void OnSystemTypeCheckChanged(object sender, EventArgs e)
+ {
+ if (AltoII1KROMRadioButton.Checked)
+ {
+ _selectedSystemType = SystemType.OneKRom;
+ }
+ else if (AltoII2KROMRadioButton.Checked)
+ {
+ _selectedSystemType = SystemType.TwoKRom;
+ }
+ else if (AltoII3KRAMRadioButton.Checked)
+ {
+ _selectedSystemType = SystemType.ThreeKRam;
+ }
+ }
+
+ private void OnEthernetTypeCheckedChanged(object sender, EventArgs e)
+ {
+ if (UDPRadioButton.Checked)
+ {
+ _selectedInterfaceType = PacketInterfaceType.UDPEncapsulation;
+ }
+ else
+ {
+ _selectedInterfaceType = PacketInterfaceType.EthernetEncapsulation;
+ }
+
+ PopulateNetworkAdapterList(UDPRadioButton.Checked);
+ }
+
+ private void OKButton_Click(object sender, EventArgs e)
+ {
+ try
+ {
+ int testValue = Convert.ToByte(AltoEthernetAddressTextBox.Text, 8);
+
+ if (testValue < 1 || testValue > 254)
+ {
+ throw new ArgumentOutOfRangeException("Invalid host address.");
+ }
+ }
+ catch
+ {
+ MessageBox.Show("The Alto Ethernet address must be an octal value between 1 and 376.");
+ return;
+ }
+
+ //
+ // Commit changes back to Configuration.
+ //
+
+ EthernetInterface iface = (EthernetInterface)EthernetInterfaceListBox.SelectedItem;
+
+ //
+ // First warn the user of changes that require a restart.
+ //
+ if (Configuration.HostPacketInterfaceName.ToLowerInvariant() != iface.Description.ToLowerInvariant() ||
+ Configuration.HostPacketInterfaceType != _selectedInterfaceType ||
+ Configuration.SystemType != _selectedSystemType)
+ {
+ MessageBox.Show("Changes to CPU or Ethernet configuration will not take effect until ContrAlto is restarted.");
+ }
+
+ //System
+ Configuration.SystemType = _selectedSystemType;
+
+ // Ethernet
+ Configuration.HostAddress = Convert.ToByte(AltoEthernetAddressTextBox.Text, 8);
+ Configuration.HostPacketInterfaceName = iface.Description;
+ Configuration.HostPacketInterfaceType = _selectedInterfaceType;
+
+ // Display
+ Configuration.InterlaceDisplay = InterlaceDisplayCheckBox.Checked;
+ Configuration.ThrottleSpeed = ThrottleSpeedCheckBox.Checked;
+
+ Configuration.WriteConfiguration();
+
+ this.Close();
+
+ }
+
+ private void CancelButton_Click(object sender, EventArgs e)
+ {
+ this.Close();
+ }
+
+ private PacketInterfaceType _selectedInterfaceType;
+ private SystemType _selectedSystemType;
+
+
+ }
+}
diff --git a/Contralto/UI/SystemOptions.resx b/Contralto/UI/SystemOptions.resx
new file mode 100644
index 0000000..1af7de1
--- /dev/null
+++ b/Contralto/UI/SystemOptions.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file