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

563 lines
20 KiB
C#

/*
This file is part of ContrAlto.
ContrAlto is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ContrAlto is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with ContrAlto. If not, see <http://www.gnu.org/licenses/>.
*/
using Contralto.Logging;
using System;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
namespace Contralto
{
/// <summary>
/// The configuration of the Alto to emulate
/// </summary>
public enum SystemType
{
/// <summary>
/// Alto I System with 1K ROM, 1K RAM
/// </summary>
AltoI,
/// <summary>
/// Alto II XM System with the standard 1K ROM, 1K RAM
/// </summary>
OneKRom,
/// <summary>
/// Alto II XM System with 2K ROM, 1K RAM
/// </summary>
TwoKRom,
/// <summary>
/// Alto II XM 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>
/// No encapsulation; sent packets are dropped on the floor and no packets are received.
/// </summary>
None,
}
public enum AlternateBootType
{
None,
Disk,
Ethernet,
}
public enum PlatformType
{
Windows,
Unix
}
/// <summary>
/// Encapsulates user-configurable settings. To be enhanced.
/// </summary>
public class Configuration
{
static Configuration()
{
// Initialize things to defaults.
HostAddress = 0x22;
BootAddress = 0;
BootFile = 0;
SystemType = SystemType.OneKRom;
InterlaceDisplay = false;
ThrottleSpeed = true;
switch(Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
case PlatformID.Unix:
Platform = PlatformType.Unix;
break;
default:
Platform = PlatformType.Windows;
break;
}
// See if PCap is available.
TestPCap();
try
{
ReadConfiguration();
}
catch
{
Console.WriteLine("Warning: unable to load configuration. Assuming default settings.");
}
// Special case: On first startup, AlternateBoot will come back as "None" which
// is an invalid UI setting; default to Ethernet in this case.
if (AlternateBootType == AlternateBootType.None)
{
AlternateBootType = AlternateBootType.Ethernet;
}
//
// If configuration specifies fewer than 8 Trident disks, we need to pad the list out to 8 with empty entries.
//
if (TridentImages == null)
{
TridentImages = new StringCollection();
}
int start = TridentImages.Count;
for (int i = start; i < 8; i++)
{
TridentImages.Add(String.Empty);
}
}
/// <summary>
/// What kind of system we're running on. (Not technically configurable.)
/// </summary>
public static PlatformType Platform;
/// <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 currently loaded images for the Trident controller.
/// </summary>
public static StringCollection TridentImages;
/// <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>
/// The type of Alternate Boot to apply
/// </summary>
public static AlternateBootType AlternateBootType;
/// <summary>
/// The address to boot at reset for a disk alternate boot
/// </summary>
public static ushort BootAddress;
/// <summary>
/// The file to boot at reset for an ethernet alternate boot
/// </summary>
public static ushort BootFile;
/// <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>
/// Whether to enable the DAC used for the Smalltalk music system.
/// </summary>
public static bool EnableAudioDAC;
/// <summary>
/// Whether to enable capture of the DAC output to file.
/// </summary>
public static bool EnableAudioDACCapture;
/// <summary>
/// The path to store DAC capture (if enabled).
/// </summary>
public static string AudioDACCapturePath;
/// <summary>
/// Whether to enable printing via the Orbit / DoverROS interface.
/// </summary>
public static bool EnablePrinting;
/// <summary>
/// Path for print output.
/// </summary>
public static string PrintOutputPath;
/// <summary>
/// Whether to reverse the page order when printing.
/// </summary>
public static bool ReversePageOrder;
/// <summary>
/// The components to enable debug logging for.
/// </summary>
public static LogComponent LogComponents;
/// <summary>
/// The types of logging to enable.
/// </summary>
public static LogType LogTypes;
public static string GetAltoIRomPath(string romFileName)
{
return Path.Combine("ROM", "AltoI", romFileName);
}
public static string GetAltoIIRomPath(string romFileName)
{
return Path.Combine("ROM", "AltoII", romFileName);
}
public static string GetRomPath(string romFileName)
{
return Path.Combine("ROM", romFileName);
}
/// <summary>
/// Reads the current configuration file from the appropriate place.
/// </summary>
public static void ReadConfiguration()
{
if (Configuration.Platform == PlatformType.Windows
&& Program.StartupArgs.Length == 0)
{
//
// By default, on Windows we use the app Settings functionality
// to store settings in the registry on a per-user basis.
// If a configuration file is specified, we will use it instead.
//
ReadConfigurationWindows();
}
else
{
//
// On UNIX platforms we read in a configuration file.
// This is mostly because Mono's support for Properties.Settings
// is broken in inexplicable ways and I'm tired of fighting with it.
//
ReadConfigurationUnix();
}
}
/// <summary>
/// Commits the current configuration to the app's settings.
/// </summary>
public static void WriteConfiguration()
{
if (Configuration.Platform == PlatformType.Windows)
{
WriteConfigurationWindows();
}
else
{
//
// At the moment the configuration files are read-only
// on UNIX platforms.
//
}
}
private static void ReadConfigurationWindows()
{
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
Drive0Image = Properties.Settings.Default.Drive0Image;
Drive1Image = Properties.Settings.Default.Drive1Image;
TridentImages = Properties.Settings.Default.TridentImages;
SystemType = (SystemType)Properties.Settings.Default.SystemType;
HostAddress = Properties.Settings.Default.HostAddress;
HostPacketInterfaceName = Properties.Settings.Default.HostPacketInterfaceName;
HostPacketInterfaceType = (PacketInterfaceType)Properties.Settings.Default.HostPacketInterfaceType;
AlternateBootType = (AlternateBootType)Properties.Settings.Default.AlternateBootType;
BootAddress = Properties.Settings.Default.BootAddress;
BootFile = Properties.Settings.Default.BootFile;
InterlaceDisplay = Properties.Settings.Default.InterlaceDisplay;
ThrottleSpeed = Properties.Settings.Default.ThrottleSpeed;
EnableAudioDAC = Properties.Settings.Default.EnableAudioDAC;
EnableAudioDACCapture = Properties.Settings.Default.EnableAudioDACCapture;
AudioDACCapturePath = Properties.Settings.Default.AudioDACCapturePath;
EnablePrinting = Properties.Settings.Default.EnablePrinting;
PrintOutputPath = Properties.Settings.Default.PrintOutputPath;
ReversePageOrder = Properties.Settings.Default.ReversePageOrder;
}
private static void WriteConfigurationWindows()
{
Properties.Settings.Default.Drive0Image = Drive0Image;
Properties.Settings.Default.Drive1Image = Drive1Image;
Properties.Settings.Default.TridentImages = TridentImages;
Properties.Settings.Default.SystemType = (int)SystemType;
Properties.Settings.Default.HostAddress = HostAddress;
Properties.Settings.Default.HostPacketInterfaceName = HostPacketInterfaceName;
Properties.Settings.Default.HostPacketInterfaceType = (int)HostPacketInterfaceType;
Properties.Settings.Default.AlternateBootType = (int)AlternateBootType;
Properties.Settings.Default.BootAddress = BootAddress;
Properties.Settings.Default.BootFile = BootFile;
Properties.Settings.Default.InterlaceDisplay = InterlaceDisplay;
Properties.Settings.Default.ThrottleSpeed = ThrottleSpeed;
Properties.Settings.Default.EnableAudioDAC = EnableAudioDAC;
Properties.Settings.Default.EnableAudioDACCapture = EnableAudioDACCapture;
Properties.Settings.Default.AudioDACCapturePath = AudioDACCapturePath;
Properties.Settings.Default.EnablePrinting = EnablePrinting;
Properties.Settings.Default.PrintOutputPath = PrintOutputPath;
Properties.Settings.Default.ReversePageOrder = ReversePageOrder;
Properties.Settings.Default.Save();
}
private static void ReadConfigurationUnix()
{
string configFilePath = null;
if (Program.StartupArgs.Length > 0)
{
configFilePath = Program.StartupArgs[0];
}
else
{
// No config file specified, default.
configFilePath = "Contralto.cfg";
}
//
// Check that the configuration file exists.
// If not, we will warn the user and use default settings.
//
if (!File.Exists(configFilePath))
{
Console.WriteLine("Configuration file {0} does not exist or cannot be accessed. Using default settings.", configFilePath);
return;
}
using (StreamReader configStream = new StreamReader(configFilePath))
{
//
// 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().Trim();
if (string.IsNullOrEmpty(line))
{
// Empty line, ignore.
continue;
}
if (line.StartsWith("#"))
{
// Comment to EOL, ignore.
continue;
}
// Find the '=' separating tokens and ensure there are just two.
string[] tokens = line.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length < 2)
{
Console.WriteLine(
"{0} line {1}: Invalid syntax.", configFilePath, lineNumber);
continue;
}
string parameter = tokens[0].Trim();
string value = tokens[1].Trim();
// Reflect over the public, static properties in this class and see if the parameter matches one of them
// If not, it's an error, if it is then we attempt to coerce the value to the correct type.
System.Reflection.FieldInfo[] info = typeof(Configuration).GetFields(BindingFlags.Public | BindingFlags.Static);
bool bMatch = false;
foreach (FieldInfo field in info)
{
// Case-insensitive compare.
if (field.Name.ToLowerInvariant() == parameter.ToLowerInvariant())
{
bMatch = true;
//
// Switch on the type of the field and attempt to convert the value to the appropriate type.
// At this time we support only strings and integers.
//
try
{
switch (field.FieldType.Name)
{
case "Int32":
{
int v = Convert.ToInt32(value, 8);
field.SetValue(null, v);
}
break;
case "UInt16":
{
UInt16 v = Convert.ToUInt16(value, 8);
field.SetValue(null, v);
}
break;
case "Byte":
{
byte v = Convert.ToByte(value, 8);
field.SetValue(null, v);
}
break;
case "String":
{
field.SetValue(null, value);
}
break;
case "Boolean":
{
bool v = bool.Parse(value);
field.SetValue(null, v);
}
break;
case "SystemType":
{
field.SetValue(null, Enum.Parse(typeof(SystemType), value, true));
}
break;
case "PacketInterfaceType":
{
field.SetValue(null, Enum.Parse(typeof(PacketInterfaceType), value, true));
}
break;
case "AlternateBootType":
{
field.SetValue(null, Enum.Parse(typeof(AlternateBootType), value, true));
}
break;
case "LogType":
{
field.SetValue(null, Enum.Parse(typeof(LogType), value, true));
}
break;
case "LogComponent":
{
field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true));
}
break;
case "StringCollection":
{
// value is expected to be a comma-delimited set.
StringCollection sc = new StringCollection();
string[] strings = value.Split(',');
foreach(string s in strings)
{
sc.Add(s);
}
field.SetValue(null, sc);
}
break;
}
}
catch
{
Console.WriteLine(
"{0} line {1}: Value '{2}' is invalid for parameter '{3}'.", configFilePath, lineNumber, value, parameter);
}
}
}
if (!bMatch)
{
Console.WriteLine(
"{0} line {1}: Unknown configuration parameter '{2}'.", configFilePath, lineNumber, parameter);
}
}
}
}
private static void TestPCap()
{
// Just try enumerating interfaces, if this fails for any reason we assume
// PCap is not properly installed.
try
{
SharpPcap.CaptureDeviceList devices = SharpPcap.CaptureDeviceList.Instance;
Configuration.HostRawEthernetInterfacesAvailable = true;
}
catch
{
Configuration.HostRawEthernetInterfacesAvailable = false;
}
}
}
}