From d1ba8e2a27562527e5a78f0d2fdc5db639e87717 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sun, 15 Dec 2019 14:45:01 -0600 Subject: [PATCH 1/2] Implement search for branch-link in thumb code, branch-link commands are relative. That means that the instruction for the same destination will look different depending on the source. As a user, I want to know what thumb code calls a routine I'm looking at. That's most likely going to be via a branch-link command, although it could be through a blx. Let the user use Find -> "bl " to search for branch-links that jump to a given location. --- .../Models/Code/armReference.txt | 2 +- .../Models/HardcodeTablesModel.cs | 4 +- src/HexManiac.Core/ViewModels/ViewPort.cs | 46 +++++++++++++++++++ src/HexManiac.Tests/NavigationTests.cs | 16 +++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/HexManiac.Core/Models/Code/armReference.txt b/src/HexManiac.Core/Models/Code/armReference.txt index 382e86f2..cb0da629 100644 --- a/src/HexManiac.Core/Models/Code/armReference.txt +++ b/src/HexManiac.Core/Models/Code/armReference.txt @@ -21,7 +21,7 @@ lt=1011 @ < less than (signed) gt=1100 @ > greater than (signed) le=1101 @ <= less than or equal (signed) al=1110 @ always -ne=1111 @ never +nv=1111 @ never @ opcodes and args 0000000000000000 | nop @ does nothing diff --git a/src/HexManiac.Core/Models/HardcodeTablesModel.cs b/src/HexManiac.Core/Models/HardcodeTablesModel.cs index 914d5c21..06e13ff9 100644 --- a/src/HexManiac.Core/Models/HardcodeTablesModel.cs +++ b/src/HexManiac.Core/Models/HardcodeTablesModel.cs @@ -259,7 +259,9 @@ namespace HavenSoft.HexManiac.Core.Models { // wild pokemon source = Find("0348048009E00000FFFF0000"); string table(int length) => $"<[rate:: list<[low. high. species:pokenames]{length}>]1>"; - AddTable(source, WildTableName, $"[bank. map. unused: grass{table(12)} surf{table(5)} tree{table(5)} fish{table(10)}]"); + using (ModelCacheScope.CreateScope(this)) { // cares about pokenames + AddTable(source, WildTableName, $"[bank. map. unused: grass{table(12)} surf{table(5)} tree{table(5)} fish{table(10)}]"); + } // specials switch (gameCode) { diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs index 895cee9b..57189031 100644 --- a/src/HexManiac.Core/ViewModels/ViewPort.cs +++ b/src/HexManiac.Core/ViewModels/ViewPort.cs @@ -9,6 +9,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -899,6 +900,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels { results.AddRange(Search(searchBytes).Select(result => (result, result + 3))); } + // it might be a bl command + if (cleanedSearchString.StartsWith("BL ") && cleanedSearchString.Contains("<") && cleanedSearchString.EndsWith(">")) { + results.AddRange(FindBranchLink(cleanedSearchString)); + } + // attempt to parse the search string fully if (TryParseSearchString(searchBytes, cleanedSearchString, errorOnParseError: results.Count == 0)) { // find matches @@ -915,6 +921,46 @@ namespace HavenSoft.HexManiac.Core.ViewModels { return results; } + private IEnumerable<(int start, int end)> FindBranchLink(string command) { + var addressStart = command.IndexOf(" <") + 2; + var addressEnd = command.LastIndexOf(">"); + if (addressEnd < addressStart) yield break; + var addressText = command.Substring(addressStart, addressEnd - addressStart); + if (!int.TryParse(addressText, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out int address)) { + address = Model.GetAddressFromAnchor(CurrentChange, -1, addressText); + if (address < 0 || address >= Model.Count) yield break; + } + + // I want to know, for any given point in the raw data, if it's possible a branch-link command pointing to `address` + // branch link commands are always 4 bytes and have the following format: + // 11111 #11 11110 #11, where #=pc+#*2+4 + // note that this command is 4 bytes long, stored byte reversed. So in the data, it's: + // 8 bits: bits 11-18 of a 22 bit signed offset + // 8 bits: + // the low 3 bits are bits 19-21 of a 22 bit signed offset + // the high 5 bits are always 11110 + // 8 bits: bits 0-7 of a 22 bit signed offset + // 8 bits: + // the low 3 bits are bits 8-10 of a 22 bit signed offset + // the high 5 bits are always 11111 + // the command is always 2-byte aligned + // + // bit order is really weird (11-18, 19-21, 0-7, 8-10) because BL is made of **2** instructions, + // and each instruction is stored little-endian + + // start as early as possible in the file: maximum offset, or offset for source=0 + int offset = Math.Min(0b0111111111111111111111, (address - 4) / 2); + for (; true; offset--) { // traveling down the offsets means traveling up the source options + int source = address - 4 - offset * 2; + if (source + 4 > Model.RawData.Length) break; + if (Model.RawData[source + 2] != (byte)offset) continue; // check source+2 first because it's the simplest, and thus fastest + if (Model.RawData[source + 0] != (byte)(offset >> 11)) continue; + if (Model.RawData[source + 3] != (0b11111000 | (0b111 & offset >> 8))) continue; + if (Model.RawData[source + 1] != (0b11110000 | (0b111 & offset >> 19))) continue; + yield return (source, source + 3); + } + } + private IEnumerable<(int start, int end)> FindUnquotedText(string cleanedSearchString, List searchBytes) { var pcsBytes = PCSString.Convert(cleanedSearchString); pcsBytes.RemoveAt(pcsBytes.Count - 1); // remove the 0xFF that was added, since we're searching for a string segment instead of a whole string. diff --git a/src/HexManiac.Tests/NavigationTests.cs b/src/HexManiac.Tests/NavigationTests.cs index 7098ad89..9f986d4c 100644 --- a/src/HexManiac.Tests/NavigationTests.cs +++ b/src/HexManiac.Tests/NavigationTests.cs @@ -2,6 +2,7 @@ using HavenSoft.HexManiac.Core.Models; using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; @@ -335,6 +336,21 @@ namespace HavenSoft.HexManiac.Tests { } } + [Fact] + public void CanSearchForBranchLink() { + var test = new BaseViewModelTestClass(); + var command1 = test.ViewPort.Tools.CodeTool.Parser.Compile(test.Model, 0x0010, "bl <000060>").ToArray(); + var command2 = test.ViewPort.Tools.CodeTool.Parser.Compile(test.Model, 0x0100, "bl <000060>").ToArray(); + Array.Copy(command1, 0, test.Model.RawData, 0x0010, command1.Length); + Array.Copy(command2, 0, test.Model.RawData, 0x0100, command2.Length); + + var results = test.ViewPort.Find("bl <000060>").ToList(); + + Assert.Equal(2, results.Count); + Assert.Equal(0x010, results[0].start); + Assert.Equal(0x100, results[1].start); + } + private void StandardSetup(out byte[] data, out PokemonModel model, out ViewPort viewPort) { data = Enumerable.Repeat((byte)0xFF, 0x200).ToArray(); model = new PokemonModel(data); From 6af45977f8a3309fda2d7fde33acc6b1b365fbb0 Mon Sep 17 00:00:00 2001 From: Benjamin Popp Date: Sun, 15 Dec 2019 15:04:54 -0600 Subject: [PATCH 2/2] Improve tutor expansion using CFRU, I found another offset that I need to change. Using bl-search, I was able to learn that that offset is called from the tutor special. Go ahead and add code to fix it up for both FireRed and LeafGreen. Emerald needs testing. --- .../QuickEditItems/MakeTutorsExpandable.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTutorsExpandable.cs b/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTutorsExpandable.cs index 86b9ef00..51e1451d 100644 --- a/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTutorsExpandable.cs +++ b/src/HexManiac.Core/ViewModels/QuickEditItems/MakeTutorsExpandable.cs @@ -53,6 +53,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.QuickEditItems { var (getTutorMove, canPokemonLearnTutorMove, getTutorMove_Length, canPokemonLearnTutorMove_Length) = GetOffsets(viewPort, gameCode); var specialsAddress = model.GetAddressFromAnchor(token, -1, HardcodeTablesModel.SpecialsTable); var tutorSpecial = model.ReadPointer(specialsAddress + 397 * 4); // Emerald tutors is actually special 477, but we don't need to edit it so it doesn't matter. + tutorSpecial -= 1; // the pointer is to thumb code, so it's off by one. var tutormoves = model.GetAddressFromAnchor(viewPort.CurrentChange, -1, MoveTutors); var tutorcompatibility = model.GetAddressFromAnchor(viewPort.CurrentChange, -1, TutorCompatibility); @@ -139,11 +140,18 @@ namespace HavenSoft.HexManiac.Core.ViewModels.QuickEditItems { private void UpdateRoutine_TutorSpecial(ViewPort viewPort, int tutorSpecial, string gameCode) { if (gameCode == Emerald) return; // Emerald's tutor special doesn't have a limiter, so it doesn't need to be updated. - // change the code from 'branch-hi' to 'branch-never' so that the standard codepath is taken for tutorID>14 - const int instructionIndex = 5; - const int instructionWidth = 2; + // change the code from 'branch-hi' to 'nop' so that the standard codepath is taken for tutorID>14 + int instructionIndex = 5; + int instructionWidth = 2; var branchOffset = tutorSpecial + instructionIndex * instructionWidth; - viewPort.CurrentChange.ChangeData(viewPort.Model, branchOffset, 0xDF); + viewPort.Model.WriteMultiByteValue(branchOffset, 2, viewPort.CurrentChange, 0x0000); + + // a separate routine several layers down also needs to be updated + // change the code from 'branch-hi' to 'nop' so that the standard codepath is taken for tutorID>14 + instructionIndex = 20; + branchOffset = (gameCode == FireRed) ? 0x11F430 : 0x11F408; // FireRed / LeafGreen + branchOffset += instructionIndex * instructionWidth; + viewPort.Model.WriteMultiByteValue(branchOffset, 2, viewPort.CurrentChange, 0x0000); } } }