From 8a5e5c793c09513ba875cb7d7d954bdfd2a2cdc1 Mon Sep 17 00:00:00 2001 From: haven1433 Date: Fri, 9 Dec 2022 08:40:49 -0600 Subject: [PATCH] implement additional UBL features universal branchlink can now handle selection/cut/paste in cases involving pointers and anchors --- src/HexManiac.Core/Models/Code/ThumbParser.cs | 27 +++++--- src/HexManiac.Tests/CodeToolTests.cs | 62 +++++++++++++++++-- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/HexManiac.Core/Models/Code/ThumbParser.cs b/src/HexManiac.Core/Models/Code/ThumbParser.cs index 55a62a47..f25d5f32 100644 --- a/src/HexManiac.Core/Models/Code/ThumbParser.cs +++ b/src/HexManiac.Core/Models/Code/ThumbParser.cs @@ -1,13 +1,11 @@ using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; -using IronPython.Compiler; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Reflection.Emit; using System.Text; namespace HavenSoft.HexManiac.Core.Models.Code { @@ -330,6 +328,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } foreach (var token in longBranchTokens.Values) { + while ((start + result.Count) % 4 != 0) result.Add(0); token.Write(start + result.Count, result); } @@ -393,26 +392,33 @@ namespace HavenSoft.HexManiac.Core.Models.Code { } public static bool IsThumbSelection(IDataModel data, int start, int length) { - var firstRun = data.GetNextRun(start); - if (firstRun.Start == start && firstRun is NoInfoRun && !string.IsNullOrEmpty(data.GetAnchorFromAddress(-1, start))) { + var run = data.GetNextRun(start); + if (run.Start == start && run is NoInfoRun && !string.IsNullOrEmpty(data.GetAnchorFromAddress(-1, start))) { // this is fine, but the next run may be a problem - firstRun = data.GetNextRun(start + 1); + run = data.GetNextRun(start + 1); } - if (firstRun.Start < start + length) { - return false; // no formatting is allowed + // only pointer formats are allowed + while (run.Start < start + length) { + if (run is not PointerRun) return false; + run = data.GetNextRun(run.Start + run.Length); } if (start < 0 || start + length > data.Count) return false; if (data[start + 1] != 0xB5) return false; if (start + length == data.Count) return true; - if (start + length + 1 == data.Count || data[start + length + 1] != 0xB5) return false; - return true; + if (start + length + 1 == data.Count) return false; + if (data[start + length] == 0xFF && data[start + length + 1] == 0xFF) return true; + return data[start + length + 1] == 0xB5; } public static int GetSelectionLength(IReadOnlyList data, int start) { if (start < 0 || start >= data.Count - 1) return -1; if (data[start + 1] != 0xB5) return -1; int i = start + 3; - while (i < data.Count && data[i] != 0xB5) i += 2; + while (i < data.Count) { + if (data[i] == 0xB5) break; + if (data.ReadMultiByteValue(i - 1, 2) == 0xFFFF) break; + i += 2; + } if (i >= data.Count) return -1; var length = i - 1 - start; if (length > 1000) return -1; @@ -435,6 +441,7 @@ namespace HavenSoft.HexManiac.Core.Models.Code { data.ClearFormat(token, start, 1); // clear remaining bytes + if (length > write.Count + 4) data.ClearFormat(token, start + write.Count + 4, length - write.Count - 4); for (int i = write.Count + 4; i < length; i++) token.ChangeData(data, start + i, 0xFF); return true; } diff --git a/src/HexManiac.Tests/CodeToolTests.cs b/src/HexManiac.Tests/CodeToolTests.cs index 06964155..4545642c 100644 --- a/src/HexManiac.Tests/CodeToolTests.cs +++ b/src/HexManiac.Tests/CodeToolTests.cs @@ -1,5 +1,6 @@ using HavenSoft.HexManiac.Core; using HavenSoft.HexManiac.Core.Models; +using HavenSoft.HexManiac.Core.Models.Code; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.Tools; using HavenSoft.HexManiac.Core.ViewModels.Visitors; @@ -342,12 +343,65 @@ namespace HavenSoft.HexManiac.Tests { Assert.Equal(19, ViewPort.ConvertViewPointToAddress(ViewPort.SelectionEnd)); } - // TODO include a 'nop' if needed for alignment + [Fact] + public void LongBranchLink_OddNumberOfCommands_NopInsertedBeforeLongBranchLinkCode() { + ThumbScript = "bl ; bx r0"; + Assert.Equal(0, Model.ReadMultiByteValue(6, 2)); + } - // TODO double-click to select should work even if there's a pointer run involved + [Fact] + public void LongBranchLinkOddNumberCommands_StartsAtMultipleOfTwo_NoNopInserted() { + ViewPort.Goto.Execute(2); + ThumbScript = "bl ; bx r0"; + Assert.NotEqual(0, Model.ReadMultiByteValue(8, 2)); + } - // TODO double-click to select should work even if there's an named-anchor at the starting address + [Fact] + public void CodeWithPointer_DoubleClick_SelectCodeAndPointer() { + ThumbScript = "push {lr}; ldr r0, =<800000>; pop {pc}"; + ViewPort.SelectionStart = ViewPort.ConvertAddressToViewPoint(12); + ThumbScript = "push {lr}"; - // TODO cut should paste a UBL even if there's a pointer run involved + ViewPort.Goto.Execute(0); + ViewPort.ExpandSelection(0, 0); + + Assert.IsType(Model.GetNextRun(8)); + Assert.Equal(11, ViewPort.ConvertViewPointToAddress(ViewPort.SelectionEnd)); + } + + [Fact] + public void CodeWithPointer_ReplaceWithLongBranchLink_Success() { + ThumbScript = "push {lr}; ldr r0, =<800000>; pop {pc}"; + ViewPort.Goto.Execute(12); + ThumbScript = "push {lr}"; + + ViewPort.Goto.Execute(0); + ViewPort.ExpandSelection(0, 0); + ViewPort.Cut(FileSystem); + + // if we succeeded, then the value at 0x8 should be + Assert.Equal(0, Model.ReadMultiByteValue(8, 4)); + } + + [Fact] + public void AnchorAtStart_ExpandSelectionToThumbRoutine_SelectionExpanded() { + ThumbScript = "push {lr}; pop {pc}; push {lr}"; + ViewPort.Edit("^some.anchor "); + + ViewPort.ExpandSelection(0, 0); + + Assert.Equal(3, ViewPort.ConvertViewPointToAddress(ViewPort.SelectionEnd)); + } + + [Fact] + public void ThumbRoutineWithPointer_Cut_RemovePointerFormat() { + ThumbScript = "push {lr}; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; ldr r0, =<800000>; pop {pc}"; + + ViewPort.ExpandSelection(0, 0); + ViewPort.Cut(FileSystem); + + Assert.IsType(Model.GetNextRun(8)); + Assert.IsNotType(Model.GetNextRun(0xC)); + } } }