diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj
index ab544a3e..c60b63a4 100644
--- a/src/HexManiac.Core/HexManiac.Core.csproj
+++ b/src/HexManiac.Core/HexManiac.Core.csproj
@@ -52,6 +52,7 @@
+
diff --git a/src/HexManiac.Core/Models/AutoSearchModel.cs b/src/HexManiac.Core/Models/AutoSearchModel.cs
index 4e086a2a..5cc52945 100644
--- a/src/HexManiac.Core/Models/AutoSearchModel.cs
+++ b/src/HexManiac.Core/Models/AutoSearchModel.cs
@@ -38,6 +38,7 @@ namespace HavenSoft.HexManiac.Core.Models {
DecodeHeader();
DecodeNameArrays();
DecodeDataArrays();
+ DecodeStreams();
}
}
@@ -54,12 +55,12 @@ namespace HavenSoft.HexManiac.Core.Models {
private void DecodeNameArrays() {
// movenames
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var movenames)) {
- ObserveAnchorWritten(noChangeDelta, "movenames", movenames);
+ ObserveAnchorWritten(noChangeDelta, EggMoveRun.MoveNamesTable, movenames);
}
// pokenames
if (TrySearch(this, noChangeDelta, "[name\"\"11]", out var pokenames)) {
- ObserveAnchorWritten(noChangeDelta, "pokenames", pokenames);
+ ObserveAnchorWritten(noChangeDelta, EggMoveRun.PokemonNameTable, pokenames);
}
// abilitynames / trainer names
@@ -116,5 +117,11 @@ namespace HavenSoft.HexManiac.Core.Models {
// @3D4294 ^itemicons[image<> palette<>]items
// @4886E8 ^movedescriptions[description<>]354
}
+
+ private void DecodeStreams() {
+ if (EggMoveRun.TrySearch(this, noChangeDelta, out var eggmoves)) {
+ ObserveAnchorWritten(noChangeDelta, "eggmoves", eggmoves);
+ }
+ }
}
}
diff --git a/src/HexManiac.Core/Models/IDataModel.cs b/src/HexManiac.Core/Models/IDataModel.cs
index 56d73380..f9e15cbe 100644
--- a/src/HexManiac.Core/Models/IDataModel.cs
+++ b/src/HexManiac.Core/Models/IDataModel.cs
@@ -11,6 +11,7 @@ namespace HavenSoft.HexManiac.Core.Models {
byte[] RawData { get; }
new byte this[int index] { get; set; }
IReadOnlyList Arrays { get; }
+ IReadOnlyList Streams { get; }
///
/// If dataIndex is in the middle of a run, returns that run.
@@ -64,6 +65,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public BaseModel(byte[] data) => RawData = data;
public virtual IReadOnlyList Arrays { get; } = new List();
+ public virtual IReadOnlyList Streams { get; } = new List();
public byte this[int index] { get => RawData[index]; set => RawData[index] = value; }
diff --git a/src/HexManiac.Core/Models/PokemonModel.cs b/src/HexManiac.Core/Models/PokemonModel.cs
index 64b19dc0..b59127a0 100644
--- a/src/HexManiac.Core/Models/PokemonModel.cs
+++ b/src/HexManiac.Core/Models/PokemonModel.cs
@@ -29,6 +29,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public virtual int EarliestAllowedAnchor => 0;
public override IReadOnlyList Arrays => runs.OfType().ToList();
+ public override IReadOnlyList Streams => runs.OfType().ToList();
#region Constructor
@@ -738,7 +739,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public override string Copy(Func changeToken, int start, int length) {
var text = new StringBuilder();
var run = GetNextRun(start);
- if (run.Start < start && !(run is ArrayRun)) {
+ if (run.Start < start && !(run is ArrayRun) && !(run is EggMoveRun)) {
length += start - run.Start;
start = run.Start;
}
@@ -784,6 +785,11 @@ namespace HavenSoft.HexManiac.Core.Models {
text.Append(" ");
length -= run.Start + run.Length - start;
start = run.Start + run.Length;
+ } else if (run is EggMoveRun eggRun) {
+ eggRun.AppendTo(text, start, length);
+ text.Append(" ");
+ length -= run.Start + run.Length - start;
+ start = run.Start + run.Length;
} else {
throw new NotImplementedException();
}
@@ -979,6 +985,8 @@ namespace HavenSoft.HexManiac.Core.Models {
} else {
return new ErrorInfo($"Ascii runs must include a length.");
}
+ } else if (format.StartsWith(StreamDelimeter + "egg" + StreamDelimeter)) {
+ run = new EggMoveRun(model, dataIndex);
} else {
var errorInfo = TryParse(model, format, dataIndex, null, out var arrayRun);
if (errorInfo == ErrorInfo.NoError) {
@@ -1083,6 +1091,8 @@ namespace HavenSoft.HexManiac.Core.Models {
newRun = new PCSRun(newStart, run.Length, run.PointerSources);
} else if (run is ArrayRun array) {
newRun = array.Move(newStart);
+ } else if (run is EggMoveRun egg) {
+ newRun = new EggMoveRun(this, newStart);
} else {
throw new NotImplementedException();
}
diff --git a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs
index 656fead1..af34e9f8 100644
--- a/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs
+++ b/src/HexManiac.Core/Models/Runs/ArrayRunElementSegment.cs
@@ -116,8 +116,12 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
private IReadOnlyList cachedOptions;
public IReadOnlyList GetOptions(IDataModel model) {
if (cachedOptions != null) return cachedOptions;
+ cachedOptions = GetOptions(model, EnumName);
+ return cachedOptions;
+ }
- if (!model.TryGetNameArray(EnumName, out var enumArray)) return null;
+ public static IReadOnlyList GetOptions(IDataModel model, string enumName) {
+ if (!model.TryGetNameArray(enumName, out var enumArray)) return null;
// array must be at least as long as than the current value
var optionCount = enumArray.ElementCount;
@@ -139,7 +143,6 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
results.Add(value);
}
- cachedOptions = results;
return results;
}
diff --git a/src/HexManiac.Core/Models/Runs/EggMoveRun.cs b/src/HexManiac.Core/Models/Runs/EggMoveRun.cs
new file mode 100644
index 00000000..6a22b867
--- /dev/null
+++ b/src/HexManiac.Core/Models/Runs/EggMoveRun.cs
@@ -0,0 +1,219 @@
+using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+
+namespace HavenSoft.HexManiac.Core.Models.Runs {
+ public class EggMoveRun : IFormattedRun {
+ public const int MagicNumber = 0x4E20; // anything above this number is a pokemon, anything below it is a move
+ public const int EndStream = 0xFFFF;
+ public const string PokemonNameTable = "pokenames";
+ public const string MoveNamesTable = "movenames";
+ public const string GroupStart = "[";
+ public const string GroupEnd = "]";
+ private readonly IDataModel model;
+
+ public int Start { get; }
+ public int Length { get; }
+ public IReadOnlyList PointerSources { get; private set; }
+ public string FormatString => AsciiRun.StreamDelimeter + "egg" + AsciiRun.StreamDelimeter;
+
+ public EggMoveRun(IDataModel dataModel, int dataIndex) {
+ model = dataModel;
+ Start = dataIndex;
+ Length = 0;
+ for (int i = Start; i < model.Count; i += 2) {
+ if (model[i] == 0xFF && model[i + 1] == 0xFF) {
+ Length = i - Start + 2;
+ break;
+ }
+ }
+ }
+
+ public static bool TrySearch(IDataModel data, ModelDelta token, out EggMoveRun eggmoves) {
+ eggmoves = default;
+ var pokenames = data.GetNextRun(data.GetAddressFromAnchor(token, -1, PokemonNameTable)) as ArrayRun;
+ var movenames = data.GetNextRun(data.GetAddressFromAnchor(token, -1, MoveNamesTable)) as ArrayRun;
+ if (pokenames == null || movenames == null) return false;
+
+ for (var run = data.GetNextRun(0); run.Start < int.MaxValue; run = data.GetNextRun(run.Start + run.Length)) {
+ if (run is ArrayRun || run is PCSRun || run.PointerSources == null) continue;
+
+ // verify expected pointers to this
+ if (run.PointerSources.Count != 2) continue;
+
+ // verify limiter
+ var length = data.ReadMultiByteValue(run.PointerSources[1] - 4, 4);
+
+ // we just read the 'length' from basically a random byte... verify that it could make sense as a length
+ if (length < 0) continue;
+ if (run.Start + length * 2 + 3 < 0) continue;
+ if (run.Start + length * 2 + 3 > data.Count) continue;
+ var endValue = data.ReadMultiByteValue(run.Start + length * 2 + 2, 2);
+ if (endValue != EndStream) continue;
+
+ // verify content
+ bool possibleMatch = true;
+ for (int i = 0; i < length - 2; i++) {
+ var value = data.ReadMultiByteValue(run.Start + i * 2, 2);
+ if (value == EndStream) break; // early exit, the data was edited, but that's ok. Everything still matches up.
+ if (value >= MagicNumber) {
+ value -= MagicNumber;
+ if (value < pokenames.ElementCount) continue;
+ }
+ if (value < movenames.ElementCount) continue;
+ possibleMatch = false;
+ break;
+ }
+ if (!possibleMatch) continue;
+
+ // content is correct, length is correct: this is it!
+ eggmoves = new EggMoveRun(data, run.Start);
+ return true;
+ }
+
+ return false;
+ }
+
+ private IReadOnlyList cachedPokenames, cachedMovenames;
+ private int lastFormatRequest = int.MinValue;
+ public IDataFormat CreateDataFormat(IDataModel data, int dataIndex) {
+ Debug.Assert(data == model);
+ if (dataIndex != lastFormatRequest + 1) {
+ cachedPokenames = ArrayRunEnumSegment.GetOptions(model, PokemonNameTable) ?? new List();
+ cachedMovenames = ArrayRunEnumSegment.GetOptions(model, MoveNamesTable) ?? new List();
+ }
+ lastFormatRequest = dataIndex;
+
+ var position = dataIndex - Start;
+ var groupStart = position % 2 == 1 ? position - 1 : position;
+ position -= groupStart;
+ var value = data.ReadMultiByteValue(Start + groupStart, 2);
+ if (value >= MagicNumber) {
+ value -= MagicNumber;
+ string content = cachedPokenames.Count > value ? cachedPokenames[value] : value.ToString();
+ if (value == EndStream - MagicNumber) content = string.Empty;
+ if (content.StartsWith("\"")) content = content.Substring(1);
+ if (content.EndsWith("\"")) content = content.Substring(0, content.Length - 1);
+ return new EggSection(groupStart + Start, position, $"{GroupStart}{content}{GroupEnd}");
+ } else {
+ string content = cachedMovenames.Count > value ? cachedMovenames[value] : value.ToString();
+ return new EggItem(groupStart + Start, position, content);
+ }
+ }
+
+ public IFormattedRun MergeAnchor(IReadOnlyList sources) {
+ var newSources = new HashSet();
+ if (sources != null) newSources.AddRange(sources);
+ if (PointerSources != null) newSources.AddRange(PointerSources);
+ return new EggMoveRun(model, Start) { PointerSources = newSources.ToList() };
+ }
+
+ public IFormattedRun RemoveSource(int source) {
+ var sources = PointerSources.ToList();
+ sources.Remove(source);
+ return new EggMoveRun(model, Start) { PointerSources = sources };
+ }
+
+ public int GetPokemonNumber(string input) {
+ if (input.StartsWith(GroupStart)) input = input.Substring(1, input.Length - 2);
+ var names = cachedPokenames.Select(name => name.Trim('"').ToLower()).ToList();
+ return GetNumber(input.ToLower(), names);
+ }
+
+ public int GetMoveNumber(string input) {
+ input = input.Trim('"').ToLower();
+ var names = cachedMovenames.Select(name => name.Trim('"').ToLower()).ToList();
+ return GetNumber(input, names);
+ }
+
+ public string SerializeForTool() {
+ var builder = new StringBuilder();
+ for (int i = 0; i < Length - 2; i += 2) {
+ var address = Start + i;
+ var value = model.ReadMultiByteValue(address, 2);
+ if (value >= MagicNumber) {
+ value -= MagicNumber;
+ builder.Append($"{GroupStart}{cachedPokenames[value].Trim('"')}{GroupEnd}");
+ } else {
+ builder.Append(cachedMovenames[value].Trim('"'));
+ }
+ if (i < Length - 4) builder.AppendLine();
+ }
+ return builder.ToString();
+ }
+
+ public int DeserializeFromTool(string content, ModelDelta token) {
+ var data = new List();
+ var pokemonNames = cachedPokenames.Select(name => $"{GroupStart}{name.Trim('"').ToLower()}{GroupEnd}").ToList();
+ var moveNames = cachedMovenames.Select(name => name.Trim('"').ToLower()).ToList();
+ var lines = content.ToLower().Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
+ foreach (var line in lines) {
+ var index = pokemonNames.IndexOf(line);
+ if (index != -1) { data.Add(index + MagicNumber); continue; }
+ index = moveNames.IndexOf(line);
+ if (index != -1) { data.Add(index); continue; }
+
+ // didn't find an exact match... look for a partial pokemon match
+ var matchFound = false;
+ for (int i = 0; i < pokemonNames.Count; i++) {
+ if (pokemonNames[i].Contains(line)) { data.Add(i + MagicNumber); matchFound = true; break; }
+ }
+ if (matchFound) continue;
+
+ // look for a partial move match
+ for (int i = 0; i < moveNames.Count; i++) {
+ if (moveNames[i].Contains(line)) { data.Add(i); break; }
+ }
+ }
+ var run = model.RelocateForExpansion(token, this, data.Count * 2 + 2);
+ for (int i = 0; i < data.Count; i++) model.WriteMultiByteValue(run.Start + i * 2, 2, token, data[i]);
+ model.WriteMultiByteValue(run.Start + data.Count * 2, 2, token, EndStream); // write the new end token
+ for (int i = data.Count + 2; i < Length / 2; i++) model.WriteMultiByteValue(run.Start + i * 2, 2, token, EndStream); // fill any remaining old space with FF
+ return run.Start;
+ }
+
+ public IEnumerable GetAutoCompleteOptions() {
+ var pokenames = cachedPokenames.Select(name => $"{GroupStart}{name}{GroupEnd}");
+ var movenames = cachedMovenames.Select(name => name + " ");
+ return pokenames.Concat(movenames);
+ }
+
+ private static int GetNumber(string input, IList names) {
+ var matchIndex = names.IndexOf(input);
+ if (matchIndex != -1) return matchIndex;
+ var match = names.FirstOrDefault(name => name.Contains(input));
+ if (match == null) return -1;
+ return names.IndexOf(match);
+ }
+
+ public void AppendTo(StringBuilder text, int start, int length) {
+ while (length > 0 && start < Start + Length) {
+ var value = model.ReadMultiByteValue(start, 2);
+ if (value == EndStream) {
+ text.Append($"{GroupStart}{GroupEnd}");
+ } else if (value >= MagicNumber) {
+ value -= MagicNumber;
+ if (value >= cachedPokenames.Count) text.Append($"{GroupStart}{value}{GroupEnd}");
+ else text.Append($"{GroupStart}{cachedPokenames[value].Trim('"')}{GroupEnd}");
+ } else {
+ if (value >= cachedMovenames.Count) text.Append($"{value}");
+ else text.Append($"{cachedMovenames[value]}");
+ }
+ start += 2;
+ length -= 2;
+ if (length > 0 && start < Start + Length) text.Append(" ");
+ }
+ }
+
+ public bool UpdateLimiter(ModelDelta token) {
+ if (PointerSources.Count != 2) return false;
+ var address = PointerSources.Last() - 4;
+ var limiter = Length / 2 - 2;
+ model.WriteMultiByteValue(address, 4, token, limiter);
+ return true;
+ }
+ }
+}
diff --git a/src/HexManiac.Core/SystemExtensions.cs b/src/HexManiac.Core/SystemExtensions.cs
index 12eced9b..a456cd41 100644
--- a/src/HexManiac.Core/SystemExtensions.cs
+++ b/src/HexManiac.Core/SystemExtensions.cs
@@ -21,5 +21,8 @@ namespace HavenSoft.HexManiac.Core {
return true;
}
+ public static void AddRange(this HashSet set, IEnumerable items) {
+ foreach (var item in items) set.Add(item);
+ }
}
}
diff --git a/src/HexManiac.Core/ViewModels/ConvertCellToText.cs b/src/HexManiac.Core/ViewModels/ConvertCellToText.cs
index 2ac6e205..e69c0567 100644
--- a/src/HexManiac.Core/ViewModels/ConvertCellToText.cs
+++ b/src/HexManiac.Core/ViewModels/ConvertCellToText.cs
@@ -46,5 +46,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public void Visit(Integer integer, byte data) => Result = integer.Value.ToString();
public void Visit(IntegerEnum integerEnum, byte data) => Result = integerEnum.Value;
+
+ public void Visit(EggSection section, byte data) => Result = section.SectionName;
+
+ public void Visit(EggItem item, byte data) => Result = item.ItemName;
}
}
diff --git a/src/HexManiac.Core/ViewModels/DataFormats.cs b/src/HexManiac.Core/ViewModels/DataFormats.cs
index 7dc38456..7cb9d0d6 100644
--- a/src/HexManiac.Core/ViewModels/DataFormats.cs
+++ b/src/HexManiac.Core/ViewModels/DataFormats.cs
@@ -11,6 +11,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
void Visit(IDataFormatVisitor visitor, byte data);
}
+ public interface IDataFormatInstance : IDataFormat {
+ int Source { get; } // the beginning of the run/group that this instance belongs to
+ int Position { get; } // the index in the run/group that this instance belongs to
+ }
+
public interface IDataFormatVisitor {
void Visit(Undefined dataFormat, byte data);
void Visit(None dataFormat, byte data);
@@ -23,6 +28,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
void Visit(Ascii ascii, byte data);
void Visit(Integer integer, byte data);
void Visit(IntegerEnum integer, byte data);
+ void Visit(EggSection section, byte data);
+ void Visit(EggItem item, byte data);
}
///
@@ -82,7 +89,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
}
}
- public class Pointer : IDataFormat {
+ public class Pointer : IDataFormatInstance {
public const int NULL = -0x08000000;
public int Source { get; } // 6 hex digits
public int Position { get; } // 0 through 3
@@ -127,7 +134,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
- public class PCS : IDataFormat {
+ public class PCS : IDataFormatInstance {
public int Source { get; }
public int Position { get; }
public string FullString { get; }
@@ -143,7 +150,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
- public class EscapedPCS : IDataFormat {
+ public class EscapedPCS : IDataFormatInstance {
public int Source { get; }
public int Position { get; }
public string FullString { get; }
@@ -159,7 +166,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
- public class ErrorPCS : IDataFormat {
+ public class ErrorPCS : IDataFormatInstance {
public int Source { get; }
public int Position { get; }
public string FullString { get; }
@@ -175,7 +182,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
- public class Ascii : IDataFormat {
+ public class Ascii : IDataFormatInstance {
public int Source { get; }
public int Position { get; }
public char ThisCharacter { get; }
@@ -190,7 +197,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
- public class Integer : IDataFormat {
+ public class Integer : IDataFormatInstance {
public int Source { get; }
public int Position { get; }
public int Value { get; }
@@ -227,4 +234,44 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public override void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
}
+
+ public class EggSection : IDataFormatInstance {
+ public int Source { get; }
+ public int Position { get; }
+ public int Length => 2;
+ public string SectionName { get; }
+
+ public EggSection(int source, int position, string name) => (Source, Position, SectionName) = (source, position, name);
+
+ public bool Equals(IDataFormat other) {
+ if (other is EggSection that) {
+ return that.SectionName == SectionName &&
+ that.Source == Source &&
+ that.Length == Length;
+ }
+ return false;
+ }
+
+ public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
+ }
+
+ public class EggItem : IDataFormatInstance {
+ public int Source { get; }
+ public int Position { get; }
+ public int Length { get; }
+ public string ItemName { get; }
+
+ public EggItem(int source, int position, string name) => (Source, Position, ItemName) = (source, position, name);
+
+ public bool Equals(IDataFormat other) {
+ if (other is EggItem that) {
+ return that.ItemName == ItemName &&
+ that.Source == Source &&
+ that.Length == Length;
+ }
+ return false;
+ }
+
+ public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
+ }
}
diff --git a/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs b/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
index 5c3bd8ba..1e6640be 100644
--- a/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
+++ b/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
@@ -86,7 +86,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
CanExecute = arg => viewPort?.Goto != null,
Execute = arg => {
var text = Text;
- if (CompletionIndex != -1) text = AutoCompleteOptions[CompletionIndex].CompletionText;
+ var index = completionIndex.LimitToRange(-1, AutoCompleteOptions.Count - 1);
+ if (index != -1) text = AutoCompleteOptions[index].CompletionText;
if (arg is string) text = (string)arg;
viewPort?.Goto?.Execute(text);
ControlVisible = false;
diff --git a/src/HexManiac.Core/ViewModels/Selection.cs b/src/HexManiac.Core/ViewModels/Selection.cs
index e01537b0..726503f1 100644
--- a/src/HexManiac.Core/ViewModels/Selection.cs
+++ b/src/HexManiac.Core/ViewModels/Selection.cs
@@ -42,7 +42,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (selectionStart.Equals(value)) return;
if (!Scroll.ScrollToPoint(ref value)) {
- PreviewSelectionStartChanged?.Invoke(this, rawSelectionStart);
+ PreviewSelectionStartChanged?.Invoke(this, getSpan(rawSelectionStart).start);
}
rawSelectionStart = value;
diff --git a/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs b/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs
index 1eedfc9a..12c0b19e 100644
--- a/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs
+++ b/src/HexManiac.Core/ViewModels/Tools/PCSTool.cs
@@ -66,6 +66,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (run.Start > address) return; // wrong run, don't adjust
if (run is PCSRun pcsRun) UpdateRun(pcsRun);
if (run is ArrayRun arrayRun) UpdateRun(arrayRun);
+ if (run is EggMoveRun eggRun) UpdateRun(eggRun);
}
}
}
@@ -77,7 +78,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
if (ignoreExternalUpdates) return;
var run = model.GetNextRun(value);
if (TryUpdate(ref address, value)) {
- if ((run is PCSRun || run is ArrayRun) && run.Start <= value) {
+ if ((run is PCSRun || run is ArrayRun || run is EggMoveRun) && run.Start <= value) {
runner.Schedule(DataForCurrentRunChanged);
Enabled = true;
ShowMessage = false;
@@ -174,6 +175,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
}
}
return;
+ } else if (run is EggMoveRun egg) {
+ var newContent = egg.SerializeForTool();
+ ignoreSelectionUpdates = true;
+ using (new StubDisposable { Dispose = () => ignoreSelectionUpdates = false }) {
+ TryUpdate(ref content, newContent, nameof(Content));
+ }
+ return;
}
throw new NotImplementedException();
@@ -186,11 +194,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
return newContent;
}
+ ///
+ /// If a selection update is requested due to a change in the Content, ignore it.
+ /// Otherwise, the selection update could send us back to the begining of the run.
+ ///
private bool ignoreSelectionUpdates;
private void UpdateSelectionFromTool() {
if (ignoreSelectionUpdates) return;
var run = model.GetNextRun(Address);
- if (!(run is ArrayRun) && !(run is PCSRun)) return;
+ if (!(run is ArrayRun) && !(run is PCSRun) && !(run is EggMoveRun)) return;
// for arrays, the address must be at the start of a string segment within the first element of the array
if (run is ArrayRun arrayRun) {
@@ -216,6 +228,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
var afterLines = content.Substring(0, contentIndex + selectionLength).Split(new[] { Environment.NewLine }, StringSplitOptions.None);
var selectionEnd = textStart + (afterLines.Length - 1) * array.ElementLength + afterLines[afterLines.Length - 1].Length;
selectionLength = selectionEnd - selectionStart;
+ } else if (run is EggMoveRun egg) {
+ var beforeSelection = content.Substring(0, selectionStart);
+ var beforeLineCount = (beforeSelection.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Length - 1).LimitToRange(0, int.MaxValue);
+ var withSelection = content.Substring(0, selectionStart + selectionLength);
+ var withSelectionLineCount = (withSelection.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Length - 1).LimitToRange(0, int.MaxValue);
+
+ selectionStart = egg.Start + beforeLineCount * 2;
+ var selectionEnd = egg.Start + withSelectionLineCount * 2;
+ selectionLength = selectionEnd - selectionStart;
}
selection.SelectionStart = selection.Scroll.DataIndexToViewPoint(selectionStart);
@@ -272,5 +293,21 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
TryUpdate(ref address, run.Start, nameof(Address));
ignoreExternalUpdates = false;
}
+
+ private void UpdateRun(EggMoveRun run) {
+ ignoreExternalUpdates = true;
+ var newStart = run.DeserializeFromTool(content, history.CurrentChange);
+ var newRun = new EggMoveRun(model, newStart);
+ if (newRun.Length != run.Length) {
+ model.ObserveRunWritten(history.CurrentChange, newRun);
+ newRun = (EggMoveRun)model.GetNextRun(newRun.Start);
+ history.CurrentChange.AddRun(newRun);
+ newRun.UpdateLimiter(history.CurrentChange);
+ }
+
+ if (run.Start != newRun.Start) ModelDataMoved?.Invoke(this, (run.Start, newRun.Start));
+ ModelDataChanged?.Invoke(this, newRun);
+ ignoreExternalUpdates = false;
+ }
}
}
diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs
index aa99d180..060c7d58 100644
--- a/src/HexManiac.Core/ViewModels/ViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/ViewPort.cs
@@ -141,7 +141,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
ClearEdits(location);
} else {
var endEdit = " ";
- if (underEdit.CurrentText.Count(c => c == '"') % 2 == 1) endEdit = "\"";
+ if (underEdit.CurrentText.Count(c => c == StringDelimeter) % 2 == 1) endEdit = StringDelimeter.ToString();
+ var originalFormat = underEdit.OriginalFormat;
+ if (originalFormat is Anchor anchor) originalFormat = anchor.OriginalFormat;
+ if (underEdit.CurrentText.StartsWith(EggMoveRun.GroupStart) && (originalFormat is EggSection || originalFormat is EggItem)) endEdit = EggMoveRun.GroupEnd;
currentView[location.X, location.Y] = new HexElement(element.Value, underEdit.Edit(endEdit));
if (!TryCompleteEdit(location)) ClearEdits(location);
}
@@ -165,7 +168,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var offsets = array.ConvertByteOffsetToArrayOffset(dataIndex);
Tools.StringTool.Address = offsets.SegmentStart - offsets.ElementIndex * array.ElementLength;
Tools.TableTool.Address = array.Start + array.ElementLength * offsets.ElementIndex;
- } else if (run.Start <= dataIndex && run is PCSRun) {
+ } else if (run.Start <= dataIndex && (run is PCSRun || run is EggMoveRun)) {
Tools.StringTool.Address = run.Start;
} else {
Tools.StringTool.Address = dataIndex;
@@ -587,6 +590,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
run = Model.GetNextRun(index);
+ var cellToText = new ConvertCellToText(Model, run.Start);
+ var cell = currentView[point.X, point.Y];
if (run is PCSRun pcs) {
for (int i = index; i < run.Start + run.Length; i++) history.CurrentChange.ChangeData(Model, i, 0xFF);
@@ -605,19 +610,16 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
RefreshBackingData();
SelectionStart = scroll.DataIndexToViewPoint(index - 1);
} else if (array.ElementContent[offsets.SegmentIndex].Type == ElementContentType.Pointer) {
- var cell = currentView[point.X, point.Y];
PrepareForMultiSpaceEdit(point, 4);
var destination = ((Pointer)cell.Format).DestinationAsText;
destination = destination.Substring(0, destination.Length - 1);
currentView[point.X, point.Y] = new HexElement(cell.Value, new UnderEdit(cell.Format, destination, 4));
} else if (array.ElementContent[offsets.SegmentIndex].Type == ElementContentType.Integer) {
- var cell = currentView[point.X, point.Y];
- var format = (Integer)cell.Format;
- PrepareForMultiSpaceEdit(point, format.Length);
- var text = format.Value.ToString();
- if (format is IntegerEnum intEnum) text = intEnum.Value;
+ PrepareForMultiSpaceEdit(point, ((Integer)cell.Format).Length);
+ cell.Format.Visit(cellToText, cell.Value);
+ var text = cellToText.Result;
text = text.Substring(0, text.Length - 1);
- currentView[point.X, point.Y] = new HexElement(cell.Value, new UnderEdit(format, text, format.Length));
+ currentView[point.X, point.Y] = new HexElement(cell.Value, new UnderEdit(cell.Format, text, ((Integer)cell.Format).Length));
} else {
throw new NotImplementedException();
}
@@ -625,23 +627,32 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
return;
}
+ if (run is EggMoveRun eggRun) {
+ PrepareForMultiSpaceEdit(point, 2);
+ cell.Format.Visit(cellToText, cell.Value);
+ var text = cellToText.Result;
+ text = text.Substring(0, text.Length - 1);
+ currentView[point.X, point.Y] = new HexElement(cell.Value, new UnderEdit(cell.Format, text, 2));
+ NotifyCollectionChanged(ResetArgs);
+ return;
+ }
+
if (run.Start <= index && run.Start + run.Length > index) {
// I want to do a backspace at the end of this run
SelectionStart = scroll.DataIndexToViewPoint(run.Start);
- var cellToText = new ConvertCellToText(Model, run.Start);
var element = currentView[SelectionStart.X, SelectionStart.Y];
element.Format.Visit(cellToText, element.Value);
var text = cellToText.Result;
var editLength = 1;
if (element.Format is Pointer pointer) editLength = 4;
- // if (element.Format is Integer integer) editLength = integer.Length;
for (int i = 0; i < run.Length; i++) {
var p = scroll.DataIndexToViewPoint(run.Start + i);
string editString = i == 0 ? text.Substring(0, text.Length - 1) : string.Empty;
if (i > 0) editLength = 1;
- currentView[p.X, p.Y] = new HexElement(currentView[p.X, p.Y].Value, new UnderEdit(currentView[p.X, p.Y].Format, editString, editLength));
+ var format = new UnderEdit(currentView[p.X, p.Y].Format, editString, editLength);
+ currentView[p.X, p.Y] = new HexElement(currentView[p.X, p.Y].Value, format);
}
} else {
SelectionStart = scroll.DataIndexToViewPoint(index);
@@ -697,38 +708,70 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var textResults = Search(searchBytes).ToList();
Model.ConsiderResultsAsTextRuns(history.CurrentChange, textResults);
foreach (var result in textResults) {
- // if the result is in an array, we care about things that use that array
if (Model.GetNextRun(result) is ArrayRun parentArray && parentArray.LengthFromAnchor == string.Empty) {
- var offsets = parentArray.ConvertByteOffsetToArrayOffset(result);
- var parentArrayName = Model.GetAnchorFromAddress(-1, parentArray.Start);
- if (offsets.SegmentIndex == 0 && parentArray.ElementContent[offsets.SegmentIndex].Type == ElementContentType.PCS) {
- foreach (var child in Model.Arrays) {
- // option 1: another table has a row named after this element
- if (child.LengthFromAnchor == parentArrayName) {
- var address = child.Start + child.ElementLength * offsets.ElementIndex;
- yield return (address, address + child.ElementLength - 1);
- }
+ foreach (var dataResult in FindMatchingDataResultsFromArrayElement(parentArray, result)) yield return dataResult;
+ }
- // option 2: another table has an enum named after this element
- var segmentOffset = 0;
- foreach (var segment in child.ElementContent) {
- if (!(segment is ArrayRunEnumSegment enumSegment) || enumSegment.EnumName != parentArrayName) {
- segmentOffset += segment.Length;
- continue;
- }
- for (int i = 0; i < child.ElementCount; i++) {
- var address = child.Start + child.ElementLength * i + segmentOffset;
- var enumValue = Model.ReadMultiByteValue(address, segment.Length);
- if (enumValue != offsets.ElementIndex) continue;
- yield return (address, address + segment.Length - 1);
- }
- segmentOffset += segment.Length;
+ yield return (result, result + pcsBytes.Count - 1);
+ }
+ }
+
+ ///
+ /// When performing a search, sometimes one of the search results is text from a table.
+ /// If so, then we also care about places where that table value is used.
+ /// This function finds uses of an element in a table.
+ ///
+ private IEnumerable<(int start, int end)> FindMatchingDataResultsFromArrayElement(ArrayRun parentArray, int parentIndex) {
+ var offsets = parentArray.ConvertByteOffsetToArrayOffset(parentIndex);
+ var parentArrayName = Model.GetAnchorFromAddress(-1, parentArray.Start);
+ if (offsets.SegmentIndex == 0 && parentArray.ElementContent[offsets.SegmentIndex].Type == ElementContentType.PCS) {
+ var arrayUses = FindTableUsages(offsets, parentArrayName);
+ var streamUses = FindStreamUsages(offsets, parentArrayName);
+ return arrayUses.Concat(streamUses);
+ }
+ return Enumerable.Empty<(int, int)>();
+ }
+
+ private IEnumerable<(int start, int end)> FindTableUsages(ArrayOffset offsets, string parentArrayName) {
+ foreach (var child in Model.Arrays) {
+ // option 1: another table has a row named after this element
+ if (child.LengthFromAnchor == parentArrayName) {
+ var address = child.Start + child.ElementLength * offsets.ElementIndex;
+ yield return (address, address + child.ElementLength - 1);
+ }
+
+ // option 2: another table has an enum named after this element
+ var segmentOffset = 0;
+ foreach (var segment in child.ElementContent) {
+ if (!(segment is ArrayRunEnumSegment enumSegment) || enumSegment.EnumName != parentArrayName) {
+ segmentOffset += segment.Length;
+ continue;
+ }
+ for (int i = 0; i < child.ElementCount; i++) {
+ var address = child.Start + child.ElementLength * i + segmentOffset;
+ var enumValue = Model.ReadMultiByteValue(address, segment.Length);
+ if (enumValue != offsets.ElementIndex) continue;
+ yield return (address, address + segment.Length - 1);
+ }
+ segmentOffset += segment.Length;
+ }
+ }
+ }
+
+ private IEnumerable<(int start, int end)> FindStreamUsages(ArrayOffset offsets, string parentArrayName) {
+ foreach (var child in Model.Streams) {
+ // option 1: the value is used by egg moves
+ if (child is EggMoveRun eggRun) {
+ var groupStart = 0;
+ if (parentArrayName == EggMoveRun.PokemonNameTable) groupStart = EggMoveRun.MagicNumber;
+ if (parentArrayName == EggMoveRun.PokemonNameTable || parentArrayName == EggMoveRun.MoveNamesTable) {
+ for (int i = 0; i < eggRun.Length - 2; i += 2) {
+ if (Model.ReadMultiByteValue(eggRun.Start + i, 2) == offsets.ElementIndex + groupStart) {
+ yield return (eggRun.Start + i, eggRun.Start + i + 1);
}
}
}
}
-
- yield return (result, result + pcsBytes.Count - 1);
}
}
@@ -868,6 +911,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
Tools.SelectedIndex = Enumerable.Range(0, Tools.Count).First(i => Tools[i] is PCSTool);
}
+ if (format is EggSection || format is EggItem) {
+ var byteOffset = scroll.ViewPointToDataIndex(new Point(x, y));
+ var currentRun = Model.GetNextRun(byteOffset);
+ Tools.StringTool.Address = currentRun.Start;
+ Tools.SelectedIndex = Enumerable.Range(0, Tools.Count).First(i => Tools[i] is PCSTool);
+ }
}
public void ExpandSelection(int x, int y) {
@@ -950,6 +999,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
}
private IReadOnlyList GetAutocompleteOptions(IDataFormat originalFormat, string newText, int selectedIndex = -1) {
+ if (originalFormat is Anchor anchor) originalFormat = anchor.OriginalFormat;
if (newText.StartsWith(PointerStart.ToString())) {
return Model.GetNewPointerAutocompleteOptions(newText, selectedIndex);
} else if (newText.StartsWith(GotoMarker.ToString())) {
@@ -959,6 +1009,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var segment = (ArrayRunEnumSegment)array.ElementContent[array.ConvertByteOffsetToArrayOffset(intEnum.Source).SegmentIndex];
var options = segment.GetOptions(Model).Select(option => option + " "); // autocomplete needs to complete after selection, so add a space
return AutoCompleteSelectionItem.Generate(options.Where(option => option.MatchesPartial(newText)), selectedIndex);
+ } else if (originalFormat is EggSection || originalFormat is EggItem) {
+ var eggRun = (EggMoveRun)Model.GetNextRun(((IDataFormatInstance)originalFormat).Source);
+ var allOptions = eggRun.GetAutoCompleteOptions();
+ return AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(newText)), selectedIndex);
} else {
throw new NotImplementedException();
}
@@ -1035,12 +1089,20 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var run = Model.GetNextRun(index);
if (run.Start > index) return (p, p);
- if (run is PointerRun) return (scroll.DataIndexToViewPoint(run.Start), scroll.DataIndexToViewPoint(run.Start + run.Length - 1));
+ (Point, Point) pair(int start, int end) => (scroll.DataIndexToViewPoint(start), scroll.DataIndexToViewPoint(end));
+
+ if (run is PointerRun) return pair(run.Start, run.Start + run.Length - 1);
+ if (run is EggMoveRun) {
+ var even = (index - run.Start) % 2 == 0;
+ if (even) return pair(index, index + 1);
+ return pair(index - 1, index);
+ }
if (!(run is ArrayRun array)) return (p, p);
var offset = array.ConvertByteOffsetToArrayOffset(index);
- if (array.ElementContent[offset.SegmentIndex].Type == ElementContentType.Pointer || array.ElementContent[offset.SegmentIndex].Type == ElementContentType.Integer) {
- return (scroll.DataIndexToViewPoint(offset.SegmentStart), scroll.DataIndexToViewPoint(offset.SegmentStart + array.ElementContent[offset.SegmentIndex].Length - 1));
+ var type = array.ElementContent[offset.SegmentIndex].Type;
+ if (type == ElementContentType.Pointer || type == ElementContentType.Integer) {
+ return pair(offset.SegmentStart, offset.SegmentStart + array.ElementContent[offset.SegmentIndex].Length - 1);
}
return (p, p);
@@ -1101,6 +1163,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
RefreshBackingData();
}
var run = Model.GetNextRun(completeEditOperation.NewDataIndex);
+ if (run.Start > completeEditOperation.NewDataIndex) run = new NoInfoRun(Model.Count);
if (completeEditOperation.DataMoved) UpdateToolsFromSelection(run.Start);
if (run is ArrayRun) Tools.Schedule(Tools.TableTool.DataForCurrentRunChanged);
if (run is ArrayRun || run is PCSRun) Tools.Schedule(Tools.StringTool.DataForCurrentRunChanged);
diff --git a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs
index 3bb15014..abb4e250 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/CompleteEditOperation.cs
@@ -19,8 +19,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public int NewDataIndex { get; private set; } // for completed edits, where should the selection move to?
public bool DataMoved { get; private set; }
public string MessageText { get; private set; } // is there a message to display to the user? For example, when data gets moved.
- public string ErrorText { get; private set; } // is there an error to display to the user? For example, invalide pointer
- public HexElement NewCell { get; private set; } // if result is true and this is not null, asign this one value back to the one cell
+ public string ErrorText { get; private set; } // is there an error to display to the user? For example, invalid pointer
+ public HexElement NewCell { get; private set; } // if result is true and this is not null, assign this one value back to the one cell
// and refresh the one cell (along with any other UnderEdit cells)
// if result is true and this _is_ null, then the entire screen needs to be refreshed.
@@ -109,6 +109,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
}
}
+ public void Visit(EggSection section, byte data) => CompleteEggEdit();
+
+ public void Visit(EggItem item, byte data) => CompleteEggEdit();
+
private void CompleteIntegerEdit(Integer integer) {
if (!int.TryParse(CurrentText, out var result)) {
ErrorText = $"Could not parse {CurrentText} as a number";
@@ -279,5 +283,73 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
CurrentChange.ChangeData(Model, memoryLocation, byteValue);
NewDataIndex = memoryLocation + 1;
}
+
+ private void CompleteEggEdit() {
+ var endChar = CurrentText[CurrentText.Length - 1];
+ if (!$"{EggMoveRun.GroupEnd} {StringDelimeter}".Contains(endChar)) return;
+ if (CurrentText.Count(c => c == StringDelimeter) % 2 != 0) return;
+
+ NewDataIndex = memoryLocation + 2;
+ Result = true;
+ var run = (EggMoveRun)Model.GetNextRun(memoryLocation);
+
+ if (CurrentText == EggMoveRun.GroupStart + EggMoveRun.GroupEnd) {
+ Model.WriteMultiByteValue(memoryLocation, 2, CurrentChange, 0xFFFF);
+ // clear all data after this and shorten the run
+ for (int i = memoryLocation + 2; i < run.Start + run.Length; i += 2) {
+ Model.WriteMultiByteValue(i, 2, CurrentChange, 0xFFFF);
+ }
+ var newRun = new EggMoveRun(Model, run.Start);
+ Model.ObserveRunWritten(CurrentChange, newRun);
+ newRun = (EggMoveRun)Model.GetNextRun(newRun.Start);
+ newRun.UpdateLimiter(CurrentChange);
+ } else if (CurrentText.EndsWith(EggMoveRun.GroupEnd)) {
+ var value = run.GetPokemonNumber(CurrentText);
+ if (value == -1) {
+ ErrorText = $"Could not parse {CurrentText} as a pokemon name";
+ NewDataIndex -= 2;
+ } else {
+ WriteNormalEggEdit(run, value + EggMoveRun.MagicNumber);
+ }
+ } else {
+ var text = CurrentText.Trim();
+ var value = run.GetMoveNumber(text);
+ if (value == -1) {
+ // wasn't a move... try again as a pokemon even though they didn't use the []
+ value = run.GetPokemonNumber(text);
+ if (value == -1) {
+ ErrorText = $"Could not parse {text} as a move name or pokemon name";
+ NewDataIndex -= 2;
+ } else {
+ WriteNormalEggEdit(run, value + EggMoveRun.MagicNumber);
+ }
+ } else {
+ WriteNormalEggEdit(run, value);
+ }
+ }
+ }
+
+ ///
+ /// Before we write this change to the model, see if we need to extend the egg run to make it fit.
+ ///
+ private void WriteNormalEggEdit(EggMoveRun run, int value) {
+ int memoryLocation = this.memoryLocation;
+ var initialItemValue = Model.ReadMultiByteValue(memoryLocation, 2);
+ Model.WriteMultiByteValue(memoryLocation, 2, CurrentChange, value);
+ if (initialItemValue == 0xFFFF) {
+ var newRun = Model.RelocateForExpansion(CurrentChange, run, run.Length + 2);
+ if (newRun.Start != run.Start) {
+ MessageText = $"Egg Moves were automatically moved to {newRun.Start.ToString("X6")}. Pointers were updated.";
+ memoryLocation += newRun.Start - run.Start;
+ NewDataIndex = memoryLocation + 2;
+ DataMoved = true;
+ }
+ Model.WriteMultiByteValue(memoryLocation + 2, 2, CurrentChange, 0xFFFF);
+ var eggRun = new EggMoveRun(Model, newRun.Start);
+ Model.ObserveRunWritten(CurrentChange, eggRun);
+ eggRun = (EggMoveRun)Model.GetNextRun(eggRun.Start);
+ eggRun.UpdateLimiter(CurrentChange);
+ }
+ }
}
}
diff --git a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
index 94676de2..26ea4f8b 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/ContextItemFactory.cs
@@ -117,6 +117,16 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
Results.AddRange(GetTableChildren(arrayRun));
}
+ public void Visit(EggSection section, byte data) {
+ var point = ViewPort.SelectionStart;
+ Results.Add(new ContextItem("Open In Text Tool", arg => ViewPort.FollowLink(point.X, point.Y)) { ShortcutText = "Ctrl+Click" });
+ }
+
+ public void Visit(EggItem item, byte data) {
+ var point = ViewPort.SelectionStart;
+ Results.Add(new ContextItem("Open In Text Tool", arg => ViewPort.FollowLink(point.X, point.Y)) { ShortcutText = "Ctrl+Click" });
+ }
+
private IEnumerable GetTableChildren(ArrayRun array) {
if (ViewPort.Tools.TableTool.Append.CanExecute(null)) {
yield return new ContextItem("Extend Table", ViewPort.Tools.TableTool.Append.Execute);
diff --git a/src/HexManiac.Core/ViewModels/Visitors/ContinueCellEdit.cs b/src/HexManiac.Core/ViewModels/Visitors/ContinueCellEdit.cs
index 461d3e6a..ee9acf5b 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/ContinueCellEdit.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/ContinueCellEdit.cs
@@ -1,4 +1,5 @@
using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
using System;
using System.Linq;
@@ -71,5 +72,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
".~".Contains(Input) ||
char.IsWhiteSpace(Input);
}
+
+ public void Visit(EggSection section, byte data) => VisitEgg();
+ public void Visit(EggItem item, byte data) => VisitEgg();
+ public void VisitEgg() {
+ var specialCharacters = ". '-\\"; // mr. mime, farfetch'd, double-edge, nidoran
+ if (UnderEdit.CurrentText.StartsWith(EggMoveRun.GroupStart)) specialCharacters += ']';
+ if (UnderEdit.CurrentText.StartsWith(StringDelimeter.ToString())) specialCharacters += StringDelimeter;
+ Result =
+ char.IsLetterOrDigit(Input) ||
+ specialCharacters.Contains(Input) ||
+ char.IsWhiteSpace(Input);
+ }
}
}
diff --git a/src/HexManiac.Core/ViewModels/Visitors/DataClear.cs b/src/HexManiac.Core/ViewModels/Visitors/DataClear.cs
index 5d7c5b4b..409e3581 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/DataClear.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/DataClear.cs
@@ -1,4 +1,5 @@
using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
using System;
@@ -40,8 +41,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public void Visit(Ascii ascii, byte data) => currentChange.ChangeData(buffer, index, 0xFF);
- public void Visit(Integer integer, byte data) => buffer.WriteValue(currentChange, index, 0);
+ public void Visit(Integer integer, byte data) => buffer.WriteMultiByteValue(index, integer.Length, currentChange, 0);
- public void Visit(IntegerEnum integerEnum, byte data) => buffer.WriteValue(currentChange, index, 0);
+ public void Visit(IntegerEnum integerEnum, byte data) => buffer.WriteMultiByteValue(index, integerEnum.Length, currentChange, 0);
+
+ public void Visit(EggSection section, byte data) => buffer.WriteMultiByteValue(index, 2, currentChange, EggMoveRun.MagicNumber);
+
+ public void Visit(EggItem item, byte data) => buffer.WriteMultiByteValue(index, 2, currentChange, 0x0000);
}
}
diff --git a/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs b/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs
index babd636a..1cf29833 100644
--- a/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs
+++ b/src/HexManiac.Core/ViewModels/Visitors/StartCellEdit.cs
@@ -49,7 +49,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
// just continue the existing edit.
public void Visit(UnderEdit underEdit, byte data) {
// handle special cases of "anywhere" formats first
- if (underEdit.CurrentText[0] == ViewPort.GotoMarker) {
+ if (underEdit.CurrentText.StartsWith(ViewPort.GotoMarker.ToString())) {
Result = char.IsLetterOrDigit(Input) || Input == ArrayAnchorSeparator || char.IsWhiteSpace(Input);
return;
} else if (underEdit.CurrentText.StartsWith(AnchorStart.ToString())) {
@@ -67,10 +67,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
}
public void Visit(Pointer pointer, byte data) {
- if (Input != PointerStart && !char.IsLetterOrDigit(Input)) {
- Result = false;
- return;
- }
+ if (Input != PointerStart && !char.IsLetterOrDigit(Input)) return;
var editText = Input.ToString();
// if the user tries to edit the pointer but forgets the opening bracket, add it for them.
@@ -89,10 +86,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public void Visit(PCS pcs, byte data) {
if (Model.GetNextRun(MemoryLocation) is ArrayRun array) {
var offsets = array.ConvertByteOffsetToArrayOffset(MemoryLocation);
- if (offsets.SegmentStart == MemoryLocation && Input == ' ') {
- Result = false; // don't let it start with a space unless it's in quotes (for copy/paste)
- return;
- }
+ // don't let it start with a space unless it's in quotes (for copy/paste)
+ if (offsets.SegmentStart == MemoryLocation && Input == ' ') return;
}
Result = Input == StringDelimeter || PCSString.PCS.Any(str => str != null && str.StartsWith(Input.ToString()));
@@ -109,20 +104,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public void Visit(Ascii ascii, byte data) => Result = true;
public void Visit(Integer intFormat, byte data) {
- if (!intFormat.CanStartWithCharacter(Input)) {
- Result = false;
- return;
- }
+ if (!intFormat.CanStartWithCharacter(Input)) return;
NewFormat = new UnderEdit(intFormat, Input.ToString(), intFormat.Length, null);
Result = true;
}
public void Visit(IntegerEnum integer, byte data) {
- if (!integer.CanStartWithCharacter(Input)) {
- Result = false;
- return;
- }
+ if (!integer.CanStartWithCharacter(Input)) return;
var arrayRun = (ArrayRun)Model.GetNextRun(MemoryLocation);
var offsets = arrayRun.ConvertByteOffsetToArrayOffset(MemoryLocation);
@@ -132,5 +121,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
NewFormat = new UnderEdit(integer, Input.ToString(), integer.Length, autocomplete);
Result = true;
}
+
+ public void Visit(EggSection section, byte data) => VisitEgg(section);
+ public void Visit(EggItem item, byte data) => VisitEgg(item);
+ public void VisitEgg(IDataFormat eggFormat) {
+ if (!char.IsLetterOrDigit(Input) && !$"{StringDelimeter}[".Contains(Input)) return;
+
+ var stream = (EggMoveRun)Model.GetNextRun(MemoryLocation);
+ var allOptions = stream.GetAutoCompleteOptions();
+ var autocomplete = AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(Input.ToString())), -1);
+ NewFormat = new UnderEdit(eggFormat, Input.ToString(), 2, autocomplete);
+ Result = true;
+ }
}
}
diff --git a/src/HexManiac.Tests/AutoSearchTests.cs b/src/HexManiac.Tests/AutoSearchTests.cs
index 81003cbd..350fc9ff 100644
--- a/src/HexManiac.Tests/AutoSearchTests.cs
+++ b/src/HexManiac.Tests/AutoSearchTests.cs
@@ -30,7 +30,7 @@ namespace HavenSoft.HexManiac.Tests {
var model = LoadModel(game);
var noChange = new NoDataChangeDeltaModel();
- var address = model.GetAddressFromAnchor(noChange, -1, "pokenames");
+ var address = model.GetAddressFromAnchor(noChange, -1, EggMoveRun.PokemonNameTable);
var run = (ArrayRun)model.GetNextAnchor(address);
Assert.Equal(412, run.ElementCount);
}
@@ -41,7 +41,7 @@ namespace HavenSoft.HexManiac.Tests {
var model = LoadModel(game);
var noChange = new NoDataChangeDeltaModel();
- var address = model.GetAddressFromAnchor(noChange, -1, "movenames");
+ var address = model.GetAddressFromAnchor(noChange, -1, EggMoveRun.MoveNamesTable);
var run = (ArrayRun)model.GetNextAnchor(address);
Assert.Equal(355, run.ElementCount);
}
@@ -141,6 +141,22 @@ namespace HavenSoft.HexManiac.Tests {
for (int i = 0; i < compareSet.Length; i++) Assert.Equal(compareSet[i], poundStats[i]);
}
+ [SkippableTheory]
+ [MemberData(nameof(PokemonGames))]
+ public void EggMoveDataFound(string game) {
+ var model = LoadModel(game);
+ var noChange = new NoDataChangeDeltaModel();
+
+ var address = model.GetAddressFromAnchor(noChange, -1, "eggmoves");
+ var run = (EggMoveRun)model.GetNextAnchor(address);
+
+ Assert.Equal(2, run.PointerSources.Count);
+ var expectedLastElement = model.ReadMultiByteValue(run.PointerSources[1] - 4, 4);
+ var expectedLength = expectedLastElement + 1;
+ var actualLength = run.Length / 2 - 1; // remove the closing element.
+ Assert.InRange(actualLength, 790, expectedLength);
+ }
+
///
/// Loading the model can take a while.
/// We want to know that loading the model created the correct arrays,
diff --git a/src/HexManiac.Tests/EggMoveTests.cs b/src/HexManiac.Tests/EggMoveTests.cs
new file mode 100644
index 00000000..47deeaf5
--- /dev/null
+++ b/src/HexManiac.Tests/EggMoveTests.cs
@@ -0,0 +1,234 @@
+using HavenSoft.HexManiac.Core;
+using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.Models.Runs;
+using HavenSoft.HexManiac.Core.ViewModels;
+using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace HavenSoft.HexManiac.Tests {
+ public class EggMoveTests {
+ private readonly byte[] data;
+ private readonly PokemonModel model;
+ private readonly ViewPort viewPort;
+ private readonly List messages = new List();
+
+ #region Setup
+
+ public EggMoveTests() {
+ data = Enumerable.Range(0, 0x200).Select(i => (byte)0xFF).ToArray();
+ model = new PokemonModel(data);
+ viewPort = new ViewPort("file.gba", model);
+
+ viewPort.Goto.Execute("000080");
+ viewPort.Edit("^pokenames[name\"\"8]8 \"Bob\" \"Steve\" \"Carl\" \"Sam\" \"Bryan\" \"Ryan\" \"Ian\" \"Matt\"");
+
+ viewPort.Goto.Execute("000100");
+ viewPort.Edit("^movenames[name\"\"8]8 \"Fire\" \"Water\" \"Earth\" \"Wind\" \"Light\" \"Dark\" \"Normal\" \"Magic\"");
+
+ viewPort.Goto.Execute("000180");
+ viewPort.Edit("<000000> Dead Beef 01 00 00 00 <000000>"); // limiter is at 188 for an eggrun at 000
+
+ viewPort.Goto.Execute("000000");
+
+ viewPort.OnMessage += (sender, e) => messages.Add(e);
+ }
+
+ private void CreateSimpleRun() {
+ var token = new ModelDelta();
+ model.WriteMultiByteValue(0, 2, token, EggMoveRun.MagicNumber + 2); // Carl
+ model.WriteMultiByteValue(2, 2, token, 3); // Wind
+
+ viewPort.Edit("^eggmoves`egg` ");
+ }
+
+ #endregion
+
+ [Fact]
+ public void CanCreateEggMoveStream() {
+ viewPort.Edit("^eggmoves`egg` ");
+
+ Assert.Equal(2, model.GetNextRun(0).Length);
+ }
+
+ [Fact]
+ public void CanSeeEggMoveStreamWithCorrectFormat() {
+ CreateSimpleRun();
+
+ Assert.Equal(6, model.GetNextRun(0).Length);
+ var section = (EggSection)viewPort[1, 0].Format;
+ var item = (EggItem)viewPort[2, 0].Format;
+ var endSection = (EggSection)viewPort[5, 0].Format;
+ Assert.Equal("[Carl]", section.SectionName);
+ Assert.Equal("Wind", item.ItemName);
+ Assert.Equal("[]", endSection.SectionName);
+ }
+
+ [Fact]
+ public void SelectionDoneInPairs() {
+ CreateSimpleRun();
+
+ viewPort.SelectionStart = new Point(2, 0); // should select "Wind"
+ Assert.True(viewPort.IsSelected(new Point(3, 0)));
+
+ viewPort.MoveSelectionStart.Execute(Direction.Right); // should select "[]"
+ Assert.True(viewPort.IsSelected(new Point(4, 0)));
+ Assert.True(viewPort.IsSelected(new Point(5, 0)));
+ }
+
+ [Fact]
+ public void CanEditEggStreamManually() {
+ CreateSimpleRun();
+
+ viewPort.Edit("Dark ");
+ Assert.Equal(5, model[0]);
+
+ viewPort.Edit("[Bryan]");
+ Assert.Equal(EggMoveRun.MagicNumber + 4, model.ReadMultiByteValue(2, 2));
+ }
+
+ [Fact]
+ public void CanCopyPaste() {
+ CreateSimpleRun();
+ var fileSystem = new StubFileSystem();
+
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.SelectionEnd = new Point(4, 0);
+ viewPort.Copy.Execute(fileSystem);
+
+ Assert.Equal("Wind []", fileSystem.CopyText.value);
+ }
+
+ [Fact]
+ public void RunAutoExtends() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(4, 0);
+ viewPort.Edit("[Carl]");
+
+ Assert.Equal(8, model.GetNextRun(0).Length);
+ Assert.Equal(2, model.ReadMultiByteValue(0x188, 4));
+ }
+
+ [Fact]
+ public void RunAutoMoves() {
+ viewPort.Edit("^eggmoves`egg` ");
+ model.WriteMultiByteValue(2, 2, new ModelDelta(), 0x0206);
+ viewPort.Edit("[Carl]");
+
+ Assert.Single(messages);
+ }
+
+ [Fact]
+ public void RunAutoShortens() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.Edit("[]");
+
+ Assert.Equal(4, model.GetNextRun(0).Length);
+ Assert.Equal(0, model.ReadMultiByteValue(0x188, 4));
+ }
+
+ [Fact]
+ public void CanViewInTextTool() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(2, 2); // select off it
+ viewPort.SelectionStart = new Point(2, 0); // and then back on
+ viewPort.Tools.StringToolCommand.Execute();
+
+ Assert.Equal(@"[Carl]
+Wind", viewPort.Tools.StringTool.Content);
+
+ viewPort.Tools.StringTool.Content = @"[Carl]
+Earth
+Light
+[Ryan]
+Fire
+Water";
+
+ Assert.Equal(14, model.GetNextRun(0).Length);
+ Assert.Equal(5, model.ReadMultiByteValue(0x188, 4));
+ }
+
+ [Fact]
+ public void FollowLinkOpensTextTool() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.FollowLink(2, 0);
+
+ Assert.Equal(0, viewPort.Tools.SelectedIndex);
+ }
+
+ [Fact]
+ public void RightClickOptionToOpenTextTool() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(2, 0);
+ var items = viewPort.GetContextMenuItems(viewPort.SelectionStart);
+ Assert.Contains(items, item => item.ShortcutText == "Ctrl+Click");
+ }
+
+ [Fact]
+ public void ToolCursorChangesCauseSelectionChanges() {
+ CreateSimpleRun();
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.Tools.StringToolCommand.Execute();
+ viewPort.Tools.StringTool.ContentIndex = 5;
+
+ Assert.True(viewPort.IsSelected(new Point(0, 0)));
+ Assert.True(viewPort.IsSelected(new Point(1, 0)));
+ }
+
+ [Fact]
+ public void SearchFindsEggMoveData() {
+ CreateSimpleRun();
+
+ var pairs = viewPort.Find("wind");
+ Assert.Contains((2, 3), pairs);
+
+ pairs = viewPort.Find("carl");
+ Assert.Contains((0, 1), pairs);
+ }
+
+ [Fact]
+ public void BackspaceWorks() {
+ CreateSimpleRun();
+
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.Edit(ConsoleKey.Backspace);
+
+ var format = (UnderEdit)viewPort[2, 0].Format;
+ Assert.Equal("Win", format.CurrentText); // Wind, but backspaced
+ }
+
+ ///
+ /// Since egg moves are multiple cells long, we want to know that
+ /// changes get reverted when 'selectionstart' is the rightmost cell
+ ///
+ [Fact]
+ public void SelectLeftThenBackspaceThenDownCompletesEdits() {
+ CreateSimpleRun();
+
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.MoveSelectionStart.Execute(Direction.Left);
+ viewPort.Edit(ConsoleKey.Backspace);
+ viewPort.MoveSelectionStart.Execute(Direction.Down);
+
+ Assert.IsNotType(viewPort[1, 0].Format);
+ }
+
+ [Fact]
+ public void CanClearCellContent() {
+ CreateSimpleRun();
+
+ viewPort.SelectionStart = new Point(2, 0);
+ viewPort.Edit(ConsoleKey.Backspace); // d
+ viewPort.Edit(ConsoleKey.Backspace); // n
+ viewPort.Edit(ConsoleKey.Backspace); // i
+ viewPort.Edit(ConsoleKey.Backspace); // W
+
+ var format = (UnderEdit)viewPort[2, 0].Format;
+ Assert.Equal(string.Empty, format.CurrentText);
+ }
+ }
+}
diff --git a/src/HexManiac.Tests/HexManiac.Tests.csproj b/src/HexManiac.Tests/HexManiac.Tests.csproj
index c8cbb7e8..f56c37dd 100644
--- a/src/HexManiac.Tests/HexManiac.Tests.csproj
+++ b/src/HexManiac.Tests/HexManiac.Tests.csproj
@@ -71,6 +71,7 @@
+
diff --git a/src/HexManiac.WPF/Implementations/FormatDrawer.cs b/src/HexManiac.WPF/Implementations/FormatDrawer.cs
index 88842e87..efb3a83a 100644
--- a/src/HexManiac.WPF/Implementations/FormatDrawer.cs
+++ b/src/HexManiac.WPF/Implementations/FormatDrawer.cs
@@ -1,6 +1,7 @@
using HavenSoft.HexManiac.Core.ViewModels;
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
using HavenSoft.HexManiac.WPF.Controls;
+using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -45,14 +46,7 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
var content = dataFormat.CurrentText;
- var text = new FormattedText(
- content,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- brush,
- 1.0);
+ var text = CreateText(content, FontSize, brush);
var offset = CellTextOffset;
var widthOverflow = text.Width - HexContent.CellWidth * dataFormat.EditWidth;
@@ -72,18 +66,10 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
if (dataFormat.Destination < 0) brush = Brush(nameof(Theme.Error));
Underline(brush, dataFormat.Position == 0, dataFormat.Position == 3);
- var typeface = new Typeface("Consolas");
var destination = dataFormat.DestinationAsText;
if (destination.Length > 13) destination = destination.Substring(0, 11) + "…>";
var xOffset = 51 - (dataFormat.Position * HexContent.CellWidth) - destination.Length * 4.2; // centering
- var text = new FormattedText(
- destination,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- brush,
- 1.0);
+ var text = CreateText(destination, FontSize, brush);
if (dataFormat.Position > Position.X || Position.X - dataFormat.Position > modelWidth - 4) {
context.PushClip(rectangleGeometry);
@@ -103,15 +89,7 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
}
public void Visit(PCS pcs, byte data) {
- var typeface = new Typeface("Consolas");
- var text = new FormattedText(
- pcs.ThisCharacter,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- Brush(nameof(Theme.Text1)),
- 1.0);
+ var text = CreateText(pcs.ThisCharacter, FontSize, Brush(nameof(Theme.Text1)));
var xOffset = 1 - pcs.ThisCharacter.Length;
context.DrawText(text, new Point(CellTextOffset.X + xOffset, CellTextOffset.Y));
@@ -123,33 +101,16 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
public void Visit(ErrorPCS pcs, byte data) {
var brush = Brush(nameof(Theme.Error));
- var typeface = new Typeface("Consolas");
var content = data.ToString("X2");
- var text = new FormattedText(
- content,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- brush,
- 1.0);
+ var text = CreateText(content, FontSize, brush);
context.DrawText(text, CellTextOffset);
}
public void Visit(Ascii ascii, byte data) {
- var typeface = new Typeface("Consolas");
- var text = new FormattedText(
- ascii.ThisCharacter.ToString(),
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- Brush(nameof(Theme.Text2)),
- 1.0);
-
+ var text = CreateText(ascii.ThisCharacter.ToString(), FontSize, Brush(nameof(Theme.Text2)));
context.DrawText(text, CellTextOffset);
}
@@ -158,15 +119,7 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
var stringValue = integer.Value.ToString();
- var typeface = new Typeface("Consolas");
- var text = new FormattedText(
- stringValue,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize,
- Brush(nameof(Theme.Data1)),
- 1.0);
+ var text = CreateText(stringValue, FontSize, Brush(nameof(Theme.Data1)));
var xOffset = CellTextOffset.X;
xOffset += HexContent.CellWidth / 2 * (integer.Length - 1); // adjust based on number of cells to use
@@ -178,16 +131,7 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
if (integerEnum.Position != 0) return;
var stringValue = integerEnum.Value;
-
- var typeface = new Typeface("Consolas");
- var text = new FormattedText(
- stringValue,
- CultureInfo.CurrentCulture,
- FlowDirection.LeftToRight,
- typeface,
- FontSize * 3 / 4,
- Brush(nameof(Theme.Data2)),
- 1.0);
+ var text = CreateText(stringValue, FontSize * 3 / 4, Brush(nameof(Theme.Data2)));
var xOffset = CellTextOffset.X / 2;
context.PushClip(new RectangleGeometry(new Rect(0, 0, HexContent.CellWidth * integerEnum.Length, HexContent.CellHeight)));
@@ -195,6 +139,32 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
context.Pop();
}
+ public void Visit(EggSection section, byte data) {
+ if (section.Position != 0) return;
+ var name = section.SectionName;
+
+ var text = CreateText(name, FontSize * 3 / 4, Brush(nameof(Theme.Stream1)));
+ var characterWidth = text.Width / name.Length;
+ var xOffset = HexContent.CellWidth - name.Length * characterWidth / 2;
+ if (xOffset < 0) xOffset = 0;
+ context.PushClip(new RectangleGeometry(new Rect(0, 0, HexContent.CellWidth * 2, HexContent.CellHeight)));
+ context.DrawText(text, new Point(xOffset, CellTextOffset.Y + 2));
+ context.Pop();
+ }
+
+ public void Visit(EggItem item, byte data) {
+ if (item.Position != 0) return;
+ var name = item.ItemName;
+
+ var text = CreateText(name, FontSize * 3 / 4, Brush(nameof(Theme.Stream2)));
+ var characterWidth = text.Width / name.Length;
+ var xOffset = HexContent.CellWidth - name.Length * characterWidth / 2;
+ if (xOffset < 0) xOffset = 0;
+ context.PushClip(new RectangleGeometry(new Rect(0, 0, HexContent.CellWidth * 2, HexContent.CellHeight)));
+ context.DrawText(text, new Point(xOffset, CellTextOffset.Y + 2));
+ context.Pop();
+ }
+
private void Underline(Brush brush, bool isStart, bool isEnd) {
int startPoint = isStart ? 5 : 0;
int endPoint = (int)HexContent.CellWidth - (isEnd ? 5 : 0);
@@ -227,6 +197,18 @@ namespace HavenSoft.HexManiac.WPF.Implementations {
noneVisualCache.AddRange(text);
}
+ private static readonly Typeface consolas = new Typeface("Consolas");
+ private static FormattedText CreateText(string text, double size, Brush color) {
+ return new FormattedText(
+ text,
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ consolas,
+ size,
+ color,
+ 1.0);
+ }
+
private static SolidColorBrush Brush(string name) {
return (SolidColorBrush)Application.Current.Resources.MergedDictionaries[0][name];
}
diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs
index f50d7168..afc894d7 100644
--- a/src/SharedAssemblyInfo.cs
+++ b/src/SharedAssemblyInfo.cs
@@ -6,5 +6,5 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("0.2.0.0")]
-[assembly: AssemblyFileVersion("0.2.0.0")]
+[assembly: AssemblyVersion("0.2.1.0")]
+[assembly: AssemblyFileVersion("0.2.1.0")]