1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-17 08:34:15 +00:00

Added configuration UI. Implemented ethernet encapsulation over UDP. A few minor tweaks.

This commit is contained in:
Josh Dersch 2016-02-26 17:46:50 -08:00
parent 209dea8052
commit c73fb66dee
30 changed files with 1784 additions and 242 deletions

View File

@ -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; }
}
/// <summary>
/// Time (in msec) for one system clock
/// </summary>
///
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;
}

View File

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

View File

@ -42,6 +42,17 @@ namespace Contralto.CPU
get { return _mpc; }
}
/// <summary>
/// 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.)
/// </summary>
public bool FirstInstructionAfterSwitch
{
get { return _firstInstructionAfterSwitch; }
set { _firstInstructionAfterSwitch = value; }
}
/// <summary>
/// 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;

View File

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

View File

@ -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
{
/// <summary>
/// The configuration of an Alto II to emulate
/// </summary>
public enum SystemType
{
/// <summary>
/// System with the standard 1K ROM, 1K RAM
/// </summary>
OneKRom,
/// <summary>
/// System with 2K ROM, 1K RAM
/// </summary>
TwoKRom,
/// <summary>
/// System with 3K RAM
/// </summary>
ThreeKRam,
}
public enum PacketInterfaceType
{
/// <summary>
/// Encapsulate frames inside raw ethernet frames on the host interface.
/// Requires PCAP.
/// </summary>
EthernetEncapsulation,
/// <summary>
/// Encapsulate frames inside UDP datagrams on the host interface.
/// </summary>
UDPEncapsulation,
}
/// <summary>
/// Encapsulates user-configurable settings. To be enhanced.
/// </summary>
@ -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;
/// <summary>
/// The type of Alto II to emulate
/// </summary>
public static SystemType SystemType;
/// <summary>
/// The currently loaded image for Drive 0
/// </summary>
public static string Drive0Image;
/// <summary>
/// The currently loaded image for Drive 1
/// </summary>
public static string Drive1Image;
/// <summary>
/// The Ethernet host address for this Alto
/// </summary>
public static byte HostAddress;
/// <summary>
/// The name of the Ethernet adaptor on the emulator host to use for Ethernet emulation
/// </summary>
public static string HostPacketInterfaceName;
/// <summary>
/// Whether any packet interfaces are available on the host
/// </summary>
public static bool HostRawEthernetInterfacesAvailable;
/// <summary>
/// The type of interface to use to host networking.
/// </summary>
public static PacketInterfaceType HostPacketInterfaceType;
/// <summary>
/// Whether to enable Ethernet boot at reset
/// </summary>
public static bool EthernetBootEnabled;
public static ushort EthernetBootFile;
/// <summary>
/// The address/file to boot at reset
/// </summary>
public static ushort BootAddress;
/// <summary>
/// Whether to render the display "interlaced" or not.
/// </summary>
public static bool InterlaceDisplay;
/// <summary>
/// Whether to cap execution speed at native execution speed or not.
/// </summary>
public static bool ThrottleSpeed;
/// <summary>
/// Reads the current configuration file from disk.
///
/// TODO: use reflection to do this.
/// </summary>
public static void ReadConfiguration()
{
try
{
using (StreamReader configStream = new StreamReader("contralto.cfg"))
{
//
// Config file consists of text lines containing name / value pairs:
// <Name> <Value>
// 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();
}
}
/// <summary>
/// Commits the current configuration to disk.
/// </summary>
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.");
}
}
}
}

View File

