mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 13:16:35 -05:00
@@ -65,17 +65,17 @@ namespace HavenSoft.HexManiac.Core.Models {
|
||||
|
||||
// abilitynames / trainer names
|
||||
if (gameCode == Ruby || gameCode == Sapphire || gameCode == Emerald) {
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var abilitynames)) {
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var abilitynames, run => run.PointerSources.FirstOrDefault() < 0x100000)) {
|
||||
ObserveAnchorWritten(noChangeDelta, "abilitynames", abilitynames);
|
||||
}
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var trainerclassnames)) {
|
||||
ObserveAnchorWritten(noChangeDelta, "trainerclassnames", trainerclassnames);
|
||||
}
|
||||
} else {
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var trainerclassnames)) {
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var trainerclassnames, run => run.PointerSources.Count > 1 && run.PointerSources.Count < 4)) {
|
||||
ObserveAnchorWritten(noChangeDelta, "trainerclassnames", trainerclassnames);
|
||||
}
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var abilitynames)) {
|
||||
if (TrySearch(this, noChangeDelta, "[name\"\"13]", out var abilitynames, run => run.PointerSources.FirstOrDefault() < 0x100000)) {
|
||||
ObserveAnchorWritten(noChangeDelta, "abilitynames", abilitynames);
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,17 @@ namespace HavenSoft.HexManiac.Core.Models {
|
||||
ObserveAnchorWritten(noChangeDelta, "items", itemdata);
|
||||
}
|
||||
|
||||
if (TrySearch(this, noChangeDelta, "[hp. attack. def. speed. spatk. spdef. type1.types type2.types catchRate. baseExp. evs: item1:items item2:items genderratio. steps2hatch. basehappiness. growthrate. egg1. egg2. ability1.abilitynames ability2.abilitynames runrate. unknown. padding:]pokenames", out var pokestatdata)) {
|
||||
// if the stat data doesn't match the pokenames length, use whichever is shorter.
|
||||
var format = "[hp. attack. def. speed. spatk. spdef. type1.types type2.types catchRate. baseExp. evs: item1:items item2:items genderratio. steps2hatch. basehappiness. growthrate. egg1. egg2. ability1.abilitynames ability2.abilitynames runrate. unknown. padding:]pokenames";
|
||||
var pokenames = GetNextRun(GetAddressFromAnchor(noChangeDelta, -1, EggMoveRun.PokemonNameTable)) as ArrayRun;
|
||||
if (pokenames != null && TrySearch(this, noChangeDelta, format, out var pokestatdata, run => run.PointerSources.Count > 5)) {
|
||||
if (pokestatdata.ElementCount < pokenames.ElementCount) {
|
||||
pokenames = pokenames.Append(pokestatdata.ElementCount - pokenames.ElementCount);
|
||||
ObserveAnchorWritten(noChangeDelta, EggMoveRun.PokemonNameTable, pokenames);
|
||||
} else if (pokestatdata.ElementCount > pokenames.ElementCount) {
|
||||
pokestatdata = pokestatdata.Append(pokenames.ElementCount - pokestatdata.ElementCount);
|
||||
}
|
||||
|
||||
ObserveAnchorWritten(noChangeDelta, "pokestats", pokestatdata);
|
||||
}
|
||||
|
||||
|
||||
@@ -103,12 +103,15 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
if (ElementContent.Count == 0) throw new ArrayRunParseException("Array Content must not be empty.");
|
||||
ElementLength = ElementContent.Sum(e => e.Length);
|
||||
|
||||
FormatMatchFlags flags = default;
|
||||
if (ElementContent.Count == 1) flags |= FormatMatchFlags.IsSingleSegment;
|
||||
|
||||
if (length.Length == 0) {
|
||||
var nextRun = owner.GetNextRun(Start);
|
||||
while (nextRun is NoInfoRun && nextRun.Start < owner.Count) nextRun = owner.GetNextRun(nextRun.Start + 1);
|
||||
var byteLength = 0;
|
||||
var elementCount = 0;
|
||||
while (Start + byteLength + ElementLength <= nextRun.Start && DataMatchesElementFormat(owner, Start + byteLength, ElementContent, nextRun)) {
|
||||
while (Start + byteLength + ElementLength <= nextRun.Start && DataMatchesElementFormat(owner, Start + byteLength, ElementContent, flags, nextRun)) {
|
||||
byteLength += ElementLength;
|
||||
elementCount++;
|
||||
}
|
||||
@@ -164,7 +167,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
var elementLength = elementContent.Sum(e => e.Length);
|
||||
|
||||
if (string.IsNullOrEmpty(length)) {
|
||||
var bestAddress = StandardSearch(data, elementContent, elementLength, out int bestLength);
|
||||
var bestAddress = StandardSearch(data, elementContent, elementLength, out int bestLength, runFilter);
|
||||
if (bestAddress == Pointer.NULL) return false;
|
||||
self = new ArrayRun(data, originalFormat + bestLength, string.Empty, bestAddress, bestLength, elementContent, data.GetNextRun(bestAddress).PointerSources, null);
|
||||
} else {
|
||||
@@ -178,7 +181,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int StandardSearch(IDataModel data, List<ArrayRunElementSegment> elementContent, int elementLength, out int bestLength) {
|
||||
private static int StandardSearch(IDataModel data, List<ArrayRunElementSegment> elementContent, int elementLength, out int bestLength, Func<IFormattedRun, bool> runFilter) {
|
||||
int bestAddress = Pointer.NULL;
|
||||
bestLength = 0;
|
||||
|
||||
@@ -190,18 +193,38 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
}
|
||||
var nextArray = nextRun;
|
||||
|
||||
// some searches allow special conditions on the run. For example, we could only be intersted in runs with >100 pointers leading to it.
|
||||
if (runFilter != null && !runFilter(run)) { run = nextRun; continue; }
|
||||
|
||||
FormatMatchFlags flags = default;
|
||||
if (elementContent.Count == 1) flags |= FormatMatchFlags.IsSingleSegment;
|
||||
|
||||
int currentLength = 0;
|
||||
int currentAddress = run.Start;
|
||||
while (true) {
|
||||
if (currentLength > 100) flags |= FormatMatchFlags.AllowJunkAfterText; // we've gone long enough without junk data to be fairly sure that we're looking at something real
|
||||
if (nextArray.Start < currentAddress) nextArray = data.GetNextAnchor(nextArray.Start + 1);
|
||||
if (DataMatchesElementFormat(data, currentAddress, elementContent, nextArray)) {
|
||||
if (DataMatchesElementFormat(data, currentAddress, elementContent, flags, nextArray)) {
|
||||
currentLength++;
|
||||
currentAddress += elementLength;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bestLength < currentLength) {
|
||||
|
||||
// if what we found is just a text array, then remove any trailing elements starting with a space.
|
||||
if (elementContent.Count == 1 && elementContent[0].Type == ElementContentType.PCS) {
|
||||
while (data[currentAddress - elementLength] == 0x00) {
|
||||
currentLength--;
|
||||
currentAddress -= elementLength;
|
||||
}
|
||||
}
|
||||
|
||||
// we think we found some data! Make sure it's not just a bunch of 00's and FF's
|
||||
var dataEmpty = true;
|
||||
for (int i = 0; i < currentLength && currentLength > bestLength && dataEmpty; i++) dataEmpty = data[run.Start + i] == 0xFF || data[run.Start + i] == 0x00;
|
||||
|
||||
if (bestLength < currentLength && !dataEmpty) {
|
||||
bestLength = currentLength;
|
||||
bestAddress = run.Start;
|
||||
}
|
||||
@@ -223,6 +246,9 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
bestLength = matchedRun.ElementCount;
|
||||
}
|
||||
|
||||
FormatMatchFlags flags = default;
|
||||
if (elementContent.Count == 1) flags |= FormatMatchFlags.IsSingleSegment;
|
||||
|
||||
for (var run = data.GetNextRun(0); run.Start < data.Count; run = data.GetNextRun(run.Start + run.Length + 1)) {
|
||||
if (!(run is PointerRun)) continue;
|
||||
var targetRun = data.GetNextRun(data.ReadPointer(run.Start));
|
||||
@@ -236,16 +262,22 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
bool earlyExit = false;
|
||||
for (int i = 0; i < bestLength; i++) {
|
||||
var nextArray = data.GetNextAnchor(currentAddress + 1);
|
||||
if (DataMatchesElementFormat(data, currentAddress, elementContent, nextArray)) {
|
||||
if (DataMatchesElementFormat(data, currentAddress, elementContent, flags, nextArray)) {
|
||||
currentLength++;
|
||||
currentAddress += elementLength;
|
||||
} else {
|
||||
earlyExit = true;
|
||||
// as long as this array is at least 80% of the passed in array, we're fine and can say that these are matched.
|
||||
// (the other one might have bad data at the end that needs to be removed) (example: see Gaia)
|
||||
earlyExit = bestLength * .8 > currentLength;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!earlyExit) return targetRun.Start;
|
||||
|
||||
if (!earlyExit) {
|
||||
bestLength = currentLength;
|
||||
return targetRun.Start;
|
||||
}
|
||||
}
|
||||
|
||||
return Pointer.NULL;
|
||||
@@ -455,8 +487,9 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
var format = ElementContentType.PCS;
|
||||
var formatLength = 2;
|
||||
while (formatLength < segments.Length && char.IsDigit(segments[formatLength])) formatLength++;
|
||||
var segmentLength = int.Parse(segments.Substring(2, formatLength - 2));
|
||||
return (format, formatLength, segmentLength);
|
||||
if (int.TryParse(segments.Substring(2, formatLength - 2), out var segmentLength)) {
|
||||
return (format, formatLength, segmentLength);
|
||||
}
|
||||
} else if (segments.StartsWith(DoubleByteIntegerFormat + string.Empty + DoubleByteIntegerFormat)) {
|
||||
return (ElementContentType.Integer, 2, 4);
|
||||
} else if (segments.StartsWith(DoubleByteIntegerFormat + string.Empty + SingleByteIntegerFormat) || segments.StartsWith(".:")) {
|
||||
@@ -490,35 +523,41 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
return run.ElementCount;
|
||||
}
|
||||
|
||||
private static bool DataMatchesElementFormat(IDataModel owner, int start, IReadOnlyList<ArrayRunElementSegment> segments, IFormattedRun nextAnchor) {
|
||||
private static bool DataMatchesElementFormat(IDataModel owner, int start, IReadOnlyList<ArrayRunElementSegment> segments, FormatMatchFlags flags, IFormattedRun nextAnchor) {
|
||||
foreach (var segment in segments) {
|
||||
if (start + segment.Length > owner.Count) return false;
|
||||
if (!DataMatchesSegmentFormat(owner, start, segment, nextAnchor)) return false;
|
||||
if (!DataMatchesSegmentFormat(owner, start, segment, flags, nextAnchor)) return false;
|
||||
start += segment.Length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool DataMatchesSegmentFormat(IDataModel owner, int start, ArrayRunElementSegment segment, IFormattedRun nextAnchor) {
|
||||
private static bool DataMatchesSegmentFormat(IDataModel owner, int start, ArrayRunElementSegment segment, FormatMatchFlags flags, IFormattedRun nextAnchor) {
|
||||
if (start + segment.Length > nextAnchor.Start && nextAnchor is ArrayRun) return false; // don't blap over existing arrays
|
||||
switch (segment.Type) {
|
||||
case ElementContentType.PCS:
|
||||
int readLength = PCSString.ReadString(owner, start, true, segment.Length);
|
||||
if (readLength == -1) return false;
|
||||
if (readLength < 2) return false;
|
||||
if (readLength > segment.Length) return false;
|
||||
if (Enumerable.Range(start, segment.Length).All(i => owner[i] == 0xFF)) return false;
|
||||
|
||||
// TODO test this with Altair
|
||||
// in the initial 5 ROMs, any data after the close quote is either 0x00 or 0xFF
|
||||
// but in fan games, this data may contain leftover junk bytes from what the text 'used' to be.
|
||||
// this is because other popular existing editors don't clean up after themselves.
|
||||
// in order to be compatible with games made with those editors, we have to allow automatic matches
|
||||
// to match arrays with junk PCS characters after the closing quote.
|
||||
if (Enumerable.Range(start + readLength, segment.Length - readLength).Any(i => PCSString.PCS[owner[i]] == null)) return false;
|
||||
// if we end with a space, and the next one starts with a space, we probably have the data width wrong.
|
||||
// We might be the start of a different data segment that is no longer pointed to. (Example: Vega/pokenames)
|
||||
// only do this check if the current element seems useful
|
||||
var isBlank = Enumerable.Range(start, segment.Length).All(i => owner[i] == 0x00 || owner[i] == 0xFF);
|
||||
if (!isBlank && flags.HasFlag(FormatMatchFlags.IsSingleSegment) && start % 4 == 0 && owner[start + segment.Length - 1] == 0x00 && owner[start + segment.Length] == 0x00) {
|
||||
// if the next one starts on a 4-byte boundary, then we probably just skipped a few bytes between different data types, and _this_ section is still part of the _last_ run (example, Emerald Ability names)
|
||||
// if the next one doesn't start on a 4-byte boundary, then we probably have the length wrong
|
||||
var nextWordStart = (start + segment.Length + 3) / 4 * 4;
|
||||
if (Enumerable.Range(start + segment.Length, nextWordStart - start - segment.Length).Any(i => owner[i] != 0x00) || owner[nextWordStart] == 0x00) return false;
|
||||
}
|
||||
|
||||
// require that the overall thing still ends with 'FF' or '00' to avoid finding text of the wrong width.
|
||||
// the width check is less important if we have more complex data, so relax the condition (example: Clover)
|
||||
// the width check is less important if we're already known to be in a long run (example: Gaia moves)
|
||||
var lastByteInText = owner[start + segment.Length - 1];
|
||||
if (lastByteInText != 0x00 && lastByteInText != 0xFF) return false;
|
||||
var lastByteIsReasonablEnd = lastByteInText == 0x00 || lastByteInText == 0xFF;
|
||||
if (!flags.HasFlag(FormatMatchFlags.AllowJunkAfterText) && !lastByteIsReasonablEnd && flags.HasFlag(FormatMatchFlags.IsSingleSegment)) return false;
|
||||
|
||||
return true;
|
||||
case ElementContentType.Integer:
|
||||
@@ -535,6 +574,12 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum FormatMatchFlags {
|
||||
IsSingleSegment = 0x01,
|
||||
AllowJunkAfterText = 0x02,
|
||||
}
|
||||
}
|
||||
|
||||
public class ArrayRunParseException : Exception {
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
}
|
||||
|
||||
public static IReadOnlyList<string> GetOptions(IDataModel model, string enumName) {
|
||||
if (!model.TryGetNameArray(enumName, out var enumArray)) return null;
|
||||
if (!model.TryGetNameArray(enumName, out var enumArray)) return new string[0];
|
||||
|
||||
// array must be at least as long as than the current value
|
||||
var optionCount = enumArray.ElementCount;
|
||||
|
||||
@@ -42,22 +42,27 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
|
||||
if (run is ArrayRun || run is PCSRun || run.PointerSources == null) continue;
|
||||
|
||||
// verify expected pointers to this
|
||||
if (run.PointerSources.Count != 2) continue;
|
||||
if (run.PointerSources.Count < 2 || run.PointerSources.Count > 10) 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 (length < 1000 || length > 7000) 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;
|
||||
int lastValue = -1;
|
||||
for (int i = 0; i < length - 2; i++) {
|
||||
var value = data.ReadMultiByteValue(run.Start + i * 2, 2);
|
||||
|
||||
// if the same byte pairs are repeated multiple times, then this pokemon is listed twice or has the same egg move twice.
|
||||
// that seems unlikely... this is probably the wrong data.
|
||||
if (value == lastValue) { possibleMatch = false; break; }
|
||||
lastValue = value;
|
||||
|
||||
if (value == EndStream) break; // early exit, the data was edited, but that's ok. Everything still matches up.
|
||||
if (value >= MagicNumber) {
|
||||
value -= MagicNumber;
|
||||
|
||||
@@ -488,7 +488,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
var factory = new ContextItemFactory(this);
|
||||
var cell = currentView[SelectionStart.X, SelectionStart.Y];
|
||||
cell.Format.Visit(factory, cell.Value);
|
||||
return factory.Results;
|
||||
var results = factory.Results.ToList();
|
||||
if (!SelectionStart.Equals(SelectionEnd)) {
|
||||
results.Add(new ContextItem("Copy", Copy.Execute) { ShortcutText = "Ctrl+C" });
|
||||
}
|
||||
results.Add(new ContextItem("Paste", arg => Edit(((IFileSystem)arg).CopyText)) { ShortcutText = "Ctrl+V" });
|
||||
return results;
|
||||
}
|
||||
|
||||
public bool IsSelected(Point point) => selection.IsSelected(point);
|
||||
|
||||
@@ -64,7 +64,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
if (NewCell != null) NewCell = new HexElement(NewCell.Value, new Anchor(NewCell.Format, anchor.Name, anchor.Format, anchor.Sources));
|
||||
}
|
||||
|
||||
public void Visit(PCS pcs, byte data) {
|
||||
public void Visit(PCS pcs, byte data) => VisitPCS(pcs);
|
||||
|
||||
private void VisitPCS(IDataFormatInstance pcs) {
|
||||
var currentText = CurrentText;
|
||||
if (currentText.StartsWith(StringDelimeter.ToString())) currentText = currentText.Substring(1);
|
||||
if (pcs.Position != 0 && CurrentText == StringDelimeter.ToString()) {
|
||||
@@ -82,7 +84,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
Result = true;
|
||||
}
|
||||
|
||||
public void Visit(ErrorPCS pcs, byte data) => throw new NotImplementedException();
|
||||
public void Visit(ErrorPCS pcs, byte data) => VisitPCS(pcs);
|
||||
|
||||
public void Visit(Ascii ascii, byte data) {
|
||||
CompleteAsciiEdit(ascii);
|
||||
@@ -229,7 +231,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
private void CompleteCharacterEdit(IDataFormat originalFormat) {
|
||||
var editText = CurrentText;
|
||||
if (editText.StartsWith("\"")) editText = editText.Substring(1);
|
||||
var pcs = originalFormat as PCS;
|
||||
var pcs = originalFormat as IDataFormatInstance;
|
||||
var escaped = originalFormat as EscapedPCS;
|
||||
var run = Model.GetNextRun(memoryLocation);
|
||||
|
||||
@@ -237,11 +239,11 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
byte.Parse(CurrentText, NumberStyles.HexNumber) :
|
||||
(byte)Enumerable.Range(0, 0x100).First(i => PCSString.PCS[i] == editText);
|
||||
|
||||
var position = pcs != null ? pcs.Position : escaped.Position;
|
||||
HandleLastCharacterChange(memoryLocation, editText, pcs, run, position, byteValue);
|
||||
var position = pcs == null ? escaped.Position : pcs.Position;
|
||||
HandleLastCharacterChange(memoryLocation, editText, run, position, byteValue);
|
||||
}
|
||||
|
||||
private void HandleLastCharacterChange(int memoryLocation, string editText, PCS pcs, IFormattedRun run, int position, byte byteValue) {
|
||||
private void HandleLastCharacterChange(int memoryLocation, string editText, IFormattedRun run, int position, byte byteValue) {
|
||||
if (run is PCSRun) {
|
||||
// if its the last character being edited on a normal string, try to expand
|
||||
if (run.Length == position + 1) {
|
||||
|
||||
@@ -117,14 +117,12 @@ 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(EggSection section, byte data) => Visit((EggItem)null, data);
|
||||
|
||||
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" });
|
||||
Results.AddRange(GetFormattedChildren());
|
||||
}
|
||||
|
||||
private IEnumerable<IContextItem> GetTableChildren(ArrayRun array) {
|
||||
|
||||
@@ -97,9 +97,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
|
||||
Result = ViewPort.AllHexCharacters.Contains(Input);
|
||||
}
|
||||
|
||||
public void Visit(ErrorPCS pcs, byte data) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public void Visit(ErrorPCS pcs, byte data) => Visit((PCS)null, data);
|
||||
|
||||
public void Visit(Ascii ascii, byte data) => Result = true;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core.Models.Runs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -15,13 +16,16 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
public class AutoSearchTests {
|
||||
|
||||
public static IEnumerable<object[]> PokemonGames => new[] {
|
||||
"Altair",
|
||||
"Emerald",
|
||||
"FireRed",
|
||||
"LeafGreen",
|
||||
"Ruby",
|
||||
"Sapphire",
|
||||
"DarkRisingKAIZO",
|
||||
"FireRed",
|
||||
"LeafGreen",
|
||||
"Emerald",
|
||||
"DarkRisingKAIZO", // from FireRed
|
||||
"Vega 2019-04-20", // from FireRed
|
||||
"Clover", // from FireRed
|
||||
"Gaia v3.2", // from FireRed
|
||||
"Altair", // from Emerald
|
||||
}.Select(game => new object[] { "sampleFiles/Pokemon " + game + ".gba" });
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -32,7 +36,8 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, EggMoveRun.PokemonNameTable);
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
Assert.Equal(412, run.ElementCount);
|
||||
if (game.Contains("Gaia")) Assert.Equal(914, run.ElementCount);
|
||||
else Assert.Equal(412, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -43,7 +48,10 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, EggMoveRun.MoveNamesTable);
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
Assert.Equal(355, run.ElementCount);
|
||||
if (game.Contains("Vega")) Assert.Equal(512, run.ElementCount);
|
||||
else if (game.Contains("Clover")) Assert.Equal(512, run.ElementCount);
|
||||
else if (game.Contains("Gaia")) Assert.Equal(511, run.ElementCount);
|
||||
else Assert.Equal(355, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -54,7 +62,9 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "abilitynames");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
Assert.Equal(78, run.ElementCount);
|
||||
if (game.Contains("Clover")) Assert.Equal(156, run.ElementCount);
|
||||
else if (game.Contains("Gaia")) Assert.Equal(188, run.ElementCount);
|
||||
else Assert.Equal(78, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -65,7 +75,9 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "abilitydescriptions");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
Assert.Equal(78, run.ElementCount);
|
||||
if (game.Contains("Clover")) Assert.Equal(156, run.ElementCount);
|
||||
else if (game.Contains("Gaia")) Assert.Equal(188, run.ElementCount);
|
||||
else Assert.Equal(78, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -76,7 +88,9 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "types");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
Assert.Equal(18, run.ElementCount);
|
||||
if (game.Contains("Clover")) Assert.Equal(24, run.ElementCount);
|
||||
else if (game.Contains("Gaia")) Assert.Equal(24, run.ElementCount);
|
||||
else Assert.Equal(18, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -88,12 +102,16 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "items");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
if (game.Contains("Altair")) Assert.Equal(377, run.ElementCount);
|
||||
if (game.Contains("Emerald")) Assert.Equal(377, run.ElementCount);
|
||||
if (game.Contains("FireRed")) Assert.Equal(375, run.ElementCount);
|
||||
if (game.Contains("DarkRisingKAIZO")) Assert.Equal(375, run.ElementCount);
|
||||
if (game.Contains("LeafGreen")) Assert.Equal(375, run.ElementCount);
|
||||
if (game.Contains("Ruby")) Assert.Equal(349, run.ElementCount);
|
||||
if (game.Contains("Sapphire")) Assert.Equal(349, run.ElementCount);
|
||||
else if (game.Contains("Emerald")) Assert.Equal(377, run.ElementCount);
|
||||
else if (game.Contains("FireRed")) Assert.Equal(375, run.ElementCount);
|
||||
else if (game.Contains("DarkRisingKAIZO")) Assert.Equal(375, run.ElementCount);
|
||||
else if (game.Contains("LeafGreen")) Assert.Equal(375, run.ElementCount);
|
||||
else if (game.Contains("Ruby")) Assert.Equal(349, run.ElementCount);
|
||||
else if (game.Contains("Sapphire")) Assert.Equal(349, run.ElementCount);
|
||||
else if (game.Contains("Vega")) Assert.Equal(375, run.ElementCount);
|
||||
else if (game.Contains("Clover")) Assert.Equal(375, run.ElementCount);
|
||||
else if (game.Contains("Gaia")) Assert.Equal(375, run.ElementCount);
|
||||
else throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -104,13 +122,14 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "trainerclassnames");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
if (game.Contains("Altair")) Assert.Equal(67, run.ElementCount);
|
||||
if (game.Contains("Emerald")) Assert.Equal(67, run.ElementCount);
|
||||
if (game.Contains("FireRed")) Assert.Equal(108, run.ElementCount);
|
||||
if (game.Contains("DarkRisingKAIZO")) Assert.Equal(107, run.ElementCount);
|
||||
if (game.Contains("LeafGreen")) Assert.Equal(108, run.ElementCount);
|
||||
if (game.Contains("Ruby")) Assert.Equal(59, run.ElementCount);
|
||||
if (game.Contains("Sapphire")) Assert.Equal(59, run.ElementCount);
|
||||
if (game.Contains("Altair")) Assert.Equal(66, run.ElementCount);
|
||||
else if (game.Contains("Emerald")) Assert.Equal(66, run.ElementCount);
|
||||
else if (game.Contains("FireRed")) Assert.Equal(107, run.ElementCount);
|
||||
else if (game.Contains("DarkRisingKAIZO")) Assert.Equal(107, run.ElementCount);
|
||||
else if (game.Contains("LeafGreen")) Assert.Equal(107, run.ElementCount);
|
||||
else if (game.Contains("Ruby")) Assert.Equal(58, run.ElementCount);
|
||||
else if (game.Contains("Sapphire")) Assert.Equal(58, run.ElementCount);
|
||||
else if (game.Contains("Vega")) Assert.Equal(107, run.ElementCount);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -122,9 +141,11 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "pokestats");
|
||||
var run = (ArrayRun)model.GetNextAnchor(address);
|
||||
|
||||
var bulbasaurStats = model.Skip(run.Start + run.ElementLength).Take(6).ToArray();
|
||||
var compareSet = new[] { 45, 49, 49, 45, 65, 65 };
|
||||
for (int i = 0; i < compareSet.Length; i++) Assert.Equal(compareSet[i], bulbasaurStats[i]);
|
||||
var firstPokemonStats = model.Skip(run.Start + run.ElementLength).Take(6).ToArray();
|
||||
var compareSet = new[] { 45, 49, 49, 45, 65, 65 }; // Bulbasaur
|
||||
if (game.Contains("Vega")) compareSet = new[] { 42, 53, 40, 70, 63, 40 }; // Nimbleaf
|
||||
if (game.Contains("Clover")) compareSet = new[] { 56, 60, 55, 50, 47, 50 }; // Grasshole
|
||||
for (int i = 0; i < compareSet.Length; i++) Assert.Equal(compareSet[i], firstPokemonStats[i]);
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
@@ -150,7 +171,8 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
var address = model.GetAddressFromAnchor(noChange, -1, "eggmoves");
|
||||
var run = (EggMoveRun)model.GetNextAnchor(address);
|
||||
|
||||
Assert.Equal(2, run.PointerSources.Count);
|
||||
if (game.Contains("Vega")) Assert.Equal(3, run.PointerSources.Count); // there's a false positive in Vega... for now! Would be nice if this were 2, but it doesn't much matter.
|
||||
else 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.
|
||||
|
||||
@@ -230,5 +230,14 @@ Water";
|
||||
var format = (UnderEdit)viewPort[2, 0].Format;
|
||||
Assert.Equal(string.Empty, format.CurrentText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRemoveEggMoveFormatFromContextMenu() {
|
||||
CreateSimpleRun();
|
||||
|
||||
viewPort.SelectionStart = new Point(2, 0);
|
||||
var items = viewPort.GetContextMenuItems(viewPort.SelectionStart);
|
||||
items.Single(item => item.Text == "Clear Format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core.ViewModels;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace HavenSoft.HexManiac.Tests {
|
||||
@@ -212,9 +213,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void CanExpandSelection() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(1, 0);
|
||||
@@ -233,9 +232,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
public void SelectingAnyOfAPointerSelectsAllOfAPointer(int index) {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(index, 0);
|
||||
@@ -248,9 +245,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void SelectLeftSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -265,9 +260,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void SelectRightSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -282,9 +275,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void SelectUpSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -299,9 +290,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void SelectDownSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -316,9 +305,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void HighlightLeftSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -333,9 +320,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
|
||||
[Fact]
|
||||
public void HighlightRightSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
@@ -347,5 +332,35 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContextMenuContainsCopyPaste() {
|
||||
CreateStandardTestSetup(out var viewPort, out var model, out var data);
|
||||
|
||||
viewPort.SelectionStart = new Point(2, 2);
|
||||
viewPort.SelectionEnd = new Point(5, 2);
|
||||
var items = viewPort.GetContextMenuItems(viewPort.SelectionStart);
|
||||
items.Single(item => item.Text == "Copy");
|
||||
items.Single(item => item.Text == "Paste");
|
||||
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(3, 2);
|
||||
items = viewPort.GetContextMenuItems(viewPort.SelectionStart);
|
||||
items.Single(item => item.Text == "Copy");
|
||||
items.Single(item => item.Text == "Paste");
|
||||
|
||||
viewPort.Edit("^text\"\" Hello World!\"");
|
||||
viewPort.SelectionStart = new Point(5, 2);
|
||||
viewPort.ExpandSelection(5, 2);
|
||||
items = viewPort.GetContextMenuItems(viewPort.SelectionStart);
|
||||
items.Single(item => item.Text == "Copy");
|
||||
items.Single(item => item.Text == "Paste");
|
||||
}
|
||||
|
||||
private static void CreateStandardTestSetup(out ViewPort viewPort, out PokemonModel model, out byte[] data) {
|
||||
data = new byte[0x200];
|
||||
model = new PokemonModel(data);
|
||||
viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user