diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiC64Config.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiC64Config.cs index 4dbc83c4..e7b7e10f 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiC64Config.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiC64Config.cs @@ -1,148 +1,156 @@ +using System.Diagnostics; using System.Numerics; -using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Input; +using Highbyte.DotNet6502.App.SkiaNative.SystemSetup; using Highbyte.DotNet6502.Systems.Commodore64.Config; -using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; namespace Highbyte.DotNet6502.App.SkiaNative.ConfigUI; public class SilkNetImGuiC64Config { - private C64Config? _config; - public C64Config UpdatedConfig => _config!; + private readonly SilkNetImGuiMenu _mainMenu; + C64Config _config => (C64Config)_mainMenu.GetSelectedSystemConfig(); + C64HostConfig _hostConfig => (C64HostConfig)_mainMenu.GetSelectedSystemHostConfig(); + private string? _romDirectory; private string? _kernalRomFile; private string? _basicRomFile; private string? _chargenRomFile; - public bool Visible { get; set; } - public bool Ok { get; set; } - public bool Cancel { get; set; } - - private bool _isValidConfig = true; + private bool _open; - public bool IsValidConfig => _isValidConfig; - private List? _validationErrors; + public bool IsValidConfig + { + get + { + if (_config == null) + { + _validationErrors.Clear(); + return true; + } + else + { + return _config.IsValid(out _validationErrors); + } + } + } + private List _validationErrors = new(); - private const int POS_X = 50; - private const int POS_Y = 50; - private const int WIDTH = 400; - private const int HEIGHT = 550; //private static Vector4 s_informationColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); private static Vector4 s_errorColor = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); //private static Vector4 s_warningColor = new Vector4(0.5f, 0.8f, 0.8f, 1); private static Vector4 s_okButtonColor = new Vector4(0.0f, 0.6f, 0.0f, 1.0f); - public SilkNetImGuiC64Config() + public SilkNetImGuiC64Config(SilkNetImGuiMenu mainMenu) { + _mainMenu = mainMenu; } - internal void Init(C64Config c64Config) + internal void Init() { - _config = c64Config.Clone(); - _isValidConfig = _config.IsValid(out _validationErrors); - _romDirectory = _config.ROMDirectory; _kernalRomFile = _config.HasROM(C64Config.KERNAL_ROM_NAME) ? _config.GetROM(C64Config.KERNAL_ROM_NAME).File! : ""; _basicRomFile = _config.HasROM(C64Config.KERNAL_ROM_NAME) ? _config.GetROM(C64Config.BASIC_ROM_NAME).File! : ""; _chargenRomFile = _config.HasROM(C64Config.KERNAL_ROM_NAME) ? _config.GetROM(C64Config.CHARGEN_ROM_NAME).File! : ""; - - Visible = true; - } - public void Reset(C64Config c64Config) + public void PostOnRender(string dialogLabel) { - _config = c64Config; - _isValidConfig = _config!.IsValid(out _validationErrors); - - Visible = false; - Cancel = false; - Ok = false; - } + _open = true; + if (ImGui.BeginPopupModal(dialogLabel, ref _open, ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.Text("C64 model"); + ImGui.LabelText("C64 model", $"{_config!.C64Model}"); + ImGui.LabelText("VIC2 model", $"{_config!.Vic2Model}"); - public void PostOnRender() - { - ImGui.SetNextWindowSize(new Vector2(WIDTH, HEIGHT), ImGuiCond.Once); - ImGui.SetNextWindowPos(new Vector2(POS_X, POS_Y), ImGuiCond.Once); - ImGui.SetNextWindowFocus(); + ImGui.Text("ROMs"); + if (ImGui.InputText("Directory", ref _romDirectory, 255)) + { + _config!.ROMDirectory = _romDirectory; + } + if (ImGui.InputText("Kernal file", ref _kernalRomFile, 100)) + { + _config!.SetROM(C64Config.KERNAL_ROM_NAME, _kernalRomFile); + } + if (ImGui.InputText("Basic file", ref _basicRomFile, 100)) + { + _config!.SetROM(C64Config.BASIC_ROM_NAME, _basicRomFile); + } + if (ImGui.InputText("CharGen file", ref _chargenRomFile, 100)) + { + _config!.SetROM(C64Config.CHARGEN_ROM_NAME, _chargenRomFile); + } - ImGui.Begin($"C64 config"); - //ImGui.BeginPopupModal($"C64 config"); + // Joystick + ImGui.Text("Joystick:"); + ImGui.SameLine(); + ImGui.PushItemWidth(35); + if (ImGui.Combo("##joystick", ref _mainMenu.C64SelectedJoystick, _mainMenu.C64AvailableJoysticks, _mainMenu.C64AvailableJoysticks.Length)) + { + _hostConfig.InputConfig.CurrentJoystick = _mainMenu.C64SelectedJoystick + 1; + } + ImGui.PopItemWidth(); - ImGui.Text("C64 model"); - ImGui.LabelText("C64 model", $"{_config!.C64Model}"); - ImGui.LabelText("VIC2 model", $"{_config!.Vic2Model}"); + ImGui.BeginDisabled(disabled: true); + foreach (var mapKey in _hostConfig.InputConfig.GamePadToC64JoystickMap[_hostConfig.InputConfig.CurrentJoystick]) + { + ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}"); + } + ImGui.EndDisabled(); - ImGui.Text("ROMs"); - if (ImGui.InputText("Directory", ref _romDirectory, 255)) - { - _config!.ROMDirectory = _romDirectory; - } - if (ImGui.InputText("Kernal file", ref _kernalRomFile, 100)) - { - _config!.SetROM(C64Config.KERNAL_ROM_NAME, _kernalRomFile); - } - if (ImGui.InputText("Basic file", ref _basicRomFile, 100)) - { - _config!.SetROM(C64Config.BASIC_ROM_NAME, _basicRomFile); - } - if (ImGui.InputText("CharGen file", ref _chargenRomFile, 100)) - { - _config!.SetROM(C64Config.CHARGEN_ROM_NAME, _chargenRomFile); - } + // Keyboard joystick + ImGui.Text($"Keyboard Joystick"); + ImGui.SameLine(); + ImGui.PushItemWidth(35); + if (ImGui.Combo("##keyboardJoystick", ref _mainMenu.C64KeyboardJoystick, _mainMenu.C64AvailableJoysticks, _mainMenu.C64AvailableJoysticks.Length)) + { + _config.KeyboardJoystick = _mainMenu.C64KeyboardJoystick + 1; + } + ImGui.PopItemWidth(); + var keyToJoystickMap = _config!.KeyboardJoystickMap; + ImGui.BeginDisabled(disabled: true); + foreach (var mapKey in keyToJoystickMap.GetMap(_config.KeyboardJoystick)) + { + ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}"); + } + ImGui.EndDisabled(); - int joystick = 2; - ImGui.Text($"Joystick {joystick}"); - ImGui.BeginDisabled(disabled: true); - foreach (var mapKey in C64SilkNetGamepad.SilkNetGamePadToC64JoystickMap) - { - ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}"); - } - ImGui.EndDisabled(); + // Update validation fields + if (_config!.IsDirty) + { + _config.ClearDirty(); + } + if (!IsValidConfig) + { + ImGui.PushStyleColor(ImGuiCol.Text, s_errorColor); + foreach (var error in _validationErrors!) + { + ImGui.TextWrapped($"Error: {error}"); + } + ImGui.PopStyleColor(); + } - ImGui.Text($"Keyboard Joystick {joystick}"); - var keyToJoystickMap = _config!.KeyboardJoystickMap; - ImGui.BeginDisabled(disabled: true); - foreach (var mapKey in keyToJoystickMap.GetMap(joystick)) - { - ImGui.LabelText($"{string.Join(",", mapKey.Key)}", $"{string.Join(",", mapKey.Value)}"); - } - ImGui.EndDisabled(); + // Close buttons + if (ImGui.Button("Cancel")) + { + Debug.WriteLine("Cancel pressed"); + ImGui.CloseCurrentPopup(); + _mainMenu.RestoreOriginalConfigs(); + } - if (_config!.IsDirty) - { - _config.ClearDirty(); - _isValidConfig = _config!.IsValid(out _validationErrors); - } - if (!_isValidConfig) - { - ImGui.PushStyleColor(ImGuiCol.Text, s_errorColor); - foreach (var error in _validationErrors!) + ImGui.SameLine(); + ImGui.BeginDisabled(disabled: !IsValidConfig); + ImGui.PushStyleColor(ImGuiCol.Button, s_okButtonColor); + if (ImGui.Button("Ok")) { - ImGui.TextWrapped($"Error: {error}"); + Debug.WriteLine("Ok pressed"); + ImGui.CloseCurrentPopup(); + _mainMenu.UpdateCurrentSystemConfig(_config, _hostConfig); } ImGui.PopStyleColor(); - } + ImGui.EndDisabled(); - if (ImGui.Button("Cancel")) - { - Visible = false; - Cancel = true; + ImGui.EndPopup(); } - - ImGui.SameLine(); - ImGui.BeginDisabled(disabled: !_isValidConfig); - ImGui.PushStyleColor(ImGuiCol.Button, s_okButtonColor); - if (ImGui.Button("Ok")) - { - Visible = false; - Ok = true; - } - ImGui.PopStyleColor(); - ImGui.EndDisabled(); - - //ImGui.EndPopup(); - ImGui.End(); } } diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiGenericComputerConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiGenericComputerConfig.cs index 1bac3ae9..a77b0998 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiGenericComputerConfig.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/ConfigUI/SilkNetImGuiGenericComputerConfig.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.Numerics; using Highbyte.DotNet6502.Systems.Generic.Config; @@ -5,189 +6,178 @@ namespace Highbyte.DotNet6502.App.SkiaNative.ConfigUI; public class SilkNetImGuiGenericComputerConfig { - private GenericComputerConfig? _config; - public GenericComputerConfig UpdatedConfig => _config!; + private readonly SilkNetImGuiMenu _mainMenu; + GenericComputerConfig _config => (GenericComputerConfig)_mainMenu.GetSelectedSystemConfig(); - public bool Visible { get; set; } - public bool Ok { get; set; } - public bool Cancel { get; set; } - - private bool _isValidConfig = true; + private bool _open; private string _programBinaryFile; - public bool IsValidConfig => _isValidConfig; - private List _validationErrors; - + public bool IsValidConfig + { + get + { + if (_config == null) + { + _validationErrors.Clear(); + return true; + } + else + { + return _config.IsValid(out _validationErrors); + } + } + } + private List _validationErrors = new(); - private const int POS_X = 50; - private const int POS_Y = 50; - private const int WIDTH = 850; - private const int HEIGHT = 500; static Vector4 s_InformationColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); static Vector4 s_ErrorColor = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); static Vector4 s_WarningColor = new Vector4(0.5f, 0.8f, 0.8f, 1); static Vector4 s_OkButtonColor = new Vector4(0.0f, 0.6f, 0.0f, 1.0f); - public SilkNetImGuiGenericComputerConfig() + public SilkNetImGuiGenericComputerConfig(SilkNetImGuiMenu mainMenu) { + _mainMenu = mainMenu; } - internal void Init(GenericComputerConfig config) + internal void Init() { - _config = config.Clone(); - _isValidConfig = _config.IsValid(out _validationErrors); - _programBinaryFile = _config.ProgramBinaryFile; - - Visible = true; } - public void Reset(GenericComputerConfig config) + public void PostOnRender(string dialogLabel) { - _config = config; - _isValidConfig = _config!.IsValid(out _validationErrors); - - Visible = false; - Cancel = false; - Ok = false; - } - - public void PostOnRender() - { - ImGui.SetNextWindowSize(new Vector2(WIDTH, HEIGHT), ImGuiCond.Once); - ImGui.SetNextWindowPos(new Vector2(POS_X, POS_Y), ImGuiCond.Once); - ImGui.SetNextWindowFocus(); - - ImGui.Begin($"GenericComputer config"); - //ImGui.BeginPopupModal($"C64 config"); + _open = true; + if (ImGui.BeginPopupModal(dialogLabel, ref _open, ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.Text("ProgramBinaryFile:"); - ImGui.Text("ProgramBinaryFile:"); + ImGui.PushItemWidth(800); + if (ImGui.InputText("", ref _programBinaryFile, 512)) + { + _config!.ProgramBinaryFile = _programBinaryFile; + } + ImGui.PopItemWidth(); - ImGui.PushItemWidth(800); - if (ImGui.InputText("", ref _programBinaryFile, 512)) - { - _config!.ProgramBinaryFile = _programBinaryFile; - } - ImGui.PopItemWidth(); + //ImGui.Text("ProgramBinary: "); // Byte array, used if ProgramBinaryFile is not set. + //ImGui.SameLine(); + //ImGui.Text(Config!.ProgramBinary); - //ImGui.Text("ProgramBinary: "); // Byte array, used if ProgramBinaryFile is not set. - //ImGui.SameLine(); - //ImGui.Text(Config!.ProgramBinary); + ImGui.Text("StopAtBRK: "); + ImGui.SameLine(); + ImGui.Text(_config!.StopAtBRK.ToString()); - ImGui.Text("StopAtBRK: "); - ImGui.SameLine(); - ImGui.Text(_config!.StopAtBRK.ToString()); + ImGui.Text("CPUCyclesPerFrame: "); + ImGui.SameLine(); + ImGui.Text(_config!.CPUCyclesPerFrame.ToString()); - ImGui.Text("CPUCyclesPerFrame: "); - ImGui.SameLine(); - ImGui.Text(_config!.CPUCyclesPerFrame.ToString()); + ImGui.Text("ScreenRefreshFrequencyHz: "); + ImGui.SameLine(); + ImGui.Text(_config!.ScreenRefreshFrequencyHz.ToString()); - ImGui.Text("ScreenRefreshFrequencyHz: "); - ImGui.SameLine(); - ImGui.Text(_config!.ScreenRefreshFrequencyHz.ToString()); + // Memory - Screen + ImGui.Text("Memory - Screen"); - // Memory - Screen - ImGui.Text("Memory - Screen"); + ImGui.Text(" Cols: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.Cols.ToString()); - ImGui.Text(" Cols: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.Cols.ToString()); + ImGui.Text(" Rows: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.Rows.ToString()); - ImGui.Text(" Rows: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.Rows.ToString()); + ImGui.Text(" BorderCols: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.BorderCols.ToString()); - ImGui.Text(" BorderCols: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.BorderCols.ToString()); + ImGui.Text(" BorderRows: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.BorderRows.ToString()); - ImGui.Text(" BorderRows: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.BorderRows.ToString()); + ImGui.Text(" ScreenStartAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.ScreenStartAddress.ToHex()); - ImGui.Text(" ScreenStartAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.ScreenStartAddress.ToHex()); + ImGui.Text(" ScreenColorStartAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.ScreenColorStartAddress.ToHex()); - ImGui.Text(" ScreenColorStartAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.ScreenColorStartAddress.ToHex()); + ImGui.Text(" ScreenBackgroundColorAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.ScreenBackgroundColorAddress.ToHex()); - ImGui.Text(" ScreenBackgroundColorAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.ScreenBackgroundColorAddress.ToHex()); + ImGui.Text(" ScreenBorderColorAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.ScreenBorderColorAddress.ToHex()); - ImGui.Text(" ScreenBorderColorAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.ScreenBorderColorAddress.ToHex()); + ImGui.Text(" DefaultFgColor: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.DefaultFgColor.ToString()); - ImGui.Text(" DefaultFgColor: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.DefaultFgColor.ToString()); + ImGui.Text(" DefaultBgColor: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.DefaultBgColor.ToString()); - ImGui.Text(" DefaultBgColor: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.DefaultBgColor.ToString()); + ImGui.Text(" DefaultBorderColor: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Screen.DefaultBorderColor.ToString()); - ImGui.Text(" DefaultBorderColor: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Screen.DefaultBorderColor.ToString()); + // Memory - Input + ImGui.Text("Memory - Input"); - // Memory - Input - ImGui.Text("Memory - Input"); + ImGui.Text(" KeyDownAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Input.KeyDownAddress.ToHex()); - ImGui.Text(" KeyDownAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Input.KeyDownAddress.ToHex()); + ImGui.Text(" KeyPressedAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Input.KeyPressedAddress.ToHex()); - ImGui.Text(" KeyPressedAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Input.KeyPressedAddress.ToHex()); + ImGui.Text(" KeyReleasedAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Input.KeyReleasedAddress.ToHex()); - ImGui.Text(" KeyReleasedAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Input.KeyReleasedAddress.ToHex()); + ImGui.Text(" RandomValueAddress: "); + ImGui.SameLine(); + ImGui.Text(_config!.Memory.Input.RandomValueAddress.ToHex()); - ImGui.Text(" RandomValueAddress: "); - ImGui.SameLine(); - ImGui.Text(_config!.Memory.Input.RandomValueAddress.ToHex()); + if (_config!.IsDirty) + { + _config.ClearDirty(); + } + if (!IsValidConfig) + { + ImGui.PushStyleColor(ImGuiCol.Text, s_ErrorColor); + foreach (var error in _validationErrors) + { + ImGui.TextWrapped($"Error: {error}"); + } + ImGui.PopStyleColor(); + } + // Close buttons + if (ImGui.Button("Cancel")) + { + Debug.WriteLine("Cancel pressed"); + ImGui.CloseCurrentPopup(); + _mainMenu.RestoreOriginalConfigs(); + } - if (_config!.IsDirty) - { - _config.ClearDirty(); - _isValidConfig = _config!.IsValid(out _validationErrors); - } - if (!_isValidConfig) - { - ImGui.PushStyleColor(ImGuiCol.Text, s_ErrorColor); - foreach (var error in _validationErrors) + ImGui.SameLine(); + ImGui.BeginDisabled(disabled: !IsValidConfig); + ImGui.PushStyleColor(ImGuiCol.Button, s_OkButtonColor); + if (ImGui.Button("Ok")) { - ImGui.TextWrapped($"Error: {error}"); + Debug.WriteLine("Ok pressed"); + ImGui.CloseCurrentPopup(); + _mainMenu.UpdateCurrentSystemConfig(_config, null); } ImGui.PopStyleColor(); - } + ImGui.EndDisabled(); - if (ImGui.Button("Cancel")) - { - Visible = false; - Cancel = true; - } + ImGui.EndPopup(); - ImGui.SameLine(); - ImGui.BeginDisabled(disabled: !_isValidConfig); - ImGui.PushStyleColor(ImGuiCol.Button, s_OkButtonColor); - if (ImGui.Button("Ok")) - { - Visible = false; - Ok = true; } - ImGui.PopStyleColor(); - ImGui.EndDisabled(); - - //ImGui.EndPopup(); - ImGui.End(); } } diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/EmulatorConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/EmulatorConfig.cs index d0b25c7c..df431f72 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/EmulatorConfig.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/EmulatorConfig.cs @@ -12,8 +12,11 @@ public class EmulatorConfig public string DefaultEmulator { get; set; } public float DefaultDrawScale { get; set; } + public float CurrentDrawScale { get; set; } public MonitorConfig? Monitor { get; set; } + public Dictionary HostSystemConfigs = new(); + public EmulatorConfig() { DefaultDrawScale = 3.0f; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/Highbyte.DotNet6502.App.SkiaNative.csproj b/src/apps/Highbyte.DotNet6502.App.SkiaNative/Highbyte.DotNet6502.App.SkiaNative.csproj index 39150d69..699ea8dc 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/Highbyte.DotNet6502.App.SkiaNative.csproj +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/Highbyte.DotNet6502.App.SkiaNative.csproj @@ -17,6 +17,7 @@ + diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/IHostSystemConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/IHostSystemConfig.cs new file mode 100644 index 00000000..1693c865 --- /dev/null +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/IHostSystemConfig.cs @@ -0,0 +1,5 @@ +namespace Highbyte.DotNet6502.App.SkiaNative; + +public interface IHostSystemConfig : ICloneable +{ +} diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/Program.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/Program.cs index ad0e772b..4331c6e6 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/Program.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/Program.cs @@ -1,12 +1,16 @@ +using AutoMapper; using Highbyte.DotNet6502.App.SkiaNative; using Highbyte.DotNet6502.App.SkiaNative.SystemSetup; using Highbyte.DotNet6502.Impl.NAudio; using Highbyte.DotNet6502.Impl.SilkNet; +using Highbyte.DotNet6502.Impl.SilkNet.Commodore64; using Highbyte.DotNet6502.Impl.Skia; using Highbyte.DotNet6502.Logging; using Highbyte.DotNet6502.Logging.InMem; using Highbyte.DotNet6502.Monitor; using Highbyte.DotNet6502.Systems; +using Highbyte.DotNet6502.Systems.Commodore64; +using Highbyte.DotNet6502.Systems.Generic; using Microsoft.Extensions.Logging; // Fix for starting in debug mode from VS Code. By default the OS current directory is set to the project folder, not the folder containing the built .exe file... @@ -27,7 +31,8 @@ // ---------- var systemList = new SystemList(); -var c64Setup = new C64Setup(loggerFactory); +var c64HostConfig = new C64HostConfig(); +var c64Setup = new C64Setup(loggerFactory, c64HostConfig); await systemList.AddSystem(c64Setup); var genericComputerSetup = new GenericComputerSetup(loggerFactory); @@ -46,10 +51,24 @@ //DefaultDirectory = "../../../../../../samples/Assembler/Generic/Build" //DefaultDirectory = "%USERPROFILE%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" //DefaultDirectory = "%HOME%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" + }, + HostSystemConfigs = new Dictionary + { + { C64.SystemName, c64HostConfig } + //{ GenericComputer.SystemName, new GenericComputerHostConfig() } } }; emulatorConfig.Validate(systemList); +// TODO: Make Automapper configuration more generic, incorporate in classes that need it? +var mapperConfiguration = new MapperConfiguration( + cfg => + { + cfg.CreateMap(); + } +); +var mapper = mapperConfiguration.CreateMapper(); + // ---------- // Silk.NET Window // ---------- @@ -74,5 +93,5 @@ //windowOptions.PreferredDepthBufferBits = 24; // Depth buffer bits must be set explicitly on MacOS (tested on M1), otherwise there will be be no depth buffer (for OpenGL 3d). IWindow window = Window.Create(windowOptions); -var silkNetWindow = new SilkNetWindow(emulatorConfig.Monitor, window, systemList, emulatorConfig.DefaultDrawScale, emulatorConfig.DefaultEmulator, logStore, logConfig, loggerFactory); +var silkNetWindow = new SilkNetWindow(emulatorConfig, window, systemList, logStore, logConfig, loggerFactory, mapper); silkNetWindow.Run(); diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetImGuiMenu.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetImGuiMenu.cs index 6ab80888..81611b3f 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetImGuiMenu.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetImGuiMenu.cs @@ -1,7 +1,8 @@ using System.Diagnostics; using System.Numerics; +using AutoMapper; using Highbyte.DotNet6502.App.SkiaNative.ConfigUI; -using Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Input; +using Highbyte.DotNet6502.App.SkiaNative.SystemSetup; using Highbyte.DotNet6502.Systems; using Highbyte.DotNet6502.Systems.Commodore64; using Highbyte.DotNet6502.Systems.Commodore64.Config; @@ -21,7 +22,7 @@ public class SilkNetImGuiMenu : ISilkNetImGuiWindow private const int POS_X = 10; private const int POS_Y = 10; private const int WIDTH = 400; - private const int HEIGHT = 380; + private const int HEIGHT = 430; private static Vector4 s_informationColor = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); private static Vector4 s_errorColor = new Vector4(1.0f, 0.0f, 0.0f, 1.0f); private static Vector4 s_warningColor = new Vector4(0.5f, 0.8f, 0.8f, 1); @@ -31,15 +32,22 @@ public class SilkNetImGuiMenu : ISilkNetImGuiWindow private bool _audioEnabled; private float _audioVolumePercent; + private readonly IMapper _mapper; + + public bool C64KeyboardJoystickEnabled; + public int C64KeyboardJoystick; + public int C64SelectedJoystick; + public string[] C64AvailableJoysticks; - private bool _c64KeyboardJoystickEnabled; private string SelectedSystemName => _silkNetWindow.SystemList.Systems.ToArray()[_selectedSystemItem]; + private ISystemConfig? _originalSystemConfig; + private IHostSystemConfig? _originalHostSystemConfig; private SilkNetImGuiC64Config? _c64ConfigUI; private SilkNetImGuiGenericComputerConfig? _genericComputerConfigUI; - public SilkNetImGuiMenu(SilkNetWindow silkNetWindow, string defaultSystemName, bool defaultAudioEnabled, float defaultAudioVolumePercent) + public SilkNetImGuiMenu(SilkNetWindow silkNetWindow, string defaultSystemName, bool defaultAudioEnabled, float defaultAudioVolumePercent, IMapper mapper) { _silkNetWindow = silkNetWindow; _screenScaleString = silkNetWindow.CanvasScale.ToString(); @@ -49,10 +57,17 @@ public SilkNetImGuiMenu(SilkNetWindow silkNetWindow, string defaultSystemName, b _audioEnabled = defaultAudioEnabled; _audioVolumePercent = defaultAudioVolumePercent; - ISystemConfig systemConfig = _silkNetWindow.SystemList.GetCurrentSystemConfig(SelectedSystemName).Result; + _mapper = mapper; + + ISystemConfig systemConfig = GetSelectedSystemConfig(); if (systemConfig is C64Config c64Config) { - _c64KeyboardJoystickEnabled = c64Config.KeyboardJoystickEnabled; + C64KeyboardJoystickEnabled = c64Config.KeyboardJoystickEnabled; + C64KeyboardJoystick = c64Config.KeyboardJoystick - 1; + + var c64HostSystemConfig = (C64HostConfig)GetSelectedSystemHostConfig(); + C64SelectedJoystick = c64HostSystemConfig.InputConfig.CurrentJoystick - 1; + C64AvailableJoysticks = c64HostSystemConfig.InputConfig.AvailableJoysticks.Select(x => x.ToString()).ToArray(); } } @@ -156,7 +171,7 @@ public void PostOnRender() if (!string.IsNullOrEmpty(SelectedSystemName)) { // Common audio settings - ISystemConfig systemConfig = _silkNetWindow.SystemList.GetCurrentSystemConfig(SelectedSystemName).Result; + ISystemConfig systemConfig = GetSelectedSystemConfig(); ImGui.BeginDisabled(disabled: !(systemConfig.AudioSupported && EmulatorState == EmulatorState.Uninitialized)); ImGui.PushStyleColor(ImGuiCol.Text, s_informationColor); @@ -225,10 +240,10 @@ public void PostOnRender() switch (SelectedSystemName) { case "C64": - DrawC64Config(systemConfig); + DrawC64Config(); break; case "Generic": - DrawGenericComputerConfig(systemConfig); + DrawGenericComputerConfig(); break; default: break; @@ -247,9 +262,10 @@ public void PostOnRender() ImGui.End(); } - private void DrawC64Config(ISystemConfig systemConfig) + private void DrawC64Config() { - var c64Config = (C64Config)systemConfig; + var c64Config = (C64Config)GetSelectedSystemConfig(); + var c64HostConfig = (C64HostConfig)GetSelectedSystemHostConfig(); // Joystick input with keyboard //ImGui.BeginDisabled(disabled: EmulatorState != EmulatorState.Uninitialized); @@ -257,21 +273,46 @@ private void DrawC64Config(ISystemConfig systemConfig) //ImGui.SetKeyboardFocusHere(0); ImGui.PushItemWidth(40); - if (ImGui.Checkbox("Keyboard Joystick", ref _c64KeyboardJoystickEnabled)) + ImGui.Text("Joystick:"); + ImGui.SameLine(); + ImGui.PushItemWidth(35); + if (ImGui.Combo("##joystick", ref C64SelectedJoystick, C64AvailableJoysticks, C64AvailableJoysticks.Length)) + { + c64HostConfig.InputConfig.CurrentJoystick = C64SelectedJoystick + 1; + } + ImGui.PopItemWidth(); + + if (ImGui.Checkbox("Keyboard Joystick", ref C64KeyboardJoystickEnabled)) { if (EmulatorState == EmulatorState.Uninitialized) { - c64Config.KeyboardJoystickEnabled = _c64KeyboardJoystickEnabled; + c64Config.KeyboardJoystickEnabled = C64KeyboardJoystickEnabled; } else { C64 c64 = (C64)_silkNetWindow.SystemList.GetSystem(SelectedSystemName).Result; - c64.Cia.Joystick.KeyboardJoystickEnabled = _c64KeyboardJoystickEnabled; + c64.Cia.Joystick.KeyboardJoystickEnabled = C64KeyboardJoystickEnabled; } } + ImGui.SameLine(); + ImGui.BeginDisabled(!C64KeyboardJoystickEnabled); + ImGui.PushItemWidth(35); + if (ImGui.Combo("##keyboardJoystick", ref C64KeyboardJoystick, C64AvailableJoysticks, C64AvailableJoysticks.Length)) + { + if (EmulatorState == EmulatorState.Uninitialized) + { + c64Config.KeyboardJoystick = C64KeyboardJoystick + 1; + } + else + { + C64 c64 = (C64)_silkNetWindow.SystemList.GetSystem(SelectedSystemName).Result; + c64.Cia.Joystick.KeyboardJoystick = C64KeyboardJoystick + 1; + } + } + ImGui.PopItemWidth(); + ImGui.EndDisabled(); ImGui.PopStyleColor(); ImGui.PopItemWidth(); - //ImGui.EndDisabled(); // Basic load/save commands ImGui.BeginDisabled(disabled: EmulatorState == EmulatorState.Uninitialized); @@ -342,21 +383,18 @@ private void DrawC64Config(ISystemConfig systemConfig) // C64 config ImGui.BeginDisabled(disabled: !(EmulatorState == EmulatorState.Uninitialized)); - if (_c64ConfigUI == null) { - _c64ConfigUI = new SilkNetImGuiC64Config(); - _c64ConfigUI.Reset(c64Config); + _c64ConfigUI = new SilkNetImGuiC64Config(this); } - if (ImGui.Button("C64 config")) { - if (!_c64ConfigUI.Visible) - { - _c64ConfigUI.Init(c64Config); - } + RememberOriginalConfigs(); + _c64ConfigUI.Init(); + ImGui.OpenPopup("C64 config"); } ImGui.EndDisabled(); + _c64ConfigUI.PostOnRender("C64 config"); if (!_c64ConfigUI.IsValidConfig) { @@ -364,45 +402,26 @@ private void DrawC64Config(ISystemConfig systemConfig) ImGui.TextWrapped($"Config has errors. Press C64 Config button."); ImGui.PopStyleColor(); } - - if (_c64ConfigUI.Visible) - { - _c64ConfigUI.PostOnRender(); - if (_c64ConfigUI.Ok) - { - Debug.WriteLine("Ok pressed"); - var updatedSystemConfig = (ISystemConfig)_c64ConfigUI.UpdatedConfig; - _silkNetWindow.SystemList.ChangeCurrentSystemConfig(SelectedSystemName, updatedSystemConfig); - _c64ConfigUI.Reset(c64Config); - } - else if (_c64ConfigUI.Cancel) - { - Debug.WriteLine("Cancel pressed"); - _c64ConfigUI.Reset(c64Config); - } - } } - private void DrawGenericComputerConfig(ISystemConfig systemConfig) + private void DrawGenericComputerConfig() { + var genericComputerConfig = (GenericComputerConfig)GetSelectedSystemConfig(); + ImGui.BeginDisabled(disabled: !(EmulatorState == EmulatorState.Uninitialized)); if (_genericComputerConfigUI == null) { - _genericComputerConfigUI = new SilkNetImGuiGenericComputerConfig(); - var genericComputerConfig = (GenericComputerConfig)systemConfig; - _genericComputerConfigUI.Reset(genericComputerConfig); + _genericComputerConfigUI = new SilkNetImGuiGenericComputerConfig(this); } - if (ImGui.Button("GenericComputer config")) { - if (!_genericComputerConfigUI.Visible) - { - var genericComputerConfig = (GenericComputerConfig)systemConfig; - _genericComputerConfigUI.Init(genericComputerConfig); - } + RememberOriginalConfigs(); + _genericComputerConfigUI.Init(); + ImGui.OpenPopup("GenericComputer config"); } ImGui.EndDisabled(); + _genericComputerConfigUI.PostOnRender("GenericComputer config"); if (!_genericComputerConfigUI.IsValidConfig) { @@ -410,32 +429,49 @@ private void DrawGenericComputerConfig(ISystemConfig systemConfig) ImGui.TextWrapped($"Config has errors. Press GenericComputerConfig button."); ImGui.PopStyleColor(); } - - if (_genericComputerConfigUI.Visible) - { - _genericComputerConfigUI.PostOnRender(); - if (_genericComputerConfigUI.Ok) - { - Debug.WriteLine("Ok pressed"); - GenericComputerConfig genericComputerConfig = _genericComputerConfigUI.UpdatedConfig; - var updateSystemConfig = (ISystemConfig)genericComputerConfig; - _silkNetWindow.SystemList.ChangeCurrentSystemConfig(SelectedSystemName, updateSystemConfig); - _genericComputerConfigUI.Reset(genericComputerConfig); - } - else if (_genericComputerConfigUI.Cancel) - { - Debug.WriteLine("Cancel pressed"); - var genericComputerConfig = (GenericComputerConfig)systemConfig; - _genericComputerConfigUI.Reset(genericComputerConfig); - } - } } private bool SelectedSystemConfigIsValid() { return _silkNetWindow.SystemList.IsValidConfig(SelectedSystemName).Result; } + internal ISystemConfig GetSelectedSystemConfig() + { + return _silkNetWindow.SystemList.GetCurrentSystemConfig(SelectedSystemName).Result; + } + internal IHostSystemConfig GetSelectedSystemHostConfig() + { + if (!_silkNetWindow.EmulatorConfig.HostSystemConfigs.ContainsKey(SelectedSystemName)) + return null; + return _silkNetWindow.EmulatorConfig.HostSystemConfigs[SelectedSystemName]; + } + + internal void RememberOriginalConfigs() + { + _originalSystemConfig = (ISystemConfig)GetSelectedSystemConfig().Clone(); + + var hostSystemConfig = (IHostSystemConfig)GetSelectedSystemHostConfig(); + if (hostSystemConfig != null) + _originalHostSystemConfig = (IHostSystemConfig)GetSelectedSystemHostConfig().Clone(); + } + internal void RestoreOriginalConfigs() + { + UpdateCurrentSystemConfig(_originalSystemConfig, _originalHostSystemConfig); + } + + internal void UpdateCurrentSystemConfig(ISystemConfig config, IHostSystemConfig? hostSystemConfig) + { + // Update the system config + _silkNetWindow.SystemList.ChangeCurrentSystemConfig(SelectedSystemName, config); + // Update the existing host system config, it is referenced from different objects (thus we cannot replace it with a new one). + if (hostSystemConfig != null) + { + var org = _silkNetWindow.EmulatorConfig.HostSystemConfigs[SelectedSystemName]; + if (org != null && hostSystemConfig != null) + _mapper.Map(hostSystemConfig, org); + } + } public void Run() { _silkNetWindow.EmulatorState = EmulatorState.Running; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetWindow.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetWindow.cs index 787a3c7f..6c54eda0 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetWindow.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SilkNetWindow.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using AutoMapper; using Highbyte.DotNet6502.App.SkiaNative.Instrumentation.Stats; using Highbyte.DotNet6502.App.SkiaNative.Stats; using Highbyte.DotNet6502.Impl.NAudio; @@ -22,25 +23,26 @@ public enum EmulatorState public class SilkNetWindow { private readonly ILogger _logger; - private readonly MonitorConfig _monitorConfig; private readonly IWindow _window; + private readonly EmulatorConfig _emulatorConfig; + public EmulatorConfig EmulatorConfig => _emulatorConfig; + private readonly SystemList _systemList; public SystemList SystemList => _systemList; - private float _canvasScale; - private readonly string _defaultSystemName; private readonly DotNet6502InMemLogStore _logStore; private readonly DotNet6502InMemLoggerConfiguration _logConfig; private string _currentSystemName; private readonly bool _defaultAudioEnabled; private float _defaultAudioVolumePercent; private readonly ILoggerFactory _loggerFactory; + private readonly IMapper _mapper; public float CanvasScale { - get { return _canvasScale; } - set { _canvasScale = value; } + get { return _emulatorConfig.CurrentDrawScale; } + set { _emulatorConfig.CurrentDrawScale = value; } } public const int DEFAULT_WIDTH = 1000; @@ -99,26 +101,26 @@ public float CanvasScale public SilkNetWindow( - MonitorConfig monitorConfig, + EmulatorConfig emulatorConfig, IWindow window, SystemList systemList, - float scale, - string defaultSystemName, DotNet6502InMemLogStore logStore, DotNet6502InMemLoggerConfiguration logConfig, - Microsoft.Extensions.Logging.ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + IMapper mapper + ) { - _monitorConfig = monitorConfig; + _emulatorConfig = emulatorConfig; + _emulatorConfig.CurrentDrawScale = _emulatorConfig.DefaultDrawScale; _window = window; _systemList = systemList; - _canvasScale = scale; - _defaultSystemName = defaultSystemName; _logStore = logStore; _logConfig = logConfig; _defaultAudioEnabled = true; _defaultAudioVolumePercent = 20.0f; _loggerFactory = loggerFactory; + _mapper = mapper; _logger = loggerFactory.CreateLogger(typeof(SilkNetWindow).Name); } @@ -148,11 +150,11 @@ protected void OnLoad() InitImGui(); // Init main menu UI - _menu = new SilkNetImGuiMenu(this, _defaultSystemName, _defaultAudioEnabled, _defaultAudioVolumePercent); + _menu = new SilkNetImGuiMenu(this, _emulatorConfig.DefaultEmulator, _defaultAudioEnabled, _defaultAudioVolumePercent, _mapper); // Create other UI windows _statsPanel = CreateStatsUI(); - _monitor = CreateMonitorUI(_statsPanel, _monitorConfig); + _monitor = CreateMonitorUI(_statsPanel, _emulatorConfig.Monitor); _logsPanel = CreateLogsUI(_logStore, _logConfig); // Add all ImGui windows to a list @@ -215,7 +217,7 @@ private void InitRendering() getProcAddress, _window.FramebufferSize.X, _window.FramebufferSize.Y, - _canvasScale * (_window.FramebufferSize.X / _window.Size.X)); + _emulatorConfig.CurrentDrawScale * (_window.FramebufferSize.X / _window.Size.X)); } public void SetCurrentSystem(string systemName) @@ -231,7 +233,7 @@ public void SetCurrentSystem(string systemName) { var system = _systemList.GetSystem(systemName).Result; var screen = system.Screen; - Window.Size = new Vector2D((int)(screen.VisibleWidth * _canvasScale), (int)(screen.VisibleHeight * _canvasScale)); + Window.Size = new Vector2D((int)(screen.VisibleWidth * CanvasScale), (int)(screen.VisibleHeight * CanvasScale)); Window.UpdatesPerSecond = screen.RefreshFrequencyHz; InitCustomSystemStats(system); diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64HostConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64HostConfig.cs new file mode 100644 index 00000000..ce9aa7ef --- /dev/null +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64HostConfig.cs @@ -0,0 +1,16 @@ +using Highbyte.DotNet6502.Impl.SilkNet.Commodore64; + +namespace Highbyte.DotNet6502.App.SkiaNative.SystemSetup +{ + public class C64HostConfig : IHostSystemConfig, ICloneable + { + public C64SilkNetConfig InputConfig { get; set; } = new C64SilkNetConfig(); + + public object Clone() + { + var clone = (C64HostConfig)this.MemberwiseClone(); + clone.InputConfig = (C64SilkNetConfig)InputConfig.Clone(); + return clone; + } + } +} diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64Setup.cs b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64Setup.cs index b3e7aa45..84ded20b 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64Setup.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaNative/SystemSetup/C64Setup.cs @@ -16,10 +16,12 @@ public class C64Setup : SystemConfigurer C64.SystemName; private readonly ILoggerFactory _loggerFactory; + private readonly C64HostConfig _c64HostConfig; - public C64Setup(ILoggerFactory loggerFactory) + public C64Setup(ILoggerFactory loggerFactory, C64HostConfig c64HostConfig) { _loggerFactory = loggerFactory; + _c64HostConfig = c64HostConfig; } public async Task GetNewConfig(string configurationVariant) @@ -62,7 +64,7 @@ public async Task GetNewConfig(string configurationVariant) AudioEnabled = true, }; - c64Config.Validate(); + //c64Config.Validate(); return c64Config; } @@ -88,7 +90,7 @@ NAudioAudioHandlerContext audioHandlerContext ) { var renderer = new C64SkiaRenderer(); - var inputHandler = new C64SilkNetInputHandler(_loggerFactory); + var inputHandler = new C64SilkNetInputHandler(_loggerFactory, _c64HostConfig.InputConfig); var audioHandler = new C64NAudioAudioHandler(_loggerFactory); var c64 = (C64)system; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Highbyte.DotNet6502.App.SkiaWASM.csproj b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Highbyte.DotNet6502.App.SkiaWASM.csproj index 989669e9..fd99551b 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Highbyte.DotNet6502.App.SkiaWASM.csproj +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Highbyte.DotNet6502.App.SkiaWASM.csproj @@ -29,6 +29,7 @@ + diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64ConfigUI.razor b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64ConfigUI.razor index ff49a2e2..81016a94 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64ConfigUI.razor +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64ConfigUI.razor @@ -40,188 +40,241 @@ else {

@LoadedROMCount/@_maxC64AllowedRomFiles ROMs loaded

@if (!string.IsNullOrEmpty(_validationMessage)) -{ -

@_validationMessage

-} + { +

@_validationMessage

+ }
    @foreach (var romName in C64Config.RequiredROMs) - { - @if (GetLoadedRoms().ContainsKey(romName)) { -
  • @romName @GetLoadedRoms()[romName].Length bytes
  • - } - else - { -
  • @romName - not loaded
  • + @if (GetLoadedRoms().ContainsKey(romName)) + { +
  • @romName @GetLoadedRoms()[romName].Length bytes
  • + } + else + { +
  • @romName - not loaded
  • + } } - }
}

