예제 #1
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, bool includeAllTableIndex = false)
        {
            // find all palettes that could be applied to this sprite run
            var noChange = new NoDataChangeDeltaModel();
            var results  = new List <IPaletteRun>();

            if (spriteRun?.SpriteFormat.BitsPerPixel < 4)
            {
                return(results);                                       // 1- and 2-bit sprites don't have palettes
            }
            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 ITableRun;
            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 address = model.GetAddressFromAnchor(noChange, -1, hint);
                var run     = model.GetNextRun(address);
                if (run is IPaletteRun palRun && palRun.Start == address)
                {
                    // 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 ITableRun;
                    if (offset.ElementIndex != -1 && paletteTable != null)
                    {
                        var paletteIndex = model.ReadMultiByteValue(enumArray.Start + enumArray.ElementLength * offset.ElementIndex, enumArray.ElementLength);
                        var destination  = model.ReadPointer(paletteTable.Start + paletteTable.ElementLength * paletteIndex);
                        var tempRun      = model.GetNextRun(destination);
                        if (tempRun is IPaletteRun pRun && pRun.Start == destination)
                        {
                            results.Add(pRun);
                        }
                    }
                }
예제 #2
0
      public static bool TrySearch(IDataModel data, ModelDelta token, out EggMoveRun eggmoves) {
         eggmoves = default;
         var pokenames = data.GetNextRun(data.GetAddressFromAnchor(token, -1, PokemonNameTable)) as ArrayRun;
         var movenames = data.GetNextRun(data.GetAddressFromAnchor(token, -1, MoveNamesTable)) as ArrayRun;
         if (pokenames == null || movenames == null) return false;

         for (var run = data.GetNextRun(0); run.Start < int.MaxValue; run = data.GetNextRun(run.Start + run.Length)) {
            if (run is ArrayRun || run is PCSRun || run.PointerSources == null) continue;

            // verify expected pointers to this
            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 < 1000 || length > 7000) continue;
            if (run.Start + length * 2 + 3 < 0) continue;
            if (run.Start + length * 2 + 3 > data.Count) 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;
                  if (value < pokenames.ElementCount) continue;
               }
               if (value < movenames.ElementCount) continue;
               possibleMatch = false;
               break;
            }
            if (!possibleMatch) continue;

            // content is correct, length is correct: this is it!
            eggmoves = new EggMoveRun(data, run.Start);
            return true;
         }

         return false;
      }
예제 #3
0
        public override IDataFormat CreateDataFormat(IDataModel data, int index)
        {
            var inner = ITableRunExtensions.CreateSegmentDataFormat(this, data, index);

            if (index > lastFormatCreated)
            {
                lastFormatCreated = index;
                return(inner);
            }

            if ((index - Start) % ElementLength != 0)
            {
                return(inner);
            }
            var address = data.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, TilemapAnchor);
            var run     = data.GetNextRun(address) as ITilemapRun;
            var pixels  = data.CurrentCacheScope.GetImage(run);

            if (pixels == null)
            {
                return(inner);
            }

            var missingTopRows = (index - Start) / ElementLength;

            pixels            = Crop(pixels, -Margins.Left * 8, -Margins.Top * 8, -Margins.Right * 8, -Margins.Bottom * 8);
            pixels            = DuplicateDown(pixels, Margins.LengthMultiplier);
            pixels            = Crop(pixels, 0, missingTopRows * 8, 0, 0);
            lastFormatCreated = index;
            return(new SpriteDecorator(inner, pixels, pixels.PixelWidth / 8, pixels.PixelHeight / 8));
        }
예제 #4
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();

            if (tiles == null)
            {
                return(null);
            }
            return(GetPixels(mapData, tiles, Format, BytesPerTile));
        }
