some initial logging

doesn't actually send the logs anywhere long-term yet, it just shows them in an optional tool. But I can now watch some navigation commands and anchor changes
This commit is contained in:
haven1433 2025-11-29 12:00:09 -06:00
parent 5938e4f5f2
commit a54b87db1b
10 changed files with 113 additions and 25 deletions

View File

@ -113,6 +113,8 @@ namespace HavenSoft.HexManiac.Core.Models {
/// </summary>
public override int EarliestAllowedAnchor => 0x200;
public List<string> LoadingMessages { get; } = new();
public HardcodeTablesModel(Singletons singletons, byte[] data, StoredMetadata metadata = null, bool devMode = false) : base(data, metadata, singletons, devMode) {
gameCode = this.GetGameCode();
if (metadata != null && !metadata.IsEmpty) {
@ -422,8 +424,14 @@ namespace HavenSoft.HexManiac.Core.Models {
if (interruptingSourceRun.Start < source && interruptingSourceRun.Start + interruptingSourceRun.Length > source) {
if (interruptingSourceRun is ITableRun tableRun) {
var tableOffset = tableRun.ConvertByteOffsetToArrayOffset(source);
if (tableOffset.SegmentOffset != 0) return;
if (tableRun.ElementContent[tableOffset.SegmentIndex].Type != ElementContentType.Pointer) return;
if (tableOffset.SegmentOffset != 0) {
LoadingMessages.Add($"Failed to add table {name}: it conflicted with an existing table found at {tableRun.Start.ToAddress()}.");
return;
}
if (tableRun.ElementContent[tableOffset.SegmentIndex].Type != ElementContentType.Pointer) {
LoadingMessages.Add($"Failed to add table {name} at {destination.ToAddress()}: data in the table didn't match pointers in the file.");
return;
}
} else {
// the source isn't actually a pointer, we shouldn't write anything
return;
@ -443,6 +451,7 @@ namespace HavenSoft.HexManiac.Core.Models {
}
if (array.ElementCount + desiredChange <= 0) {
// erase the entire run
LoadingMessages.Add($"When trying to add table {name}, had to clear existing format from {array.Start.ToAddress()} to {(array.Start + array.Length).ToAddress()} because a formatting conflict was found.");
ClearFormat(noChangeDelta, array.Start, array.Length);
} else {
var arrayName = GetAnchorFromAddress(-1, array.Start);

View File

@ -790,6 +790,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (!Singletons.GameReferenceTables.TryGetValue(model.GetGameCode(), out var refTable)) refTable = null;
fileSystem.SaveMetadata(file.Name, viewPort.Model.ExportMetadata(refTable, Singletons.MetadataInfo).Serialize());
Debug.Assert(viewPort.ChangeHistory.IsSaved, "Put a breakpoint in ChangeHistory.CurrentChange, because a changable token is being created too soon!");
if (model is HardcodeTablesModel hardcode) viewPort.Tools.LogTool.LogMessages.AddRange(hardcode.LoadingMessages);
}, TaskContinuationOptions.ExecuteSynchronously);
}
Add(viewPort);
@ -1286,7 +1287,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var collection = CreateGotoShortcuts(GotoViewModel);
if (collection != null) GotoViewModel.Shortcuts = new ObservableCollection<GotoShortcutViewModel>(collection);
GotoViewModel.PropertyChanged += GotoPropertyChanged;
if(SelectedTab is IEditableViewPort vp1 && vp1.Model is BaseModel bm) {
if (SelectedTab is IEditableViewPort vp1 && vp1.Model is BaseModel bm) {
var vm = GotoViewModel;
vp1.InitializationWorkload.ContinueWith(task => {
if (vm == GotoViewModel) vm.UpdateDocs(bm.GenerateDocumentationLabels(Singletons.ScriptLines));
@ -1409,9 +1410,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (e.PropertyName == nameof(gotoViewModel.ShowAll)) FocusOnGotoShortcuts = !gotoViewModel.ShowAll;
}
private void AcceptError(object sender, string message) => ErrorMessage = message;
private void AcceptError(object sender, string message) {
if (sender is MapEditorViewModel map) map.ViewPort.Tools.LogTool.LogMessages.Add("Error: " + message);
ErrorMessage = message;
}
private void AcceptMessage(object sender, string message) => InformationMessage = message;
private void AcceptMessage(object sender, string message) {
if (sender is MapEditorViewModel map) map.ViewPort.Tools.LogTool.LogMessages.Add("Message: " + message);
InformationMessage = message;
}
private void AcceptMessageClear(object sender, EventArgs e) => HideSearchControls.Execute();

View File

@ -521,7 +521,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
foreach (var newM in newMaps) {
bool match = false;
foreach (var existingM in VisibleMaps) {
if (existingM.UpdateFrom(newM)) {
if (existingM.UpdateFrom(newM)) {
match = true;
break;
}
@ -631,15 +631,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public bool ShowBeneath { get => showBeneath; set => Set(ref showBeneath, value, old => PrimaryMap.ShowBeneath = ShowBeneath); }
private bool hideEvents;
public bool HideEvents { get => hideEvents; set => Set(ref hideEvents, value, old => {
foreach (var m in VisibleMaps) m.ShowEvents = hideEvents ? MapDisplayOptions.NoEvents : MapDisplayOptions.AllEvents;
Tutorials.Complete(Tutorial.Ctrl_HideEvents);
}); }
public bool HideEvents {
get => hideEvents; set => Set(ref hideEvents, value, old => {
foreach (var m in VisibleMaps) m.ShowEvents = hideEvents ? MapDisplayOptions.NoEvents : MapDisplayOptions.AllEvents;
Tutorials.Complete(Tutorial.Ctrl_HideEvents);
});
}
// if it returns an empty array: no hover tip to display
// if it returns null: continue displaying previous hover tip
// if it returns content: display that as the new hover tip
private static readonly object[] EmptyTooltip = new object[0];
private static readonly object[] EmptyTooltip = new object[0];
public object Hover(double x, double y) {
var map = MapUnderCursor(x, y);
if (map == null) return EmptyTooltip;
@ -2309,5 +2311,5 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
public enum MapDisplayOptions { AllEvents, ObjectEvents, NoEvents }
public record TileSelection(int[]Tiles, int Width);
public record TileSelection(int[] Tiles, int Width);
}

View File

@ -70,7 +70,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
var model = viewPort.Model;
var run = model.GetNextRun(address) as ITableRun;
if (run == null) return;
var originalRun = run;
run = model.RelocateForExpansion(token, run, run.Length + 5);
if (originalRun.Start != run.Start) viewPort.RaiseMessage($"Repointed data from {originalRun.Start.ToAddress()} to {run.Start.ToAddress()}.");
run = run.Append(token, 1);
address = run.Start;
var newScript = model.FindFreeSpace(model.FreeSpaceStart, 1);
@ -273,8 +275,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Map {
var run = model.GetNextRun(address);
if (run is not ITableRun tableRun) return;
var originalRun = tableRun;
tableRun = tableRun.Append(token, 1);
model.ObserveRunWritten(token, tableRun);
if (originalRun.Start != tableRun.Start) viewPort.RaiseMessage($"Repointed data from {originalRun.Start.ToAddress()} to {tableRun.Start.ToAddress()}.");
// add new element data
var newScriptStart = model.FindFreeSpace(model.FreeSpaceStart, 1);

View File

@ -366,10 +366,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public ICommand SpriteToolCommand { get; } = new StubCommand();
public ICommand CodeToolCommand { get; } = new StubCommand();
public PCSTool StringTool => null;
public TableTool TableTool => null;
public SpriteTool SpriteTool => null;
public CodeTool CodeTool => null;
public PCSTool? StringTool => null;
public TableTool? TableTool => null;
public SpriteTool? SpriteTool => null;
public CodeTool? CodeTool => null;
public LogTool? LogTool => null;
public IDisposable DeferUpdates => new StubDisposable();

View File

@ -36,6 +36,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
SpriteTool? SpriteTool { get; }
LogTool? LogTool { get; }
IDisposable DeferUpdates { get; }
event EventHandler<string> OnError;

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace HavenSoft.HexManiac.Core.ViewModels.Tools;
public class LogTool : ViewModelCore, IToolViewModel {
public string Name => "Logs";
public void DataForCurrentRunChanged() {
AllLogText = string.Join(Environment.NewLine, LogMessages);
NotifyPropertyChanged(nameof(AllLogText));
}
public List<string> LogMessages { get; } = new();
public string AllLogText { get; private set; }
}

View File

@ -40,6 +40,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public SpriteTool? SpriteTool => null;
public LogTool? LogTool => null;
public IDisposable DeferUpdates => new StubDisposable();
public int Count => 0;
@ -60,7 +62,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public class ToolTray : ViewModelCore, IToolTrayViewModel {
private readonly IList<IToolViewModel> tools;
private readonly StubCommand hideCommand;
private readonly StubCommand stringToolCommand, tableToolCommand, codeToolCommand, spriteToolCommand;
private readonly StubCommand stringToolCommand, tableToolCommand, codeToolCommand, spriteToolCommand, logToolCommand;
private readonly HashSet<Action> deferredWork = new HashSet<Action>();
private readonly IDataModel model;
private readonly Selection selection;
@ -75,6 +77,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (SelectedTool == TableTool) TableTool.DataForCurrentRunChanged();
if (SelectedTool == SpriteTool) SpriteTool.DataForCurrentRunChanged();
if (SelectedTool == StringTool) StringTool.DataForCurrentRunChanged();
if (SelectedTool == LogTool) LogTool.DataForCurrentRunChanged();
CodeTool.IsSelected = SelectedTool == CodeTool;
}
}
@ -94,6 +97,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public ICommand TableToolCommand => tableToolCommand;
public ICommand CodeToolCommand => codeToolCommand;
public ICommand SpriteToolCommand => spriteToolCommand;
public ICommand LogToolCommand => logToolCommand;
public PCSTool StringTool => (PCSTool)tools[1];
@ -103,6 +107,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public SpriteTool SpriteTool => (SpriteTool)tools[2];
public LogTool LogTool => (LogTool)tools[4];
private bool runningDeferredWork;
private StubDisposable currentDeferralToken;
public IDisposable DeferUpdates {
@ -137,6 +143,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
new PCSTool(viewPort, history, this),
new SpriteTool(viewPort, history),
new CodeTool(singletons, viewPort, selection, history, viewPort),
new LogTool(),
};
StubCommand commandFor(int i) => new StubCommand {
@ -153,6 +160,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
stringToolCommand = commandFor(1);
spriteToolCommand = commandFor(2);
codeToolCommand = commandFor(3);
logToolCommand = commandFor(4);
hideCommand = new StubCommand {
CanExecute = arg => SelectedIndex != -1,
@ -182,6 +190,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
TableTool.DataForCurrentRunChanged();
SpriteTool.DataForCurrentRunChanged();
CodeTool.DataForCurrentRunChanged();
LogTool.DataForCurrentRunChanged();
}
public IEnumerator<IToolViewModel> GetEnumerator() => tools.GetEnumerator();

View File

@ -221,6 +221,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
dispatcher.BlockOnUIWork(() => {
TabChangeRequestedEventArgs args;
if (arg is string str) {
tools.LogTool.LogMessages.Add("Attempting to Goto Token: " + str);
var possibleMatches = Model.GetExtendedAutocompleteOptions(str);
if (possibleMatches.Count == 1) str = possibleMatches[0];
else if (possibleMatches.Count > 1 && possibleMatches.All(match => Model.GetMatchedWords(match).Any())) str = possibleMatches[0];
@ -308,6 +309,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
}
if (arg is int address) {
tools.LogTool.LogMessages.Add("Attempting to Goto Address: " + address.ToString());
}
selection.Goto.Execute(arg);
args = new TabChangeRequestedEventArgs(this);
@ -891,7 +896,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
#endregion
public int FreeSpaceStart { get => Model.FreeSpaceStart; set {
public int FreeSpaceStart {
get => Model.FreeSpaceStart; set {
if (Model.FreeSpaceStart != value) {
Model.FreeSpaceStart = value;
NotifyPropertyChanged();
@ -943,6 +949,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
index = Model.ReadPointer(run.Start);
run = Model.GetNextRun(index);
}
tools.LogTool.LogMessages.Add($"Attempting to Update Anchor at address {run.Start.ToAddress()}: {anchorText}");
if (run.Start <= index) {
var token = new NoDataChangeDeltaModel();
@ -1410,7 +1417,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (tableRun.ElementContent[contentIndex].Type == ElementContentType.Pointer) {
Model.ClearFormat(CurrentChange, index + i, 1);
}
}
}
if (run is PointerRun) Model.ClearFormat(CurrentChange, index + i, 1);
}
CurrentChange.ChangeData(Model, index + i, value);
@ -1502,7 +1509,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
return false;
}
public void RaiseError(string text) => OnError?.Invoke(this, text);
public void RaiseError(string text) {
tools.LogTool.LogMessages.Add("Error: " + text);
OnError?.Invoke(this, text);
}
private string deferredMessage;
public void RaiseMessage(string text) {
@ -1510,7 +1520,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
deferredMessage = text;
tools.Schedule(RaiseMessage);
}
private void RaiseMessage() => OnMessage?.Invoke(this, deferredMessage);
private void RaiseMessage() {
tools.LogTool.LogMessages.Add("Message: " + deferredMessage);
OnMessage?.Invoke(this, deferredMessage);
}
public void RaiseRequestTabChange(ITabContent tab) => RequestTabChange?.Invoke(this, new(tab));
public void RaiseRequestTabChange(TabChangeRequestedEventArgs args) => RequestTabChange?.Invoke(this, args);
@ -2353,6 +2366,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
// follow pointer
if (format is Pointer pointer) {
if (pointer.Destination != Pointer.NULL) {
tools.LogTool.LogMessages.Add($"Follow Link {pointer.DestinationAsText}");
selection.GotoAddress(pointer.Destination);
} else if (string.IsNullOrEmpty(pointer.DestinationName)) {
OnError(this, $"null pointers point to nothing, so going to their source isn't possible.");
@ -2457,7 +2471,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
// try again soon.
RequestDelayedWork?.Invoke(this, () => ConsiderReload(fileSystem));
}
};
}
;
if (fileSystem is IWorkDispatcher dispatcher) {
dispatcher.BlockOnUIWork(action);
@ -2553,6 +2568,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
private List<ViewPort> RecentDuplicates = new();
public void GotoScript(int address) {
tools.LogTool.LogMessages.Add("Goto Script: " + address.ToAddress());
if (RecentDuplicates.Count == 0) RecentDuplicates.Add(this);
int start;
@ -2720,7 +2736,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var startPlaces = Model.FindPossibleTextStartingPlaces(left, length);
// do the actual search now that we know places to start
var foundCount = Model.ConsiderResultsAsTextRuns( () => history.InsertCustomChange(new NoDataChangeDeltaModel()), startPlaces);
var foundCount = Model.ConsiderResultsAsTextRuns(() => history.InsertCustomChange(new NoDataChangeDeltaModel()), startPlaces);
if (foundCount == 0) {
OnError?.Invoke(this, "Failed to automatically find text at that location.");
} else {
@ -3096,7 +3112,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
RaiseError($"Writing {length} 00 bytes would overwrite existing data.");
exitEditEarly = true;
}
} else if (command.StartsWith("ff(")&&paramsEnd>3&&command.Substring(3,paramsEnd-3).TryParseInt(out length)) {
} else if (command.StartsWith("ff(") && paramsEnd > 3 && command.Substring(3, paramsEnd - 3).TryParseInt(out length)) {
var currentRun = Model.GetNextRun(index);
if (currentRun.Start < index || (currentRun.Start == index && currentRun is not NoInfoRun) || (currentRun.Start > index && currentRun.Start < index + length)) {
RaiseError($"Writing {length} FF bytes would overwrite formatted data.");
@ -3314,7 +3330,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
if (run is ITableRun && sender != Tools.StringTool && Model.GetNextRun(Tools.StringTool.Address).Start == run.Start) Tools.StringTool.DataForCurrentRunChanged();
if (run is ITableRun && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged();
if (run is ITableRun && Model.GetNextRun(Tools.TableTool.Address).Start == run.Start) Tools.TableTool.DataForCurrentRunChanged();
}
private void ModelDataMovedByTool(object sender, (int originalLocation, int newLocation) locations) {

View File

@ -118,6 +118,17 @@
</Style>
</Button.Style>
</Button>
<Button Content="Logs" Command="{Binding Tools.LogToolCommand}" Margin="0,2" LayoutTransform="{StaticResource Sideways}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Style.Triggers>
<DataTrigger Binding="{Binding Tools.SelectedIndex}" Value="4">
<Setter Property="BorderBrush" Value="{DynamicResource Accent}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
@ -1121,6 +1132,19 @@
</DockPanel>
</Grid>
</DockPanel>
<Grid Name="LogToolPanel">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedIndex}" Value="4">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBox Text="{Binding LogTool.AllLogText, Mode=OneWay}" IsReadOnly="True" Margin="4" />
</Grid>
</Grid>
<GridSplitter Grid.Column="1" Focusable="False" HorizontalAlignment="Stretch" Background="{DynamicResource Backlight}" Width="4" MouseDoubleClick="ResetLeftToolsPane"/>