public override void UpdateNewRunFromPointerFormat(IDataModel model, ModelDelta token, string name, ref IFormattedRun run) { var length = PCSString.ReadString(model, run.Start, true); if (length > 0) { var newRun = new PCSRun(model, run.Start, length, run.PointerSources); if (!newRun.Equals(run)) { model.ClearFormat(token, newRun.Start, newRun.Length); } run = newRun; } }
public override void UpdateNewRunFromPointerFormat(IDataModel model, ModelDelta token, string name, IReadOnlyList <ArrayRunElementSegment> sourceSegments, int parentIndex, ref IFormattedRun run) { var length = PCSString.ReadString(model, run.Start, true); if (length > 0) { var newRun = new PCSRun(model, run.Start, length, run.PointerSources); if (!newRun.Equals(run)) { model.ClearFormat(token, newRun.Start, newRun.Length); } run = newRun; } }
public override ErrorInfo TryParseData(IDataModel model, string name, int dataIndex, ref IFormattedRun run) { var length = PCSString.ReadString(model, dataIndex, true); if (length < 0) { return(new ErrorInfo($"Format was specified as a string, but no string was recognized.")); } else if (PokemonModel.SpanContainsAnchor(model, dataIndex, length)) { return(new ErrorInfo($"Format was specified as a string, but a string would overlap the next anchor.")); } run = new PCSRun(model, dataIndex, length); return(ErrorInfo.NoError); }
public void UsingBackspaceMidStringMakesTheStringEndThere() { SetFullModel(0xFF); var(model, viewPort) = (Model, ViewPort); for (int i = 0; i < 0x10; i++) { model[i] = 0x00; } // add an anchor with some data on the 2nd line viewPort.SelectionStart = new Point(0, 1); viewPort.Edit("^bob\"\" \"Hello World!\""); viewPort.SelectionStart = new Point(6, 1); viewPort.Edit(ConsoleKey.Backspace); Assert.Equal("\"Hello \"", PCSString.Convert(model, 0x10, PCSString.ReadString(model, 0x10, true))); }
public override bool TryAddFormatAtDestination(IDataModel owner, ModelDelta token, int source, int destination, string name, IReadOnlyList <ArrayRunElementSegment> sourceSegments) { var length = PCSString.ReadString(owner, destination, true); if (length < 1) { return(false); } // our token will be a no-change token if we're in the middle of exploring the data. // If so, don't actually add the run. It's enough to know that we _can_ add the run. if (!(token is NoDataChangeDeltaModel)) { owner.ObserveRunWritten(token, new PCSRun(owner, destination, length)); } // even if we didn't add the format, we're _capable_ of adding it... so return true return(true); }
public void UsingBackspaceMidStringMakesTheStringEndThere() { var buffer = Enumerable.Repeat((byte)0xFF, 0x200).ToArray(); for (int i = 0; i < 0x10; i++) { buffer[i] = 0x00; } var model = new PokemonModel(buffer); var viewPort = new ViewPort("test.txt", model) { Width = 0x10, Height = 0x10 }; // add an anchor with some data on the 2nd line viewPort.SelectionStart = new Point(0, 1); viewPort.Edit("^bob\"\" \"Hello World!\""); viewPort.SelectionStart = new Point(6, 1); viewPort.Edit(ConsoleKey.Backspace); Assert.Equal("\"Hello \"", PCSString.Convert(model, 0x10, PCSString.ReadString(model, 0x10, true))); }
private void Decapitalize(IDataModel model, ModelDelta token, int address) { if (address < 0 || address >= model.Count) { return; } var textLength = PCSString.ReadString(model, address, true); if (textLength < 3) { return; } var text = model.TextConverter.Convert(model, address, textLength).ToCharArray(); for (int i = 1; i < text.Length; i++) { if (IsLetter(text[i - 1]) && IsCap(text[i])) { text[i] += (char)('a' - 'A'); } } token.ChangeData(model, address, model.TextConverter.Convert(new string(text), out var _)); }
public static string ReadString(IReadOnlyList <byte> data, int start) { var length = PCSString.ReadString(data, start, true); return(PCSString.Convert(data, start, length)); }
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 < 2) { return(false); } if (readLength > segment.Length) { return(false); } if (Enumerable.Range(start, segment.Length).All(i => owner[i] == 0xFF)) { 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]; var lastByteIsReasonablEnd = lastByteInText == 0x00 || lastByteInText == 0xFF; if (!flags.HasFlag(FormatMatchFlags.AllowJunkAfterText) && !lastByteIsReasonablEnd && flags.HasFlag(FormatMatchFlags.IsSingleSegment)) { return(false); } return(true); case ElementContentType.Integer: if (segment is ArrayRunEnumSegment enumSegment) { return(owner.ReadMultiByteValue(start, segment.Length) < enumSegment.GetOptions(owner).Count); } else { return(true); } case ElementContentType.Pointer: var destination = owner.ReadPointer(start); if (destination == Pointer.NULL) { return(true); } if (0 > destination || destination > owner.Count) { return(false); } if (segment is ArrayRunPointerSegment pointerSegment) { if (!pointerSegment.DestinationDataMatchesPointerFormat(owner, new NoDataChangeDeltaModel(), destination)) { return(false); } } return(true); case ElementContentType.BitArray: var bitArraySegment = (ArrayRunBitArraySegment)segment; var bits = bitArraySegment.GetOptions(owner).Count; bits %= 8; if (bits == 0) { return(true); } var finalByte = owner[start + bitArraySegment.Length - 1]; finalByte >>= bits; return(finalByte == 0); // all the unneeded bits should be set to zero default: throw new NotImplementedException(); } }