* bitfields should support names with the `'` character.
* don't let tables/ascii runs go past the end of the file
* don't let arrays have zero length per element
* allow editing record enums in tables
* don't create change tokens when no change is needed
This commit is contained in:
haven1433 2022-11-14 22:08:02 -06:00
parent e1da957c30
commit 5543b64e15
11 changed files with 68 additions and 8 deletions

View File

@ -2445,6 +2445,8 @@ namespace HavenSoft.HexManiac.Core.Models {
return new ErrorInfo("An existing anchor starts before the new one ends.");
} else if (!name.All(c => char.IsLetterOrDigit(c) || "-._".Contains(c))) { // at this point, the name might have a "-1" on the end, so still allow the dash
return new ErrorInfo("Anchor names must contain only letters, numbers, dots, and underscores.");
} else if (runToWrite.Start + runToWrite.Length > model.Count) {
return new ErrorInfo("Anchor format must not go past the end of the file.");
} else {
return ErrorInfo.NoError;
}

View File

@ -528,6 +528,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
ElementContent = ParseSegments(segments, data);
if (ElementContent.Count == 0) throw new ArrayRunParseException("Array Content must not be empty.");
ElementLength = ElementContent.Sum(e => e.Length);
if (ElementLength == 0) throw new ArrayRunParseException("Array Content Length must not be zero.");
FormatMatchFlags flags = default;
if (ElementContent.Count == 1) flags |= FormatMatchFlags.IsSingleSegment;
@ -1243,6 +1244,7 @@ namespace HavenSoft.HexManiac.Core.Models.Runs {
segments = segments.Substring(nameEnd);
var (format, formatLength, segmentLength) = ExtractSingleFormat(segments, model);
if (name == string.Empty && format != ElementContentType.Splitter) throw new ArrayRunParseException("expected name, but none was found: " + segments);
if (format == ElementContentType.PCS && segmentLength < 1) throw new ArrayRunParseException("Cannot have 0-length text: " + name);
// check to see if a name or length is part of the format
if (format == ElementContentType.Integer && segments.Length > formatLength && segments[formatLength] != ' ') {

View File

@ -2601,7 +2601,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
// normal case: whether or not to accept the edit depends on the existing cell format
var dataIndex = scroll.ViewPointToDataIndex(point);
var completeEditOperation = new CompleteCellEdit(Model, scroll, dataIndex, underEdit.CurrentText, history.CurrentChange);
var completeEditOperation = new CompleteCellEdit(Model, scroll, dataIndex, underEdit.CurrentText, () => history.CurrentChange);
using (ModelCacheScope.CreateScope(Model)) {
(underEdit.OriginalFormat ?? Undefined.Instance).Visit(completeEditOperation, element.Value);

View File

@ -113,7 +113,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
private void GenerateOptions(IDataFormatInstance format) {
var arrayRun = (ITableRun)Model.GetNextRun(format.Source);
var offsets = arrayRun.ConvertByteOffsetToArrayOffset(format.Source);
var segment = (IHasOptions)arrayRun.ElementContent[offsets.SegmentIndex];
var contentSegment = arrayRun.ElementContent[offsets.SegmentIndex];
if (contentSegment is ArrayRunRecordSegment record) contentSegment = record.CreateConcrete(Model, format.Source);
var segment = (IHasOptions)contentSegment;
var allOptions = segment.GetOptions(Model).Where(option => option != null).Select(option => option + " ");
Result = AutoCompleteSelectionItem.Generate(allOptions.Where(option => option.MatchesPartial(InputText, onlyCheckLettersAndDigits: true)), -1);
}

View File

@ -15,7 +15,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
private readonly IDataModel Model;
private readonly int memoryLocation;
private readonly string CurrentText;
private readonly ModelDelta CurrentChange;
private readonly Func<ModelDelta> currentChange;
private readonly ScrollRegion scroll;
public bool Result { get; private set; } // if true, the edit was completed correctly
@ -27,12 +27,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
// 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.
public CompleteCellEdit(IDataModel model, ScrollRegion scroll, int memoryLocation, string currentText, ModelDelta currentChange) {
private ModelDelta CurrentChange => currentChange();
public CompleteCellEdit(IDataModel model, ScrollRegion scroll, int memoryLocation, string currentText, Func<ModelDelta> currentChange) {
Model = model;
this.scroll = scroll;
this.memoryLocation = memoryLocation;
CurrentText = currentText;
CurrentChange = currentChange;
this.currentChange = currentChange;
NewDataIndex = memoryLocation;
}
@ -585,7 +587,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
string sanitizedText = CurrentText.Replace(')', ' ');
var array = (ITableRun)Model.GetNextRun(memoryLocation);
var offsets = array.ConvertByteOffsetToArrayOffset(memoryLocation);
var segment = (ArrayRunEnumSegment)array.ElementContent[offsets.SegmentIndex];
var contentSegment = array.ElementContent[offsets.SegmentIndex];
if (contentSegment is ArrayRunRecordSegment record) contentSegment = record.CreateConcrete(Model, memoryLocation);
var segment = (ArrayRunEnumSegment)contentSegment;
if (segment.TryParse(Model, sanitizedText, out int value)) {
Model.WriteMultiByteValue(offsets.SegmentStart, segment.Length, CurrentChange, value);
NewDataIndex = offsets.SegmentStart + segment.Length;

View File

@ -98,7 +98,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
public void Visit(IntegerEnum integer, byte data) {
Result = integer.CanStartWithCharacter(Input) ||
".'~|,_&%)".Contains(Input) ||
".'~|,_&%()".Contains(Input) ||
char.IsWhiteSpace(Input);
}
@ -137,7 +137,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Visitors {
}
public void Visit(BitArray array, byte data) {
Result = char.IsLetterOrDigit(Input) || Input.IsAny('"', '-', ' ');
Result = char.IsLetterOrDigit(Input) || "?-\" .'~|,_&%)".Contains(Input);
}
public void Visit(MatchedWord word, byte data) => Visit((None)null, data);

View File

@ -980,6 +980,13 @@ namespace HavenSoft.HexManiac.Tests {
Assert.Empty(Errors);
}
[Fact]
public void NoChanges_StartChangeThenCancel_NoSaveOption() {
ViewPort.Edit("<");
Assert.False(ViewPort.Save.CanExecute(FileSystem));
}
private void StandardSetup(out byte[] data, out PokemonModel model, out ViewPort viewPort) {
data = new byte[0x200];
model = new PokemonModel(data);

View File

@ -757,6 +757,18 @@ namespace HavenSoft.HexManiac.Tests {
Assert.IsAssignableFrom<ITableRun>(Model.GetNextRun(0));
}
[Fact]
public void TableWithZeroLengthString_Error() {
ViewPort.Edit("^table[text\"\"0]1 ");
Assert.Single(Errors);
}
[Fact]
public void Table_OnlyZeroLengthSegments_Error() {
ViewPort.Edit("^table[field|=() ]1 ");
Assert.Single(Errors);
}
private void HackTextConverter(string game) {
var converter = new PCSConverter(game);
var property = Model.GetType().GetProperty(nameof(Model.TextConverter));

View File

@ -357,5 +357,15 @@ namespace HavenSoft.HexManiac.Tests {
Assert.Equal(10, ViewPort.DataLength);
}
[Theory]
[InlineData("^table[a: b: c: d: e:]3 ")]
[InlineData("^text`asc`20 ")]
public void EndOfModel_LongRunFormat_Error(string format) {
ViewPort.Goto.Execute(0x1F0);
ViewPort.Edit(format);
Assert.Single(Errors);
}
}
}

View File

@ -1,6 +1,7 @@
using HavenSoft.HexManiac.Core;
using HavenSoft.HexManiac.Core.Models;
using HavenSoft.HexManiac.Core.Models.Runs;
using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
using HavenSoft.HexManiac.Core.ViewModels.Tools;
using HavenSoft.HexManiac.Core.ViewModels.Visitors;
using System;
@ -317,5 +318,15 @@ DefaultHash = '''0BEEDA92'''
Assert.Equal("name2", list[2]);
Assert.Equal(3, list.Count);
}
[Fact]
public void ListWithApostrophe_TypeIntoBitFieldFromList_AcceptsApostrophe() {
Model.SetList("list", new[] { "Item", "Arby's", "Content" });
ViewPort.Edit("^table[content|b[]list]1 ");
ViewPort.Edit("arby'");
Assert.IsType<UnderEdit>(ViewPort[0, 0].Format);
}
}
}

View File

@ -1322,6 +1322,16 @@ namespace HavenSoft.HexManiac.Tests {
Assert.Equal(0x108, ViewPort.ConvertViewPointToAddress(ViewPort.SelectionStart));
}
[Fact]
public void SwitchFormat_EditEnum_NoError() {
CreateTextTable("names", 0x100, "adam", "bob", "carl", "dave");
ViewPort.Edit("@000 ^table[input: arg:|s=input(0=names)]2 ");
ViewPort.Edit("@002 carl ");
Assert.Equal(2, Model[2]);
}
private void ArrangeTrainerPokemonTeamData(byte structType, byte pokemonCount, int trainerCount) {
CreateTextTable(HardcodeTablesModel.PokemonNameTable, 0x180, "ABCDEFGHIJKLMNOP".Select(c => c.ToString()).ToArray());
CreateTextTable(HardcodeTablesModel.MoveNamesTable, 0x1B0, "qrstuvwxyz".Select(c => c.ToString()).ToArray());