diff --git a/src/HexManiac.Core/Models/AutoSearchModel.cs b/src/HexManiac.Core/Models/AutoSearchModel.cs index 70addf74..82739a51 100644 --- a/src/HexManiac.Core/Models/AutoSearchModel.cs +++ b/src/HexManiac.Core/Models/AutoSearchModel.cs @@ -153,6 +153,7 @@ namespace HavenSoft.HexManiac.Core.Models { }; var list = Find(originalCode, 0x120B00, 0x120C00); + if (list.Count == 0) return; tutorMoves = ReadPointer(list[0] - 4); tutorCompatibility = ReadPointer(list[0] + originalCode.Length); if (tutorMoves < 0 || tutorMoves > Count || tutorCompatibility < 0 || tutorCompatibility > Count) return; diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs index f1aa421f..7315a888 100644 --- a/src/HexManiac.Core/Models/PokemonModel.cs +++ b/src/HexManiac.Core/Models/PokemonModel.cs @@ -749,6 +749,17 @@ namespace HavenSoft.HexManiac.Core.Models { return; } + // case 1.5: unnamed anchor doesn't start where the delete starts, but the pointer to it lives in an array + // this anchor is real, but the format is wrong. Keep the anchor, lose the format. + if (run.Start != originalStart && run.PointerSources != null && run.PointerSources.Any(source => GetNextRun(source) is ArrayRun)) { + var newRun = new NoInfoRun(run.Start, run.PointerSources); + runIndex = BinarySearch(run.Start); + changeToken.RemoveRun(run); + changeToken.AddRun(newRun); + runs[runIndex] = newRun; + return; + } + // case 2: unnamed anchor doesn't start where the delete starts // this anchor shouldn't exist. The things that point to it aren't real pointers. if (run.Start != originalStart) { @@ -797,7 +808,7 @@ namespace HavenSoft.HexManiac.Core.Models { } else if (runs[~index - 1] is ArrayRun array) { ClearPointerWithinArray(changeToken, start, ~index - 1, array); } else { - throw new NotImplementedException(); + Debug.Fail($"Trying to clear a pointer that starts at {start:X6}, but the run in that area seems to be not be a pointer run."); } } else if (sourceToUnmappedName.TryGetValue(start, out var name)) { changeToken.RemoveUnmappedPointer(start, name); diff --git a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs index 491725fc..e0a7ec7e 100644 --- a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs @@ -262,10 +262,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { var destination = model.ReadPointer(start); - // by the time we get this far, we're guaranteed that this will be a PCSRun. - // if it's not a PCSRun, we shouldn't have asked to construct this object. + // by the time we get this far, we're nearly guaranteed that this will be a IStreamRun. + // if it's not an IStreamRun, it's because the pointer in the array doesn't actually point to a valid stream. + // at which point, we don't want to display any content. var run = (IStreamRun)model.GetNextRun(destination); - content = run.SerializeRun(); + content = run.SerializeRun() ?? string.Empty; } } diff --git a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs index f5f0132e..560e9324 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs @@ -164,7 +164,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { viewModel.DataChanged += ForwardModelChanged; if (item is ArrayRunPointerSegment pointerSegment) { var destination = model.ReadPointer(itemAddress); - if (destination != Pointer.NULL && pointerSegment.DestinationDataMatchesPointerFormat(model, new NoDataChangeDeltaModel(), destination)) { + if (destination != Pointer.NULL && model.GetNextRun(destination) is IStreamRun && pointerSegment.DestinationDataMatchesPointerFormat(model, new NoDataChangeDeltaModel(), destination)) { if (pointerSegment.InnerFormat == PCSRun.SharedFormatString || pointerSegment.InnerFormat == PLMRun.SharedFormatString) { var streamElement = new StreamArrayElementViewModel(history, (FieldArrayElementViewModel)viewModel, model, item.Name, itemAddress); streamElement.DataChanged += ForwardModelChanged; diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 87d8a05b..138e154e 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -329,14 +329,16 @@ namespace HavenSoft.HexManiac.Core.ViewModels { OnError?.Invoke(this, string.Empty); var newRun = Model.GetNextRun(index); if (newRun is ArrayRun array) { - // if the format changed (ignoring length), run a goto to update the display width - if (run is ArrayRun array2 && !array.HasSameSegments(array2)) { - selection.PropertyChanged -= SelectionPropertyChanged; // to keep from double-updating the AnchorText - Goto.Execute(index.ToString("X2")); - selection.PropertyChanged += SelectionPropertyChanged; + using (ModelCacheScope.CreateScope(Model)) { + // if the format changed (ignoring length), run a goto to update the display width + if (run is ArrayRun array2 && !array.HasSameSegments(array2)) { + selection.PropertyChanged -= SelectionPropertyChanged; // to keep from double-updating the AnchorText + Goto.Execute(index.ToString("X2")); + selection.PropertyChanged += SelectionPropertyChanged; + } + UpdateColumnHeaders(); + Tools.RefreshContent(); } - UpdateColumnHeaders(); - Tools.RefreshContent(); } RefreshBackingData(); } else { @@ -941,17 +943,19 @@ namespace HavenSoft.HexManiac.Core.ViewModels { // open tool var byteOffset = scroll.ViewPointToDataIndex(new Point(x, y)); var currentRun = Model.GetNextRun(byteOffset); - if (currentRun is IStreamRun) { - Tools.StringTool.Address = currentRun.Start; - Tools.SelectedIndex = Tools.IndexOf(Tools.StringTool); - } else if (currentRun is ArrayRun array) { - var offsets = array.ConvertByteOffsetToArrayOffset(byteOffset); - if (format is PCS) { - Tools.StringTool.Address = offsets.SegmentStart - offsets.ElementIndex * array.ElementLength; + using (ModelCacheScope.CreateScope(Model)) { + if (currentRun is IStreamRun) { + Tools.StringTool.Address = currentRun.Start; Tools.SelectedIndex = Tools.IndexOf(Tools.StringTool); - } else { - Tools.TableTool.Address = array.Start + offsets.ElementIndex * array.ElementLength; - Tools.SelectedIndex = Tools.IndexOf(Tools.TableTool); + } else if (currentRun is ArrayRun array) { + var offsets = array.ConvertByteOffsetToArrayOffset(byteOffset); + if (format is PCS) { + Tools.StringTool.Address = offsets.SegmentStart - offsets.ElementIndex * array.ElementLength; + Tools.SelectedIndex = Tools.IndexOf(Tools.StringTool); + } else { + Tools.TableTool.Address = array.Start + offsets.ElementIndex * array.ElementLength; + Tools.SelectedIndex = Tools.IndexOf(Tools.TableTool); + } } } } diff --git a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs index b1184e94..c15d259c 100644 --- a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs +++ b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs @@ -241,8 +241,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors { Model.ExpandData(CurrentChange, memoryLocation + 3); var currentRun = Model.GetNextRun(memoryLocation); - bool inArray = currentRun.Start <= memoryLocation && currentRun is ArrayRun; - var sources = currentRun.PointerSources; + if (currentRun.Start > memoryLocation) currentRun = null; + bool inArray = currentRun is ArrayRun && currentRun.Start <= memoryLocation; + var sources = currentRun?.PointerSources; if (!inArray) { if (destination != string.Empty) { diff --git a/src/HexManiac.Tests/AutoSearchTests.cs b/src/HexManiac.Tests/AutoSearchTests.cs index 4eac8d9b..47ccbdb3 100644 --- a/src/HexManiac.Tests/AutoSearchTests.cs +++ b/src/HexManiac.Tests/AutoSearchTests.cs @@ -209,7 +209,8 @@ namespace HavenSoft.HexManiac.Tests { var compatibilityLocation = model.GetAddressFromAnchor(noChange, -1, "tutorcompatibility"); // ruby and sapphire have no tutors - if (game.Contains("Ruby") || game.Contains("Sapphire")) { + // Gaia has move tutors, but it does a bunch of custom stuff (multiple tables) so I don't feel bad about not supporting it by default. + if (game.Contains("Ruby") || game.Contains("Sapphire") || game.Contains("Gaia")) { Assert.Equal(Pointer.NULL, movesLocation); Assert.Equal(Pointer.NULL, compatibilityLocation); return;