public ISpriteRun SetPixels(IDataModel model, ModelDelta token, int page, int[,] pixels) { var spriteStart = model.ReadPointer(Start + ElementLength * page); if (!(model.GetNextRun(spriteStart) is ISpriteRun spriteRun)) { return(this); } spriteRun.SetPixels(model, token, 0, pixels); return(this); // moving a page will never move the list }
public void AddMatchedWord(IDataModel model, int memoryLocation, string parentName, int byteCount) { var parentAddress = model.GetAddressFromAnchor(this, -1, parentName); var parent = model.GetNextRun(parentAddress) as ArrayRun; if (parent != null && !(this is NoDataChangeDeltaModel)) { model.WriteMultiByteValue(memoryLocation, byteCount, this, parent.ElementCount); } using (CaptureNonDataChange()) addedMatchedWords[memoryLocation] = parentName; }
// TODO do some sort of caching: rendering these images every time probably sucks for performance. public IEnumerable <ComboOption> GetComboOptions(IDataModel model) { var defaultOptions = GetOptions(model) .Select((option, i) => new ComboOption(option, i)) .Where(combo => combo.Text != null) .ToList(); if (!(model.GetNextRun(model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, EnumName)) is ITableRun tableRun)) { return(defaultOptions); } if (!(tableRun.ElementContent[0] is ArrayRunPointerSegment pointerSegment)) { return(defaultOptions); } if (!LzSpriteRun.TryParseSpriteFormat(pointerSegment.InnerFormat, out var _) && !SpriteRun.TryParseSpriteFormat(pointerSegment.InnerFormat, out var _)) { return(defaultOptions); } var imageOptions = new List <ComboOption>(); for (int i = 0; i < tableRun.ElementCount; i++) { var destination = model.ReadPointer(tableRun.Start + tableRun.ElementLength * i); if (!(model.GetNextRun(destination) is ISpriteRun run)) { return(defaultOptions); } var sprite = run.GetPixels(model, 0); var paletteAddress = SpriteTool.FindMatchingPalette(model, run, 0); var paletteRun = model.GetNextRun(paletteAddress) as IPaletteRun; var palette = paletteRun?.GetPalette(model, paletteRun.PaletteFormat.InitialBlankPages) ?? TileViewModel.CreateDefaultPalette(16); var image = SpriteTool.Render(sprite, palette, paletteRun?.PaletteFormat.InitialBlankPages ?? default, 0); var option = VisualComboOption.CreateFromSprite(defaultOptions[i].Text, image, sprite.GetLength(0), i); imageOptions.Add(option); } return(imageOptions); }
public ModelArrayElement this[string value] { get { var table = model.GetNextRun(arrayAddress) as ITableRun; if (ArrayRunEnumSegment.TryMatch(value, table.ElementNames, out int index)) { return(this[index]); } else { throw new NotImplementedException(); } } }
public void Visit(Pointer pointer, byte data) { var run = Model.GetNextRun(memoryLocation); if (run is ITableRun && CurrentText[0] != PointerStart) { ErrorText = "Pointers in tables cannot be removed without removing the table."; return; } Visit((None)null, data); }
public TilemapRun(IDataModel model, int start, TilemapFormat format, SortedSpan <int> sources = null) : base(start, sources) { Model = model; Format = format; string hint = null; var address = Model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, Format.MatchingTileset); if (address >= 0 && address < Model.Count) { var tileset = Model.GetNextRun(address) as ISpriteRun; if (tileset == null) { tileset = Model.GetNextRun(arrayTilesetAddress) as ISpriteRun; } if (tileset != null && !(tileset is LzTilemapRun)) { hint = tileset.SpriteFormat.PaletteHint; } } SpriteFormat = new SpriteFormat(format.BitsPerPixel, format.TileWidth, format.TileHeight, hint); }
public int[,] GetPixels(IDataModel model, int page) { var mapData = Decompress(model, Start); if (mapData == null) { return(null); } var tilesetAddress = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, Format.MatchingTileset); var tileset = model.GetNextRun(tilesetAddress) as ISpriteRun; if (tileset == null) { tileset = model.GetNextRun(arrayTilesetAddress) as ISpriteRun; } if (tileset == null || tileset is ITilemapRun) { return(new int[Format.TileWidth * 8, Format.TileHeight * 8]); // relax the conditions slightly: if the run we found is an LZSpriteRun, that's close enough, we can use it as a tileset. } var tiles = tileset.GetData(); return(GetPixels(mapData, tiles, Format, BytesPerTile)); }
public byte[] GetData() { var tilesetAddress = model.ReadPointer(PointerSources[0] - 8); var tileset = (ITilesetRun)model.GetNextRun(tilesetAddress); var tilesetData = tileset.GetData(); var blockData = new byte[Length]; Array.Copy(model.RawData, Start, blockData, 0, Length); var result = new byte[8 * 8 * SpriteFormat.TileWidth * SpriteFormat.TileHeight]; //var paletteAddress = model.ReadPointer(PointerSources[0] - 4); //var palette = (IPaletteRun)model.GetNextRun(paletteAddress); var lineWidth = SpriteFormat.TileWidth * 64; var cellWidth = 64; for (int y = 0; y < SpriteFormat.TileHeight / 2; y++) { for (int x = 0; x < SpriteFormat.TileWidth / 2; x++) { var block = y * SpriteFormat.TileWidth / 2 + x; int blockStart = block * 16; var resultOffset = y * lineWidth * 2 + x * cellWidth * 2; var tileBytes = GetTile(tilesetData, blockData, blockStart + 0); Array.Copy(result, resultOffset, tileBytes, 0, tileBytes.Length); tileBytes = GetTile(tilesetData, blockData, blockStart + 1); Array.Copy(result, resultOffset + cellWidth, tileBytes, 0, tileBytes.Length); tileBytes = GetTile(tilesetData, blockData, blockStart + 2); Array.Copy(result, resultOffset + lineWidth, tileBytes, 0, tileBytes.Length); tileBytes = GetTile(tilesetData, blockData, blockStart + 3); Array.Copy(result, resultOffset + lineWidth + cellWidth, tileBytes, 0, tileBytes.Length); tileBytes = GetTile(tilesetData, blockData, blockStart + 4); WriteTile(result, resultOffset, tileBytes); tileBytes = GetTile(tilesetData, blockData, blockStart + 5); WriteTile(result, resultOffset + cellWidth, tileBytes); tileBytes = GetTile(tilesetData, blockData, blockStart + 6); WriteTile(result, resultOffset + lineWidth, tileBytes); tileBytes = GetTile(tilesetData, blockData, blockStart + 7); WriteTile(result, resultOffset + lineWidth + cellWidth, tileBytes); } } model.ReadMultiByteValue(Start, 2); return(result); }
// this implementation puts all the child sprites into separate pages public int[,] GetPixels(IDataModel model, int page) { var width = SpriteFormat.TileWidth * 8; var height = SpriteFormat.TileHeight * 8; var spriteStart = model.ReadPointer(Start + ElementLength * page); if (!(model.GetNextRun(spriteStart) is ISpriteRun spriteRun)) { return(new int[width, height]); } var spritePixels = spriteRun.GetPixels(model, page: 0); if (spritePixels.GetLength(0) < width || spritePixels.GetLength(1) < height) { return(new int[width, height]); } return(spritePixels); }
public override bool TryAddFormatAtDestination(IDataModel owner, ModelDelta token, int source, int destination, string name, IReadOnlyList <ArrayRunElementSegment> sourceSegments, int parentIndex) { if (TableStreamRun.TryParseTableStream(owner, destination, new SortedSpan <int>(source), name, Format, sourceSegments, out var tsRun)) { if (token is not NoDataChangeDeltaModel) { // we know that the data format matches, but there may already be a run there that starts sooner if (owner.GetNextRun(tsRun.Start) is ITableRun existingTable && existingTable.Start < tsRun.Start) { // there is already a format that starts sooner: do nothing, but return true because the format matches } else { owner.ClearFormat(token, tsRun.Start + 1, tsRun.Length - 1); owner.ObserveRunWritten(token, tsRun); } } return(true); }
public static void Run(IDataModel model, ModelDelta token) { ArrayRun get(string name) => model.GetNextRun(model.GetAddressFromAnchor(token, -1, name)) as ArrayRun; var regional = get(HardcodeTablesModel.RegionalDexTableName); var national = get(HardcodeTablesModel.NationalDexTableName); var convert = get(HardcodeTablesModel.ConversionDexTableName); for (int i = 0; i < regional.ElementCount; i++) { var regionalIndex = model.ReadMultiByteValue(regional.Start + i * 2, 2); var nationalIndex = model.ReadMultiByteValue(national.Start + i * 2, 2); var conversionIndex = model.ReadMultiByteValue(convert.Start + (regionalIndex - 1) * 2, 2); if (nationalIndex != conversionIndex) { model.WriteMultiByteValue(convert.Start + (regionalIndex - 1) * 2, 2, token, nationalIndex); } } }
public static IEnumerable <string> GetOptions(IDataModel model, string enumName) { if (int.TryParse(enumName, out var result)) { return(result.Range().Select(i => i.ToString())); } IEnumerable <string> options = model.GetOptions(enumName); // we _need_ options for the table tool // if we have none, just create "0", "1", ..., "n-1" based on the length of the EnumName table. if (!options.Any()) { if (model.GetNextRun(model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, enumName)) is ITableRun tableRun) { options = tableRun.ElementCount.Range().Select(i => i.ToString()); } } return(options); }
public void Visit(Pointer pointer, byte data) { Content.Add(pointer.DestinationAsText); var destinationRun = model.GetNextRun(pointer.Destination); var runSpecificContent = BuildContentForRun(model, pointer.Source, pointer.Destination, destinationRun); if (runSpecificContent != null) { // special case: add images for pointers to OWs if (destinationRun is ITableRun table && table.ElementCount == 1 && table.ElementContent.Any(seg => seg.Name == "sprites")) { if (model.GetNextRun(table.ReadPointer(model, 0, "sprites")) is OverworldSpriteListRun osl) { if (model.GetNextRun(osl.ReadPointer(model, 0, 0)) is ISpriteRun spriteRun) { var image = ReadonlyPixelViewModel.Create(model, spriteRun, true); Content.Add(image); } } } Content.Add(runSpecificContent); } }
public MapAnimationTilesRun UpdateFromParent(ModelDelta token, int segmentIndex, int pointerSource, out bool childrenMoved) { childrenMoved = false; if (segmentIndex == 1) { var newTableCount = model.ReadMultiByteValue(pointerSource + 4, 2); var self = model.RelocateForExpansion(token, this, newTableCount * 4); for (int i = ElementCount; i < newTableCount; i++) { model.WritePointer(token, self.Start + 4 * i, Pointer.NULL); } return(new MapAnimationTilesRun(model, self.Start, self.PointerSources)); } else if (segmentIndex == 3) { var newTileCount = model[pointerSource + 7]; var tilesetFormat = new TilesetFormat(4, newTileCount, -1, default); for (int i = 0; i < ElementCount; i++) { var destination = model.ReadPointer(Start + ElementLength * i); if (destination == Pointer.NULL) { continue; } var child = (ISpriteRun)model.GetNextRun(destination); var newChild = model.RelocateForExpansion(token, child, newTileCount * 32); if (newChild.Start != child.Start) { childrenMoved = true; } model.ObserveRunWritten(token, new TilesetRun(tilesetFormat, model, newChild.Start, newChild.PointerSources)); } return(this); } else { return(this); } }
private int ParseLengthFromAnchor() { // length is based on another array int address = owner.GetAddressFromAnchor(new ModelDelta(), -1, LengthFromAnchor); if (address == Pointer.NULL) { // the requested name was unknown... length is zero for now return(0); } var run = owner.GetNextRun(address) as ArrayRun; if (run == null || run.Start != address) { // the requested name was not an array, or did not start where anticipated // length is zero for now return(0); } return(run.ElementCount); }
public TilemapTableRun(IDataModel model, string anchorName, ArrayRunElementSegment segment, TilemapMargins margins, int start, SortedSpan <int> sources = null) : base(start, sources) { this.model = model; TilemapAnchor = anchorName; Segment = segment; Margins = margins; var tilemapAddress = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, anchorName); var tilemap = model.GetNextRun(tilemapAddress) as ITilemapRun; if (tilemap != null) { var height = tilemap.SpriteFormat.TileHeight + margins.Top + margins.Bottom; var width = tilemap.SpriteFormat.TileWidth + margins.Left + margins.Right; ElementCount = height * margins.LengthMultiplier; ElementLength = width * Segment.Length; ElementContent = width.Range().Select(i => segment).ToArray(); } Length = ElementCount * ElementLength; FormatString = $"[{segment.SerializeFormat}]{anchorName}{Margins}"; }
public static int[,] ReadSprite(this IDataModel model, int address) { var sprite = (ISpriteRun)model.GetNextRun(address); return(sprite.GetPixels(model, 0)); }
public OverworldSpriteListRun(IDataModel model, IReadOnlyList <ArrayRunElementSegment> parent, string paletteHint, int runIndex, int start, SortedSpan <int> sources = null) : base(start, sources) { this.model = model; this.parent = parent; PaletteHint = paletteHint; RunIndex = runIndex; var nextStartBuilder = new List <int>(); int parentLength = parent.Sum(seg => seg.Length); if (parent != null && sources != null && sources.Count > 0) { for (int nextSource = sources[0] + parentLength; true; nextSource += parentLength) { var nextDest = model.ReadPointer(nextSource); if (nextDest < 0 || nextDest >= model.Count) { break; } nextStartBuilder.Add(nextDest); } } var nextStart = nextStartBuilder.ToArray(); var segments = new List <ArrayRunElementSegment> { new ArrayRunPointerSegment("sprite", "`ucs4x1x2`"), new ArrayRunElementSegment("length", ElementContentType.Integer, 4), }; ElementContent = segments; ElementCount = 1; Length = ElementLength; SpriteFormat = new SpriteFormat(4, 1, 1, string.Empty); if (sources == null || sources.Count == 0) { return; } // initialize format from parent info var listOffset = GetOffset <ArrayRunPointerSegment>(parent, pSeg => pSeg.InnerFormat.StartsWith("`osl")); var widthOffset = GetOffset(parent, seg => seg.Name == "width"); var heightOffset = GetOffset(parent, seg => seg.Name == "height"); var keyOffset = GetOffset(parent, seg => seg.Name == "paletteid"); if (widthOffset == parentLength) { widthOffset = -1; } if (heightOffset == parentLength) { heightOffset = -1; } if (keyOffset == parentLength) { keyOffset = -1; } var elementStart = sources[0] - listOffset; var width = widthOffset >= 0 ? Math.Max(1, model.ReadMultiByteValue(elementStart + widthOffset, 2)) : 0; var height = heightOffset >= 0 ? Math.Max(1, model.ReadMultiByteValue(elementStart + heightOffset, 2)) : 0; // if there was no height/width found, assume that it's square and based on the first element length var pixelCount = model.ReadMultiByteValue(start + 4, 4) * 2; // number of pixels is twice the number of bytes for all OW sprites bool adjustDimensions = true; if (width == 0) { width = (int)Math.Sqrt(pixelCount); adjustDimensions = true; } if (height == 0) { height = width; adjustDimensions = true; } var tileWidth = (int)Math.Max(1, Math.Ceiling(width / 8.0)); var tileHeight = (int)Math.Max(1, Math.Ceiling(height / 8.0)); while (adjustDimensions) { adjustDimensions = false; while (tileWidth * tileHeight * 64 > pixelCount) { adjustDimensions = true; tileHeight -= 1; } if (tileHeight == 0) { break; } while (tileWidth * tileHeight * 64 < pixelCount) { if (tileWidth > 500) { break; } adjustDimensions = true; tileWidth += 1; } } var key = model.ReadMultiByteValue(elementStart + keyOffset, 2); var hint = $"{HardcodeTablesModel.OverworldPalettes}:id={key:X4}"; if (!string.IsNullOrEmpty(paletteHint)) { hint = PaletteHint + $"={runIndex:X4}"; } if (paletteHint != null && paletteHint.Contains("=")) { hint = PaletteHint + $"{key:X4}"; } var format = $"`ucs4x{tileWidth}x{tileHeight}|{hint}`"; if (keyOffset == -1 && string.IsNullOrEmpty(paletteHint)) { format = $"`ucs4x{tileWidth}x{tileHeight}`"; hint = string.Empty; } segments[0] = new ArrayRunPointerSegment("sprite", format); // calculate the element count var byteLength = tileWidth * tileHeight * TileSize; var nextAnchorStart = model.GetNextAnchor(Start + 1).Start; ElementCount = 0; Length = 0; while (Start + Length < nextAnchorStart) { if (model[start + Length + 3] != 0x08) { break; } if (model.ReadMultiByteValue(start + Length + 4, 4) != byteLength) { break; } var nextRun = model.GetNextRun(start + Length); if (Length > 0 && (start + Length).IsAny(nextStart)) { break; // metric: if there's a pointer in the parent table that points here, then it's the next list, not this list. } ElementCount += 1; Length += ElementLength; if (ElementCount == MaxOverworldSprites) { break; // overworld sprite lists can only have so many elements } } SpriteFormat = new SpriteFormat(4, tileWidth, tileHeight, hint); ElementNames = ElementCount.Range().Select(i => string.Empty).ToList(); }
private ArrayRun(IDataModel data, string format, int start, IReadOnlyList <int> pointerSources) : base(start, pointerSources) { owner = data; FormatString = format; SupportsPointersToElements = format.StartsWith(AnchorStart.ToString()); if (SupportsPointersToElements) { format = format.Substring(1); } var closeArray = format.LastIndexOf(ArrayEnd.ToString()); if (!format.StartsWith(ArrayStart.ToString()) || closeArray == -1) { throw new ArrayRunParseException($"Array Content must be wrapped in {ArrayStart}{ArrayEnd}."); } var segments = format.Substring(1, closeArray - 1); var length = format.Substring(closeArray + 1); ElementContent = ParseSegments(segments, data); 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, flags, nextRun)) { byteLength += ElementLength; elementCount++; } LengthFromAnchor = string.Empty; ElementCount = Math.Max(1, elementCount); // if the user said there's a format here, then there is, even if the format it wrong. } else if (int.TryParse(length, out int result)) { // fixed length is easy LengthFromAnchor = string.Empty; ElementCount = Math.Max(1, result); } else { LengthFromAnchor = length; ElementCount = Math.Max(1, ParseLengthFromAnchor()); } Length = ElementLength * ElementCount; }
private static int KnownLengthSearch(IDataModel data, List <ArrayRunElementSegment> elementContent, int elementLength, string lengthToken, out int bestLength, Func <IFormattedRun, bool> runFilter) { var noChange = new NoDataChangeDeltaModel(); if (!int.TryParse(lengthToken, out bestLength)) { var matchedArrayName = lengthToken; var matchedArrayAddress = data.GetAddressFromAnchor(noChange, -1, matchedArrayName); if (matchedArrayAddress == Pointer.NULL) { return(Pointer.NULL); } var matchedRun = data.GetNextRun(matchedArrayAddress) as ArrayRun; if (matchedRun == null) { return(Pointer.NULL); } 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)); if (targetRun is ArrayRun) { continue; } // 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(targetRun)) { continue; } // tolerate a few errors in the data. We know what length we're looking for, so if most of the elements match, then // most likely we're just looking at the right collection but with some user-created bugs. int errorsToTolerate = bestLength / 80; int encounterErrors = 0; int lastGoodLength = 0; int currentLength = 0; int currentAddress = targetRun.Start; bool earlyExit = false; for (int i = 0; i < bestLength; i++) { var nextArray = data.GetNextAnchor(currentAddress + 1); bool match = DataMatchesElementFormat(data, currentAddress, elementContent, flags, nextArray); currentLength++; currentAddress += elementLength; if (match) { lastGoodLength = currentLength; } else { encounterErrors++; if (encounterErrors > errorsToTolerate) { // 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 > lastGoodLength; break; } } } currentLength = lastGoodLength; if (!earlyExit) { var dataEmpty = Enumerable.Range(targetRun.Start, currentLength * elementLength).Select(i => data[i]).All(d => d == 0xFF || d == 0x00); if (dataEmpty) { continue; // don't accept the run if it contains no data } bestLength = currentLength; return(targetRun.Start); } } return(Pointer.NULL); }
/// <summary> /// Uses the hint, as well as this sprite's table location (if any), to find palettes that can be applied to this sprite. /// (1) if the sprite's hint is the name of a palette, return that. Example: title screen pokemon sprite/palette pair. /// (2) if the sprite's hint is the name of an enum table, use that enum's source as a list of palettes and get the appropriate one from the matching index of the enum table. Example: pokemon icons /// (3) if the sprite's hint is a table name followed by a key=value pair, go grab the a palette from the element within that table such that it's key equals that value. Example: Overworld sprites /// (4) if the sprite's hint is a table name, return all palettes within the matching index of that table. Example: trainer sprites/palettes. /// (5) if the sprite has no hint, return all palettes in arrays with matching length from the same index. Example: pokemon sprites. Leaving it empty allows both normal and shiny palettes to match. /// </summary> public static IReadOnlyList <IPaletteRun> FindRelatedPalettes(this ISpriteRun spriteRun, IDataModel model, int primarySource = -1, string hint = null) { // find all palettes that could be applied to this sprite run var noChange = new NoDataChangeDeltaModel(); var results = new List <IPaletteRun>(); hint = hint ?? spriteRun?.SpriteFormat.PaletteHint; if (primarySource == -1) { var pointerCount = spriteRun?.PointerSources?.Count ?? 0; for (int i = 0; i < pointerCount; i++) { if (!(model.GetNextRun(spriteRun.PointerSources[i]) is ArrayRun)) { continue; } primarySource = spriteRun.PointerSources[i]; break; } } var spriteTable = model.GetNextRun(primarySource) as ArrayRun; var offset = spriteTable?.ConvertByteOffsetToArrayOffset(primarySource) ?? new ArrayOffset(-1, -1, -1, -1); if (primarySource < 0) { offset = new ArrayOffset(-1, -1, -1, -1); } if (!string.IsNullOrEmpty(hint)) { var run = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, hint)); if (run is IPaletteRun palRun) { // option 1: hint is to a palette results.Add(palRun); return(results); } else if (run is ArrayRun enumArray && enumArray.ElementContent.Count == 1 && enumArray.ElementContent[0] is ArrayRunEnumSegment enumSegment) { // option 2: hint is to index into paletteTable, and I'm in a table var paletteTable = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, enumSegment.EnumName)) as ArrayRun; if (offset.ElementIndex != -1) { var paletteIndex = model.ReadMultiByteValue(enumArray.Start + enumArray.ElementLength * offset.ElementIndex, enumArray.ElementLength); if (model.GetNextRun(model.ReadPointer(paletteTable.Start + paletteTable.ElementLength * paletteIndex)) is IPaletteRun pRun) { results.Add(pRun); } } } else if (hint.Contains(":")) { // option 3: hint is a table name, followed by a identifier=value pair var tableKeyPair = hint.Split(':'); var identifierValuePair = tableKeyPair.Length == 2 ? tableKeyPair[1].Split("=") : new string[0]; if (identifierValuePair.Length == 2) { var paletteTable = model.GetNextRun(model.GetAddressFromAnchor(noChange, -1, tableKeyPair[0])) as ITableRun; var segment = paletteTable?.ElementContent.FirstOrDefault(seg => seg.Name == identifierValuePair[0]); var pSegment = paletteTable?.ElementContent.FirstOrDefault(seg => seg is ArrayRunPointerSegment pSeg && PaletteRun.TryParsePaletteFormat(pSeg.InnerFormat, out var _)); var rawValue = identifierValuePair[1]; int keyValue; if (segment is ArrayRunEnumSegment eSegment) { keyValue = eSegment.GetOptions(model).ToList().IndexOf(rawValue); } else if (segment is ArrayRunHexSegment hSegment) { int.TryParse(rawValue, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out keyValue); } else { int.TryParse(rawValue, out keyValue); } if (segment != null) { var segmentOffset = paletteTable.ElementContent.Until(seg => seg == segment).Sum(seg => seg.Length); var pSegmentOffset = paletteTable.ElementContent.Until(seg => seg == pSegment).Sum(seg => seg.Length); var tableIndex = Enumerable.Range(0, paletteTable.ElementCount).FirstOrDefault(i => model.ReadMultiByteValue(paletteTable.Start + i * paletteTable.ElementLength + segmentOffset, segment.Length) == keyValue); var paletteStart = model.ReadPointer(paletteTable.Start + tableIndex * paletteTable.ElementLength + pSegmentOffset); if (model.GetNextRun(paletteStart) is IPaletteRun pRun) { results.Add(pRun); } } } } // option 4: I'm in a table, and my hint is a table name if (offset.ElementIndex == -1) { return(results); } if (!(run is ArrayRun array)) { return(results); } results.AddRange(model.GetPointedChildren <IPaletteRun>(array, offset.ElementIndex)); }
public static object BuildContentForRun(IDataModel model, int source, int destination, IFormattedRun destinationRun, int preferredPaletteStart = -1, int preferredSpritePage = 0) { if (destination != destinationRun.Start) { return(null); } if (destinationRun is PCSRun pcs) { return(PCSString.Convert(model, pcs.Start, pcs.Length)); } else if (destinationRun is ISpriteRun sprite) { if (sprite is LzTilemapRun tilemap) { tilemap.FindMatchingTileset(model); } var paletteRuns = sprite.FindRelatedPalettes(model, source); var paletteRun = paletteRuns.FirstOrDefault(); if (preferredPaletteStart >= 0) { paletteRun = paletteRuns.FirstOrDefault(pRun => pRun.Start == preferredPaletteStart) ?? model.GetNextRun(preferredPaletteStart) as IPaletteRun; } var pixels = sprite.GetPixels(model, preferredSpritePage); if (pixels == null) { return(null); } var colors = paletteRun?.AllColors(model) ?? TileViewModel.CreateDefaultPalette((int)Math.Pow(2, sprite.SpriteFormat.BitsPerPixel)); var imageData = SpriteTool.Render(pixels, colors, paletteRun?.PaletteFormat.InitialBlankPages ?? 0, 0); return(new ReadonlyPixelViewModel(sprite.SpriteFormat, imageData)); } else if (destinationRun is IPaletteRun paletteRun) { var colors = paletteRun.GetPalette(model, 0); return(new ReadonlyPaletteCollection(colors)); } else if (destinationRun is IStreamRun streamRun) { using (ModelCacheScope.CreateScope(model)) { var lines = streamRun.SerializeRun().Split(Environment.NewLine); if (lines.Length > 20) { lines = lines.Take(20).ToArray(); } return(Environment.NewLine.Join(lines)); } } else if (destinationRun is ArrayRun arrayRun) { var stream = new StringBuilder(); arrayRun.AppendTo(model, stream, arrayRun.Start, arrayRun.ElementLength * Math.Min(20, arrayRun.ElementCount), false); return(stream.ToString()); } else { return(null); } }
public static ITableRun GetTable(this IDataModel model, string name) { var address = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, name); return(model.GetNextRun(address) as ITableRun); }
public static int ParseValue(IDataModel model, ITableRun table, int elementIndex, string content) { if (string.IsNullOrEmpty(content)) { return(0); } if (int.TryParse(content, out int simpleValue)) { return(simpleValue); } if (content == "last") { return(table.ElementCount - 1); } if (table != null && table.ElementContent.Any(seg => seg.Name == content)) { return(table.ReadValue(model, elementIndex, content)); } if (content.MatchesPartial("(/=)/")) { var parts = content.Split("(/=)".ToCharArray()); var message = $"Expected {content} to fit the form (table/field=local)/field. But it didn't."; if (parts.Length != 6) { throw new NotImplementedException(message); } if (!string.IsNullOrEmpty(parts[0]) || !string.IsNullOrEmpty(parts[4])) { throw new NotImplementedException(message); } var matchTableName = parts[1]; var matchTableField = parts[2]; var matchLocalField = parts[3]; var localFieldValue = ParseValue(model, table, elementIndex, matchLocalField); var valueField = parts[5]; var matchTable = model.GetTable(matchTableName); if (matchTable == null) { return(0); } for (int i = 0; i < matchTable.ElementCount; i++) { if (matchTable.ReadValue(model, i, matchTableField) != localFieldValue) { continue; } return(ParseValue(model, matchTable, i, valueField)); } return(ParseValue(model, matchTable, matchTable.ElementCount - 1, valueField)); } if (content.MatchesPartial("//")) { var parts = content.Split("/"); var message = $"Expected {content} to fit the form field/index/field. But it didn't."; if (parts.Length != 3) { throw new NotImplementedException(message); } var destination = table.ReadPointer(model, elementIndex, parts[0]); if (destination == Pointer.NULL) { return(0); } var childTable = model.GetNextRun(destination) as ITableRun; if (childTable == null) { throw new NotImplementedException(message); } var childTableIndex = ParseValue(model, childTable, elementIndex, parts[1]); return(ParseValue(model, childTable, childTableIndex, parts[2])); } throw new NotImplementedException(); }
public ModelArrayElement(IDataModel model, int address, int index) { (this.model, arrayAddress, arrayIndex) = (model, address, index); table = (ITableRun)model.GetNextRun(arrayAddress); }
public static bool TrySearch(IDataModel data, ModelDelta changeToken, string originalFormat, out ArrayRun self, Func <IFormattedRun, bool> runFilter = null) { self = null; var format = originalFormat; var allowPointersToEntries = format.StartsWith(AnchorStart.ToString()); if (allowPointersToEntries) { format = format.Substring(1); } var closeArray = format.LastIndexOf(ArrayEnd.ToString()); if (!format.StartsWith(ArrayStart.ToString()) || closeArray == -1) { throw new ArrayRunParseException($"Array Content must be wrapped in {ArrayStart}{ArrayEnd}"); } var segments = format.Substring(1, closeArray - 1); var length = format.Substring(closeArray + 1); var elementContent = ParseSegments(segments, data); if (elementContent.Count == 0) { return(false); } var elementLength = elementContent.Sum(e => e.Length); using (ModelCacheScope.CreateScope(data)) { if (string.IsNullOrEmpty(length)) { 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 { var bestAddress = KnownLengthSearch(data, elementContent, elementLength, length, out int bestLength, runFilter); if (bestAddress == Pointer.NULL) { return(false); } var lengthFromAnchor = int.TryParse(length, out var _) ? string.Empty : length; self = new ArrayRun(data, originalFormat, lengthFromAnchor, bestAddress, bestLength, elementContent, data.GetNextRun(bestAddress).PointerSources, null); } } if (allowPointersToEntries) { self = self.AddSourcesPointingWithinArray(changeToken); } return(true); }
protected ITableRun GetTable(IDataModel model, int pointerAddress) => (ITableRun)model.GetNextRun(pointerAddress);