diff --git a/src/Gen3Hex.Core/Gen3Hex.Core.csproj b/src/Gen3Hex.Core/Gen3Hex.Core.csproj
index aed3e12f..d2219b23 100644
--- a/src/Gen3Hex.Core/Gen3Hex.Core.csproj
+++ b/src/Gen3Hex.Core/Gen3Hex.Core.csproj
@@ -46,6 +46,7 @@
+
diff --git a/src/Gen3Hex.Core/Models/PCSString.cs b/src/Gen3Hex.Core/Models/PCSString.cs
index b1c26e9d..a48c1c30 100644
--- a/src/Gen3Hex.Core/Models/PCSString.cs
+++ b/src/Gen3Hex.Core/Models/PCSString.cs
@@ -50,6 +50,7 @@ namespace HavenSoft.Gen3Hex.Core.Models {
}
public static List Convert(string input) {
+ if (input.StartsWith("\"")) input = input.Substring(1); // trim leading " at start of string
var result = new List();
int index = 0;
diff --git a/src/Gen3Hex.Core/Models/SearchByte.cs b/src/Gen3Hex.Core/Models/SearchByte.cs
new file mode 100644
index 00000000..d91d0c13
--- /dev/null
+++ b/src/Gen3Hex.Core/Models/SearchByte.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace HavenSoft.Gen3Hex.Core.Models {
+ public interface ISearchByte {
+ bool Match(byte value);
+ }
+ public class SearchByte : ISearchByte {
+ private readonly byte value;
+ public SearchByte(int value) => this.value = (byte)value;
+ public static explicit operator SearchByte (byte value) => new SearchByte(value);
+ public bool Match(byte value) => value == this.value;
+ }
+ public class PCSSearchByte : ISearchByte {
+ private readonly byte match1, match2;
+ public PCSSearchByte(int value) {
+ match1 = (byte)value;
+ match2 = match1;
+ if (PCSString.PCS[match1] == null) return;
+ var valueAsChar = PCSString.PCS[match1][0];
+ if (char.IsUpper(valueAsChar)) {
+ Debug.Assert(IndexOf(PCSString.PCS, "a") - IndexOf(PCSString.PCS, "A") == 0x1A);
+ match2 += 0x1A;
+ }
+ }
+ public bool Match(byte value) => value == match1 || value == match2;
+ private static int IndexOf(IReadOnlyList pcs, string value) => Enumerable.Range(0, 0x100).Single(i => pcs[i] == value);
+ }
+}
diff --git a/src/Gen3Hex.Core/ViewModels/ViewPort.cs b/src/Gen3Hex.Core/ViewModels/ViewPort.cs
index 49609351..5e832c31 100644
--- a/src/Gen3Hex.Core/ViewModels/ViewPort.cs
+++ b/src/Gen3Hex.Core/ViewModels/ViewPort.cs
@@ -344,23 +344,40 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels {
public IReadOnlyList Find(string rawSearch) {
var results = new List();
- var cleanedSearchString = rawSearch.Replace(" ", string.Empty).ToUpper();
- var searchBytes = new List();
+ var cleanedSearchString = rawSearch.ToUpper();
+ var searchBytes = new List();
var hex = "0123456789ABCDEF";
+ // precheck: it might be a string with no quotes, we should check for matches for that.
+ if (cleanedSearchString.Length > 3 && !cleanedSearchString.Contains('"')) {
+ var pcsBytes = PCSString.Convert(cleanedSearchString);
+ searchBytes.AddRange(pcsBytes.Select(b => new PCSSearchByte(b)));
+ for (int i = 0; i < Model.Count - searchBytes.Count; i++) {
+ for (int j = 0; j < searchBytes.Count; j++) {
+ if (!searchBytes[j].Match(Model[i + j])) break;
+ if (j == searchBytes.Count - 1) results.Add(i);
+ }
+ }
+ searchBytes.Clear();
+ }
+
for (int i = 0; i < cleanedSearchString.Length;) {
+ if (cleanedSearchString[i] == ' ') {
+ i++;
+ continue;
+ }
if (cleanedSearchString[i] == '<') {
var pointerEnd = cleanedSearchString.IndexOf('>', i);
if (pointerEnd == -1) { OnError(this, "Search mismatch: no closing >"); return results; }
var pointerContents = cleanedSearchString.Substring(i + 1, pointerEnd - i - 2);
var address = Model.GetAddressFromAnchor(-1, pointerContents);
if (address != Pointer.NULL) {
- searchBytes.Add((byte)(address >> 0));
- searchBytes.Add((byte)(address >> 8));
- searchBytes.Add((byte)(address >> 16));
- searchBytes.Add(0x08);
+ searchBytes.Add((SearchByte)(address >> 0));
+ searchBytes.Add((SearchByte)(address >> 8));
+ searchBytes.Add((SearchByte)(address >> 16));
+ searchBytes.Add((SearchByte)0x08);
} else if (pointerContents.All(hex.Contains) && pointerContents.Length <= 6) {
- searchBytes.AddRange(Parse(pointerContents).Reverse().Append((byte)0x08));
+ searchBytes.AddRange(Parse(pointerContents).Reverse().Append((byte)0x08).Select(b => (SearchByte)b));
} else {
OnError(this, $"Could not parse pointer <{pointerContents}>");
return results;
@@ -368,18 +385,31 @@ namespace HavenSoft.Gen3Hex.Core.ViewModels {
i = pointerEnd + 1;
continue;
}
+ if (cleanedSearchString[i] == '"') {
+ var endIndex = cleanedSearchString.IndexOf('"', i + 1);
+ while (endIndex > i && cleanedSearchString[endIndex - 1] == '\\') endIndex = cleanedSearchString.IndexOf('"', endIndex + 1);
+ if (endIndex > i) {
+ var pcsBytes = PCSString.Convert(cleanedSearchString.Substring(i, endIndex + 1 - i));
+ i = endIndex + 1;
+ if (i == cleanedSearchString.Length) pcsBytes.RemoveAt(pcsBytes.Count - 1);
+ searchBytes.AddRange(pcsBytes.Select(b => new PCSSearchByte(b)));
+ continue;
+ }
+ }
if (cleanedSearchString.Length >= i + 2 && cleanedSearchString.Substring(i, 2).All(hex.Contains)) {
- searchBytes.AddRange(Parse(cleanedSearchString.Substring(i, 2)));
+ searchBytes.AddRange(Parse(cleanedSearchString.Substring(i, 2)).Select(b => (SearchByte)b));
i += 2;
continue;
}
- OnError(this, $"Could not parse search term {cleanedSearchString.Substring(i)}");
+ if (results.Count == 0) {
+ OnError(this, $"Could not parse search term {cleanedSearchString.Substring(i)}");
+ }
return results;
}
for (int i = 0; i < Model.Count - searchBytes.Count; i++) {
for (int j = 0; j < searchBytes.Count; j++) {
- if (Model[i + j] != searchBytes[j]) break;
+ if (!searchBytes[j].Match(Model[i + j])) break;
if (j == searchBytes.Count - 1) results.Add(i);
}
}
diff --git a/src/Gen3Hex.Tests/StringModelTests.cs b/src/Gen3Hex.Tests/StringModelTests.cs
index ad21b8cf..8030d6e5 100644
--- a/src/Gen3Hex.Tests/StringModelTests.cs
+++ b/src/Gen3Hex.Tests/StringModelTests.cs
@@ -231,7 +231,31 @@ namespace HavenSoft.Gen3Hex.Tests {
Assert.Equal("^bob\"\" \"Hello World!\"", fileSystem.CopyText);
}
- // TODO Find
+ [Fact]
+ public void FindForStringsIsNotCaseSensitive() {
+ var buffer = Enumerable.Repeat((byte)0xFF, 0x200).ToArray();
+ for (int i = 0; i < 0x10; i++) buffer[i] = 0x00;
+ var model = new PointerAndStringModel(buffer);
+ var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 };
+ viewPort.Edit("^bob\"\" \"Text and BULBASAUR!\"");
+
+ var results = viewPort.Find("\"bulbasaur\"");
+ Assert.Single(results);
+ Assert.Equal(9, results[0]);
+ }
+
+ [Fact]
+ public void FindForStringsWorksWithoutQuotes() {
+ var buffer = Enumerable.Repeat((byte)0xFF, 0x200).ToArray();
+ for (int i = 0; i < 0x10; i++) buffer[i] = 0x00;
+ var model = new PointerAndStringModel(buffer);
+ var viewPort = new ViewPort(new LoadedFile("test.txt", buffer), model) { Width = 0x10, Height = 0x10 };
+ viewPort.Edit("^bob\"\" \"Text and BULBASAUR!\"");
+
+ var results = viewPort.Find("bulbasaur");
+ Assert.Single(results);
+ Assert.Equal(9, results[0]);
+ }
// TODO undo/redo