-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add a structure viewer to the memory viewer. * Don't crash without structure file * Fix hyperlink style * Replace fluent with semi * More progress towards a functional structure viewer * WIP progress, but stumped on hexview scroll issue * Resolve merge conflicts * update Structurizer * Replace iffy FileSystemWatcher with own poller implementation * Fix scroll issue with new document * Refactor and document StructureViewModel * Always allow address input * Formatting, styling and cleanup * Resolve merge conflicts
- Loading branch information
1 parent
46eaf53
commit 2e8f178
Showing
20 changed files
with
904 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
namespace Spice86.Converters; | ||
|
||
using Avalonia.Data; | ||
using Avalonia.Data.Converters; | ||
|
||
using Spice86.Shared.Emulator.Memory; | ||
|
||
using System.Globalization; | ||
using System.Text.RegularExpressions; | ||
|
||
public partial class SegmentedAddressConverter : IValueConverter { | ||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { | ||
return value switch { | ||
null => null, | ||
SegmentedAddress segmentedAddress => $"{segmentedAddress.Segment:X4}:{segmentedAddress.Offset:X4}", | ||
_ => new BindingNotification(new InvalidCastException(), BindingErrorType.Error) | ||
}; | ||
} | ||
|
||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { | ||
if (value is not string str || string.IsNullOrWhiteSpace(str)) { | ||
return new SegmentedAddress(); | ||
} | ||
|
||
Match match = SegmentedAddressRegex().Match(str); | ||
if (match.Success) { | ||
return new SegmentedAddress( | ||
ushort.Parse(match.Groups[1].Value, NumberStyles.HexNumber, culture), | ||
ushort.Parse(match.Groups[2].Value, NumberStyles.HexNumber, culture) | ||
); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
[GeneratedRegex(@"^([0-9A-Fa-f]{4}):([0-9A-Fa-f]{4})$")] | ||
private static partial Regex SegmentedAddressRegex(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
namespace Spice86.DataTemplates; | ||
|
||
using Avalonia; | ||
using Avalonia.Controls; | ||
using Avalonia.Controls.Templates; | ||
using Avalonia.Layout; | ||
using Avalonia.Media; | ||
|
||
using CommunityToolkit.Mvvm.Input; | ||
|
||
using Structurizer.Types; | ||
|
||
using System.Text; | ||
|
||
public static class DataTemplateProvider { | ||
public static FuncDataTemplate<StructureMember> StructureMemberValueTemplate { get; } = new(BuildStructureMemberValuePresenter); | ||
|
||
private static Control? BuildStructureMemberValuePresenter(StructureMember? structureMember, INameScope scope) { | ||
if (structureMember is null) { | ||
return null; | ||
} | ||
if (structureMember.Type is {IsPointer: true, IsArray: false}) { | ||
return new Button { | ||
Content = FormatPointer(structureMember), | ||
Command = new RelayCommand(() => throw new NotImplementedException("This should open a new memory view at the address the pointer points to")), | ||
Classes = {"hyperlink"}, | ||
HorizontalAlignment = HorizontalAlignment.Right, | ||
HorizontalContentAlignment = HorizontalAlignment.Right, | ||
VerticalAlignment = VerticalAlignment.Center, | ||
Margin = new Thickness(0,0,5,0) | ||
}; | ||
} | ||
|
||
return new TextBlock { | ||
Text = FormatValue(structureMember), | ||
TextAlignment = TextAlignment.Right, | ||
VerticalAlignment = VerticalAlignment.Center, | ||
Margin = new Thickness(0,0,5,0) | ||
}; | ||
} | ||
|
||
private static string FormatValue(StructureMember structureMember) { | ||
if (structureMember.Members != null && structureMember.Type.Type != "char") { | ||
return string.Empty; | ||
} | ||
if (structureMember.Type.EnumType != null) { | ||
return FormatEnum(structureMember.Type.EnumType, structureMember.Data); | ||
} | ||
if (structureMember.Type is {IsPointer: true, Count: 1}) { | ||
return FormatPointer(structureMember); | ||
} | ||
|
||
return structureMember.Type.Type switch { | ||
"char" when structureMember.Type.IsArray => '"' + Encoding.ASCII.GetString(structureMember.Data) + '"', | ||
"__int8" or "char" or "_BYTE" => FormatChar(structureMember.Data[0]), | ||
"__int16" or "short" or "int" when structureMember.Type.Unsigned => FormatUnsignedShort(structureMember), | ||
"__int16" or "short" or "int" => FormatShort(structureMember), | ||
"__int32" or "long" when structureMember.Type.Unsigned => FormatUnsignedLong(structureMember), | ||
"__int32" or "long" => FormatLong(structureMember), | ||
_ => FormatHex(structureMember) | ||
}; | ||
} | ||
|
||
private static string FormatHex(StructureMember structureMember) { | ||
switch (structureMember.Size) { | ||
case 1: | ||
return $"0x{structureMember.Data[0]:X2}"; | ||
case 2: | ||
return $"0x{BitConverter.ToUInt16(structureMember.Data):X4}"; | ||
case 4: | ||
return $"0x{BitConverter.ToUInt32(structureMember.Data):X8}"; | ||
default: | ||
return "???"; | ||
} | ||
} | ||
|
||
private static string FormatPointer(StructureMember structureMember) { | ||
Span<byte> bytes = structureMember.Data.AsSpan(); | ||
ushort targetSegment; | ||
ushort targetOffset; | ||
if (structureMember.Type.IsNear && bytes.Length == 2) { | ||
targetSegment = 0; | ||
targetOffset = BitConverter.ToUInt16(bytes); | ||
} else if (bytes.Length == 4) { | ||
targetSegment = BitConverter.ToUInt16(bytes[2..]); | ||
targetOffset = BitConverter.ToUInt16(bytes[..2]); | ||
} else { | ||
throw new ArgumentException($"Invalid pointer size: {bytes.Length * 8}"); | ||
} | ||
|
||
return structureMember.Type.IsNear | ||
? $"DS:{targetOffset:X4}" | ||
: $"{targetSegment:X4}:{targetOffset:X4}"; | ||
} | ||
|
||
private static string FormatEnum(EnumType enumType, byte[] bytes) { | ||
uint value = enumType.MemberSize switch { | ||
1 => bytes[0], | ||
2 => BitConverter.ToUInt16(bytes), | ||
4 => BitConverter.ToUInt32(bytes), | ||
_ => throw new NotSupportedException($"Enum member size {enumType.MemberSize} not supported") | ||
}; | ||
|
||
if (enumType.Members.TryGetValue(value, out string? name)) { | ||
return $"{name} [0x{value:X}]"; | ||
} | ||
|
||
throw new ArgumentOutOfRangeException(nameof(bytes), $"Enum value {value} not found in enum"); | ||
} | ||
|
||
private static string FormatLong(StructureMember structureMember) { | ||
int value = BitConverter.ToInt32(structureMember.Data); | ||
|
||
return $"{value} [0x{value:X8}]"; | ||
} | ||
|
||
private static string FormatUnsignedLong(StructureMember structureMember) { | ||
uint value = BitConverter.ToUInt32(structureMember.Data); | ||
|
||
return $"{value} [0x{value:X8}]"; | ||
} | ||
|
||
private static string FormatShort(StructureMember structureMember) { | ||
short value = BitConverter.ToInt16(structureMember.Data); | ||
|
||
return $"{value} [0x{value:X4}]"; | ||
} | ||
|
||
private static string FormatUnsignedShort(StructureMember structureMember) { | ||
ushort value = BitConverter.ToUInt16(structureMember.Data); | ||
|
||
return $"{value} [0x{value:X4}]"; | ||
} | ||
|
||
private static string FormatChar(byte value) { | ||
return value is < 32 or > 126 | ||
? $"{value} [0x{value:X2}]" | ||
: $"{value} '{(char)value}' [0x{value:X2}]"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
namespace Spice86.Infrastructure; | ||
|
||
using PropertyModels.Extensions; | ||
|
||
using System; | ||
using System.IO; | ||
using System.Timers; | ||
|
||
public class FilePoller { | ||
private readonly string _filePath; | ||
private ulong _lastHash; | ||
private bool _fileChanged; | ||
private readonly Timer _timer; | ||
private readonly Action _actionToPerform; | ||
|
||
public FilePoller(string filePath, Action actionToPerformOnChange, double interval = 1000) { | ||
_filePath = filePath; | ||
_actionToPerform = actionToPerformOnChange; | ||
_timer = new Timer(interval); | ||
_timer.Elapsed += CheckFileChange; | ||
_timer.AutoReset = true; | ||
} | ||
|
||
public void Start() { | ||
_timer.Start(); | ||
} | ||
|
||
public void Stop() { | ||
_timer.Stop(); | ||
} | ||
|
||
private void CheckFileChange(object? sender, ElapsedEventArgs e) { | ||
if (!File.Exists(_filePath)) | ||
return; | ||
|
||
ulong currentHash = CalculateHash(_filePath); | ||
|
||
if (_lastHash != 0 && _lastHash != currentHash) { | ||
_fileChanged = !_fileChanged; | ||
} else if (_lastHash == currentHash && _fileChanged) { | ||
_fileChanged = false; | ||
_actionToPerform(); | ||
} | ||
|
||
_lastHash = currentHash; | ||
} | ||
|
||
private static ulong CalculateHash(string filePath) { | ||
string fileContents = File.ReadAllText(filePath); | ||
|
||
return fileContents.GetDeterministicHashCode64(); | ||
} | ||
} |
Oops, something went wrong.