-

General settings

-
-
-
Audio enabled (experimental):
-
+ +
+
+

General settings

+
+
+
Audio enabled (experimental):
+
+
+
- -

Joystick

-@{ - var gampadToJoystickMap = C64AspNetGamepad.AspNetGamePadToC64JoystickMap; - int joystick = 2; -} -
-
-
Joystick @joystick action
-
Gampad button
-
- @{ - foreach (var mapKey in gampadToJoystickMap) - { +
+
+

Joystick

+ @{ + var gampadToJoystickMap = C64HostConfig.InputConfig.GamePadToC64JoystickMap[C64HostConfig.InputConfig.CurrentJoystick]; + } +
+
+
Select current joystick
+
+ +
+
-
@string.Join(",", mapKey.Value)
-
@string.Join(",", mapKey.Key)
+
Joystick @C64HostConfig.InputConfig.CurrentJoystick action
+
Gampad button
+ @{ + foreach (var mapKey in gampadToJoystickMap) + { +
+
@string.Join(",", mapKey.Value)
+
@string.Join(",", mapKey.Key)
+
+ } + } +
+
+ +
+ +

Joystick keyboard

+ @{ + var keyToJoystickMap = C64Config.KeyboardJoystickMap; } - } -
+
+
+
Enabled
+
+
-

Joystick keyboard

-@{ - var keyToJoystickMap = C64Config.KeyboardJoystickMap; -} -
-
-
Enabled
-
-
-
-
Joystick @joystick action
-
Key
-
- @{ - foreach (var mapKey in keyToJoystickMap.GetMap(joystick)) - {
-
@string.Join(",", mapKey.Value)
-
@string.Join(",", mapKey.Key)
+
Select current keyboard joystick
+
+ +
- } - } + +
+
Joystick @C64Config.KeyboardJoystick action
+
Key
+
+ @{ + foreach (var mapKey in keyToJoystickMap.GetMap(C64Config.KeyboardJoystick)) + { +
+
@string.Join(",", mapKey.Value)
+
@string.Join(",", mapKey.Key)
+
+ } + } +
+
+
+

@code { -[CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!; + [CascadingParameter] BlazoredModalInstance BlazoredModal { get; set; } = default!; -[Parameter] public ISystemConfig SystemConfig { get; set; } + [Parameter] public ISystemConfig SystemConfig { get; set; } -public C64Config C64Config -{ - get + [Parameter] public IHostSystemConfig HostSystemConfig { get; set; } + + public C64Config C64Config { + get + { return (C64Config)SystemConfig; + } + } + + public C64HostConfig C64HostConfig + { + get + { + return (C64HostConfig)HostSystemConfig; + } } -} -protected override void OnInitialized() => BlazoredModal.SetTitle("C64 config"); + protected override void OnInitialized() => BlazoredModal.SetTitle("C64 config"); -private async Task UnloadROMs() => C64Config.ROMs = new List(); + private async Task UnloadROMs() => C64Config.ROMs = new List(); -private async Task Ok() => await BlazoredModal.CloseAsync(ModalResult.Ok(SystemConfig)); + private async Task Ok() => await BlazoredModal.CloseAsync(ModalResult.Ok((SystemConfig, HostSystemConfig))); -private bool _isLoadingC64Roms; -private long _maxC64RomFileSize = 1024 * 8; -private int _maxC64AllowedRomFiles = 3; + private bool _isLoadingC64Roms; + private long _maxC64RomFileSize = 1024 * 8; + private int _maxC64AllowedRomFiles = 3; -private Dictionary GetLoadedRoms() -{ - var dict = new Dictionary(); - foreach (var rom in C64Config.ROMs) + private Dictionary GetLoadedRoms() { - dict[rom.Name] = rom.Data == null ? new byte[]{} : rom.Data; + var dict = new Dictionary(); + foreach (var rom in C64Config.ROMs) + { + dict[rom.Name] = rom.Data == null ? new byte[] { } : rom.Data; + } + return dict; } - return dict; -} -private int LoadedROMCount => GetLoadedRoms().Count; + private int LoadedROMCount => GetLoadedRoms().Count; -private string _validationMessage = ""; + private string _validationMessage = ""; -private async Task OnC64RomFilePickerChange(InputFileChangeEventArgs e) -{ - if (C64Config == null) - return; + private async Task OnC64RomFilePickerChange(InputFileChangeEventArgs e) + { + if (C64Config == null) + return; - _isLoadingC64Roms = true; - _validationMessage = ""; + _isLoadingC64Roms = true; + _validationMessage = ""; - foreach (var file in e.GetMultipleFiles(_maxC64AllowedRomFiles)) - { - try + foreach (var file in e.GetMultipleFiles(_maxC64AllowedRomFiles)) { - if (file.Size > _maxC64RomFileSize) + try { - _isLoadingC64Roms = false; - _validationMessage += $"File {file.Name} size {file.Size} is more than limit {_maxC64RomFileSize}. "; - continue; - } + if (file.Size > _maxC64RomFileSize) + { + _isLoadingC64Roms = false; + _validationMessage += $"File {file.Name} size {file.Size} is more than limit {_maxC64RomFileSize}. "; + continue; + } + + bool isKernal = file.Name.Contains("kern", StringComparison.InvariantCultureIgnoreCase); + bool isBasic = file.Name.Contains("bas", StringComparison.InvariantCultureIgnoreCase); + bool isChargen = file.Name.Contains("char", StringComparison.InvariantCultureIgnoreCase); + if (!isKernal && !isBasic && !isChargen) + { + _isLoadingC64Roms = false; + _validationMessage = $"File name {file.Name} does not contain one of the following string: kern, bas, char "; + continue; + } - bool isKernal = file.Name.Contains("kern", StringComparison.InvariantCultureIgnoreCase); - bool isBasic = file.Name.Contains("bas", StringComparison.InvariantCultureIgnoreCase); - bool isChargen = file.Name.Contains("char", StringComparison.InvariantCultureIgnoreCase); - if (!isKernal && !isBasic && !isChargen) + var fileBuffer = new byte[file.Size]; + //var fileStream = e.File.OpenReadStream(file.Size); + await file.OpenReadStream().ReadAsync(fileBuffer); + var fileSize = fileBuffer.Length; + + if (isKernal) + SetROM(C64Config.KERNAL_ROM_NAME, fileBuffer); + else if (isBasic) + SetROM(C64Config.BASIC_ROM_NAME, fileBuffer); + else if (isChargen) + SetROM(C64Config.CHARGEN_ROM_NAME, fileBuffer); + } + catch (Exception ex) { - _isLoadingC64Roms = false; - _validationMessage = $"File name {file.Name} does not contain one of the following string: kern, bas, char "; - continue; + //Logger.LogError("File: {Filename} Error: {Error}", + // file.Name, ex.Message); } + } + _isLoadingC64Roms = false; + this.StateHasChanged(); + } - var fileBuffer = new byte[file.Size]; - //var fileStream = e.File.OpenReadStream(file.Size); - await file.OpenReadStream().ReadAsync(fileBuffer); - var fileSize = fileBuffer.Length; - - if (isKernal) - SetROM(C64Config.KERNAL_ROM_NAME, fileBuffer); - else if (isBasic) - SetROM(C64Config.BASIC_ROM_NAME, fileBuffer); - else if (isChargen) - SetROM(C64Config.CHARGEN_ROM_NAME, fileBuffer); + private void SetROM(string romName, byte[] data) + { + ROM rom; + if (!C64Config.ROMs.Exists(x => x.Name == romName)) + { + rom = new() + { + Name = romName + }; + C64Config.ROMs.Add(rom); } - catch (Exception ex) + else { - //Logger.LogError("File: {Filename} Error: {Error}", - // file.Name, ex.Message); + rom = C64Config.ROMs.Single(x => x.Name == romName); } + rom.Data = data; } - _isLoadingC64Roms = false; - this.StateHasChanged(); -} -private void SetROM(string romName, byte[] data) -{ - ROM rom; - if (!C64Config.ROMs.Exists(x => x.Name == romName)) - { - rom = new() - { - Name = romName - }; - C64Config.ROMs.Add(rom); - } - else - { - rom = C64Config.ROMs.Single(x => x.Name == romName); - } - rom.Data = data; -} + private async Task OnSelectJoystickChanged(ChangeEventArgs e) + => C64HostConfig.InputConfig.CurrentJoystick = int.Parse(e.Value.ToString()); + + private async Task OnSelectKeyboardJoystickChanged(ChangeEventArgs e) + => C64Config.KeyboardJoystick = int.Parse(e.Value.ToString()); + } diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64Menu.razor b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64Menu.razor index f7c2259b..41647567 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64Menu.razor +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Commodore64/C64Menu.razor @@ -1,12 +1,28 @@ -@using Highbyte.DotNet6502.Systems.Commodore64.Config; +@using Highbyte.DotNet6502.App.SkiaWASM.Skia; +@using Highbyte.DotNet6502.Systems.Commodore64.Config; @using Highbyte.DotNet6502.Systems.Commodore64; @using Highbyte.DotNet6502.Systems.Commodore64.Video; @using static Highbyte.DotNet6502.App.SkiaWASM.Pages.Index;
+ + + +

+

Load/save files

@@ -66,7 +82,7 @@ // Note: The current config object (reference) is stored in this variable so that the UI can bind it's properties (not possible to use async call to _systemList.GetSystemConfig() in property ) private C64Config? _c64Config => Parent.SystemConfig as C64Config; - + private C64HostConfig? _c64HostConfig => Parent.HostSystemConfig as C64HostConfig; private bool JoystickKeyboardEnabled { @@ -89,6 +105,36 @@ } } + private async Task OnSelectJoystickChanged(ChangeEventArgs e) + => _c64HostConfig.InputConfig.CurrentJoystick = int.Parse(e.Value.ToString()); + + private async Task OnSelectKeyboardJoystickChanged(ChangeEventArgs e) + => KeyboardJoystick = int.Parse(e.Value.ToString()); + + private bool SelectKeyboardJoystickDisabled => !JoystickKeyboardEnabled; + + private int KeyboardJoystick + { + get + { + if (_c64Config == null) + return 0; + return _c64Config?.KeyboardJoystick ?? 0; + } + set + { + if (_c64Config == null) + return; + _c64Config.KeyboardJoystick = value; + if (Parent.CurrentEmulatorState != EmulatorState.Uninitialized) + { + C64 c64 = (C64)Parent.WasmHost.SystemList.GetSystem(SYSTEM_NAME).Result; + c64.Cia.Joystick.KeyboardJoystick = value; + } + } + } + + private readonly Dictionary _assemblyExampleFiles = new() { {"","" }, diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericConfigUI.razor b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericConfigUI.razor index 937221d0..0f945f41 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericConfigUI.razor +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericConfigUI.razor @@ -60,11 +60,13 @@ Memory addresses [Parameter] public ISystemConfig SystemConfig { get; set; } + [Parameter] public IHostSystemConfig HostSystemConfig { get; set; } + public GenericComputerConfig GenericComputerConfig => (GenericComputerConfig)SystemConfig; protected override void OnInitialized() => BlazoredModal.SetTitle("Generic config"); - private async Task Ok() => await BlazoredModal.CloseAsync(ModalResult.Ok(SystemConfig)); + private async Task Ok() => await BlazoredModal.CloseAsync(ModalResult.Ok((SystemConfig, HostSystemConfig))); private string _validationMessage = ""; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericMenu.razor b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericMenu.razor index 27f5a68f..e6622a94 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericMenu.razor +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Generic/GenericMenu.razor @@ -40,7 +40,7 @@ private bool _wasRunningBeforeFileDialog = false; - private async Task ShowConfigUI() => await Parent.ShowConfigUI(); + private async Task ShowConfigUI() => await Parent.ShowConfigUI(); [Parameter] public Highbyte.DotNet6502.App.SkiaWASM.Pages.Index Parent { get; set; } = default!; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Index.razor.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Index.razor.cs index d98c37b0..c6e42b6c 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Index.razor.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Pages/Index.razor.cs @@ -4,7 +4,6 @@ using Highbyte.DotNet6502.App.SkiaWASM.Skia; using Highbyte.DotNet6502.Impl.AspNet; using Highbyte.DotNet6502.Impl.Skia; -using Highbyte.DotNet6502.Monitor; using Highbyte.DotNet6502.Systems; using Highbyte.DotNet6502.Impl.AspNet.JSInterop.BlazorWebAudioSync; using Microsoft.AspNetCore.WebUtilities; @@ -12,6 +11,8 @@ using Blazored.LocalStorage; using Highbyte.DotNet6502.Logging.Console; using Toolbelt.Blazor.Gamepad; +using AutoMapper; +using Highbyte.DotNet6502.Systems.Commodore64; namespace Highbyte.DotNet6502.App.SkiaWASM.Pages; @@ -38,10 +39,14 @@ public enum EmulatorState private string _selectedSystemConfigValidationMessage = ""; // Note: The current config object (reference) is stored in this variable so that the UI can bind it's properties (not possible to use async call to _systemList.GetSystemConfig() in property ) - private ISystemConfig _currentConfig = default!; - public ISystemConfig SystemConfig => _currentConfig; + private ISystemConfig _currentSystemConfig = default!; + public ISystemConfig SystemConfig => _currentSystemConfig; + private IHostSystemConfig _currentHostSystemConfig = default!; + public IHostSystemConfig HostSystemConfig => _currentHostSystemConfig; + + private bool AudioEnabledToggleDisabled => ( - (!(_currentConfig?.AudioSupported ?? true)) || + (!(_currentSystemConfig?.AudioSupported ?? true)) || (CurrentEmulatorState == EmulatorState.Running || CurrentEmulatorState == EmulatorState.Paused) ); @@ -49,11 +54,11 @@ private bool AudioEnabled { get { - return _currentConfig?.AudioEnabled ?? false; + return _currentSystemConfig?.AudioEnabled ?? false; } set { - _currentConfig.AudioEnabled = value; + _currentSystemConfig.AudioEnabled = value; } } @@ -71,33 +76,30 @@ private float MasterVolumePercent } } - private double _scale = 2.0f; private double Scale { get { - return _scale; + return _emulatorConfig.CurrentDrawScale; } set { - _scale = value; + _emulatorConfig.CurrentDrawScale = value; UpdateCanvasSize(); } } private ElementReference _monitorInputRef = default!; - private MonitorConfig _monitorConfig = default!; + private EmulatorConfig _emulatorConfig = default!; private SystemList _systemList = default!; + private WasmHost? _wasmHost = default!; public WasmHost WasmHost => _wasmHost!; private string _statsString = "Stats: calculating..."; private string _debugString = ""; - private const int DEFAULT_WINDOW_WIDTH = 640; - private const int DEFAULT_WINDOW_HEIGHT = 400; - private string _windowWidthStyle = "0px"; private string _windowHeightStyle = "0px"; @@ -126,6 +128,7 @@ private double Scale public GamepadList GamepadList { get; set; } private ILogger _logger; + private IMapper _mapper; protected override async Task OnInitializedAsync() { @@ -139,29 +142,52 @@ protected override async Task OnInitializedAsync() LocalStorage = LocalStorage! }; - _monitorConfig = new() - { - MaxLineLength = 100, // TODO: This affects help text printout, should it be set dynamically? - - //DefaultDirectory = "../../../../../../samples/Assembler/Generic/Build" - //DefaultDirectory = "%USERPROFILE%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" - //DefaultDirectory = "%HOME%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" - }; - _monitorConfig.Validate(); - + // Add systems _systemList = new SystemList(); - var c64Setup = new C64Setup(_browserContext, LoggerFactory); + var c64HostConfig = new C64HostConfig(); + var c64Setup = new C64Setup(_browserContext, LoggerFactory, c64HostConfig); await _systemList.AddSystem(c64Setup); var genericComputerSetup = new GenericComputerSetup(_browserContext, LoggerFactory); await _systemList.AddSystem(genericComputerSetup); + // Add emulator config + system-specific host configs + _emulatorConfig = new EmulatorConfig + { + DefaultEmulator = "C64", + CurrentDrawScale = 2.0, + Monitor = new() + { + MaxLineLength = 100, // TODO: This affects help text printout, should it be set dynamically? + + //DefaultDirectory = "../../../../../../samples/Assembler/Generic/Build" + //DefaultDirectory = "%USERPROFILE%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" + //DefaultDirectory = "%HOME%/source/repos/dotnet-6502/samples/Assembler/Generic/Build" + }, + HostSystemConfigs = new Dictionary + { + { C64.SystemName, c64HostConfig } + //{ GenericComputer.SystemName, new GenericComputerHostConfig() } + } + }; + _emulatorConfig.Validate(_systemList); + // Default system - _selectedSystemName = c64Setup.SystemName; + _selectedSystemName = _emulatorConfig.DefaultEmulator; await OnSelectedEmulatorChanged(new ChangeEventArgs { Value = _selectedSystemName }); + // Set parameters from query string await SetDefaultsFromQueryParams(_browserContext.Uri); + + // TODO: Make Automapper configuration more generic, incorporate in classes that need it? + var mapperConfiguration = new MapperConfiguration( + cfg => + { + cfg.CreateMap(); + } + ); + _mapper = mapperConfiguration.CreateMapper(); } private async Task SetDefaultsFromQueryParams(Uri uri) @@ -212,7 +238,8 @@ private async Task OnSelectedEmulatorChanged(ChangeEventArgs e) else _selectedSystemConfigValidationMessage = ""; - _currentConfig = await _systemList.GetCurrentSystemConfig(_selectedSystemName); + _currentSystemConfig = await _systemList.GetCurrentSystemConfig(_selectedSystemName); + _currentHostSystemConfig = _emulatorConfig.HostSystemConfigs[_selectedSystemName]; UpdateCanvasSize(); this.StateHasChanged(); @@ -227,8 +254,8 @@ private async void UpdateCanvasSize() bool isOk = await _systemList.IsValidConfig(_selectedSystemName); if (!isOk) { - _windowWidthStyle = $"{DEFAULT_WINDOW_WIDTH}px"; - _windowHeightStyle = $"{DEFAULT_WINDOW_HEIGHT}px"; + _windowWidthStyle = $"{EmulatorConfig.DEFAULT_CANVAS_WINDOW_WIDTH}px"; + _windowHeightStyle = $"{EmulatorConfig.DEFAULT_CANVAS_WINDOW_HEIGHT}px"; } else { @@ -244,7 +271,7 @@ private async void UpdateCanvasSize() private async Task InitEmulator() { - _wasmHost = new WasmHost(Js!, _selectedSystemName, _systemList, UpdateStats, UpdateDebug, SetMonitorState, _monitorConfig, ToggleDebugStatsState, LoggerFactory, (float)Scale, MasterVolumePercent); + _wasmHost = new WasmHost(Js!, _selectedSystemName, _systemList, UpdateStats, UpdateDebug, SetMonitorState, _emulatorConfig, ToggleDebugStatsState, LoggerFactory, (float)Scale, MasterVolumePercent); CurrentEmulatorState = EmulatorState.Paused; } @@ -285,6 +312,20 @@ protected async void OnPaintSurface(SKPaintGLSurfaceEventArgs e) _wasmHost.Render(e.Surface.Canvas, grContext); } + internal async Task UpdateCurrentSystemConfig(ISystemConfig config, IHostSystemConfig? hostSystemConfig) + { + // Update the system config + await _systemList.PersistNewSystemConfig(_selectedSystemName, config); + _currentSystemConfig = config; + + // Update the existing host system config, it is referenced from different objects (thus we cannot replace it with a new one). + if (hostSystemConfig != null) + { + if (_currentHostSystemConfig != null && hostSystemConfig != null) + _mapper.Map(hostSystemConfig, _currentHostSystemConfig); + } + } + /// /// Blazored.Modal instance required to open modal dialog. /// @@ -292,9 +333,9 @@ protected async void OnPaintSurface(SKPaintGLSurfaceEventArgs e) public async Task ShowConfigUI() where T : IComponent { - var systemConfig = await _systemList.GetCurrentSystemConfig(_selectedSystemName); var parameters = new ModalParameters() - .Add("SystemConfig", systemConfig); + .Add("SystemConfig", _currentSystemConfig) + .Add("HostSystemConfig", _currentHostSystemConfig); var result = await Modal.Show("Config", parameters).Result; @@ -321,9 +362,8 @@ public async Task ShowConfigUI() where T : IComponent //Dictionary userSettings = (Dictionary)result.Data; //Console.WriteLine($"Returned: {userSettings.Keys.Count} keys"); - var updatedSystemConfig = (ISystemConfig)result.Data; - - await _systemList.PersistNewSystemConfig(_selectedSystemName, updatedSystemConfig); + var resultData = ((ISystemConfig UpdatedSystemConfig, IHostSystemConfig UpdatedHostSystemConfig))result.Data; + await UpdateCurrentSystemConfig(resultData.UpdatedSystemConfig, resultData.UpdatedHostSystemConfig); } (bool isOk, List validationErrors) = await _systemList.IsValidConfigWithDetails(_selectedSystemName); @@ -478,7 +518,7 @@ private string GetDisplayStyle(string displayData) } case "AudioVolume": { - return (_currentConfig?.AudioEnabled ?? false) ? VISIBLE : HIDDEN; + return (_currentSystemConfig?.AudioEnabled ?? false) ? VISIBLE : HIDDEN; } default: return VISIBLE; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64HostConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64HostConfig.cs new file mode 100644 index 00000000..72f50694 --- /dev/null +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64HostConfig.cs @@ -0,0 +1,17 @@ + +using Highbyte.DotNet6502.Impl.AspNet.Commodore64; + +namespace Highbyte.DotNet6502.App.SkiaWASM.Skia +{ + public class C64HostConfig : IHostSystemConfig, ICloneable + { + public C64AspNetConfig InputConfig { get; set; } = new C64AspNetConfig(); + + public object Clone() + { + var clone = (C64HostConfig)MemberwiseClone(); + clone.InputConfig = (C64AspNetConfig)InputConfig.Clone(); + return clone; + } + } +} diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64Setup.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64Setup.cs index 4a519b3a..60560301 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64Setup.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/C64Setup.cs @@ -13,17 +13,19 @@ public class C64Setup : SystemConfigurer C64.SystemName; + private const string LOCAL_STORAGE_ROM_PREFIX = "rom_"; private readonly BrowserContext _browserContext; private readonly ILoggerFactory _loggerFactory; + private readonly C64HostConfig _c64HostConfig; - public C64Setup(BrowserContext browserContext, ILoggerFactory loggerFactory) + public C64Setup(BrowserContext browserContext, ILoggerFactory loggerFactory, C64HostConfig c64HostConfig) { _browserContext = browserContext; _loggerFactory = loggerFactory; + _c64HostConfig = c64HostConfig; } - public async Task GetNewConfig(string configurationVariant) { var romList = await GetROMsFromLocalStorage(); @@ -68,7 +70,7 @@ WASMAudioHandlerContext audioHandlerContext ) { var renderer = new C64SkiaRenderer(); - var inputHandler = new C64AspNetInputHandler(_loggerFactory); + var inputHandler = new C64AspNetInputHandler(_loggerFactory, _c64HostConfig.InputConfig); var audioHandler = new C64WASMAudioHandler(_loggerFactory); var c64 = (C64)system; diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/EmulatorConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/EmulatorConfig.cs new file mode 100644 index 00000000..ddfb569d --- /dev/null +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/EmulatorConfig.cs @@ -0,0 +1,32 @@ +using Highbyte.DotNet6502.Impl.AspNet; +using Highbyte.DotNet6502.Impl.Skia; +using Highbyte.DotNet6502.Monitor; +using Highbyte.DotNet6502.Systems; + +namespace Highbyte.DotNet6502.App.SkiaWASM.Skia; + +public class EmulatorConfig +{ + public const int DEFAULT_CANVAS_WINDOW_WIDTH = 640; + public const int DEFAULT_CANVAS_WINDOW_HEIGHT = 400; + + public string DefaultEmulator { get; set; } + public double DefaultDrawScale { get; set; } + public double CurrentDrawScale { get; set; } + public MonitorConfig? Monitor { get; set; } + + public Dictionary HostSystemConfigs = new(); + + public EmulatorConfig() + { + DefaultDrawScale = 2.0; + CurrentDrawScale = DefaultDrawScale; + } + + public void Validate(SystemList systemList) + { + if (!systemList.Systems.Contains(DefaultEmulator)) + throw new Exception($"Setting {nameof(DefaultEmulator)} value {DefaultEmulator} is not supported. Valid values are: {string.Join(',', systemList.Systems)}"); + Monitor.Validate(); + } +} diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/IHostSystemConfig.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/IHostSystemConfig.cs new file mode 100644 index 00000000..6ea16648 --- /dev/null +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/IHostSystemConfig.cs @@ -0,0 +1,5 @@ +namespace Highbyte.DotNet6502.App.SkiaWASM.Skia; + +public interface IHostSystemConfig : ICloneable +{ +} diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmHost.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmHost.cs index 162bc342..74ab5034 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmHost.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmHost.cs @@ -2,7 +2,6 @@ using Highbyte.DotNet6502.Impl.AspNet; using Highbyte.DotNet6502.Impl.AspNet.JSInterop.BlazorWebAudioSync; using Highbyte.DotNet6502.Impl.Skia; -using Highbyte.DotNet6502.Monitor; using Highbyte.DotNet6502.Systems; using Toolbelt.Blazor.Gamepad; @@ -32,10 +31,9 @@ public class WasmHost : IDisposable private readonly Action _updateStats; private readonly Action _updateDebug; private readonly Func _setMonitorState; - private readonly MonitorConfig _monitorConfig; + private readonly EmulatorConfig _emulatorConfig; private readonly Func _toggleDebugStatsState; private readonly ILoggerFactory _loggerFactory; - private readonly float _scale; private readonly float _initialMasterVolume; private readonly ILogger _logger; @@ -65,7 +63,7 @@ public WasmHost( Action updateStats, Action updateDebug, Func setMonitorState, - MonitorConfig monitorConfig, + EmulatorConfig emulatorConfig, Func toggleDebugStatsState, ILoggerFactory loggerFactory, float scale = 1.0f, @@ -77,9 +75,8 @@ public WasmHost( _updateStats = updateStats; _updateDebug = updateDebug; _setMonitorState = setMonitorState; - _monitorConfig = monitorConfig; + _emulatorConfig = emulatorConfig; _toggleDebugStatsState = toggleDebugStatsState; - _scale = scale; _initialMasterVolume = initialMasterVolume; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(typeof(WasmHost).Name); @@ -111,7 +108,7 @@ public async Task Init(SKCanvas canvas, GRContext grContext, AudioContextSync au _systemRunner = await _systemList.BuildSystemRunner(_systemName); - Monitor = new WasmMonitor(_jsRuntime, _systemRunner, _monitorConfig, _setMonitorState); + Monitor = new WasmMonitor(_jsRuntime, _systemRunner, _emulatorConfig, _setMonitorState); var system = await _systemList.GetSystem(_systemName); InitCustomSystemStats(system); @@ -263,7 +260,7 @@ public void Render(SKCanvas canvas, GRContext grContext) _grContext = grContext; _skCanvas = canvas; - _skCanvas.Scale(_scale); + _skCanvas.Scale((float)_emulatorConfig.CurrentDrawScale); using (_renderTime.Measure()) { _systemRunner.Draw(); diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmMonitor.cs b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmMonitor.cs index 56abe407..6ad8decb 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmMonitor.cs +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/Skia/WasmMonitor.cs @@ -16,7 +16,6 @@ public class WasmMonitor : MonitorBase private bool _hasBeenInitializedOnce = false; private readonly IJSRuntime _jsRuntime; private readonly Func _setMonitorState; - private readonly MonitorConfig _monitorConfig; private ushort? _lastTriggeredLoadBinaryForceLoadAddress = null; private Action? _lastTriggeredAfterLoadCallback = null; @@ -24,13 +23,12 @@ public class WasmMonitor : MonitorBase public WasmMonitor( IJSRuntime jsRuntime, SystemRunner systemRunner, - MonitorConfig monitorConfig, + EmulatorConfig emulatorConfig, Func setMonitorState - ) : base(systemRunner, monitorConfig) + ) : base(systemRunner, emulatorConfig.Monitor) { _jsRuntime = jsRuntime; - _monitorConfig = monitorConfig; _setMonitorState = setMonitorState; } diff --git a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/wwwroot/css/app.css b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/wwwroot/css/app.css index ab183769..2176a3eb 100644 --- a/src/apps/Highbyte.DotNet6502.App.SkiaWASM/wwwroot/css/app.css +++ b/src/apps/Highbyte.DotNet6502.App.SkiaWASM/wwwroot/css/app.css @@ -284,7 +284,7 @@ h4 { font-size: 1.0em; border-radius: 0.5vw; box-sizing: border-box; - width: 500px; + width: 800px; margin-top: 10px; margin-left: auto; margin-right: auto; diff --git a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/AspNetInputHandlerContext.cs b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/AspNetInputHandlerContext.cs index 9596690c..e1b3a1ac 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/AspNetInputHandlerContext.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/AspNetInputHandlerContext.cs @@ -1,4 +1,3 @@ -using Highbyte.DotNet6502.Instructions; using Highbyte.DotNet6502.Systems; using Microsoft.Extensions.Logging; using Toolbelt.Blazor.Gamepad; @@ -17,6 +16,7 @@ public class AspNetInputHandlerContext : IInputHandlerContext // Gamepad private readonly GamepadList _gamepadList; private readonly System.Timers.Timer _gamepadUpdateTimer = new System.Timers.Timer(50) { Enabled = true }; + private readonly System.Timers.Timer _gamepadConnectCheckTimer = new System.Timers.Timer(1000) { Enabled = true }; private Gamepad? _currentGamepad; public HashSet GamepadButtonsDown = new(); @@ -29,15 +29,21 @@ public AspNetInputHandlerContext(ILoggerFactory loggerFactory, GamepadList gamep public void Init() { _gamepadUpdateTimer.Elapsed += GamepadUpdateTimer_Elapsed; + _gamepadConnectCheckTimer.Elapsed += GamepadConectCheckTimer_Elapsed; } - private async void GamepadUpdateTimer_Elapsed(object sender, EventArgs args) + private async void GamepadConectCheckTimer_Elapsed(object sender, EventArgs args) + { + await DetectConnectedGamepad(); + } + private async Task DetectConnectedGamepad() { try { var gamepads = await _gamepadList.GetGamepadsAsync(); - var gamePad = gamepads.LastOrDefault(); + var gamePad = gamepads.FirstOrDefault(gp => gp.Id.Contains("xbox 360", StringComparison.InvariantCultureIgnoreCase)) + ?? gamepads.LastOrDefault(); if (gamePad != _currentGamepad) { _currentGamepad = gamePad; @@ -46,19 +52,30 @@ private async void GamepadUpdateTimer_Elapsed(object sender, EventArgs args) else _logger.LogInformation($"Gamepad disconnected"); } + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.ToString()); + throw; + } + } + private async void GamepadUpdateTimer_Elapsed(object sender, EventArgs args) + { + try + { GamepadButtonsDown.Clear(); - if (_currentGamepad != null && _currentGamepad.Connected) + if (_currentGamepad == null || !_currentGamepad.Connected) + return; + + for (int buttonIndex = 0; buttonIndex < _currentGamepad.Buttons.Count; buttonIndex++) { - for (int buttonIndex = 0; buttonIndex < _currentGamepad.Buttons.Count; buttonIndex++) - { - var button = _currentGamepad.Buttons[buttonIndex]; - if (!button.Pressed) - continue; - GamepadButtonsDown.Add(buttonIndex); - _logger.LogInformation($"Gamepad button pressed: {buttonIndex} ({button.Pressed})"); - } + var button = _currentGamepad.Buttons[buttonIndex]; + if (!button.Pressed) + continue; + GamepadButtonsDown.Add(buttonIndex); + _logger.LogInformation($"Gamepad button pressed: {buttonIndex} ({button.Pressed})"); } } catch (Exception e) diff --git a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/C64AspNetConfig.cs b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/C64AspNetConfig.cs new file mode 100644 index 00000000..424284d5 --- /dev/null +++ b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/C64AspNetConfig.cs @@ -0,0 +1,42 @@ +using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; + +namespace Highbyte.DotNet6502.Impl.AspNet.Commodore64 +{ + public class C64AspNetConfig : ICloneable + { + public int CurrentJoystick = 2; + + public List AvailableJoysticks = new() { 1, 2 }; + + public Dictionary> GamePadToC64JoystickMap = new() + { + { + 1, + new Dictionary + { + { new[] { 0 }, new[] { C64JoystickAction.Fire } }, + { new[] { 12 }, new[] { C64JoystickAction.Up} }, + { new[] { 13 }, new[] { C64JoystickAction.Down } }, + { new[] { 14 }, new[] { C64JoystickAction.Left } }, + { new[] { 15 }, new[] { C64JoystickAction.Right } }, + } + }, + { + 2, + new Dictionary + { + { new[] { 0 }, new[] { C64JoystickAction.Fire } }, + { new[] { 12 }, new[] { C64JoystickAction.Up} }, + { new[] { 13 }, new[] { C64JoystickAction.Down } }, + { new[] { 14 }, new[] { C64JoystickAction.Left } }, + { new[] { 15 }, new[] { C64JoystickAction.Right } }, + } + } + }; + + public object Clone() + { + return MemberwiseClone(); + } + } +} diff --git a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetGamepad.cs b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetGamepad.cs index 9892d7d3..7293a030 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetGamepad.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetGamepad.cs @@ -1,15 +1,6 @@ -using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; - namespace Highbyte.DotNet6502.Impl.AspNet.Commodore64.Input; -public static class C64AspNetGamepad +public class C64AspNetGamepad { - public static Dictionary AspNetGamePadToC64JoystickMap = new() - { - { new[] { 0 }, new[] { C64JoystickAction.Fire } }, - { new[] { 12 }, new[] { C64JoystickAction.Up} }, - { new[] { 13 }, new[] { C64JoystickAction.Down } }, - { new[] { 14 }, new[] { C64JoystickAction.Left } }, - { new[] { 15 }, new[] { C64JoystickAction.Right } }, - }; + } diff --git a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetInputHandler.cs b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetInputHandler.cs index 094b327e..30aadafc 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetInputHandler.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetInputHandler.cs @@ -11,11 +11,12 @@ public class C64AspNetInputHandler : IInputHandler _logger; private C64AspNetKeyboard _c64AspNetKeyboard; + private readonly C64AspNetConfig _c64AspNetConfig; - public C64AspNetInputHandler(ILoggerFactory loggerFactory) + public C64AspNetInputHandler(ILoggerFactory loggerFactory, C64AspNetConfig c64AspNetConfig) { _logger = loggerFactory.CreateLogger(); - + _c64AspNetConfig = c64AspNetConfig; } public void Init(C64 system, AspNetInputHandlerContext inputHandlerContext) @@ -42,6 +43,7 @@ public void Init(ISystem system, IInputHandlerContext inputHandlerContext) public void ProcessInput(C64 c64) { + c64.Cia.Joystick.ClearJoystickActions(); CaptureKeyboard(c64); CaptureJoystick(c64); } @@ -103,14 +105,17 @@ private List GetC64KeysFromAspNetKeys(HashSet keysDown, out bool private void CaptureJoystick(C64 c64) { var c64JoystickActions = GetC64JoystickActionsFromAspNetGamepad(_inputHandlerContext!.GamepadButtonsDown); - c64.Cia.Joystick.SetJoystick2Actions(c64JoystickActions); + // Note: Assume Keyboard input has been processed before this, so that Joystick actions based on keypresses has resulted + // in the current joystick actions being initialized this frame (and may contain actions from keyboard). + // Thus "overwrite" is set to false so that keyboard actions are not overwritten. + c64.Cia.Joystick.SetJoystickActions(_c64AspNetConfig.CurrentJoystick, c64JoystickActions); } private HashSet GetC64JoystickActionsFromAspNetGamepad(HashSet gamepadButtonsDown) { var c64JoystickActions = new HashSet(); var foundMappings = new List(); - var map = C64AspNetGamepad.AspNetGamePadToC64JoystickMap; + var map = _c64AspNetConfig.GamePadToC64JoystickMap[_c64AspNetConfig.CurrentJoystick]; foreach (var mapKeys in map.Keys) { int matchCount = 0; @@ -155,6 +160,8 @@ public List GetStats() if (_inputHandlerContext.KeysDown.Count > 0) list.Add($"KeysDown: {string.Join(',', _inputHandlerContext.KeysDown)}"); + if (_inputHandlerContext.GamepadButtonsDown.Count > 0) + list.Add($"GamepadDown: {string.Join(',', _inputHandlerContext.GamepadButtonsDown)}"); return list; } } diff --git a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetKeyboard.cs b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetKeyboard.cs index 3ed78cb4..8d1052f2 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetKeyboard.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.AspNet/Commodore64/Input/C64AspNetKeyboard.cs @@ -52,7 +52,7 @@ public C64AspNetKeyboard(string hostKeyboardLayout) { new[] { "KeyX" }, new[] { C64Key.X } }, { new[] { "KeyY" }, new[] { C64Key.Y } }, { new[] { "KeyZ" }, new[] { C64Key.Z } }, - { new[] { "Key0" }, new[] { C64Key.Zero } }, + { new[] { "Digit0" }, new[] { C64Key.Zero } }, { new[] { "Digit1" }, new[] { C64Key.One } }, { new[] { "Digit2" }, new[] { C64Key.Two } }, { new[] { "Digit3" }, new[] { C64Key.Three } }, diff --git a/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/C64SilkNetConfig.cs b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/C64SilkNetConfig.cs new file mode 100644 index 00000000..2391861e --- /dev/null +++ b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/C64SilkNetConfig.cs @@ -0,0 +1,42 @@ +using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; + +namespace Highbyte.DotNet6502.Impl.SilkNet.Commodore64 +{ + public class C64SilkNetConfig : ICloneable + { + public int CurrentJoystick = 2; + + public List AvailableJoysticks = new() { 1, 2 }; + + public Dictionary> GamePadToC64JoystickMap = new() + { + { + 1, + new Dictionary + { + { new[] { ButtonName.A }, new[] { C64JoystickAction.Fire } }, + { new[] { ButtonName.DPadUp }, new[] { C64JoystickAction.Up} }, + { new[] { ButtonName.DPadDown }, new[] { C64JoystickAction.Down } }, + { new[] { ButtonName.DPadLeft }, new[] { C64JoystickAction.Left } }, + { new[] { ButtonName.DPadRight }, new[] { C64JoystickAction.Right } }, + } + }, + { + 2, + new Dictionary + { + { new[] { ButtonName.A }, new[] { C64JoystickAction.Fire } }, + { new[] { ButtonName.DPadUp }, new[] { C64JoystickAction.Up} }, + { new[] { ButtonName.DPadDown }, new[] { C64JoystickAction.Down } }, + { new[] { ButtonName.DPadLeft }, new[] { C64JoystickAction.Left } }, + { new[] { ButtonName.DPadRight }, new[] { C64JoystickAction.Right } }, + } + } + }; + + public object Clone() + { + return this.MemberwiseClone(); + } + } +} diff --git a/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetGamepad.cs b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetGamepad.cs index 034ae921..6e8371d8 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetGamepad.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetGamepad.cs @@ -1,16 +1,6 @@ -using Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; - namespace Highbyte.DotNet6502.Impl.SilkNet.Commodore64.Input; -public static class C64SilkNetGamepad +public class C64SilkNetGamepad { - public static Dictionary SilkNetGamePadToC64JoystickMap = new() - { - { new[] { ButtonName.A }, new[] { C64JoystickAction.Fire } }, - { new[] { ButtonName.DPadUp }, new[] { C64JoystickAction.Up} }, - { new[] { ButtonName.DPadDown }, new[] { C64JoystickAction.Down } }, - { new[] { ButtonName.DPadLeft }, new[] { C64JoystickAction.Left } }, - { new[] { ButtonName.DPadRight }, new[] { C64JoystickAction.Right } }, - }; } diff --git a/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetInputHandler.cs b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetInputHandler.cs index eec557e9..314da6f8 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetInputHandler.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.SilkNet/Commodore64/Input/C64SilkNetInputHandler.cs @@ -13,10 +13,12 @@ public class C64SilkNetInputHandler : IInputHandler _logger; + private readonly C64SilkNetConfig _c64SilkNetConfig; - public C64SilkNetInputHandler(ILoggerFactory loggerFactory) + public C64SilkNetInputHandler(ILoggerFactory loggerFactory, C64SilkNetConfig c64SilkNetConfig) { _logger = loggerFactory.CreateLogger(); + _c64SilkNetConfig = c64SilkNetConfig; // TODO: Is there a better way to current keyboard input language? // Note: Using CurrentCulture instead of CurrentUICulture. @@ -45,6 +47,7 @@ public void Init(ISystem system, IInputHandlerContext inputHandlerContext) public void ProcessInput(C64 c64) { + c64.Cia.Joystick.ClearJoystickActions(); CaptureKeyboard(c64); CaptureJoystick(c64); } @@ -106,14 +109,17 @@ private List GetC64KeysFromSilkNetKeys(HashSet keysDown, out bool r private void CaptureJoystick(C64 c64) { var c64JoystickActions = GetC64JoystickActionsFromSilkNetGamepad(_inputHandlerContext!.GamepadButtonsDown); - c64.Cia.Joystick.SetJoystick2Actions(c64JoystickActions); + // Note: Assume Keyboard input has been processed before this, so that Joystick actions based on keypresses has resulted + // in the current joystick actions being initialized this frame (and may contain actions from keyboard). + // Thus "overwrite" is set to false so that keyboard actions are not overwritten. + c64.Cia.Joystick.SetJoystickActions(_c64SilkNetConfig.CurrentJoystick, c64JoystickActions, overwrite: false); } private HashSet GetC64JoystickActionsFromSilkNetGamepad(HashSet gamepadButtonsDown) { var c64JoystickActions = new HashSet(); var foundMappings = new List(); - var map = C64SilkNetGamepad.SilkNetGamePadToC64JoystickMap; + var map = _c64SilkNetConfig.GamePadToC64JoystickMap[_c64SilkNetConfig.CurrentJoystick]; foreach (var mapKeys in map.Keys) { int matchCount = 0; diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/C64SkiaRenderer.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/C64SkiaRenderer.cs index 4a68f71e..19904d91 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/C64SkiaRenderer.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/C64SkiaRenderer.cs @@ -420,7 +420,7 @@ public void DrawEmulatorCharacterOnScreen( SKPaint paint = characterMode switch { CharMode.Standard => _c64SkiaPaint.GetDrawCharacterPaint(characterColor), - CharMode.Extended => _c64SkiaPaint.GetDrawCharacterPaintWithBackground(characterColor, bgColor.Value), + CharMode.Extended => bgColor == null ? _c64SkiaPaint.GetDrawCharacterPaint(characterColor) : _c64SkiaPaint.GetDrawCharacterPaintWithBackground(characterColor, bgColor.Value), CharMode.MultiColor => _c64SkiaPaint.GetDrawCharacterPaintWithMultiColor(characterColor, backgroundColor1, backgroundColor2), _ => throw new NotImplementedException($"Character mode {characterMode} not implemented.") }; @@ -477,7 +477,7 @@ private void RenderSprites(C64 c64, SKCanvas canvas, bool spritesWithPriorityOve _spriteImages[sprite.SpriteNumber] = spriteGen.GenerateSpriteImage(sprite); sprite.ClearDirty(); #if DEBUG - spriteGen.DumpSpriteToImageFile(_spriteImages[sprite.SpriteNumber], $"{Path.GetTempPath()}/c64_sprite_{sprite.SpriteNumber}.png"); + //spriteGen.DumpSpriteToImageFile(_spriteImages[sprite.SpriteNumber], $"{Path.GetTempPath()}/c64_sprite_{sprite.SpriteNumber}.png"); #endif } var spriteImage = _spriteImages[sprite.SpriteNumber]; diff --git a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/Chargen.cs b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/Chargen.cs index 1ec3a724..c8e0c28c 100644 --- a/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/Chargen.cs +++ b/src/libraries/Highbyte.DotNet6502.Impl.Skia/Commodore64/Video/Chargen.cs @@ -4,9 +4,12 @@ namespace Highbyte.DotNet6502.Impl.Skia.Commodore64.Video; public class CharGen { - public static SKColor CharacterImageDrawColor = SKColors.White; - public static SKColor CharacterImageDrawMultiColorBG1 = SKColors.Blue; - public static SKColor CharacterImageDrawMultiColorBG2 = SKColors.Red; + // About the CharacterImageDraw* variables: + // - They are replaced with a transformation when drawn on screen based on the text mode, and it's configured C64 colors. + // - It doesn't matter which color they are set to, just that they are different, and not a valid C64 color. + public static SKColor CharacterImageDrawColor = SKColors.DarkKhaki; + public static SKColor CharacterImageDrawMultiColorBG1 = SKColors.DarkOrchid; + public static SKColor CharacterImageDrawMultiColorBG2 = SKColors.DarkGoldenrod; private static readonly SKPaint s_paint; private static readonly SKPaint s_paintMultiColorBG1; diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64Config.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64Config.cs index fed3e70f..fc434e35 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64Config.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64Config.cs @@ -1,4 +1,3 @@ -using System.Reflection.Metadata; using Highbyte.DotNet6502.Systems.Commodore64.Models; using Highbyte.DotNet6502.Systems.Commodore64.Video; @@ -113,6 +112,8 @@ public string ColorMapName } public bool KeyboardJoystickEnabled { get; set; } + public int KeyboardJoystick { get; set; } = 2; + public C64KeyboardJoystickMap KeyboardJoystickMap { get; private set; } public C64Config() @@ -184,17 +185,11 @@ public void SetROM(string romName, string? file = null, byte[]? data = null) _isDirty = true; } - public C64Config Clone() + public object Clone() { - return new C64Config - { - ROMDirectory = ROMDirectory, - C64Model = C64Model, - Vic2Model = Vic2Model, - ROMs = ROM.Clone(ROMs), - TimerMode = TimerMode, - AudioEnabled = AudioEnabled - }; + var clone = (C64Config)this.MemberwiseClone(); + clone.ROMs = ROM.Clone(ROMs); + return clone; } public void Validate() diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64KeyboardJoystickMap.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64KeyboardJoystickMap.cs index 2559f7df..14020d26 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64KeyboardJoystickMap.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Config/C64KeyboardJoystickMap.cs @@ -6,6 +6,11 @@ public class C64KeyboardJoystickMap { private Dictionary KeyToJoystick1Map = new() { + {C64Key.Space, C64JoystickAction.Fire}, + {C64Key.W, C64JoystickAction.Up}, + {C64Key.S, C64JoystickAction.Down}, + {C64Key.A, C64JoystickAction.Left}, + {C64Key.D, C64JoystickAction.Right} }; private Dictionary KeyToJoystick2Map = new() diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Joystick.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Joystick.cs index d224da0a..33e6924b 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Joystick.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Joystick.cs @@ -6,32 +6,52 @@ namespace Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; public class C64Joystick { private readonly ILogger _logger; + public Dictionary> CurrentJoystickActions { get; private set; } = new() + { + {1, new() }, + {2, new() } + }; public bool KeyboardJoystickEnabled { get; set; } + public int KeyboardJoystick { get; set; } = 2; public C64KeyboardJoystickMap KeyboardJoystickMap { get; private set; } - public HashSet CurrentJoystick1Actions { get; private set; } = new(); - public HashSet CurrentJoystick2Actions { get; private set; } = new(); public C64Joystick(C64Config c64Config, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); KeyboardJoystickEnabled = c64Config.KeyboardJoystickEnabled; + KeyboardJoystick = c64Config.KeyboardJoystick; KeyboardJoystickMap = c64Config.KeyboardJoystickMap; } - public void SetJoystick1Actions(HashSet joystickActions) + public void ClearJoystickActions() { - CurrentJoystick1Actions = joystickActions; - if (joystickActions.Count > 0) - _logger.LogTrace($"C64 joystick 1 pressed: {string.Join(",", joystickActions)}"); - + for (int joystick = 1; joystick <= CurrentJoystickActions.Count; joystick++) + { + CurrentJoystickActions[joystick].Clear(); + } } - public void SetJoystick2Actions(HashSet joystickActions) + public void SetJoystickActions(int joystick, HashSet joystickActions, bool overwrite = true) { - CurrentJoystick2Actions = joystickActions; + if (joystick != 1 && joystick != 2) + throw new ArgumentException($"Joystick number {joystick} is not supported. Valid values are 1 and 2."); + if (joystickActions.Count > 0) - _logger.LogTrace($"C64 joystick 2 pressed: {string.Join(",", joystickActions)}"); + _logger.LogDebug($"C64 joystick {joystick} pressed: {string.Join(",", joystickActions)}"); + + if (overwrite) + { + CurrentJoystickActions[joystick] = joystickActions; + } + else + { + foreach (var action in joystickActions) + { + if (!CurrentJoystickActions[joystick].Contains(action)) + CurrentJoystickActions[joystick].Add(action); + } + } } } /// diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Keyboard.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Keyboard.cs index fcb64941..5a47cff2 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Keyboard.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/C64Keyboard.cs @@ -167,8 +167,7 @@ private void HandleJoystickKeyboard() { if (_c64.Cia.Joystick.KeyboardJoystickEnabled) { - HandleJoystickKeyboard(1); - HandleJoystickKeyboard(2); + HandleJoystickKeyboard(_c64.Cia.Joystick.KeyboardJoystick); } } @@ -184,7 +183,7 @@ private void HandleJoystickKeyboard(int joystick) _pressedKeys.Remove(c64Key); // Remove key from pressed keys to avoid duplicate actions } } - _c64.Cia.Joystick.SetJoystick1Actions(joystickActions); + _c64.Cia.Joystick.SetJoystickActions(joystick, joystickActions); } } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/Cia.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/Cia.cs index 514de5d6..b119156c 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/Cia.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/Cia.cs @@ -71,6 +71,55 @@ public void MapIOLocations(Memory c64mem) c64mem.MapReader(CiaAddr.CIA1_CIACRB, Cia1TimerBControlLoad); c64mem.MapWriter(CiaAddr.CIA1_CIACRB, Cia1TimerBControlStore); + // CIA #1 TEMP DEBUG. TODO: Implement remaining CIA#1 registers such as Time Of Day + c64mem.MapReader(0xdc08, Cia1DebugLoad); + c64mem.MapWriter(0xdc08, Cia1DebugStore); + c64mem.MapReader(0xdc09, Cia1DebugLoad); + c64mem.MapWriter(0xdc09, Cia1DebugStore); + c64mem.MapReader(0xdc0a, Cia1DebugLoad); + c64mem.MapWriter(0xdc0a, Cia1DebugStore); + c64mem.MapReader(0xdc0b, Cia1DebugLoad); + c64mem.MapWriter(0xdc0b, Cia1DebugStore); + + + // TODO: Implement CIA #2 timers and registers + //// CIA #2 Timer A + //c64mem.MapReader(CiaAddr.CIA2_TIMAHI, Cia2TimerAHILoad); + //c64mem.MapWriter(CiaAddr.CIA2_TIMAHI, Cia2TimerAHIStore); + + //c64mem.MapReader(CiaAddr.CIA2_TIMALO, Cia2TimerALOLoad); + //c64mem.MapWriter(CiaAddr.CIA2_TIMALO, Cia2TimerALOStore); + + //// CIA #2 Timer B + //c64mem.MapReader(CiaAddr.CIA2_TIMBHI, Cia2TimerBHILoad); + //c64mem.MapWriter(CiaAddr.CIA2_TIMBHI, Cia2TimerBHIStore); + + //c64mem.MapReader(CiaAddr.CIA2_TIMBLO, Cia2TimerBLOLoad); + //c64mem.MapWriter(CiaAddr.CIA2_TIMBLO, Cia2TimerBLOStore); + + // TODO: Implement CIA #2 timers and registers + c64mem.MapReader(0xdd01, Cia2DebugLoad); + c64mem.MapWriter(0xdd01, Cia2DebugStore); + c64mem.MapReader(0xdd02, Cia2DebugLoad); + c64mem.MapWriter(0xdd02, Cia2DebugStore); + c64mem.MapReader(0xdd03, Cia2DebugLoad); + c64mem.MapWriter(0xdd03, Cia2DebugStore); + c64mem.MapReader(0xdd04, Cia2DebugLoad); + c64mem.MapWriter(0xdd04, Cia2DebugStore); + c64mem.MapReader(0xdd05, Cia2DebugLoad); + c64mem.MapWriter(0xdd05, Cia2DebugStore); + c64mem.MapReader(0xdd06, Cia2DebugLoad); + c64mem.MapWriter(0xdd06, Cia2DebugStore); + c64mem.MapReader(0xdd07, Cia2DebugLoad); + c64mem.MapWriter(0xdd07, Cia2DebugStore); + c64mem.MapReader(0xdd08, Cia2DebugLoad); + c64mem.MapWriter(0xdd08, Cia2DebugStore); + c64mem.MapReader(0xdd09, Cia2DebugLoad); + c64mem.MapWriter(0xdd09, Cia2DebugStore); + c64mem.MapReader(0xdd0a, Cia2DebugLoad); + c64mem.MapWriter(0xdd0a, Cia2DebugStore); + c64mem.MapReader(0xdd0b, Cia2DebugLoad); + c64mem.MapWriter(0xdd0b, Cia2DebugStore); } // Cia 1 Data Port A is normally read from to get joystick (#2) input. @@ -83,7 +132,7 @@ public byte Cia1DataALoad(ushort _) var value = Keyboard.GetSelectedMatrixRow(); // Also set Joystick #2 bits - foreach (var action in Joystick.CurrentJoystick2Actions) + foreach (var action in Joystick.CurrentJoystickActions[2]) { value.ClearBit((int)action); } @@ -104,7 +153,7 @@ public byte Cia1DataBLoad(ushort address) var value = Keyboard.GetPressedKeysForSelectedMatrixRow(); // Also set Joystick #1 bits - foreach (var action in Joystick.CurrentJoystick1Actions) + foreach (var action in Joystick.CurrentJoystickActions[1]) { value.ClearBit((int)action); } @@ -186,4 +235,27 @@ public void Cia1InteruptControlStore(ushort _, byte value) public byte Cia1TimerBControlLoad(ushort _) => CiaTimers[CiaTimerType.Cia1_B].TimerControl; public void Cia1TimerBControlStore(ushort _, byte value) => CiaTimers[CiaTimerType.Cia1_B].TimerControl = value; + + + public byte Cia1DebugLoad(ushort address) => _c64.ReadIOStorage(address); + public void Cia1DebugStore(ushort address, byte value) => _c64.WriteIOStorage(address, value); + + + // TODO: Implement CIA #2 timers and registers + public byte Cia2TimerAHILoad(ushort _) => CiaTimers[CiaTimerType.Cia2_A].InternalTimer.Highbyte(); + public void Cia2TimerAHIStore(ushort _, byte value) => CiaTimers[CiaTimerType.Cia2_A].SetInternalTimer_Latch_HI(value); + + public byte Cia2TimerALOLoad(ushort _) => CiaTimers[CiaTimerType.Cia2_A].InternalTimer.Lowbyte(); + public void Cia2TimerALOStore(ushort _, byte value) => CiaTimers[CiaTimerType.Cia2_A].SetInternalTimer_Latch_LO(value); + + public byte Cia2TimerBHILoad(ushort _) => CiaTimers[CiaTimerType.Cia2_B].InternalTimer.Highbyte(); + public void Cia2TimerBHIStore(ushort _, byte value) => CiaTimers[CiaTimerType.Cia2_B].SetInternalTimer_Latch_HI(value); + + public byte Cia2TimerBLOLoad(ushort _) => CiaTimers[CiaTimerType.Cia2_B].InternalTimer.Lowbyte(); + public void Cia2TimerBLOStore(ushort _, byte value) => CiaTimers[CiaTimerType.Cia2_B].SetInternalTimer_Latch_LO(value); + + public byte Cia2DebugLoad(ushort address) => _c64.ReadIOStorage(address); + public void Cia2DebugStore(ushort address, byte value) => _c64.WriteIOStorage(address, value); + + } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaAddr.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaAddr.cs index 82cd73b7..17bd8d6e 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaAddr.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaAddr.cs @@ -29,9 +29,16 @@ public static class CiaAddr public const ushort CIA1_CIACRB = 0xdc0f; - // CIA #2 Data Port A & B (VIC2 bank selection ,serial bus, rs-232, user port) + // CIA #2 Data Port A & B (VIC2 bank selection, serial bus, rs-232, user port) public const ushort CIA2_DATAA = 0xdd00; public const ushort CIA2_DATAB = 0xdd01; + // CIA #2 Timer Register A + public const ushort CIA2_TIMALO = 0xdd04; + public const ushort CIA2_TIMAHI = 0xdd05; + + // CIA #2 Timer Register B + public const ushort CIA2_TIMBLO = 0xdd06; + public const ushort CIA2_TIMBHI = 0xdd07; } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaTimer.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaTimer.cs index 4e377284..f6a5ee40 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaTimer.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/TimerAndPeripheral/CiaTimer.cs @@ -1,5 +1,3 @@ -using System.Diagnostics; - namespace Highbyte.DotNet6502.Systems.Commodore64.TimerAndPeripheral; public class CiaTimer @@ -8,8 +6,6 @@ public class CiaTimer private readonly IRQSource _iRQSource; private readonly C64 _c64; - // Current 16-bit value of the timer, decremented each cycle when timer is running. - public ushort InternalTimer { get; private set; } = 0; // Latch contains the value was written to timer registers, and is used as start value when timer is started. private ushort _internalTimer_Latch = 0; @@ -49,7 +45,10 @@ public byte TimerControl } } - private readonly Stopwatch _realTimer_Stopwatch = new(); + // Current 16-bit value of the timer, decremented each cycle when timer is running. + public ushort InternalTimer { get; private set; } = 0; + + private bool _timerIsRunning = false; public CiaTimer(CiaTimerType ciaTimerType, IRQSource iRQSource, C64 c64) { @@ -62,14 +61,12 @@ public void ProcessTimer(ulong cyclesExecuted) { var ciaIrq = _c64.Cia.CiaIRQ; - if (IsTimerStartFlagSet() && _realTimer_Stopwatch.IsRunning) + if (IsTimerStartFlagSet() && _timerIsRunning) { - var elapsedMs = _realTimer_Stopwatch.ElapsedMilliseconds; - var startValueMs = CalculateTimerMS(_internalTimer_Latch); - var remainingMs = startValueMs - elapsedMs; - if (remainingMs < 0) - remainingMs = 0; - InternalTimer = CalculateTimerValue(remainingMs); + if (InternalTimer >= cyclesExecuted) + InternalTimer -= (ushort)cyclesExecuted; + else + InternalTimer = 0; if (InternalTimer == 0) ciaIrq.ConditionSet(_iRQSource); @@ -84,7 +81,6 @@ public void ProcessTimer(ulong cyclesExecuted) if (IsTimerRunModeContinious()) { ResetTimerValue(); - StartTimer(); } else { @@ -148,37 +144,12 @@ private void ResetTimerValue() public void StartTimer() { _c64.Cia.CiaIRQ.ConditionClear(_iRQSource); - _realTimer_Stopwatch.Restart(); + _timerIsRunning = true; } private void StopTimer() { - _realTimer_Stopwatch.Stop(); - } - - /// - /// Calculates the timer interval in milliseconds based on the latch value. - /// Formula: - /// TIME (s) = LATCH VALUE / CLOCK SPEED - /// TIME (ms) = (LATCH VALUE / CLOCK SPEED) * 1000 - /// - /// - /// - private double CalculateTimerMS(ushort timerLatchValue) - { - return timerLatchValue / _c64.Model.CPUFrequencyHz * 1000.0; - } - - /// - /// Calculates the CIA timer value based on milliseconds. - /// Formula: - /// TIMER VALUE = (TIME (ms) * CLOCK SPEED) / 1000 - /// - /// - /// - private ushort CalculateTimerValue(double milliseconds) - { - return (ushort)(milliseconds * _c64.Model.CPUFrequencyHz / 1000.0); + _timerIsRunning = false; } } diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Video/Vic2.cs b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Video/Vic2.cs index 0dfa5877..edf377e9 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Video/Vic2.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Commodore64/Video/Vic2.cs @@ -452,7 +452,7 @@ public byte ScrollXLoad(ushort address) public void MemorySetupStore(ushort address, byte value) { - C64.WriteIOStorage(address, value); + C64.WriteIOStorage(address, (byte)(value | 0b0000001)); // Set unused bit 0 to 1 (as it seems to be on a real C64) // 0xd018, bit 0: Unused @@ -469,7 +469,7 @@ public void MemorySetupStore(ushort address, byte value) private void VideoMatrixBaseAddressUpdate(byte videoMatrixBaseAddressSetting) { // From VIC 2 perspective, IO address 0xd018 bits 4-7 controls where within a VIC 2 "Bank" - // the text screen and sprite pointeras are defined. It's a offset from the start of VIC 2 memory. + // the text screen and sprite pointers are defined. It's a offset from the start of VIC 2 memory. // // The parameter videoMatrixBaseAddressSetting contains that 4-bit value. // @@ -490,9 +490,12 @@ private void VideoMatrixBaseAddressUpdate(byte videoMatrixBaseAddressSetting) // %1110, E: Screen: 0x3800-0x3BE7, 14336-15335. Sprite Pointers: 0x3BF8-0x3BFF, 15352-15359. // %1111, F: Screen: 0x3C00-0x3FE7, 15336-16335. Sprite Pointers: 0x3FF8-0x3FFF, 16352-16359. + var oldVideoMatrixBaseAddress = VideoMatrixBaseAddress; VideoMatrixBaseAddress = (ushort)(videoMatrixBaseAddressSetting * 0x400); - - SpriteManager.SetAllDirty(); + if (oldVideoMatrixBaseAddress != VideoMatrixBaseAddress) + { + SpriteManager.SetAllDirty(); + } } public byte MemorySetupLoad(ushort address) diff --git a/src/libraries/Highbyte.DotNet6502.Systems/Generic/Config/GenericComputerConfig.cs b/src/libraries/Highbyte.DotNet6502.Systems/Generic/Config/GenericComputerConfig.cs index 669eb862..1dfedc84 100644 --- a/src/libraries/Highbyte.DotNet6502.Systems/Generic/Config/GenericComputerConfig.cs +++ b/src/libraries/Highbyte.DotNet6502.Systems/Generic/Config/GenericComputerConfig.cs @@ -1,3 +1,5 @@ +using System.Runtime.InteropServices.ObjectiveC; + namespace Highbyte.DotNet6502.Systems.Generic.Config; public class GenericComputerConfig : ISystemConfig @@ -111,20 +113,11 @@ public GenericComputerConfig() WaitForHostToAcknowledgeFrame = true; } - public GenericComputerConfig Clone() + public object Clone() { - return new GenericComputerConfig - { - ProgramBinary = ProgramBinary, - ProgramBinaryFile = ProgramBinaryFile, - StopAtBRK = StopAtBRK, - CPUCyclesPerFrame = CPUCyclesPerFrame, - ScreenRefreshFrequencyHz = ScreenRefreshFrequencyHz, - WaitForHostToAcknowledgeFrame = WaitForHostToAcknowledgeFrame, - Memory = Memory.Clone(), - AudioSupported = false, - AudioEnabled = false - }; + var clone = (GenericComputerConfig)this.MemberwiseClone(); + clone.Memory = (EmulatorMemoryConfig)Memory.Clone(); + return clone; } public void Validate() diff --git a/src/libraries/Highbyte.DotNet6502/Systems/ISystemConfig.cs b/src/libraries/Highbyte.DotNet6502/Systems/ISystemConfig.cs index ae6f5917..8e0c3cc7 100644 --- a/src/libraries/Highbyte.DotNet6502/Systems/ISystemConfig.cs +++ b/src/libraries/Highbyte.DotNet6502/Systems/ISystemConfig.cs @@ -1,6 +1,6 @@ namespace Highbyte.DotNet6502.Systems; -public interface ISystemConfig +public interface ISystemConfig : ICloneable { void Validate();