diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 91dbf11cb..f7a8af354 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -34,20 +34,20 @@ jobs: - name: Upload NuGet Bufdio.Spice86 working-directory: ./src/Bufdio.Spice86/bin/Release - run: nuget push Bufdio.Spice86.7.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate + run: nuget push Bufdio.Spice86.8.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate - name: Upload NuGet Spice86.Shared working-directory: ./src/Spice86.Shared/bin/Release - run: nuget push Spice86.Shared.7.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate + run: nuget push Spice86.Shared.8.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate - name: Upload NuGet Spice86.Logging working-directory: ./src/Spice86.Logging/bin/Release - run: nuget push Spice86.Logging.7.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate + run: nuget push Spice86.Logging.8.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate - name: Upload NuGet Spice86.Core working-directory: ./src/Spice86.Core/bin/Release - run: nuget push Spice86.Core.7.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate + run: nuget push Spice86.Core.8.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate - name: Upload NuGet Spice86 working-directory: ./src/Spice86/bin/Release - run: nuget push Spice86.7.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate + run: nuget push Spice86.8.0.0.nupkg ${{secrets.NUGET_API_KEY}} -Source 'https://api.nuget.org/v3/index.json' -SkipDuplicate diff --git a/README.md b/README.md index 83998754b..19e250ef6 100644 --- a/README.md +++ b/README.md @@ -390,7 +390,7 @@ Stunts: Betrayal at Krondor: -![](doc/BaK.PNG) +![](doc/BaK.png) ## Credits diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9eb0bdbcc..9f4176dee 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -16,8 +16,8 @@ - 7.0.0 - Some breaking API changes (SegmentRegisters.cs), WIP new CFG_CPU, addtionnal memory/disasm views to the internal debugger, replaced UI DI framework with Microsoft.DI. + 8.0.0 + Some breaking API changes (replaced Microosft.DI with compile-time DI, see Spice86DependencyInjection), Breakpoint support in Internal Debugger, SegmentRegisters replaced by SegmentRegisterIndex, and dependendencies to the Machine class have been replaced with direct dependencies in all classes, which is also a breaking change for projects using Spice86 compared to Nuget v7.0.0. Kevin Ferrare, Maximilien Noal, Joris van Eijden, Artjom Vejsel reverse-engineering;avalonia;debugger;assembly;emulator;cross-platform Reverse engineer and rewrite real mode dos programs diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5c5b49e6a..d1000669b 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,25 +4,26 @@ true - - + + - - - - - + + + + + - + - - + + + - + @@ -34,10 +35,10 @@ - - + + - + diff --git a/src/Spice86.Core/Emulator/Devices/Video/Renderer.cs b/src/Spice86.Core/Emulator/Devices/Video/Renderer.cs index 470a82c5b..2ac6fd302 100644 --- a/src/Spice86.Core/Emulator/Devices/Video/Renderer.cs +++ b/src/Spice86.Core/Emulator/Devices/Video/Renderer.cs @@ -10,7 +10,7 @@ namespace Spice86.Core.Emulator.Devices.Video; /// public class Renderer : IVgaRenderer { private static readonly object RenderLock = new(); - private readonly IVideoMemory _memory; + private readonly VideoMemory _memory; private readonly IVideoState _state; /// @@ -194,7 +194,10 @@ private MemoryWidth DetermineMemoryWidthMode() { return memoryWidthMode; } - private (byte plane0, byte plane1, byte plane2, byte plane3) ReadVideoMemory(MemoryWidth memoryWidthMode, int memoryAddressCounter, bool scanLineBit0ForAddressBit13, int scanline, bool scanLineBit0ForAddressBit14, IReadOnlyList planesEnabled) { + private (byte plane0, byte plane1, byte plane2, byte plane3) ReadVideoMemory( + MemoryWidth memoryWidthMode, int memoryAddressCounter, + bool scanLineBit0ForAddressBit13, int scanline, + bool scanLineBit0ForAddressBit14, ReadOnlySpan planesEnabled) { // Convert logical address to physical address. ushort physicalAddress = memoryWidthMode switch { MemoryWidth.Byte => (ushort)memoryAddressCounter, diff --git a/src/Spice86.Core/Emulator/Devices/Video/VideoMemory.cs b/src/Spice86.Core/Emulator/Devices/Video/VideoMemory.cs index ee00583a1..03537039f 100644 --- a/src/Spice86.Core/Emulator/Devices/Video/VideoMemory.cs +++ b/src/Spice86.Core/Emulator/Devices/Video/VideoMemory.cs @@ -142,7 +142,7 @@ private void HandleWriteMode2(byte value, Register8 planeEnable, bool[] writePla } } - private void HandleWriteMode1(Register8 planeEnable, IReadOnlyList writePlane, uint offset) { + private void HandleWriteMode1(Register8 planeEnable, ReadOnlySpan writePlane, uint offset) { // Foreach plane for (int plane = 0; plane < 4; plane++) { if (planeEnable[plane] && writePlane[plane]) { @@ -151,7 +151,8 @@ private void HandleWriteMode1(Register8 planeEnable, IReadOnlyList writePl } } - private void HandleWriteMode0(byte value, Register8 planeEnable, IReadOnlyList writePlane, Register8 setResetEnable, Register8 setReset, uint offset) { + private void HandleWriteMode0(byte value, Register8 planeEnable, + ReadOnlySpan writePlane, Register8 setResetEnable, Register8 setReset, uint offset) { Debug.Assert(offset < 0x10000); if (_state.GraphicsControllerRegisters.DataRotateRegister.RotateCount != 0) { value.Ror(_state.GraphicsControllerRegisters.DataRotateRegister.RotateCount); diff --git a/src/Spice86.Core/Emulator/Gdb/GdbCommandBreakPointHandler.cs b/src/Spice86.Core/Emulator/Gdb/GdbCommandBreakPointHandler.cs index 81db3bde4..3edbeb5cd 100644 --- a/src/Spice86.Core/Emulator/Gdb/GdbCommandBreakPointHandler.cs +++ b/src/Spice86.Core/Emulator/Gdb/GdbCommandBreakPointHandler.cs @@ -39,9 +39,11 @@ public GdbCommandBreakpointHandler(EmulatorBreakpointsManager emulatorBreakpoint /// A response string to send back to GDB. public string AddBreakpoint(string commandContent) { BreakPoint? breakPoint = ParseBreakPoint(commandContent); - _emulatorBreakpointsManager.ToggleBreakPoint(breakPoint, true); - if (_loggerService.IsEnabled(LogEventLevel.Debug)) { - _loggerService.Debug("Breakpoint added!\n{@BreakPoint}", breakPoint); + if(breakPoint is not null) { + _emulatorBreakpointsManager.ToggleBreakPoint(breakPoint, true); + if (_loggerService.IsEnabled(LogEventLevel.Debug)) { + _loggerService.Debug("Breakpoint added!\n{@BreakPoint}", breakPoint); + } } return _gdbIo.GenerateResponse("OK"); } diff --git a/src/Spice86.Core/Emulator/Gdb/GdbCommandHandler.cs b/src/Spice86.Core/Emulator/Gdb/GdbCommandHandler.cs index 467967605..5b8dec024 100644 --- a/src/Spice86.Core/Emulator/Gdb/GdbCommandHandler.cs +++ b/src/Spice86.Core/Emulator/Gdb/GdbCommandHandler.cs @@ -135,12 +135,12 @@ private string Kill() { private Tuple ParseSupportedQuery(string item) { Tuple res; - if (item.EndsWith("+")) { + if (item.EndsWith('+')) { res = Tuple.Create(item[0..^1], (object)true); - } else if (item.EndsWith("-")) { + } else if (item.EndsWith('-')) { res = Tuple.Create(item[0..^1], (object)false); } else { - string[] split = item.Split("="); + string[] split = item.Split('='); res = Tuple.Create(split[0], new object()); if (split.Length == 2) { res = Tuple.Create(split[0], (object)split[1]); @@ -171,12 +171,12 @@ private string QueryVariable(string command) { return _gdbIo.GenerateResponse(""); } - if (command.StartsWith("L")) { + if (command.StartsWith('L')) { string nextthread = command[4..]; return _gdbIo.GenerateResponse($"qM011{nextthread}00000001"); } - if (command.StartsWith("P")) { + if (command.StartsWith('P')) { return _gdbIo.GenerateResponse(""); } diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Common/MemoryWriter/MemoryAsmWriter.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Common/MemoryWriter/MemoryAsmWriter.cs index 3b4acd3a5..b9406f0b2 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Common/MemoryWriter/MemoryAsmWriter.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Common/MemoryWriter/MemoryAsmWriter.cs @@ -30,7 +30,7 @@ public MemoryAsmWriter(IIndexable memory, SegmentedAddress beginningAddress, Cal /// Callback index /// Action to run when this callback is executed by the CPU public void RegisterAndWriteCallback(byte callbackNumber, Action runnable) { - ICallback callback = new Callback(callbackNumber, runnable, GetCurrentAddressCopy()); + Callback callback = new Callback(callbackNumber, runnable, GetCurrentAddressCopy()); _callbackHandler.AddCallback(callback); WriteCallback(callback.Index); } diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs index 166591625..f2e7cd102 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs @@ -37,7 +37,7 @@ public class DosInt21Handler : InterruptHandler { private StringBuilder _displayOutputBuilder = new(); private readonly DosFileManager _dosFileManager; - private readonly IList _devices; + private readonly List _devices; private readonly Dos _dos; private readonly KeyboardInt16Handler _keyboardInt16Handler; private readonly IVgaFunctionality _vgaFunctionality; diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaFunctionality.cs b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaFunctionality.cs index 1bf052a63..ac89125fd 100644 --- a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaFunctionality.cs +++ b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/VgaFunctionality.cs @@ -792,9 +792,9 @@ private SegmentedAddress GetInterruptVectorAddress(byte vector) { return new SegmentedAddress(_interruptVectorTable[vector]); } - private int MemCmp(IReadOnlyList bytes, ushort segment, ushort offset, int length) { + private int MemCmp(ReadOnlySpan bytes, ushort segment, ushort offset, int length) { int i = 0; - while (length-- > 0 && i < bytes.Count) { + while (length-- > 0 && i < bytes.Length) { int difference = bytes[i] - _memory.UInt8[segment, offset]; if (difference != 0) { return difference < 0 ? -1 : 1; @@ -1245,7 +1245,7 @@ private void SetAttributeControllerIndex(byte value) { WriteByteToIoPort(value, VgaPort.AttributeAddress); } - private void WriteToDac(IReadOnlyList palette, byte startIndex, int count) { + private void WriteToDac(ReadOnlySpan palette, byte startIndex, int count) { WriteByteToIoPort(startIndex, VgaPort.DacAddressWriteIndex); int i = 0; while (count > 0) { @@ -1333,26 +1333,26 @@ private void FillRegisters(VideoMode videoMode) { WriteToMiscellaneousOutput(miscellaneousRegisterValue); } - private void SetCrtControllerRegisters(VgaPort crtControllerPort, IReadOnlyList videoModeCrtControllerRegisterValues) { + private void SetCrtControllerRegisters(VgaPort crtControllerPort, ReadOnlySpan videoModeCrtControllerRegisterValues) { for (byte i = 0; i <= 0x18; i++) { WriteToCrtController(crtControllerPort, i, videoModeCrtControllerRegisterValues[i]); } } - private void SetGraphicsControllerRegisters(IReadOnlyList videoModeGraphicsControllerRegisterValues) { - for (byte i = 0; i < videoModeGraphicsControllerRegisterValues.Count; i++) { + private void SetGraphicsControllerRegisters(ReadOnlySpan videoModeGraphicsControllerRegisterValues) { + for (byte i = 0; i < videoModeGraphicsControllerRegisterValues.Length; i++) { WriteToGraphicsController(i, videoModeGraphicsControllerRegisterValues[i]); } } - private void SetSequencerRegisters(IReadOnlyList videoModeSequencerRegisterValues) { - for (byte i = 0; i < videoModeSequencerRegisterValues.Count; i++) { + private void SetSequencerRegisters(ReadOnlySpan videoModeSequencerRegisterValues) { + for (byte i = 0; i < videoModeSequencerRegisterValues.Length; i++) { WriteToSequencer(i, videoModeSequencerRegisterValues[i]); } } - private void SetAttributeControllerRegisters(IReadOnlyList videoModeAttributeControllerRegisterValues) { - for (byte i = 0; i < videoModeAttributeControllerRegisterValues.Count; i++) { + private void SetAttributeControllerRegisters(ReadOnlySpan videoModeAttributeControllerRegisterValues) { + for (byte i = 0; i < videoModeAttributeControllerRegisterValues.Length; i++) { WriteToAttributeController(i, videoModeAttributeControllerRegisterValues[i]); } } diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs index f778a9141..53b7029e6 100644 --- a/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs +++ b/src/Spice86.Core/Emulator/OperatingSystem/DosPathResolver.cs @@ -41,7 +41,7 @@ public DosPathResolver(string? cDriveFolderPath, string? executablePath, char cu /// public DosFileOperationResult GetCurrentDosDirectory(byte driveNumber, out string currentDir) { //0 = default drive - if (driveNumber == 0 && _driveMap.Any()) { + if (driveNumber == 0 && _driveMap.Count > 0) { MountedFolder mountedFolder = _driveMap[_currentDrive]; currentDir = mountedFolder.CurrentDosDirectory; return DosFileOperationResult.NoValue(); diff --git a/src/Spice86.Core/Emulator/VM/EmulatorBreakpointsManager.cs b/src/Spice86.Core/Emulator/VM/EmulatorBreakpointsManager.cs index 1603be1cb..1841bd47b 100644 --- a/src/Spice86.Core/Emulator/VM/EmulatorBreakpointsManager.cs +++ b/src/Spice86.Core/Emulator/VM/EmulatorBreakpointsManager.cs @@ -49,11 +49,8 @@ public void OnMachineStop() { /// /// The breakpoint to toggle. /// True to turn the breakpoint on, false to turn it off. - public void ToggleBreakPoint(BreakPoint? breakPoint, bool on) { - if (breakPoint is null) { - return; - } - BreakPointType? breakPointType = breakPoint.BreakPointType; + public void ToggleBreakPoint(BreakPoint breakPoint, bool on) { + BreakPointType breakPointType = breakPoint.BreakPointType; switch (breakPointType) { case BreakPointType.EXECUTION: _executionBreakPoints.ToggleBreakPoint(breakPoint, on); diff --git a/src/Spice86/App.axaml b/src/Spice86/App.axaml index e04817df9..c69487e25 100644 --- a/src/Spice86/App.axaml +++ b/src/Spice86/App.axaml @@ -1,27 +1,28 @@ + x:Class="Spice86.App" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" + RequestedThemeVariant="Default"> - - - avares://Spice86/Assets#Roboto Mono - Consolas - - - - - + + + avares://Spice86/Assets#Roboto Mono + Consolas + + + + + - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/Spice86/Controls/GroupBox.cs b/src/Spice86/Controls/GroupBox.cs index b9a6c3161..cf87c7828 100644 --- a/src/Spice86/Controls/GroupBox.cs +++ b/src/Spice86/Controls/GroupBox.cs @@ -7,7 +7,7 @@ namespace Spice86.Controls; /// internal sealed class GroupBox : HeaderedContentControl { static GroupBox() { - IsTabStopProperty.OverrideDefaultValue(typeof(GroupBox), false); - FocusableProperty.OverrideDefaultValue(typeof(GroupBox), false); + IsTabStopProperty.OverrideDefaultValue(false); + FocusableProperty.OverrideDefaultValue(false); } } \ No newline at end of file diff --git a/src/Spice86/Controls/StatusBar.cs b/src/Spice86/Controls/StatusBar.cs index 3503d3172..2b93d26e2 100644 --- a/src/Spice86/Controls/StatusBar.cs +++ b/src/Spice86/Controls/StatusBar.cs @@ -16,7 +16,7 @@ internal sealed class StatusBar : StackPanel { protected override Type StyleKeyOverride { get; } = typeof(StackPanel); static StatusBar() { - OrientationProperty.OverrideDefaultValue(typeof(StatusBar), Orientation.Horizontal); + OrientationProperty.OverrideDefaultValue(Orientation.Horizontal); HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Stretch); } } \ No newline at end of file diff --git a/src/Spice86/Controls/StatusBarItem.cs b/src/Spice86/Controls/StatusBarItem.cs index 4aff1e4b2..475b4fe36 100644 --- a/src/Spice86/Controls/StatusBarItem.cs +++ b/src/Spice86/Controls/StatusBarItem.cs @@ -9,6 +9,6 @@ namespace Spice86.Controls; internal sealed class StatusBarItem : ContentControl { static StatusBarItem() { HorizontalContentAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Stretch); - IsTabStopProperty.OverrideDefaultValue(typeof(StatusBarItem), false); + IsTabStopProperty.OverrideDefaultValue(false); } } \ No newline at end of file diff --git a/src/Spice86/Converters/BoolToBrushConverter.cs b/src/Spice86/Converters/BreakpointToBrushConverter.cs similarity index 56% rename from src/Spice86/Converters/BoolToBrushConverter.cs rename to src/Spice86/Converters/BreakpointToBrushConverter.cs index 96fb082d2..d2d8fb48f 100644 --- a/src/Spice86/Converters/BoolToBrushConverter.cs +++ b/src/Spice86/Converters/BreakpointToBrushConverter.cs @@ -6,21 +6,31 @@ using Avalonia.Media; using Avalonia.Styling; +using Spice86.ViewModels; + using System; using System.Globalization; -internal class BoolToBrushConverter : IValueConverter { +internal class BreakpointToBrushConverter : IValueConverter { private static readonly SolidColorBrush DarkRed = new SolidColorBrush(Color.FromRgb(128, 0,0)); private static readonly SolidColorBrush LightRed = new SolidColorBrush(Color.FromRgb(255, 150,150)); + + private static readonly SolidColorBrush LightGrey = new SolidColorBrush(Color.FromRgb(240, 240, 240)); + private static readonly SolidColorBrush DarkGrey = new SolidColorBrush(Color.FromRgb(64, 64, 64)); + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - if (value is bool source) { - if(source is true) { - ThemeVariant themeVariant = Application.Current!.ActualThemeVariant; + if (value is BreakpointViewModel source) { + ThemeVariant themeVariant = Application.Current!.ActualThemeVariant; + if (source.IsEnabled) { return themeVariant == ThemeVariant.Dark ? DarkRed : LightRed; + } + else { + return themeVariant == ThemeVariant.Dark ? DarkGrey : LightGrey; } + } + else { return null; } - return BindingOperations.DoNothing; } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { diff --git a/src/Spice86/Mappers/MapperExtensions.cs b/src/Spice86/Mappers/MapperExtensions.cs new file mode 100644 index 000000000..7deb624b8 --- /dev/null +++ b/src/Spice86/Mappers/MapperExtensions.cs @@ -0,0 +1,207 @@ +namespace Spice86.Mappers; + +using Spice86.Core.Emulator.CPU; +using Spice86.Core.Emulator.Devices.Sound.Midi; +using Spice86.Core.Emulator.Devices.Video; +using Spice86.Models.Debugging; + +public static class MapperExtensions { + public static void CopyToStateInfo(this State state, StateInfo stateInfo) { + stateInfo.AH = state.AH; + stateInfo.AL = state.AL; + stateInfo.AX = state.AX; + stateInfo.EAX = state.EAX; + stateInfo.BH = state.BH; + stateInfo.BL = state.BL; + stateInfo.BX = state.BX; + stateInfo.EBX = state.EBX; + stateInfo.CH = state.CH; + stateInfo.CL = state.CL; + stateInfo.CX = state.CX; + stateInfo.ECX = state.ECX; + stateInfo.DH = state.DH; + stateInfo.DL = state.DL; + stateInfo.DX = state.DX; + stateInfo.EDX = state.EDX; + stateInfo.DI = state.DI; + stateInfo.EDI = state.EDI; + stateInfo.SI = state.SI; + stateInfo.ES = state.ES; + stateInfo.BP = state.BP; + stateInfo.EBP = state.EBP; + stateInfo.SP = state.SP; + stateInfo.ESP = state.ESP; + stateInfo.CS = state.CS; + stateInfo.DS = state.DS; + stateInfo.ES = state.ES; + stateInfo.FS = state.FS; + stateInfo.GS = state.GS; + stateInfo.SS = state.SS; + stateInfo.IP = state.IP; + stateInfo.Cycles = state.Cycles; + stateInfo.IpPhysicalAddress = state.IpPhysicalAddress; + stateInfo.StackPhysicalAddress = state.StackPhysicalAddress; + stateInfo.SegmentOverrideIndex = state.SegmentOverrideIndex; + } + + public static void CopyFlagsToStateInfo(this State state, CpuFlagsInfo cpuFlagsInfo) { + cpuFlagsInfo.AuxiliaryFlag = state.AuxiliaryFlag; + cpuFlagsInfo.CarryFlag = state.CarryFlag; + cpuFlagsInfo.DirectionFlag = state.DirectionFlag; + cpuFlagsInfo.InterruptFlag = state.InterruptFlag; + cpuFlagsInfo.OverflowFlag = state.OverflowFlag; + cpuFlagsInfo.ParityFlag = state.ParityFlag; + cpuFlagsInfo.ZeroFlag = state.ZeroFlag; + cpuFlagsInfo.ContinueZeroFlag = state.ContinueZeroFlagValue; + } + + public static void CopyToMidiInfo(this Midi midi, MidiInfo midiInfo) { + midiInfo.LastPortRead = midi.LastPortRead; + midiInfo.LastPortWritten = midi.LastPortWritten; + midiInfo.LastPortWrittenValue = midi.LastPortWrittenValue; + } + + public static void CopyToVideoCardInfo(this IVgaRenderer vgaRenderer, VideoCardInfo videoCardInfo) { + videoCardInfo.RendererWidth = vgaRenderer.Width; + videoCardInfo.RendererHeight = vgaRenderer.Height; + videoCardInfo.RendererBufferSize = vgaRenderer.BufferSize; + videoCardInfo.LastFrameRenderTime = vgaRenderer.LastFrameRenderTime; + } + + public static void CopyToVideoCardInfo(this IVideoState videoState, VideoCardInfo videoCardInfo) { + try { + videoCardInfo.GeneralMiscellaneousOutputRegister = videoState.GeneralRegisters.MiscellaneousOutput.Value; + videoCardInfo.GeneralClockSelect = videoState.GeneralRegisters.MiscellaneousOutput.ClockSelect; + videoCardInfo.GeneralEnableRam = videoState.GeneralRegisters.MiscellaneousOutput.EnableRam; + videoCardInfo.GeneralVerticalSize = videoState.GeneralRegisters.MiscellaneousOutput.VerticalSize; + videoCardInfo.GeneralHorizontalSyncPolarity = videoState.GeneralRegisters.MiscellaneousOutput.HorizontalSyncPolarity; + videoCardInfo.GeneralVerticalSyncPolarity = videoState.GeneralRegisters.MiscellaneousOutput.VerticalSyncPolarity; + videoCardInfo.GeneralIoAddressSelect = videoState.GeneralRegisters.MiscellaneousOutput.IoAddressSelect; + videoCardInfo.GeneralOddPageSelect = videoState.GeneralRegisters.MiscellaneousOutput.EvenPageSelect; + videoCardInfo.GeneralInputStatusRegister0 = videoState.GeneralRegisters.InputStatusRegister0.Value; + videoCardInfo.GeneralCrtInterrupt = videoState.GeneralRegisters.InputStatusRegister0.CrtInterrupt; + videoCardInfo.GeneralSwitchSense = videoState.GeneralRegisters.InputStatusRegister0.SwitchSense; + videoCardInfo.GeneralInputStatusRegister1 = videoState.GeneralRegisters.InputStatusRegister1.Value; + videoCardInfo.GeneralDisplayDisabled = videoState.GeneralRegisters.InputStatusRegister1.DisplayDisabled; + videoCardInfo.GeneralVerticalRetrace = videoState.GeneralRegisters.InputStatusRegister1.VerticalRetrace; + + videoCardInfo.DacReadIndex = videoState.DacRegisters.IndexRegisterReadMode; + videoCardInfo.DacWriteIndex = videoState.DacRegisters.IndexRegisterWriteMode; + videoCardInfo.DacPixelMask = videoState.DacRegisters.PixelMask; + videoCardInfo.DacData = videoState.DacRegisters.DataPeek; + + videoCardInfo.AttributeControllerColorSelect = videoState.AttributeControllerRegisters.ColorSelectRegister.Value; + videoCardInfo.AttributeControllerOverscanColor = videoState.AttributeControllerRegisters.OverscanColor; + videoCardInfo.AttributeControllerAttributeModeControl = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.Value; + videoCardInfo.AttributeControllerVideoOutput45Select = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.VideoOutput45Select; + videoCardInfo.AttributeControllerPixelWidth8 = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.PixelWidth8; + videoCardInfo.AttributeControllerPixelPanningCompatibility = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.PixelPanningCompatibility; + videoCardInfo.AttributeControllerBlinkingEnabled = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.BlinkingEnabled; + videoCardInfo.AttributeControllerLineGraphicsEnabled = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.LineGraphicsEnabled; + videoCardInfo.AttributeControllerMonochromeEmulation = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.MonochromeEmulation; + videoCardInfo.AttributeControllerGraphicsMode = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.GraphicsMode; + videoCardInfo.AttributeControllerColorPlaneEnable = videoState.AttributeControllerRegisters.ColorPlaneEnableRegister.Value; + videoCardInfo.AttributeControllerHorizontalPixelPanning = videoState.AttributeControllerRegisters.HorizontalPixelPanning; + + videoCardInfo.CrtControllerAddressWrap = videoState.CrtControllerRegisters.CrtModeControlRegister.AddressWrap; + videoCardInfo.CrtControllerBytePanning = videoState.CrtControllerRegisters.PresetRowScanRegister.BytePanning; + videoCardInfo.CrtControllerByteWordMode = videoState.CrtControllerRegisters.CrtModeControlRegister.ByteWordMode; + videoCardInfo.CrtControllerCharacterCellHeightRegister = videoState.CrtControllerRegisters.MaximumScanlineRegister.Value; + videoCardInfo.CrtControllerCharacterCellHeight = videoState.CrtControllerRegisters.MaximumScanlineRegister.MaximumScanline; + videoCardInfo.CrtControllerCompatibilityModeSupport = videoState.CrtControllerRegisters.CrtModeControlRegister.CompatibilityModeSupport; + videoCardInfo.CrtControllerCompatibleRead = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.CompatibleRead; + videoCardInfo.CrtControllerCountByFour = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.CountByFour; + videoCardInfo.CrtControllerCountByTwo = videoState.CrtControllerRegisters.CrtModeControlRegister.CountByTwo; + videoCardInfo.CrtControllerCrtcScanDouble = videoState.CrtControllerRegisters.MaximumScanlineRegister.CrtcScanDouble; + videoCardInfo.CrtControllerCrtModeControl = videoState.CrtControllerRegisters.CrtModeControlRegister.Value; + videoCardInfo.CrtControllerCursorEnd = videoState.CrtControllerRegisters.TextCursorEndRegister.Value; + videoCardInfo.CrtControllerCursorLocationHigh = videoState.CrtControllerRegisters.TextCursorLocationHigh; + videoCardInfo.CrtControllerCursorLocationLow = videoState.CrtControllerRegisters.TextCursorLocationLow; + videoCardInfo.CrtControllerCursorStart = videoState.CrtControllerRegisters.TextCursorStartRegister.Value; + videoCardInfo.CrtControllerDisableTextCursor = videoState.CrtControllerRegisters.TextCursorStartRegister.DisableTextCursor; + videoCardInfo.CrtControllerDisableVerticalInterrupt = videoState.CrtControllerRegisters.VerticalSyncEndRegister.DisableVerticalInterrupt; + videoCardInfo.CrtControllerDisplayEnableSkew = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.DisplayEnableSkew; + videoCardInfo.CrtControllerDoubleWordMode = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.DoubleWordMode; + videoCardInfo.CrtControllerEndHorizontalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.Value; + videoCardInfo.CrtControllerEndHorizontalDisplay = videoState.CrtControllerRegisters.HorizontalDisplayEnd; + videoCardInfo.CrtControllerEndHorizontalRetrace = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.Value; + videoCardInfo.CrtControllerEndVerticalBlanking = videoState.CrtControllerRegisters.VerticalBlankingEnd; + videoCardInfo.CrtControllerHorizontalBlankingEnd = videoState.CrtControllerRegisters.HorizontalBlankingEndValue; + videoCardInfo.CrtControllerHorizontalSyncDelay = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.HorizontalSyncDelay; + videoCardInfo.CrtControllerHorizontalSyncEnd = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.HorizontalSyncEnd; + videoCardInfo.CrtControllerHorizontalTotal = videoState.CrtControllerRegisters.HorizontalTotal; + videoCardInfo.CrtControllerLineCompareRegister = videoState.CrtControllerRegisters.LineCompare; + videoCardInfo.CrtControllerLineCompare = videoState.CrtControllerRegisters.LineCompareValue; + videoCardInfo.CrtControllerOffset = videoState.CrtControllerRegisters.Offset; + videoCardInfo.CrtControllerOverflow = videoState.CrtControllerRegisters.OverflowRegister.Value; + videoCardInfo.CrtControllerPresetRowScan = videoState.CrtControllerRegisters.PresetRowScanRegister.PresetRowScan; + videoCardInfo.CrtControllerPresetRowScanRegister = videoState.CrtControllerRegisters.PresetRowScanRegister.Value; + videoCardInfo.CrtControllerRefreshCyclesPerScanline = videoState.CrtControllerRegisters.VerticalSyncEndRegister.RefreshCyclesPerScanline; + videoCardInfo.CrtControllerSelectRowScanCounter = videoState.CrtControllerRegisters.CrtModeControlRegister.SelectRowScanCounter; + videoCardInfo.CrtControllerStartAddress = videoState.CrtControllerRegisters.ScreenStartAddress; + videoCardInfo.CrtControllerStartAddressHigh = videoState.CrtControllerRegisters.ScreenStartAddressHigh; + videoCardInfo.CrtControllerStartAddressLow = videoState.CrtControllerRegisters.ScreenStartAddressLow; + videoCardInfo.CrtControllerStartHorizontalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingStart; + videoCardInfo.CrtControllerStartHorizontalRetrace = videoState.CrtControllerRegisters.HorizontalSyncStart; + videoCardInfo.CrtControllerStartVerticalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingStart; + videoCardInfo.CrtControllerTextCursorEnd = videoState.CrtControllerRegisters.TextCursorEndRegister.TextCursorEnd; + videoCardInfo.CrtControllerTextCursorLocation = videoState.CrtControllerRegisters.TextCursorLocation; + videoCardInfo.CrtControllerTextCursorSkew = videoState.CrtControllerRegisters.TextCursorEndRegister.TextCursorSkew; + videoCardInfo.CrtControllerTextCursorStart = videoState.CrtControllerRegisters.TextCursorStartRegister.TextCursorStart; + videoCardInfo.CrtControllerTimingEnable = videoState.CrtControllerRegisters.CrtModeControlRegister.TimingEnable; + videoCardInfo.CrtControllerUnderlineLocation = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.Value; + videoCardInfo.CrtControllerUnderlineScanline = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.UnderlineScanline; + videoCardInfo.CrtControllerVerticalBlankingStart = videoState.CrtControllerRegisters.VerticalBlankingStartValue; + videoCardInfo.CrtControllerVerticalDisplayEnd = videoState.CrtControllerRegisters.VerticalDisplayEndValue; + videoCardInfo.CrtControllerVerticalDisplayEndRegister = videoState.CrtControllerRegisters.VerticalDisplayEnd; + videoCardInfo.CrtControllerVerticalRetraceEnd = videoState.CrtControllerRegisters.VerticalSyncEndRegister.Value; + videoCardInfo.CrtControllerVerticalRetraceStart = videoState.CrtControllerRegisters.VerticalSyncStart; + videoCardInfo.CrtControllerVerticalSyncStart = videoState.CrtControllerRegisters.VerticalSyncStartValue; + videoCardInfo.CrtControllerVerticalTimingHalved = videoState.CrtControllerRegisters.CrtModeControlRegister.VerticalTimingHalved; + videoCardInfo.CrtControllerVerticalTotal = videoState.CrtControllerRegisters.VerticalTotalValue; + videoCardInfo.CrtControllerVerticalTotalRegister = videoState.CrtControllerRegisters.VerticalTotal; + videoCardInfo.CrtControllerWriteProtect = videoState.CrtControllerRegisters.VerticalSyncEndRegister.WriteProtect; + + videoCardInfo.GraphicsDataRotate = videoState.GraphicsControllerRegisters.DataRotateRegister.Value; + videoCardInfo.GraphicsRotateCount = videoState.GraphicsControllerRegisters.DataRotateRegister.RotateCount; + videoCardInfo.GraphicsFunctionSelect = videoState.GraphicsControllerRegisters.DataRotateRegister.FunctionSelect; + videoCardInfo.GraphicsBitMask = videoState.GraphicsControllerRegisters.BitMask; + videoCardInfo.GraphicsColorCompare = videoState.GraphicsControllerRegisters.ColorCompare; + videoCardInfo.GraphicsReadMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.ReadMode; + videoCardInfo.GraphicsWriteMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.WriteMode; + videoCardInfo.GraphicsOddEven = videoState.GraphicsControllerRegisters.GraphicsModeRegister.OddEven; + videoCardInfo.GraphicsShiftRegisterMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.ShiftRegisterMode; + videoCardInfo.GraphicsIn256ColorMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.In256ColorMode; + videoCardInfo.GraphicsModeRegister = videoState.GraphicsControllerRegisters.GraphicsModeRegister.Value; + videoCardInfo.GraphicsMiscellaneousGraphics = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.Value; + videoCardInfo.GraphicsGraphicsMode = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.GraphicsMode; + videoCardInfo.GraphicsChainOddMapsToEven = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.ChainOddMapsToEven; + videoCardInfo.GraphicsMemoryMap = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.MemoryMap; + videoCardInfo.GraphicsReadMapSelect = videoState.GraphicsControllerRegisters.ReadMapSelectRegister.PlaneSelect; + videoCardInfo.GraphicsSetReset = videoState.GraphicsControllerRegisters.SetReset.Value; + videoCardInfo.GraphicsColorDontCare = videoState.GraphicsControllerRegisters.ColorDontCare; + videoCardInfo.GraphicsEnableSetReset = videoState.GraphicsControllerRegisters.EnableSetReset.Value; + + videoCardInfo.SequencerResetRegister = videoState.SequencerRegisters.ResetRegister.Value; + videoCardInfo.SequencerSynchronousReset = videoState.SequencerRegisters.ResetRegister.SynchronousReset; + videoCardInfo.SequencerAsynchronousReset = videoState.SequencerRegisters.ResetRegister.AsynchronousReset; + videoCardInfo.SequencerClockingModeRegister = videoState.SequencerRegisters.ClockingModeRegister.Value; + videoCardInfo.SequencerDotsPerClock = videoState.SequencerRegisters.ClockingModeRegister.DotsPerClock; + videoCardInfo.SequencerShiftLoad = videoState.SequencerRegisters.ClockingModeRegister.ShiftLoad; + videoCardInfo.SequencerDotClock = videoState.SequencerRegisters.ClockingModeRegister.HalfDotClock; + videoCardInfo.SequencerShift4 = videoState.SequencerRegisters.ClockingModeRegister.Shift4; + videoCardInfo.SequencerScreenOff = videoState.SequencerRegisters.ClockingModeRegister.ScreenOff; + videoCardInfo.SequencerPlaneMask = videoState.SequencerRegisters.PlaneMaskRegister.Value; + videoCardInfo.SequencerCharacterMapSelect = videoState.SequencerRegisters.CharacterMapSelectRegister.Value; + videoCardInfo.SequencerCharacterMapA = videoState.SequencerRegisters.CharacterMapSelectRegister.CharacterMapA; + videoCardInfo.SequencerCharacterMapB = videoState.SequencerRegisters.CharacterMapSelectRegister.CharacterMapB; + videoCardInfo.SequencerSequencerMemoryMode = videoState.SequencerRegisters.MemoryModeRegister.Value; + videoCardInfo.SequencerExtendedMemory = videoState.SequencerRegisters.MemoryModeRegister.ExtendedMemory; + videoCardInfo.SequencerOddEvenMode = videoState.SequencerRegisters.MemoryModeRegister.OddEvenMode; + videoCardInfo.SequencerChain4Mode = videoState.SequencerRegisters.MemoryModeRegister.Chain4Mode; + } catch (IndexOutOfRangeException) { + //A read during emulation provoked an OutOfRangeException (for example, in the DacRegisters). + // Ignore it. + } + } +} diff --git a/src/Spice86/MemoryWrappers/InstructionsDecoder.cs b/src/Spice86/MemoryWrappers/InstructionsDecoder.cs new file mode 100644 index 000000000..fb7b99ae6 --- /dev/null +++ b/src/Spice86/MemoryWrappers/InstructionsDecoder.cs @@ -0,0 +1,83 @@ +namespace Spice86.MemoryWrappers; + +using Iced.Intel; + +using Spice86.Core.Emulator.CPU; +using Spice86.Core.Emulator.Function; +using Spice86.Core.Emulator.Memory; +using Spice86.Models.Debugging; +using Spice86.ViewModels; + +using System; +using System.Collections.Generic; + +internal class InstructionsDecoder { + private readonly IMemory _memory; + private readonly State _state; + private readonly IDictionary _functions; + private readonly BreakpointsViewModel _breakpointsViewModel; + + public InstructionsDecoder( + IMemory memory, State state, IDictionary functions, BreakpointsViewModel breakpointsViewModel) { + _memory = memory; + _state = state; + _functions = functions; + _breakpointsViewModel = breakpointsViewModel; + } + + public List DecodeInstructions(uint startAddress, + int numberOfInstructionsShown) { + CodeReader codeReader = CreateCodeReader(_memory, out CodeMemoryStream emulatedMemoryStream); + using CodeMemoryStream codeMemoryStream = emulatedMemoryStream; + Decoder decoder = InitializeDecoder(codeReader, startAddress); + int byteOffset = 0; + codeMemoryStream.Position = startAddress; + var instructions = new List(); + while (instructions.Count < numberOfInstructionsShown) { + long instructionAddress = codeMemoryStream.Position; + decoder.Decode(out Instruction instruction); + CpuInstructionInfo instructionInfo = new() { + Instruction = instruction, + Address = (uint)instructionAddress, + AddressInformation = $"{instructionAddress} (0x{_state.CS:x4}:{(ushort)(_state.IP + byteOffset):X4})", + Length = instruction.Length, + IP16 = instruction.IP16, + IP32 = instruction.IP32, + MemorySegment = instruction.MemorySegment, + SegmentPrefix = instruction.SegmentPrefix, + IsStackInstruction = instruction.IsStackInstruction, + IsIPRelativeMemoryOperand = instruction.IsIPRelativeMemoryOperand, + IPRelativeMemoryAddress = instruction.IPRelativeMemoryAddress, + FlowControl = instruction.FlowControl, + Bytes = $"""{Convert.ToHexString(_memory.GetData((uint)instructionAddress, (uint)instruction.Length))} ({instruction.Length})""" + }; + if (_functions.TryGetValue((uint)instructionAddress, out FunctionInformation? functionInformation)) { + instructionInfo.FunctionName = functionInformation.Name; + } + instructionInfo.SegmentedAddress = new(_state.CS, (ushort)(_state.IP + byteOffset)); + instructionInfo.Breakpoint = _breakpointsViewModel.GetBreakpoint(instructionInfo); + instructionInfo.StringRepresentation = + $"{instructionInfo.Address:X4} ({instructionInfo.SegmentedAddress}): {instruction} ({instructionInfo.Bytes})"; + if (instructionAddress == _state.IpPhysicalAddress) { + instructionInfo.IsCsIp = true; + } + + instructions.Add(instructionInfo); + byteOffset += instruction.Length; + } + + return instructions; + } + + private static Decoder InitializeDecoder(CodeReader codeReader, uint currentIp) { + Decoder decoder = Decoder.Create(16, codeReader, currentIp, + DecoderOptions.Loadall286 | DecoderOptions.Loadall386); + return decoder; + } + + private static CodeReader CreateCodeReader(IMemory memory, out CodeMemoryStream codeMemoryStream) { + codeMemoryStream = new CodeMemoryStream(memory); + CodeReader codeReader = new StreamCodeReader(codeMemoryStream); + return codeReader; + } +} diff --git a/src/Spice86/MemoryWrappers/MemoryReadOnlyBitRangeUnion.cs b/src/Spice86/MemoryWrappers/MemoryReadOnlyBitRangeUnion.cs index e57411e71..5d785d32f 100644 --- a/src/Spice86/MemoryWrappers/MemoryReadOnlyBitRangeUnion.cs +++ b/src/Spice86/MemoryWrappers/MemoryReadOnlyBitRangeUnion.cs @@ -28,6 +28,8 @@ public MemoryReadOnlyBitRangeUnion(uint startAddress, uint endAddress) { public BitRange EnclosingRange { get; } public int Count => (int)(_endAddress - _startAddress); + public bool IsFragmented => false; + public event NotifyCollectionChangedEventHandler? CollectionChanged; public bool Contains(BitLocation location) { diff --git a/src/Spice86/Models/Debugging/CpuInstructionInfo.cs b/src/Spice86/Models/Debugging/CpuInstructionInfo.cs index e5f275c43..cf7284f92 100644 --- a/src/Spice86/Models/Debugging/CpuInstructionInfo.cs +++ b/src/Spice86/Models/Debugging/CpuInstructionInfo.cs @@ -5,10 +5,11 @@ namespace Spice86.Models.Debugging; using Iced.Intel; using Spice86.Shared.Emulator.Memory; +using Spice86.ViewModels; public partial class CpuInstructionInfo : ObservableObject { [ObservableProperty] private string? _stringRepresentation; - [ObservableProperty] private bool _hasBreakpoint; + [ObservableProperty] private BreakpointViewModel? _breakpoint; [ObservableProperty] private string? _functionName; [ObservableProperty] private bool _isCsIp; [ObservableProperty] private uint _address; diff --git a/src/Spice86/Spice86.csproj b/src/Spice86/Spice86.csproj index 909035924..b2f2c42d2 100644 --- a/src/Spice86/Spice86.csproj +++ b/src/Spice86/Spice86.csproj @@ -33,6 +33,7 @@ + diff --git a/src/Spice86/Spice86DependencyInjection.cs b/src/Spice86/Spice86DependencyInjection.cs index 940c522f0..b852fabe8 100644 --- a/src/Spice86/Spice86DependencyInjection.cs +++ b/src/Spice86/Spice86DependencyInjection.cs @@ -242,7 +242,7 @@ public Spice86DependencyInjection(ILoggerService loggerService, Configuration co DebugWindowViewModel? debugWindowViewModel = null; if (textClipboard != null && hostStorageProvider != null && uiThreadDispatcher != null) { IMessenger messenger = WeakReferenceMessenger.Default; - debugWindowViewModel = new DebugWindowViewModel(cpu, state, stack, memory, + debugWindowViewModel = new DebugWindowViewModel(state, stack, memory, midiDevice, videoState.DacRegisters.ArgbPalette, softwareMixer, vgaRenderer, videoState, cfgCpu.ExecutionContextManager, messenger, uiThreadDispatcher, textClipboard, hostStorageProvider, emulatorBreakpointsManager, diff --git a/src/Spice86/ViewModels/BreakpointViewModel.cs b/src/Spice86/ViewModels/BreakpointViewModel.cs index 56309ab6b..522b21a2b 100644 --- a/src/Spice86/ViewModels/BreakpointViewModel.cs +++ b/src/Spice86/ViewModels/BreakpointViewModel.cs @@ -7,25 +7,54 @@ namespace Spice86.ViewModels; using Spice86.Models.Debugging; public partial class BreakpointViewModel : ViewModelBase { - private readonly BreakPoint _breakPoint; + private readonly Action _onReached; private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager; + private AddressBreakPoint? _breakPoint; - public BreakpointViewModel(EmulatorBreakpointsManager emulatorBreakpointsManager, AddressBreakPoint breakPoint) { - _breakPoint = breakPoint; + public BreakpointViewModel( + BreakpointsViewModel breakpointsViewModel, + EmulatorBreakpointsManager emulatorBreakpointsManager, + uint address, + BreakPointType type, + bool isRemovedOnTrigger, + Action onReached, + string comment = "") { _emulatorBreakpointsManager = emulatorBreakpointsManager; - IsEnabled = true; - Address = breakPoint.Address; + Address = address; + Type = type; + IsRemovedOnTrigger = isRemovedOnTrigger; + if(IsRemovedOnTrigger) { + _onReached = () => { + breakpointsViewModel.RemoveBreakpointInternal(this); + onReached(); + }; + } else { + _onReached = onReached; + } + Comment = comment; + Enable(); } - public BreakPointType Type => _breakPoint.BreakPointType; + public BreakPointType Type { get; } - //Can't get out of sync since GDB can't be used at the same tiem as the internal debugger - [ObservableProperty] + //Can't get out of sync since GDB can't be used at the same time as the internal debugger private bool _isEnabled; - public bool IsRemovedOnTrigger => _breakPoint.IsRemovedOnTrigger; + public bool IsEnabled { + get => _isEnabled; + set { + if (value) { + Enable(); + } else { + Disable(); + } + SetProperty(ref _isEnabled, value); + } + } + + public bool IsRemovedOnTrigger { get; } - public long Address { get; } + public uint Address { get; } public void Toggle() { if (IsEnabled) { @@ -35,17 +64,38 @@ public void Toggle() { } } + [ObservableProperty] + private string? _comment; + + private AddressBreakPoint GetOrCreateBreakpoint() { + _breakPoint ??= + new AddressBreakPoint( + Type, + Address, + (_) => _onReached(), + IsRemovedOnTrigger); + return _breakPoint; + } + public void Enable() { - _emulatorBreakpointsManager.ToggleBreakPoint(_breakPoint, on: true); - IsEnabled = true; + if (IsEnabled) { + return; + } + _emulatorBreakpointsManager.ToggleBreakPoint(GetOrCreateBreakpoint(), on: true); + _isEnabled = true; + OnPropertyChanged(nameof(IsEnabled)); } public void Disable() { - _emulatorBreakpointsManager.ToggleBreakPoint(_breakPoint, on: false); - IsEnabled = false; + if (!IsEnabled) { + return; + } + _emulatorBreakpointsManager.ToggleBreakPoint(GetOrCreateBreakpoint(), on: false); + _isEnabled = false; + OnPropertyChanged(nameof(IsEnabled)); } internal bool IsFor(CpuInstructionInfo instructionInfo) { - return _breakPoint is AddressBreakPoint addressBreakPoint && addressBreakPoint.Address == instructionInfo.Address; + return Address == instructionInfo.Address; } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/BreakpointsViewModel.cs b/src/Spice86/ViewModels/BreakpointsViewModel.cs index fe5b12f2f..bd2d66642 100644 --- a/src/Spice86/ViewModels/BreakpointsViewModel.cs +++ b/src/Spice86/ViewModels/BreakpointsViewModel.cs @@ -15,6 +15,10 @@ public partial class BreakpointsViewModel : ViewModelBase { public BreakpointsViewModel(EmulatorBreakpointsManager emulatorBreakpointsManager) { _emulatorBreakpointsManager = emulatorBreakpointsManager; } + + public event Action? BreakpointDeleted; + public event Action? BreakpointEnabled; + public event Action? BreakpointDisabled; [ObservableProperty] private ObservableCollection _breakpoints = new(); @@ -23,6 +27,11 @@ public BreakpointsViewModel(EmulatorBreakpointsManager emulatorBreakpointsManage private void ToggleSelectedBreakpoint() { if (SelectedBreakpoint is not null) { SelectedBreakpoint.Toggle(); + if (SelectedBreakpoint.IsEnabled) { + BreakpointEnabled?.Invoke(SelectedBreakpoint); + } else { + BreakpointDisabled?.Invoke(SelectedBreakpoint); + } } } @@ -33,11 +42,31 @@ private void ToggleSelectedBreakpoint() { [NotifyCanExecuteChangedFor(nameof(ToggleSelectedBreakpointCommand))] private BreakpointViewModel? _selectedBreakpoint; - internal void AddAddressBreakpoint(AddressBreakPoint addressBreakPoint) { - var breakpointViewModel = new BreakpointViewModel( _emulatorBreakpointsManager, addressBreakPoint); + internal void AddUnconditionalBreakpoint(Action onReached, bool removedOnTrigger) { + _emulatorBreakpointsManager.ToggleBreakPoint( + new UnconditionalBreakPoint( + BreakPointType.EXECUTION, + (_) => onReached(), + removedOnTrigger), on: true); + } + + internal BreakpointViewModel AddAddressBreakpoint( + uint address, + BreakPointType type, + bool isRemovedOnTrigger, + Action onReached, + string comment = "") { + var breakpointViewModel = new BreakpointViewModel( + this, + _emulatorBreakpointsManager, + address, type, isRemovedOnTrigger, onReached, comment); Breakpoints.Add(breakpointViewModel); SelectedBreakpoint = breakpointViewModel; - SelectedBreakpoint.Enable(); + return breakpointViewModel; + } + + internal BreakpointViewModel? GetBreakpoint(CpuInstructionInfo instructionInfo) { + return Breakpoints.FirstOrDefault(x => x.IsFor(instructionInfo)); } private bool RemoveBreakpointCanExecute() => SelectedBreakpoint is not null; @@ -45,19 +74,11 @@ internal void AddAddressBreakpoint(AddressBreakPoint addressBreakPoint) { [RelayCommand(CanExecute = nameof(RemoveBreakpointCanExecute))] private void RemoveBreakpoint() { - if (SelectedBreakpoint is not null) { - DeleteBreakpoint(SelectedBreakpoint); - } - } - - internal void RemoveUserExecutionBreakpoint(CpuInstructionInfo instructionInfo) { - DeleteBreakpoint(Breakpoints.FirstOrDefault(x => x.IsFor(instructionInfo) && x is - { IsRemovedOnTrigger: false, Type: BreakPointType.EXECUTION })); + DeleteBreakpoint(SelectedBreakpoint); } - internal bool HasUserExecutionBreakpoint(CpuInstructionInfo instructionInfo) { - return Breakpoints.Any(x => x.IsFor(instructionInfo) && x is - { IsRemovedOnTrigger: false, Type: BreakPointType.EXECUTION }); + internal void RemoveBreakpointInternal(BreakpointViewModel vm) { + DeleteBreakpoint(vm); } private void DeleteBreakpoint(BreakpointViewModel? breakpoint) { @@ -66,5 +87,6 @@ private void DeleteBreakpoint(BreakpointViewModel? breakpoint) { } breakpoint.Disable(); Breakpoints.Remove(breakpoint); + BreakpointDeleted?.Invoke(breakpoint); } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/CpuViewModel.cs b/src/Spice86/ViewModels/CpuViewModel.cs index f93e1e8e0..a50a21648 100644 --- a/src/Spice86/ViewModels/CpuViewModel.cs +++ b/src/Spice86/ViewModels/CpuViewModel.cs @@ -8,6 +8,7 @@ namespace Spice86.ViewModels; using Spice86.Core.Emulator.Memory; using Spice86.Core.Emulator.VM; using Spice86.Infrastructure; +using Spice86.Mappers; using Spice86.Models.Debugging; using System.ComponentModel; @@ -71,51 +72,10 @@ void OnStatePropertyChanged(object? sender, PropertyChangedEventArgs e) { private string? _espString; private void UpdateCpuState(State state) { - State.AH = state.AH; - State.AL = state.AL; - State.AX = state.AX; - State.EAX = state.EAX; - State.BH = state.BH; - State.BL = state.BL; - State.BX = state.BX; - State.EBX = state.EBX; - State.CH = state.CH; - State.CL = state.CL; - State.CX = state.CX; - State.ECX = state.ECX; - State.DH = state.DH; - State.DL = state.DL; - State.DX = state.DX; - State.EDX = state.EDX; - State.DI = state.DI; - State.EDI = state.EDI; - State.SI = state.SI; - State.ES = state.ES; - State.BP = state.BP; - State.EBP = state.EBP; - State.SP = state.SP; - State.ESP = state.ESP; - State.CS = state.CS; - State.DS = state.DS; - State.ES = state.ES; - State.FS = state.FS; - State.GS = state.GS; - State.SS = state.SS; - State.IP = state.IP; + state.CopyToStateInfo(this.State); + state.CopyFlagsToStateInfo(this.Flags); EspString = _memory.GetZeroTerminatedString(State.ESP, 32); EsiString = _memory.GetZeroTerminatedString(State.ESI, 32); EdiString = _memory.GetZeroTerminatedString(State.EDI, 32); - State.Cycles = state.Cycles; - State.IpPhysicalAddress = state.IpPhysicalAddress; - State.StackPhysicalAddress = state.StackPhysicalAddress; - State.SegmentOverrideIndex = state.SegmentOverrideIndex; - Flags.AuxiliaryFlag = state.AuxiliaryFlag; - Flags.CarryFlag = state.CarryFlag; - Flags.DirectionFlag = state.DirectionFlag; - Flags.InterruptFlag = state.InterruptFlag; - Flags.OverflowFlag = state.OverflowFlag; - Flags.ParityFlag = state.ParityFlag; - Flags.ZeroFlag = state.ZeroFlag; - Flags.ContinueZeroFlag = state.ContinueZeroFlagValue; } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/DebugWindowViewModel.cs b/src/Spice86/ViewModels/DebugWindowViewModel.cs index 4177a8370..b41efca18 100644 --- a/src/Spice86/ViewModels/DebugWindowViewModel.cs +++ b/src/Spice86/ViewModels/DebugWindowViewModel.cs @@ -1,13 +1,11 @@ namespace Spice86.ViewModels; using Avalonia.Collections; -using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; -using Spice86.Core.Emulator; using Spice86.Core.Emulator.CPU; using Spice86.Core.Emulator.CPU.CfgCpu; using Spice86.Core.Emulator.Devices.Sound; @@ -63,7 +61,7 @@ public partial class DebugWindowViewModel : ViewModelBase, private readonly IPauseHandler _pauseHandler; - public DebugWindowViewModel(IInstructionExecutor cpu, State cpuState, Stack stack, IMemory memory, Midi externalMidiDevice, + public DebugWindowViewModel(State cpuState, Stack stack, IMemory memory, Midi externalMidiDevice, ArgbPalette argbPalette, SoftwareMixer softwareMixer, IVgaRenderer vgaRenderer, VideoState videoState, ExecutionContextManager executionContextManager, IMessenger messenger, IUIDispatcher uiDispatcher, ITextClipboard textClipboard, IHostStorageProvider storageProvider, EmulatorBreakpointsManager emulatorBreakpointsManager, @@ -81,8 +79,11 @@ public DebugWindowViewModel(IInstructionExecutor cpu, State cpuState, Stack stac pauseHandler.Pausing += () => uiDispatcher.Post(() => IsPaused = true); pauseHandler.Resumed += () => uiDispatcher.Post(() => IsPaused = false); DisassemblyViewModel disassemblyVm = new( - cpu, memory, cpuState, functionsInformation.ToDictionary(x => x.Key.ToPhysical(), x => x.Value), - BreakpointsViewModel, emulatorBreakpointsManager, pauseHandler, + emulatorBreakpointsManager, + memory, cpuState, + functionsInformation.ToDictionary(x => + x.Key.ToPhysical(), x => x.Value), + BreakpointsViewModel, pauseHandler, uiDispatcher, messenger, textClipboard); DisassemblyViewModels.Add(disassemblyVm); PaletteViewModel = new(argbPalette, uiDispatcher); diff --git a/src/Spice86/ViewModels/DisassemblyViewModel.cs b/src/Spice86/ViewModels/DisassemblyViewModel.cs index e2df25779..7185d9f96 100644 --- a/src/Spice86/ViewModels/DisassemblyViewModel.cs +++ b/src/Spice86/ViewModels/DisassemblyViewModel.cs @@ -6,8 +6,6 @@ namespace Spice86.ViewModels; using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; -using Iced.Intel; - using Spice86.Core.Emulator.CPU; using Spice86.Core.Emulator.Function; using Spice86.Core.Emulator.Memory; @@ -23,67 +21,75 @@ namespace Spice86.ViewModels; using System.Globalization; public partial class DisassemblyViewModel : ViewModelWithErrorDialog { + private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager; private readonly IMemory _memory; private readonly State _state; private readonly IMessenger _messenger; private readonly IPauseHandler _pauseHandler; - private readonly IInstructionExecutor _cpu; private readonly BreakpointsViewModel _breakpointsViewModel; - private readonly EmulatorBreakpointsManager _emulatorBreakpointsManager; private readonly IDictionary _functionsInformation; + private readonly InstructionsDecoder _instructionsDecoder; + private bool _didCsIpGoOutOfCurrentListing = true; public DisassemblyViewModel( - IInstructionExecutor cpu, IMemory memory, State state, + EmulatorBreakpointsManager emulatorBreakpointsManager, + IMemory memory, State state, IDictionary functionsInformation, - BreakpointsViewModel breakpointsViewModel, EmulatorBreakpointsManager emulatorBreakpointsManager, + BreakpointsViewModel breakpointsViewModel, IPauseHandler pauseHandler, IUIDispatcher uiDispatcher, IMessenger messenger, ITextClipboard textClipboard, bool canCloseTab = false) : base(uiDispatcher, textClipboard) { - _cpu = cpu; + _emulatorBreakpointsManager = emulatorBreakpointsManager; _functionsInformation = functionsInformation; Functions = new(functionsInformation .Select(x => new FunctionInfo() { Name = x.Value.Name, Address = x.Key, - }).OrderBy(x => x.Address)); + }).OrderBy(x => x.Address)); AreFunctionInformationProvided = functionsInformation.Count > 0; - _emulatorBreakpointsManager = emulatorBreakpointsManager; _breakpointsViewModel = breakpointsViewModel; _messenger = messenger; _memory = memory; _state = state; _pauseHandler = pauseHandler; + _instructionsDecoder = new(memory, state, functionsInformation, breakpointsViewModel); IsPaused = pauseHandler.IsPaused; pauseHandler.Pausing += OnPausing; - pauseHandler.Resumed += () => _uiDispatcher.Post(() => IsPaused = false); + pauseHandler.Resumed += OnResumed; CanCloseTab = canCloseTab; + breakpointsViewModel.BreakpointDeleted += OnBreakPointUpdateFromBreakpointsViewModel; + breakpointsViewModel.BreakpointDisabled += OnBreakPointUpdateFromBreakpointsViewModel; + breakpointsViewModel.BreakpointEnabled += OnBreakPointUpdateFromBreakpointsViewModel; + } + + private void OnBreakPointUpdateFromBreakpointsViewModel(BreakpointViewModel breakpointViewModel) { + UpdateAssemblyLineIfShown(breakpointViewModel.Address, breakpointViewModel); + } + + private void OnResumed() { + _uiDispatcher.Post(() => { + IsPaused = false; + Instructions.Clear(); + }); } [ObservableProperty] private bool _areFunctionInformationProvided; + [ObservableProperty] private FunctionInfo? _selectedFunction; - public FunctionInfo? SelectedFunction { - get => _selectedFunction; - set { - _selectedFunction = value; - OnPropertyChanged(nameof(SelectedFunction)); - if (value is not null) { - uint address = value.Address; - GoToAddress(address); - } - } - } - [ObservableProperty] private AvaloniaList _functions = new(); private void OnPausing() { _uiDispatcher.Post(() => { IsPaused = true; - if(Instructions.Count == 0 && GoToCsIpCommand.CanExecute(null)) { + if (!_didCsIpGoOutOfCurrentListing) { + UpdateDisassemblyInternal(); + } else if (GoToCsIpCommand.CanExecute(null)) { GoToCsIpCommand.Execute(null); + _didCsIpGoOutOfCurrentListing = false; } }); } @@ -125,9 +131,20 @@ private void ConfirmCreateExecutionBreakpoint() { CreatingExecutionBreakpoint = false; if (!string.IsNullOrWhiteSpace(BreakpointAddress) && TryParseMemoryAddress(BreakpointAddress, out ulong? breakpointAddressValue)) { - AddressBreakPoint addressBreakPoint = new(BreakPointType.EXECUTION, - (long)breakpointAddressValue, OnBreakPointReached, false); - _breakpointsViewModel.AddAddressBreakpoint(addressBreakPoint); + BreakpointViewModel breakpointViewModel = _breakpointsViewModel.AddAddressBreakpoint( + (uint)breakpointAddressValue.Value, + BreakPointType.EXECUTION, + isRemovedOnTrigger: false, + () => PauseAndReportAddress((long)breakpointAddressValue.Value)); + UpdateAssemblyLineIfShown((uint)breakpointAddressValue.Value, breakpointViewModel); + } + } + + private void UpdateAssemblyLineIfShown(uint breakpointAddress, BreakpointViewModel breakpointViewModel) { + CpuInstructionInfo? shownInstructionAtAddress = Instructions. + FirstOrDefault(x => x.Address == breakpointAddress); + if (shownInstructionAtAddress is not null) { + shownInstructionAtAddress.Breakpoint = breakpointViewModel; } } @@ -164,34 +181,59 @@ private void UpdateHeader(uint? address) { [RelayCommand(CanExecute = nameof(IsPaused))] private void StepOver() { - if(SelectedInstruction is null) { + if (SelectedInstruction is null) { return; } - var nextInstructionAddressInListing = SelectedInstruction.Address + SelectedInstruction.Length; - var addressBreakpoint = new AddressBreakPoint( - BreakPointType.EXECUTION, - nextInstructionAddressInListing, - (breakpoint) => { - OnBreakPointReached(breakpoint); - _uiDispatcher.Post(GoToCsIp); - }, - isRemovedOnTrigger: true); - _emulatorBreakpointsManager.ToggleBreakPoint(addressBreakpoint, on: true); + long nextInstructionAddressInListing = SelectedInstruction.Address + SelectedInstruction.Length; + _emulatorBreakpointsManager.ToggleBreakPoint(new AddressBreakPoint( + address: nextInstructionAddressInListing, + breakPointType: BreakPointType.EXECUTION, + isRemovedOnTrigger: true, + onReached: (_) => { + Pause($"Step over execution breakpoint was reached at address {nextInstructionAddressInListing}"); + }), on: true); _pauseHandler.Resume(); } + [RelayCommand(CanExecute = nameof(SelectedInstructionHasBreakpoint))] + private void DisableBreakpoint() { + if (SelectedInstruction?.Breakpoint is null) { + return; + } + SelectedInstruction.Breakpoint.Disable(); + } + + [RelayCommand(CanExecute = nameof(SelectedInstructionHasBreakpoint))] + private void EnableBreakpoint() { + if (SelectedInstruction?.Breakpoint is null) { + return; + } + SelectedInstruction.Breakpoint.Enable(); + } [RelayCommand(CanExecute = nameof(IsPaused))] private void StepInto() { - _cpu.ExecuteNext(); - _uiDispatcher.Post(GoToCsIp); + List instructionInfo = _instructionsDecoder. + DecodeInstructions(_state.IpPhysicalAddress, 1); + if(instructionInfo.Count != 0 && + instructionInfo[0].FlowControl != Iced.Intel.FlowControl.Next) { + _didCsIpGoOutOfCurrentListing = true; + } + _breakpointsViewModel.AddUnconditionalBreakpoint( + () => { + Pause("Step into unconditional breakpoint was reached"); + }, + removedOnTrigger: true); + _pauseHandler.Resume(); } [RelayCommand(CanExecute = nameof(IsPaused))] private void NewDisassemblyView() { DisassemblyViewModel disassemblyViewModel = new( - _cpu, _memory, _state, _functionsInformation, - _breakpointsViewModel, _emulatorBreakpointsManager, _pauseHandler, _uiDispatcher, _messenger, + _emulatorBreakpointsManager, + _memory, _state, _functionsInformation, + _breakpointsViewModel, + _pauseHandler, _uiDispatcher, _messenger, _textClipboard, canCloseTab: true) { IsPaused = IsPaused }; @@ -199,14 +241,21 @@ private void NewDisassemblyView() { } [RelayCommand(CanExecute = nameof(IsPaused))] - private void GoToCsIp() { + private async Task GoToCsIp() { SegmentedStartAddress = new(_state.CS, _state.IP); - GoToAddress(_state.IpPhysicalAddress); + await GoToAddress(_state.IpPhysicalAddress); + } + + [RelayCommand(CanExecute = nameof(IsPaused))] + private async Task GoToFunction(object? parameter) { + if (parameter is FunctionInfo functionInfo) { + await GoToAddress(functionInfo.Address); + } } - private void GoToAddress(uint address) { + private async Task GoToAddress(uint address) { StartAddress = address; - UpdateDisassembly(); + await UpdateDisassembly(); SelectedInstruction = Instructions.FirstOrDefault(); } @@ -224,16 +273,29 @@ private bool CanExecuteUpdateDisassembly() { return IsPaused && GetStartAddress() is not null; } + [ObservableProperty] + private bool _isLoading; + [RelayCommand(CanExecute = nameof(CanExecuteUpdateDisassembly))] - private void UpdateDisassembly() { + private async Task UpdateDisassembly() { uint? startAddress = GetStartAddress(); if (startAddress is null) { return; } - - Instructions = new(DecodeInstructions(_state, _memory, startAddress.Value, NumberOfInstructionsShown)); + Instructions.Clear(); + IsLoading = true; + Instructions.AddRange(await Task.Run( + () => DecodeCurrentWindowOfInstructions(startAddress.Value))); SelectedInstruction = Instructions.FirstOrDefault(); UpdateHeader(SelectedInstruction?.Address); + IsLoading = false; + } + + private List DecodeCurrentWindowOfInstructions(uint startAddress) { + return + _instructionsDecoder.DecodeInstructions( + startAddress, + NumberOfInstructionsShown); } private CpuInstructionInfo? _selectedInstruction; @@ -242,7 +304,7 @@ public CpuInstructionInfo? SelectedInstruction { get => _selectedInstruction; set { if (value is not null) { - _selectedFunction = Functions. + SelectedFunction = Functions. FirstOrDefault(x => x.Address == value.Address); OnPropertyChanged(nameof(SelectedFunction)); } @@ -258,59 +320,24 @@ private async Task CopyLine() { } } - private List DecodeInstructions(State state, IMemory memory, uint startAddress, - int numberOfInstructionsShown) { - CodeReader codeReader = CreateCodeReader(memory, out CodeMemoryStream emulatedMemoryStream); - using CodeMemoryStream codeMemoryStream = emulatedMemoryStream; - Decoder decoder = InitializeDecoder(codeReader, startAddress); - int byteOffset = 0; - codeMemoryStream.Position = startAddress; - var instructions = new List(); - while (instructions.Count < numberOfInstructionsShown) { - long instructionAddress = codeMemoryStream.Position; - decoder.Decode(out Instruction instruction); - CpuInstructionInfo instructionInfo = new() { - Instruction = instruction, - Address = (uint)instructionAddress, - FunctionName = Functions.FirstOrDefault(x => x.Address == instructionAddress)?.Name, - AddressInformation = $"{instructionAddress} (0x{state.CS:x4}:{(ushort)(state.IP + byteOffset):X4})", - Length = instruction.Length, - IP16 = instruction.IP16, - IP32 = instruction.IP32, - MemorySegment = instruction.MemorySegment, - SegmentPrefix = instruction.SegmentPrefix, - IsStackInstruction = instruction.IsStackInstruction, - IsIPRelativeMemoryOperand = instruction.IsIPRelativeMemoryOperand, - IPRelativeMemoryAddress = instruction.IPRelativeMemoryAddress, - FlowControl = instruction.FlowControl, - Bytes = $"{Convert.ToHexString(memory.GetData((uint)instructionAddress, (uint)instruction.Length))} ({instruction.Length})" - }; - instructionInfo.SegmentedAddress = new(state.CS, (ushort)(state.IP + byteOffset)); - instructionInfo.HasBreakpoint = _breakpointsViewModel.HasUserExecutionBreakpoint(instructionInfo); - instructionInfo.StringRepresentation = - $"{instructionInfo.Address:X4} ({instructionInfo.SegmentedAddress}): {instruction} ({instructionInfo.Bytes})"; - if (instructionAddress == state.IpPhysicalAddress) { - instructionInfo.IsCsIp = true; - } - - instructions.Add(instructionInfo); - byteOffset += instruction.Length; - } - - return instructions; + private void PauseAndReportAddress(long address) { + string message = $"Execution breakpoint was reached at address {address}."; + Pause(message); } - - private void OnBreakPointReached(BreakPoint breakPoint) { - string message = $"{breakPoint.BreakPointType} breakpoint was reached."; + + private void Pause(string message) { _pauseHandler.RequestPause(message); _uiDispatcher.Post(() => { _messenger.Send(new StatusMessage(DateTime.Now, this, message)); - if (UpdateDisassemblyCommand.CanExecute(null)) { - UpdateDisassemblyCommand.Execute(null); - } }); } + private void UpdateDisassemblyInternal() { + if (UpdateDisassemblyCommand.CanExecute(null)) { + UpdateDisassemblyCommand.Execute(null); + } + } + [RelayCommand] private void MoveCsIpHere() { if (SelectedInstruction is null) { @@ -318,46 +345,34 @@ private void MoveCsIpHere() { } _state.CS = SelectedInstruction.SegmentedAddress.Segment; _state.IP = SelectedInstruction.SegmentedAddress.Offset; - if (UpdateDisassemblyCommand.CanExecute(null)) { - UpdateDisassemblyCommand.Execute(null); - } + UpdateDisassemblyInternal(); } - private bool RemoveExecutionBreakpointHereCanExecute() => - SelectedInstruction is not null && _breakpointsViewModel.HasUserExecutionBreakpoint(SelectedInstruction); + private bool SelectedInstructionHasBreakpoint() => + SelectedInstruction?.Breakpoint is not null; - [RelayCommand(CanExecute = nameof(RemoveExecutionBreakpointHereCanExecute))] + [RelayCommand(CanExecute = nameof(SelectedInstructionHasBreakpoint))] private void RemoveExecutionBreakpointHere() { - if (SelectedInstruction is null) { + if (SelectedInstruction?.Breakpoint is null) { return; } - _breakpointsViewModel.RemoveUserExecutionBreakpoint(SelectedInstruction); - SelectedInstruction.HasBreakpoint = _breakpointsViewModel.HasUserExecutionBreakpoint(SelectedInstruction); + _breakpointsViewModel.RemoveBreakpointInternal(SelectedInstruction.Breakpoint); + SelectedInstruction.Breakpoint = _breakpointsViewModel.GetBreakpoint(SelectedInstruction); } private bool CreateExecutionBreakpointHereCanExecute() => - SelectedInstruction is not null && !_breakpointsViewModel.HasUserExecutionBreakpoint(SelectedInstruction); - + SelectedInstruction?.Breakpoint is not { Type: BreakPointType.EXECUTION }; + [RelayCommand(CanExecute = nameof(CreateExecutionBreakpointHereCanExecute))] private void CreateExecutionBreakpointHere() { if (SelectedInstruction is null) { return; } - AddressBreakPoint breakPoint = new(BreakPointType.EXECUTION, SelectedInstruction.Address, OnBreakPointReached, - isRemovedOnTrigger: false); - _breakpointsViewModel.AddAddressBreakpoint(breakPoint); - SelectedInstruction.HasBreakpoint = _breakpointsViewModel.HasUserExecutionBreakpoint(SelectedInstruction); - } - - private static Decoder InitializeDecoder(CodeReader codeReader, uint currentIp) { - Decoder decoder = Decoder.Create(16, codeReader, currentIp, - DecoderOptions.Loadall286 | DecoderOptions.Loadall386); - return decoder; - } - - private static CodeReader CreateCodeReader(IMemory memory, out CodeMemoryStream codeMemoryStream) { - codeMemoryStream = new CodeMemoryStream(memory); - CodeReader codeReader = new StreamCodeReader(codeMemoryStream); - return codeReader; + uint address = SelectedInstruction.Address; + BreakpointViewModel breakpointViewModel = _breakpointsViewModel.AddAddressBreakpoint(address, + BreakPointType.EXECUTION, isRemovedOnTrigger: false, () => { + PauseAndReportAddress(address); + }); + UpdateAssemblyLineIfShown(address, breakpointViewModel); } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/MemoryViewModel.cs b/src/Spice86/ViewModels/MemoryViewModel.cs index abb515b3d..e8ac36b21 100644 --- a/src/Spice86/ViewModels/MemoryViewModel.cs +++ b/src/Spice86/ViewModels/MemoryViewModel.cs @@ -28,6 +28,28 @@ public partial class MemoryViewModel : ViewModelWithErrorDialog { private readonly IPauseHandler _pauseHandler; private readonly BreakpointsViewModel _breakpointsViewModel; + public MemoryViewModel(IMemory memory, BreakpointsViewModel breakpointsViewModel, IPauseHandler pauseHandler, IMessenger messenger, IUIDispatcher uiDispatcher, + ITextClipboard textClipboard, IHostStorageProvider storageProvider, IStructureViewModelFactory structureViewModelFactory, + bool canCloseTab = false, uint startAddress = 0, uint endAddress = A20Gate.EndOfHighMemoryArea) : base(uiDispatcher, textClipboard) { + _pauseHandler = pauseHandler; + _breakpointsViewModel = breakpointsViewModel; + _memory = memory; + _pauseHandler.Pausing += OnPause; + IsPaused = pauseHandler.IsPaused; + pauseHandler.Resumed += () => _uiDispatcher.Post(() => IsPaused = false); + _messenger = messenger; + _storageProvider = storageProvider; + _structureViewModelFactory = structureViewModelFactory; + StartAddress = startAddress; + EndAddress = endAddress; + CanCloseTab = canCloseTab; + if (EndAddress is 0) { + EndAddress = _memory.Length; + } + IsMemoryRangeValid = GetIsMemoryRangeValid(); + TryUpdateHeaderAndMemoryDocument(); + } + public enum MemorySearchDataType { Binary, Ascii, @@ -107,25 +129,32 @@ public uint? EndAddress { [RelayCommand(CanExecute = nameof(IsSelectionRangeValid))] private void BeginCreateMemoryBreakpoint() { - if(StartAddress is not null && SelectionRange is not null) { - CreatingMemoryBreakpoint = true; - ulong address = StartAddress.Value + SelectionRange.Value.Start.ByteIndex; - BreakpointAddress = address.ToString(CultureInfo.InvariantCulture); + CreatingMemoryBreakpoint = true; + if (StartAddress is not null && SelectionRange is not null) { + ulong rangeStart = StartAddress.Value + SelectionRange.Value.Start.ByteIndex; + ulong rangEnd = StartAddress.Value + SelectionRange.Value.End.ByteIndex; + BreakpointRangeStartAddress = rangeStart.ToString(CultureInfo.InvariantCulture); + if (rangeStart != rangEnd) { + BreakpointRangeEndAddress = rangEnd.ToString(CultureInfo.InvariantCulture); + } } } [ObservableProperty] - private string? _breakpointAddress; + [NotifyCanExecuteChangedFor(nameof(ConfirmCreateMemoryBreakpointCommand))] + private string? _breakpointRangeEndAddress; + + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(ConfirmCreateMemoryBreakpointCommand))] + private string? _breakpointRangeStartAddress; [ObservableProperty] private BreakPointType _selectedBreakpointType = BreakPointType.ACCESS; public BreakPointType[] BreakpointTypes => [BreakPointType.ACCESS, BreakPointType.WRITE, BreakPointType.READ]; - private bool IsBreakpointAddressValid() => !string.IsNullOrWhiteSpace(BreakpointAddress); - - private void OnBreakPointReached(BreakPoint breakPoint) { - string message = $"{breakPoint.BreakPointType} breakpoint was reached."; + private void OnBreakPointReached() { + string message = $"Memory breakpoint was reached."; _pauseHandler.RequestPause(message); _uiDispatcher.Post(() => { _messenger.Send(new StatusMessage(DateTime.Now, this, message)); @@ -133,15 +162,35 @@ private void OnBreakPointReached(BreakPoint breakPoint) { }); } - [RelayCommand] + private bool IsBreakpointRangeValid() { + bool isValid = TryParseMemoryAddress(BreakpointRangeStartAddress, out _); + if(!string.IsNullOrWhiteSpace(BreakpointRangeEndAddress)) { + isValid = TryParseMemoryAddress(BreakpointRangeStartAddress, out _); + } + return isValid; + } + + [RelayCommand(CanExecute = nameof(IsBreakpointRangeValid))] private void ConfirmCreateMemoryBreakpoint() { - CreatingMemoryBreakpoint = false; - if(!string.IsNullOrWhiteSpace(BreakpointAddress) && - TryParseMemoryAddress(BreakpointAddress, out ulong? breakpointAddressValue)) { - AddressBreakPoint addressBreakPoint = new(SelectedBreakpointType, - (long)breakpointAddressValue, OnBreakPointReached, false); - _breakpointsViewModel.AddAddressBreakpoint(addressBreakPoint); + if (TryParseMemoryAddress(BreakpointRangeStartAddress, out ulong? breakpointStartAddressValue) + && TryParseMemoryAddress(BreakpointRangeEndAddress, out ulong? endAddressValue)) { + for (ulong i = breakpointStartAddressValue.Value; i <= endAddressValue.Value; i++) { + CreateMemoryAddressBreakpoint(i); + } } + else if (TryParseMemoryAddress(BreakpointRangeStartAddress, out ulong? breakPointAddress)) { + CreateMemoryAddressBreakpoint(breakPointAddress.Value); + } + CreatingMemoryBreakpoint = false; + } + + private void CreateMemoryAddressBreakpoint(ulong breakpointAddressValue) { + _breakpointsViewModel.AddAddressBreakpoint( + (uint)breakpointAddressValue, + SelectedBreakpointType, + isRemovedOnTrigger: false, + OnBreakPointReached, + "Memory breakpoint"); } [RelayCommand] @@ -270,35 +319,16 @@ private void GoToFoundOccurence() { private readonly IHostStorageProvider _storageProvider; - public MemoryViewModel(IMemory memory, BreakpointsViewModel breakpointsViewModel, IPauseHandler pauseHandler, IMessenger messenger, IUIDispatcher uiDispatcher, - ITextClipboard textClipboard, IHostStorageProvider storageProvider, IStructureViewModelFactory structureViewModelFactory, - bool canCloseTab = false, uint startAddress = 0, uint endAddress = A20Gate.EndOfHighMemoryArea) : base(uiDispatcher, textClipboard) { - _pauseHandler = pauseHandler; - _breakpointsViewModel = breakpointsViewModel; - _memory = memory; - _pauseHandler.Pausing += OnPause; - IsPaused = pauseHandler.IsPaused; - pauseHandler.Resumed += () => _uiDispatcher.Post(() => IsPaused = false); - _messenger = messenger; - _storageProvider = storageProvider; - _structureViewModelFactory = structureViewModelFactory; - StartAddress = startAddress; - EndAddress = endAddress; - CanCloseTab = canCloseTab; - if (EndAddress is 0) { - EndAddress = _memory.Length; - } - IsMemoryRangeValid = GetIsMemoryRangeValid(); - TryUpdateHeaderAndMemoryDocument(); - } - /// /// Handles the event when the selection range within the HexEditor changes. /// /// The source of the event, expected to be of type . /// The event arguments, not used in this method. public void OnSelectionRangeChanged(object? sender, EventArgs e) { - SelectionRange = (sender as Selection)?.Range; + Selection? selection = (sender as Selection); + if (selection != null) { + SelectionRange = selection.Range; + } } [RelayCommand(CanExecute = nameof(IsStructureInfoPresent))] @@ -312,7 +342,7 @@ public void ShowStructureView() { if (SelectionRange is {ByteLength: > 1} bitRange) { byte[] bytes = new byte[bitRange.ByteLength]; DataMemoryDocument.ReadBytes(bitRange.Start.ByteIndex, bytes); - data = new ByteArrayBinaryDocument(bytes); + data = new MemoryBinaryDocument(bytes); } else { data = DataMemoryDocument; } @@ -375,9 +405,13 @@ private async Task DumpMemory() { [RelayCommand(CanExecute = nameof(IsPaused))] private void EditMemory() { IsEditingMemory = true; + try { if (MemoryEditAddress is not null && TryParseMemoryAddress(MemoryEditAddress, out ulong? memoryEditAddressValue)) { MemoryEditValue = Convert.ToHexString(_memory.ReadRam((uint)(MemoryEditValue?.Length ?? sizeof(ushort)), (uint)memoryEditAddressValue.Value)); } + } catch (Exception e) { + ShowError(e); + } } [RelayCommand] @@ -390,7 +424,15 @@ MemoryEditValue is null || !long.TryParse(MemoryEditValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long value)) { return; } - DataMemoryDocument?.WriteBytes(address!.Value, BitConverter.GetBytes(value)); - IsEditingMemory = false; + try { + DataMemoryDocument?.WriteBytes(address!.Value, BitConverter.GetBytes(value)); + } catch (IndexOutOfRangeException e) { + ShowError(e); + MemoryEditValue = null; + MemoryEditAddress = null; + } + finally { + IsEditingMemory = false; + } } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/MidiViewModel.cs b/src/Spice86/ViewModels/MidiViewModel.cs index 28cf2519a..55ad73a86 100644 --- a/src/Spice86/ViewModels/MidiViewModel.cs +++ b/src/Spice86/ViewModels/MidiViewModel.cs @@ -6,6 +6,7 @@ namespace Spice86.ViewModels; using Spice86.Core.Emulator.Devices.Sound.Midi; using Spice86.Infrastructure; +using Spice86.Mappers; using Spice86.Models.Debugging; public partial class MidiViewModel : ViewModelBase { @@ -20,8 +21,6 @@ public MidiViewModel(Midi externalMidiDevice) { } private void UpdateValues(object? sender, EventArgs e) { - Midi.LastPortRead = _externalMidiDevice.LastPortRead; - Midi.LastPortWritten = _externalMidiDevice.LastPortWritten; - Midi.LastPortWrittenValue = _externalMidiDevice.LastPortWrittenValue; + _externalMidiDevice.CopyToMidiInfo(Midi); } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/PerformanceViewModel.cs b/src/Spice86/ViewModels/PerformanceViewModel.cs index 5fa54832a..e921ae6d2 100644 --- a/src/Spice86/ViewModels/PerformanceViewModel.cs +++ b/src/Spice86/ViewModels/PerformanceViewModel.cs @@ -34,8 +34,8 @@ private void UpdatePerformanceInfo(object? sender, EventArgs e) { return; } - InstructionsExecuted = _state.Cycles; _performanceMeasurer.UpdateValue(_state.Cycles); + InstructionsExecuted = _state.Cycles; AverageInstructionsPerSecond = _performanceMeasurer.AverageValuePerSecond; InstructionsPerMillisecond = _performanceMeasurer.ValuePerMillisecond; } diff --git a/src/Spice86/ViewModels/StructureViewModel.cs b/src/Spice86/ViewModels/StructureViewModel.cs index 82585149d..e8d739273 100644 --- a/src/Spice86/ViewModels/StructureViewModel.cs +++ b/src/Spice86/ViewModels/StructureViewModel.cs @@ -13,7 +13,6 @@ namespace Spice86.ViewModels; using Spice86.DataTemplates; using Spice86.MemoryWrappers; using Spice86.Messages; -using Spice86.Models; using Spice86.Shared.Emulator.Memory; using Structurizer; @@ -175,7 +174,7 @@ private void Update() { StructureMembers.AddRange(members); // This "zooms" the hex view to the selected structure data. - StructureMemory = new ByteArrayBinaryDocument(data); + StructureMemory = new MemoryBinaryDocument(data); } private List PopulateMembers(IEnumerable selectedStructure, byte[] data) { diff --git a/src/Spice86/ViewModels/VideoCardViewModel.cs b/src/Spice86/ViewModels/VideoCardViewModel.cs index c81ed6446..98f76d807 100644 --- a/src/Spice86/ViewModels/VideoCardViewModel.cs +++ b/src/Spice86/ViewModels/VideoCardViewModel.cs @@ -6,6 +6,7 @@ namespace Spice86.ViewModels; using Spice86.Core.Emulator.Devices.Video; using Spice86.Infrastructure; +using Spice86.Mappers; using Spice86.Models.Debugging; public partial class VideoCardViewModel : ViewModelBase { @@ -26,146 +27,10 @@ private void UpdateValues(object? sender, EventArgs e) { } private void VisitVgaRenderer(IVgaRenderer vgaRenderer) { - VideoCard.RendererWidth = vgaRenderer.Width; - VideoCard.RendererHeight = vgaRenderer.Height; - VideoCard.RendererBufferSize = vgaRenderer.BufferSize; - VideoCard.LastFrameRenderTime = vgaRenderer.LastFrameRenderTime; + vgaRenderer.CopyToVideoCardInfo(VideoCard); } private void VisitVideoState(IVideoState videoState) { - try { - VideoCard.GeneralMiscellaneousOutputRegister = videoState.GeneralRegisters.MiscellaneousOutput.Value; - VideoCard.GeneralClockSelect = videoState.GeneralRegisters.MiscellaneousOutput.ClockSelect; - VideoCard.GeneralEnableRam = videoState.GeneralRegisters.MiscellaneousOutput.EnableRam; - VideoCard.GeneralVerticalSize = videoState.GeneralRegisters.MiscellaneousOutput.VerticalSize; - VideoCard.GeneralHorizontalSyncPolarity = videoState.GeneralRegisters.MiscellaneousOutput.HorizontalSyncPolarity; - VideoCard.GeneralVerticalSyncPolarity = videoState.GeneralRegisters.MiscellaneousOutput.VerticalSyncPolarity; - VideoCard.GeneralIoAddressSelect = videoState.GeneralRegisters.MiscellaneousOutput.IoAddressSelect; - VideoCard.GeneralOddPageSelect = videoState.GeneralRegisters.MiscellaneousOutput.EvenPageSelect; - VideoCard.GeneralInputStatusRegister0 = videoState.GeneralRegisters.InputStatusRegister0.Value; - VideoCard.GeneralCrtInterrupt = videoState.GeneralRegisters.InputStatusRegister0.CrtInterrupt; - VideoCard.GeneralSwitchSense = videoState.GeneralRegisters.InputStatusRegister0.SwitchSense; - VideoCard.GeneralInputStatusRegister1 = videoState.GeneralRegisters.InputStatusRegister1.Value; - VideoCard.GeneralDisplayDisabled = videoState.GeneralRegisters.InputStatusRegister1.DisplayDisabled; - VideoCard.GeneralVerticalRetrace = videoState.GeneralRegisters.InputStatusRegister1.VerticalRetrace; - - VideoCard.DacReadIndex = videoState.DacRegisters.IndexRegisterReadMode; - VideoCard.DacWriteIndex = videoState.DacRegisters.IndexRegisterWriteMode; - VideoCard.DacPixelMask = videoState.DacRegisters.PixelMask; - VideoCard.DacData = videoState.DacRegisters.DataPeek; - - VideoCard.AttributeControllerColorSelect = videoState.AttributeControllerRegisters.ColorSelectRegister.Value; - VideoCard.AttributeControllerOverscanColor = videoState.AttributeControllerRegisters.OverscanColor; - VideoCard.AttributeControllerAttributeModeControl = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.Value; - VideoCard.AttributeControllerVideoOutput45Select = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.VideoOutput45Select; - VideoCard.AttributeControllerPixelWidth8 = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.PixelWidth8; - VideoCard.AttributeControllerPixelPanningCompatibility = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.PixelPanningCompatibility; - VideoCard.AttributeControllerBlinkingEnabled = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.BlinkingEnabled; - VideoCard.AttributeControllerLineGraphicsEnabled = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.LineGraphicsEnabled; - VideoCard.AttributeControllerMonochromeEmulation = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.MonochromeEmulation; - VideoCard.AttributeControllerGraphicsMode = videoState.AttributeControllerRegisters.AttributeControllerModeRegister.GraphicsMode; - VideoCard.AttributeControllerColorPlaneEnable = videoState.AttributeControllerRegisters.ColorPlaneEnableRegister.Value; - VideoCard.AttributeControllerHorizontalPixelPanning = videoState.AttributeControllerRegisters.HorizontalPixelPanning; - - VideoCard.CrtControllerAddressWrap = videoState.CrtControllerRegisters.CrtModeControlRegister.AddressWrap; - VideoCard.CrtControllerBytePanning = videoState.CrtControllerRegisters.PresetRowScanRegister.BytePanning; - VideoCard.CrtControllerByteWordMode = videoState.CrtControllerRegisters.CrtModeControlRegister.ByteWordMode; - VideoCard.CrtControllerCharacterCellHeightRegister = videoState.CrtControllerRegisters.MaximumScanlineRegister.Value; - VideoCard.CrtControllerCharacterCellHeight = videoState.CrtControllerRegisters.MaximumScanlineRegister.MaximumScanline; - VideoCard.CrtControllerCompatibilityModeSupport = videoState.CrtControllerRegisters.CrtModeControlRegister.CompatibilityModeSupport; - VideoCard.CrtControllerCompatibleRead = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.CompatibleRead; - VideoCard.CrtControllerCountByFour = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.CountByFour; - VideoCard.CrtControllerCountByTwo = videoState.CrtControllerRegisters.CrtModeControlRegister.CountByTwo; - VideoCard.CrtControllerCrtcScanDouble = videoState.CrtControllerRegisters.MaximumScanlineRegister.CrtcScanDouble; - VideoCard.CrtControllerCrtModeControl = videoState.CrtControllerRegisters.CrtModeControlRegister.Value; - VideoCard.CrtControllerCursorEnd = videoState.CrtControllerRegisters.TextCursorEndRegister.Value; - VideoCard.CrtControllerCursorLocationHigh = videoState.CrtControllerRegisters.TextCursorLocationHigh; - VideoCard.CrtControllerCursorLocationLow = videoState.CrtControllerRegisters.TextCursorLocationLow; - VideoCard.CrtControllerCursorStart = videoState.CrtControllerRegisters.TextCursorStartRegister.Value; - VideoCard.CrtControllerDisableTextCursor = videoState.CrtControllerRegisters.TextCursorStartRegister.DisableTextCursor; - VideoCard.CrtControllerDisableVerticalInterrupt = videoState.CrtControllerRegisters.VerticalSyncEndRegister.DisableVerticalInterrupt; - VideoCard.CrtControllerDisplayEnableSkew = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.DisplayEnableSkew; - VideoCard.CrtControllerDoubleWordMode = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.DoubleWordMode; - VideoCard.CrtControllerEndHorizontalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingEndRegister.Value; - VideoCard.CrtControllerEndHorizontalDisplay = videoState.CrtControllerRegisters.HorizontalDisplayEnd; - VideoCard.CrtControllerEndHorizontalRetrace = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.Value; - VideoCard.CrtControllerEndVerticalBlanking = videoState.CrtControllerRegisters.VerticalBlankingEnd; - VideoCard.CrtControllerHorizontalBlankingEnd = videoState.CrtControllerRegisters.HorizontalBlankingEndValue; - VideoCard.CrtControllerHorizontalSyncDelay = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.HorizontalSyncDelay; - VideoCard.CrtControllerHorizontalSyncEnd = videoState.CrtControllerRegisters.HorizontalSyncEndRegister.HorizontalSyncEnd; - VideoCard.CrtControllerHorizontalTotal = videoState.CrtControllerRegisters.HorizontalTotal; - VideoCard.CrtControllerLineCompareRegister = videoState.CrtControllerRegisters.LineCompare; - VideoCard.CrtControllerLineCompare = videoState.CrtControllerRegisters.LineCompareValue; - VideoCard.CrtControllerOffset = videoState.CrtControllerRegisters.Offset; - VideoCard.CrtControllerOverflow = videoState.CrtControllerRegisters.OverflowRegister.Value; - VideoCard.CrtControllerPresetRowScan = videoState.CrtControllerRegisters.PresetRowScanRegister.PresetRowScan; - VideoCard.CrtControllerPresetRowScanRegister = videoState.CrtControllerRegisters.PresetRowScanRegister.Value; - VideoCard.CrtControllerRefreshCyclesPerScanline = videoState.CrtControllerRegisters.VerticalSyncEndRegister.RefreshCyclesPerScanline; - VideoCard.CrtControllerSelectRowScanCounter = videoState.CrtControllerRegisters.CrtModeControlRegister.SelectRowScanCounter; - VideoCard.CrtControllerStartAddress = videoState.CrtControllerRegisters.ScreenStartAddress; - VideoCard.CrtControllerStartAddressHigh = videoState.CrtControllerRegisters.ScreenStartAddressHigh; - VideoCard.CrtControllerStartAddressLow = videoState.CrtControllerRegisters.ScreenStartAddressLow; - VideoCard.CrtControllerStartHorizontalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingStart; - VideoCard.CrtControllerStartHorizontalRetrace = videoState.CrtControllerRegisters.HorizontalSyncStart; - VideoCard.CrtControllerStartVerticalBlanking = videoState.CrtControllerRegisters.HorizontalBlankingStart; - VideoCard.CrtControllerTextCursorEnd = videoState.CrtControllerRegisters.TextCursorEndRegister.TextCursorEnd; - VideoCard.CrtControllerTextCursorLocation = videoState.CrtControllerRegisters.TextCursorLocation; - VideoCard.CrtControllerTextCursorSkew = videoState.CrtControllerRegisters.TextCursorEndRegister.TextCursorSkew; - VideoCard.CrtControllerTextCursorStart = videoState.CrtControllerRegisters.TextCursorStartRegister.TextCursorStart; - VideoCard.CrtControllerTimingEnable = videoState.CrtControllerRegisters.CrtModeControlRegister.TimingEnable; - VideoCard.CrtControllerUnderlineLocation = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.Value; - VideoCard.CrtControllerUnderlineScanline = videoState.CrtControllerRegisters.UnderlineRowScanlineRegister.UnderlineScanline; - VideoCard.CrtControllerVerticalBlankingStart = videoState.CrtControllerRegisters.VerticalBlankingStartValue; - VideoCard.CrtControllerVerticalDisplayEnd = videoState.CrtControllerRegisters.VerticalDisplayEndValue; - VideoCard.CrtControllerVerticalDisplayEndRegister = videoState.CrtControllerRegisters.VerticalDisplayEnd; - VideoCard.CrtControllerVerticalRetraceEnd = videoState.CrtControllerRegisters.VerticalSyncEndRegister.Value; - VideoCard.CrtControllerVerticalRetraceStart = videoState.CrtControllerRegisters.VerticalSyncStart; - VideoCard.CrtControllerVerticalSyncStart = videoState.CrtControllerRegisters.VerticalSyncStartValue; - VideoCard.CrtControllerVerticalTimingHalved = videoState.CrtControllerRegisters.CrtModeControlRegister.VerticalTimingHalved; - VideoCard.CrtControllerVerticalTotal = videoState.CrtControllerRegisters.VerticalTotalValue; - VideoCard.CrtControllerVerticalTotalRegister = videoState.CrtControllerRegisters.VerticalTotal; - VideoCard.CrtControllerWriteProtect = videoState.CrtControllerRegisters.VerticalSyncEndRegister.WriteProtect; - - VideoCard.GraphicsDataRotate = videoState.GraphicsControllerRegisters.DataRotateRegister.Value; - VideoCard.GraphicsRotateCount = videoState.GraphicsControllerRegisters.DataRotateRegister.RotateCount; - VideoCard.GraphicsFunctionSelect = videoState.GraphicsControllerRegisters.DataRotateRegister.FunctionSelect; - VideoCard.GraphicsBitMask = videoState.GraphicsControllerRegisters.BitMask; - VideoCard.GraphicsColorCompare = videoState.GraphicsControllerRegisters.ColorCompare; - VideoCard.GraphicsReadMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.ReadMode; - VideoCard.GraphicsWriteMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.WriteMode; - VideoCard.GraphicsOddEven = videoState.GraphicsControllerRegisters.GraphicsModeRegister.OddEven; - VideoCard.GraphicsShiftRegisterMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.ShiftRegisterMode; - VideoCard.GraphicsIn256ColorMode = videoState.GraphicsControllerRegisters.GraphicsModeRegister.In256ColorMode; - VideoCard.GraphicsModeRegister = videoState.GraphicsControllerRegisters.GraphicsModeRegister.Value; - VideoCard.GraphicsMiscellaneousGraphics = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.Value; - VideoCard.GraphicsGraphicsMode = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.GraphicsMode; - VideoCard.GraphicsChainOddMapsToEven = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.ChainOddMapsToEven; - VideoCard.GraphicsMemoryMap = videoState.GraphicsControllerRegisters.MiscellaneousGraphicsRegister.MemoryMap; - VideoCard.GraphicsReadMapSelect = videoState.GraphicsControllerRegisters.ReadMapSelectRegister.PlaneSelect; - VideoCard.GraphicsSetReset = videoState.GraphicsControllerRegisters.SetReset.Value; - VideoCard.GraphicsColorDontCare = videoState.GraphicsControllerRegisters.ColorDontCare; - VideoCard.GraphicsEnableSetReset = videoState.GraphicsControllerRegisters.EnableSetReset.Value; - - VideoCard.SequencerResetRegister = videoState.SequencerRegisters.ResetRegister.Value; - VideoCard.SequencerSynchronousReset = videoState.SequencerRegisters.ResetRegister.SynchronousReset; - VideoCard.SequencerAsynchronousReset = videoState.SequencerRegisters.ResetRegister.AsynchronousReset; - VideoCard.SequencerClockingModeRegister = videoState.SequencerRegisters.ClockingModeRegister.Value; - VideoCard.SequencerDotsPerClock = videoState.SequencerRegisters.ClockingModeRegister.DotsPerClock; - VideoCard.SequencerShiftLoad = videoState.SequencerRegisters.ClockingModeRegister.ShiftLoad; - VideoCard.SequencerDotClock = videoState.SequencerRegisters.ClockingModeRegister.HalfDotClock; - VideoCard.SequencerShift4 = videoState.SequencerRegisters.ClockingModeRegister.Shift4; - VideoCard.SequencerScreenOff = videoState.SequencerRegisters.ClockingModeRegister.ScreenOff; - VideoCard.SequencerPlaneMask = videoState.SequencerRegisters.PlaneMaskRegister.Value; - VideoCard.SequencerCharacterMapSelect = videoState.SequencerRegisters.CharacterMapSelectRegister.Value; - VideoCard.SequencerCharacterMapA = videoState.SequencerRegisters.CharacterMapSelectRegister.CharacterMapA; - VideoCard.SequencerCharacterMapB = videoState.SequencerRegisters.CharacterMapSelectRegister.CharacterMapB; - VideoCard.SequencerSequencerMemoryMode = videoState.SequencerRegisters.MemoryModeRegister.Value; - VideoCard.SequencerExtendedMemory = videoState.SequencerRegisters.MemoryModeRegister.ExtendedMemory; - VideoCard.SequencerOddEvenMode = videoState.SequencerRegisters.MemoryModeRegister.OddEvenMode; - VideoCard.SequencerChain4Mode = videoState.SequencerRegisters.MemoryModeRegister.Chain4Mode; - } catch (IndexOutOfRangeException) { - //A read during emulation provoked an OutOfRangeException (for example, in the DacRegisters). - // Ignore it. - } + videoState.CopyToVideoCardInfo(VideoCard); } } \ No newline at end of file diff --git a/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs b/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs index 2f033e9f5..ea5c8fdc7 100644 --- a/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs +++ b/src/Spice86/ViewModels/ViewModelWithErrorDialog.cs @@ -1,9 +1,5 @@ namespace Spice86.ViewModels; -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Threading; - using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -15,6 +11,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.Text.Json; public abstract partial class ViewModelWithErrorDialog : ViewModelBase { protected readonly ITextClipboard _textClipboard; @@ -78,7 +75,7 @@ protected void ShowError(Exception e) { public async Task CopyExceptionToClipboard() { if(Exception is not null) { await _textClipboard.SetTextAsync( - Newtonsoft.Json.JsonConvert.SerializeObject( + JsonSerializer.Serialize( new ExceptionInfo(Exception.TargetSite?.ToString(), Exception.Message, Exception.StackTrace))); } } diff --git a/src/Spice86/Views/BreakpointsView.axaml b/src/Spice86/Views/BreakpointsView.axaml index b85d669c8..023067ab0 100644 --- a/src/Spice86/Views/BreakpointsView.axaml +++ b/src/Spice86/Views/BreakpointsView.axaml @@ -13,21 +13,22 @@ - + - + - + + - + @@ -87,13 +92,13 @@ - + - + @@ -106,29 +111,29 @@ - + - + - - - - + + + + - + - + diff --git a/src/Spice86/Views/DisassemblyView.axaml b/src/Spice86/Views/DisassemblyView.axaml index 6eb4a079d..c94251fcb 100644 --- a/src/Spice86/Views/DisassemblyView.axaml +++ b/src/Spice86/Views/DisassemblyView.axaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:progRing="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing" xmlns:viewModels="clr-namespace:Spice86.ViewModels" xmlns:userControls="clr-namespace:Spice86.UserControls" xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia" @@ -13,7 +14,7 @@ - + -