예제 #5
0
        public static ITilemapRun SetPixels(ITilemapRun run, IDataModel model, ModelDelta token, int page, int[,] pixels, ref int arrayTilesetAddress, Func <byte[], ModelDelta, ITilemapRun> replaceData)
        {
            var tileData       = Tilize(pixels, run.Format.BitsPerPixel);
            var tiles          = GetUniqueTiles(tileData, run.BytesPerTile == 2);
            var tilesetAddress = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, run.Format.MatchingTileset);
            var tileset        = model.GetNextRun(tilesetAddress) as ITilesetRun;

            if (tileset == null)
            {
                FindMatchingTileset(run, model, ref arrayTilesetAddress);
                tileset = model.GetNextRun(arrayTilesetAddress) as ITilesetRun;
            }

            var tilesToKeep       = new HashSet <int>((tileset.DecompressedLength / tileset.TilesetFormat.BitsPerPixel / 8).Range());
            var originalUsedTiles = GetUsedTiles(run).ToHashSet();

            foreach (var tile in originalUsedTiles)
            {
                tilesToKeep.Remove(tile);
            }
            foreach (var tilemap in tileset.FindDependentTilemaps(model).Except(run))
            {
                tilesToKeep.AddRange(GetUsedTiles(tilemap));
            }
            var oldTileDataRaw = tileset.GetData();
            var previousTiles  = Tilize(oldTileDataRaw, run.Format.BitsPerPixel);

            tiles = MergeTilesets(previousTiles, tilesToKeep, tiles, run.BytesPerTile == 2);
            tileset.SetPixels(model, token, tiles);
            var mapData = run.GetTilemapData();

            var tileWidth  = tileData.GetLength(0);
            var tileHeight = tileData.GetLength(1);

            for (int y = 0; y < tileHeight; y++)
            {
                for (int x = 0; x < tileWidth; x++)
                {
                    var i = y * tileWidth + x;
                    var(tile, paletteIndex)   = tileData[x, y];
                    var(tileIndex, matchType) = FindMatch(tile, tiles, run.BytesPerTile == 2);
                    if (tileIndex == -1)
                    {
                        tileIndex = 0;
                    }
                    if (run.BytesPerTile == 2)
                    {
                        var mapping = PackMapping(paletteIndex, matchType, tileIndex);
                        mapData[i * 2 + 0] = (byte)mapping;
                        mapData[i * 2 + 1] = (byte)(mapping >> 8);
                    }
                    else
                    {
                        mapData[i] = (byte)tileIndex;
                    }
                }
            }

            return(replaceData(mapData, token));
        }
예제 #6
0
        public void AddMatchedWord(IDataModel model, int memoryLocation, string parentName)
        {
            var parentAddress = model.GetAddressFromAnchor(this, -1, parentName);
            var parent        = model.GetNextRun(parentAddress) as ArrayRun;

            model.WriteValue(this, memoryLocation, parent?.ElementCount ?? default);
            addedMatchedWords[memoryLocation] = parentName;
        }
예제 #7
0
 private int GetParentIndex(IReadOnlyList <int> pointerSources)
 {
     if (parentName == string.Empty)
     {
         var matches = pointerSources.Where(SourceIsFromParentTable).ToList();
         return(matches.Count > 0 ? matches[0] : -1);
     }
     else
     {
         return(model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, parentName));
     }
 }
예제 #8
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;
        }
        public int[,] GetPixels(IDataModel model, int page)
        {
            var result = new int[Format.TileWidth * 8, Format.TileHeight * 8];

            var mapData        = Decompress(model, Start);
            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 LZRun))
            {
                return(result);                                     // 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 = Decompress(model, tileset.Start);

            var tileSize = tileset is LzTilesetRun lztsRun ? lztsRun.Format.BitsPerPixel * 8 : tileset.SpriteFormat.BitsPerPixel * 8;

            for (int y = 0; y < Format.TileHeight; y++)
            {
                var yStart = y * 8;
                for (int x = 0; x < Format.TileWidth; x++)
                {
                    var map  = mapData.ReadMultiByteValue((Format.TileWidth * y + x) * 2, 2);
                    var tile = map & 0x3FF;

                    var tileStart = tile * tileSize;
                    var pixels    = SpriteRun.GetPixels(tiles, tileStart, 1, 1, Format.BitsPerPixel); // TODO cache this during this method so we don't load the same tile more than once
                    var hFlip     = (map >> 10) & 0x1;
                    var vFlip     = (map >> 11) & 0x1;
                    var pal       = (map >> 12) & 0xF;
                    pal <<= 4;
                    var xStart = x * 8;
                    for (int yy = 0; yy < 8; yy++)
                    {
                        for (int xx = 0; xx < 8; xx++)
                        {
                            var inX = hFlip == 1 ? 7 - xx : xx;
                            var inY = vFlip == 1 ? 7 - yy : yy;
                            result[xStart + xx, yStart + yy] = pixels[inX, inY] + pal;
                        }
                    }
                }
            }

            return(result);
        }