@ -99,6 +99,14 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="HighResTimer.cs" />
<Compile Include="IO\UDPEncapsulation.cs" />
<Compile Include="IO\IPacketEncapsulation.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="UI\AboutBox.cs">
<SubType>Form</SubType>
</Compile>
@ -140,18 +148,18 @@
<Compile Include="Display\FakeDisplayController.cs" />
<Compile Include="Display\DisplayController.cs" />
<Compile Include="Display\IAltoDisplay.cs" />
<Compile Include="UI\EthernetBootWindow.cs">
<Compile Include="UI\AlternateBootWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\EthernetBootWindow.Designer.cs">
<DependentUpon>EthernetBootWindow.cs</DependentUpon>
<Compile Include="UI\AlternateBootWindow.Designer.cs">
<DependentUpon>AlternateBootWindow.cs</DependentUpon>
</Compile>
<Compile Include="IClockable.cs" />
<Compile Include="IO\DiabloDrive.cs" />
<Compile Include="IO\DiskController.cs" />
<Compile Include="IO\DiabloPack.cs" />
<Compile Include="IO\EthernetController.cs" />
<Compile Include="IO\HostEthernet.cs" />
<Compile Include="IO\HostEthernetEncapsulation.cs" />
<Compile Include="IO\Keyboard.cs" />
<Compile Include="IO\Mouse.cs" />
<Compile Include="Logging\Log.cs" />
@ -163,6 +171,12 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scheduler.cs" />
<Compile Include="ExecutionController.cs" />
<Compile Include="UI\SystemOptions.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="UI\SystemOptions.Designer.cs">
<DependentUpon>SystemOptions.cs</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="Disassembly\altocode24.mu" />
@ -334,6 +348,10 @@
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="UI\AboutBox.resx">
<DependentUpon>AboutBox.cs</DependentUpon>
</EmbeddedResource>
@ -343,8 +361,11 @@
<EmbeddedResource Include="UI\Debugger.resx">
<DependentUpon>Debugger.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\EthernetBootWindow.resx">
<DependentUpon>EthernetBootWindow.cs</DependentUpon>
<EmbeddedResource Include="UI\AlternateBootWindow.resx">
<DependentUpon>AlternateBootWindow.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="UI\SystemOptions.resx">
<DependentUpon>SystemOptions.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

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

191
Contralto/HighResTimer.cs Normal file
View File

@ -0,0 +1,191 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace Contralto
{
/// <summary>
/// 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.
/// </summary>
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();
}
}
/// <summary>
/// Returns the current time in seconds.
/// </summary>
/// <returns></returns>
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);
/// <summary>
/// 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.
/// </summary>
/// <param name="framesPerSecond">The frame rate to sync to.</param>
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();
}
/// <summary>
/// Waits for the timer to fire.
/// </summary>
public void WaitForFrame()
{
_event.WaitOne();
}
/// <summary>
/// Callback from timer queue. Work done here is executed on the timer's thread, so must be quick.
/// </summary>
/// <param name="lpParameter"></param>
/// <param name="TimerOrWaitFired"></param>
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;
}
}

View File

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

View File

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

View File

@ -0,0 +1,23 @@
using System.IO;
namespace Contralto.IO
{
public delegate void ReceivePacketDelegate(MemoryStream data);
public interface IPacketEncapsulation
{
/// <summary>
/// Registers a callback delegate to handle packets that are received.
/// </summary>
/// <param name="callback"></param>
void RegisterReceiveCallback(ReceivePacketDelegate callback);
/// <summary>
/// Sends the specified word array
/// </summary>
/// <param name="packet"></param>
/// <param name="length"></param>
void Send(ushort[] packet, int length);
}
}

View File

@ -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
{
/// <summary>
/// Implements the logic for encapsulating a 3mbit ethernet packet into/out of UDP datagrams.
/// Sent packets are broadcast to the subnet.
/// </summary>
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();
}
/// <summary>
/// Sends an array of bytes over the ethernet as a 3mbit packet encapsulated in a 10mbit packet.
/// </summary>
/// <param name="packet"></param>
/// <param name="hostId"></param>
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.");
}
/// <summary>
/// Begin receiving packets, forever.
/// </summary>
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;
}
}

View File

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

View File

@ -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 <address>" : specifies ethernet host address in octal (1-377)
// "-hostinterface <name>" : specifies the name of the host ethernet interface to use
// "-listinterfaces" : lists ethernet interfaces known by pcap
// "-drive0 <image>" : attaches disk image to drive 0
// "-drive1 <image>" : 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<EthernetInterface> 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<EthernetInterface> 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;
}
}
}

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Contralto.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -1,6 +1,6 @@
namespace Contralto
{
partial class EthernetBootWindow
partial class AlternateBootOptions
{
/// <summary>
/// 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;

View File

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

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

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

View File

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

324
Contralto/UI/SystemOptions.Designer.cs generated Normal file
View File

@ -0,0 +1,324 @@
namespace Contralto.UI
{
partial class SystemOptions
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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();
}
/// <summary>
/// Populates the UI fields with data from the current configuration.
/// </summary>
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;
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>