예제 #1
0
        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
        }
예제 #2
0
        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);
        }
예제 #4
0
 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();
         }
     }
 }
예제 #5
0
        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);
        }
예제 #6
0
        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);
        }
예제 #7
0
        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));
        }
예제 #8
0
    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);
    }
예제 #9
0
        // 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);
        }
예제 #10
0
 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);
     }
예제 #11
0
        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);
        }
예제 #13
0
        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);
     }
 }
예제 #15
0
        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);
        }
예제 #16
0
        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}";
        }
예제 #17
0
        public static int[,] ReadSprite(this IDataModel model, int address)
        {
            var sprite = (ISpriteRun)model.GetNextRun(address);

            return(sprite.GetPixels(model, 0));
        }
예제 #18
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();
        }
예제 #19
0
        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;
        }
예제 #20
0
        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);
        }
예제 #21
0
        /// <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();
        }
예제 #25
0
 public ModelArrayElement(IDataModel model, int address, int index)
 {
     (this.model, arrayAddress, arrayIndex) = (model, address, index);
     table = (ITableRun)model.GetNextRun(arrayAddress);
 }
예제 #26
0
        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);
        }
예제 #27
0
 protected ITableRun GetTable(IDataModel model, int pointerAddress) => (ITableRun)model.GetNextRun(pointerAddress);