예제 #10
0
        /// <summary>
        /// Finds what 4 moves a pokemon would have by default, based on lvlmoves and the given level
        /// </summary>
        private IReadOnlyList <int> GetDefaultMoves(int pokemon, int currentLevel)
        {
            var results           = new List <int>();
            var levelMovesAddress = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, HardcodeTablesModel.LevelMovesTableName);
            var lvlMoves          = model.GetNextRun(levelMovesAddress) as ArrayRun;

            if (lvlMoves != null)
            {
                var movesStart = model.ReadPointer(lvlMoves.Start + lvlMoves.ElementLength * pokemon);
                if (model.GetNextRun(movesStart) is ITableRun run)
                {
                    for (int i = 0; i < run.ElementCount; i += 1)
                    {
                        int level = 0, move = 0;
                        if (run.ElementContent.Count == 1)
                        {
                            var pair = model.ReadMultiByteValue(run.Start + i * run.ElementLength, run.ElementLength);
                            (level, move) = PLMRun.SplitToken(pair);
                        }
                        else if (run.ElementContent.Count == 2)
                        {
                            move  = model.ReadMultiByteValue(run.Start + i * run.ElementLength, run.ElementContent[0].Length);
                            level = model.ReadMultiByteValue(run.Start + i * run.ElementLength + run.ElementContent[0].Length, run.ElementContent[1].Length);
                        }
                        else
                        {
                            level = int.MaxValue;
                        }

                        if (currentLevel >= level)
                        {
                            results.Add(move);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
            while (results.Count > 4)
            {
                results.RemoveAt(0);                    // restrict to the last 4 moves
            }
            while (results.Count < 4)
            {
                results.Add(0);                         // pad with extra '0' moves if needed
            }
            return(results);
        }
예제 #11
0
        public ISpriteRun SetPixels(IDataModel model, ModelDelta token, int page, int[,] pixels)
        {
            var tileData       = Tilize(pixels);
            var tiles          = GetUniqueTiles(tileData);
            var tilesetAddress = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, Format.MatchingTileset);
            var tileset        = model.GetNextRun(tilesetAddress) as LzTilesetRun;

            if (tileset == null)
            {
                tileset = model.GetNextRun(arrayTilesetAddress) as LzTilesetRun;
            }
            tileset.SetPixels(model, token, tiles);
            if (tiles.Length > 0x400)
            {
                // TODO fail: too many unique tiles
                return(this);
            }
            var mapData = Decompress(model, Start);

            var tileWidth  = tileData.GetLength(0);
            var tileHeight = tileData.GetLength(1);

            for (int y = 0; y < tileHeight; y++)
            {
                for (int x = 0; x < tileWidth; x++)
                {
                    var i = y * tileWidth + x;
                    var(tile, paletteIndex)   = tileData[x, y];
                    var(tileIndex, matchType) = FindMatch(tile, tiles);
                    var mapping = PackMapping(paletteIndex, matchType, tileIndex);
                    mapData[i * 2 + 0] = (byte)mapping;
                    mapData[i * 2 + 1] = (byte)(mapping >> 8);
                }
            }

            var newModelData = Compress(mapData, 0, mapData.Length);
            var newRun       = (ISpriteRun)model.RelocateForExpansion(token, this, newModelData.Count);

            for (int i = 0; i < newModelData.Count; i++)
            {
                token.ChangeData(model, newRun.Start + i, newModelData[i]);
            }
            for (int i = newModelData.Count; i < Length; i++)
            {
                token.ChangeData(model, newRun.Start + i, 0xFF);
            }
            newRun = new LzTilemapRun(Format, model, newRun.Start, newRun.PointerSources);
            model.ObserveRunWritten(token, newRun);
            return(newRun);
        }
예제 #12
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);
        }
        // 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);
        }
예제 #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 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 static ITableRun GetTable(this IDataModel model, string name)
        {
            var address = model.GetAddressFromAnchor(new NoDataChangeDeltaModel(), -1, name);

            return(model.GetNextRun(address) as ITableRun);
        }
예제 #19
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);
        }
예제 #20
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));
            }