Beispiel #1
0
 public TilesetRun(TilesetFormat tilesetFormat, IDataModel model, int start, SortedSpan <int> sources = null) : base(start, sources)
 {
     if (tilesetFormat.Tiles == -1)
     {
         var nextRun = model.GetNextAnchor(start + 1);
         if (nextRun.Start <= start)
         {
             nextRun = model.GetNextAnchor(nextRun.Start + nextRun.Length);
         }
         var tiles = (nextRun.Start - start) / (8 * tilesetFormat.BitsPerPixel);
         tilesetFormat = new TilesetFormat(tilesetFormat.BitsPerPixel, tiles, tilesetFormat.PaletteHint);
     }
     TilesetFormat = tilesetFormat;
     this.model    = model;
 }
        public OverworldSpriteListRun(IDataModel model, IReadOnlyList <ArrayRunElementSegment> parent, int start, SortedSpan <int> sources = null) : base(start, sources)
        {
            this.model  = model;
            this.parent = parent;
            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 == SharedFormatString);
            var widthOffset  = GetOffset(parent, seg => seg.Name == "width");
            var heightOffset = GetOffset(parent, seg => seg.Name == "height");
            var keyOffset    = GetOffset(parent, seg => seg.Name == "paletteid");

            var elementStart = sources[0] - listOffset;
            var width        = Math.Max(1, model.ReadMultiByteValue(elementStart + widthOffset, 2));
            var height       = Math.Max(1, model.ReadMultiByteValue(elementStart + heightOffset, 2));
            var tileWidth    = (int)Math.Max(1, Math.Ceiling(width / 8.0));
            var tileHeight   = (int)Math.Max(1, Math.Ceiling(height / 8.0));
            var key          = model.ReadMultiByteValue(elementStart + keyOffset, 2);
            var hint         = $"overworld.palettes:id={key:X4}";

            var format = $"`ucs4x{width / 8}x{height / 8}|{hint}`";

            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;
                }
                ElementCount += 1;
                Length       += ElementLength;
            }

            SpriteFormat = new SpriteFormat(4, tileWidth * ElementCount, tileHeight, hint);
            ElementNames = Enumerable.Range(0, ElementCount).Select(i => string.Empty).ToList();
        }
        public bool DestinationDataMatchesPointerFormat(IDataModel owner, ModelDelta token, int source, int destination, IReadOnlyList <ArrayRunElementSegment> sourceSegments, int parentIndex)
        {
            if (destination == Pointer.NULL)
            {
                return(true);
            }
            var run = owner.GetNextAnchor(destination);

            if (run.Start < destination)
            {
                return(false);
            }
            if (run.Start > destination || (run.Start == destination && (run is NoInfoRun || run is PointerRun)))
            {
                // hard case: no format found, so check the data
                if (destination < 0 || destination >= owner.Count)
                {
                    return(false);
                }
                return(Factory.GetStrategy(InnerFormat)?.TryAddFormatAtDestination(owner, token, source, destination, Name, sourceSegments, parentIndex) ?? false);
            }
            else
            {
                // easy case: already have a format, just see if it matches
                var strategy = Factory.GetStrategy(InnerFormat);
                if (strategy == null)
                {
                    return(false);
                }
                if (strategy.Matches(run))
                {
                    return(true);
                }

                // special test: the format of the data is wrong?
                return(strategy.TryAddFormatAtDestination(owner, token, source, destination, Name, sourceSegments, parentIndex));
            }
        }
Beispiel #4
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 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);
        }
        private static int StandardSearch(IDataModel data, List <ArrayRunElementSegment> elementContent, int elementLength, out int bestLength, Func <IFormattedRun, bool> runFilter)
        {
            int bestAddress = Pointer.NULL;

            bestLength = 0;

            var run = data.GetNextAnchor(0);

            for (var nextRun = data.GetNextAnchor(run.Start + run.Length); run.Start < int.MaxValue; nextRun = data.GetNextAnchor(nextRun.Start + nextRun.Length))
            {
                if (run is ArrayRun || run.PointerSources == null)
                {
                    run = nextRun;
                    continue;
                }
                var nextArray = nextRun;

                // 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(run))
                {
                    run = nextRun; continue;
                }

                FormatMatchFlags flags = default;
                if (elementContent.Count == 1)
                {
                    flags |= FormatMatchFlags.IsSingleSegment;
                }

                int currentLength  = 0;
                int currentAddress = run.Start;
                while (true)
                {
                    if (currentLength > 100)
                    {
                        flags |= FormatMatchFlags.AllowJunkAfterText;                 // we've gone long enough without junk data to be fairly sure that we're looking at something real
                    }
                    if (nextArray.Start < currentAddress)
                    {
                        nextArray = data.GetNextAnchor(nextArray.Start + 1);
                    }
                    if (DataMatchesElementFormat(data, currentAddress, elementContent, flags, nextArray))
                    {
                        currentLength++;
                        currentAddress += elementLength;
                    }
                    else
                    {
                        break;
                    }
                }

                // if what we found is just a text array, then remove any trailing elements starting with a space.
                if (elementContent.Count == 1 && elementContent[0].Type == ElementContentType.PCS)
                {
                    while (data[currentAddress - elementLength] == 0x00)
                    {
                        currentLength--;
                        currentAddress -= elementLength;
                    }
                }

                // we think we found some data! Make sure it's not just a bunch of 00's and FF's
                var dataEmpty = true;
                for (int i = 0; i < currentLength && currentLength > bestLength && dataEmpty; i++)
                {
                    dataEmpty = data[run.Start + i] == 0xFF || data[run.Start + i] == 0x00;
                }

                if (bestLength < currentLength && !dataEmpty)
                {
                    bestLength  = currentLength;
                    bestAddress = run.Start;
                }

                run = nextRun;
            }

            return(bestAddress);
        }
Beispiel #7
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.GetNextAnchor(Start + ElementLength);
                for (; true; nextRun = owner.GetNextAnchor(nextRun.Start + nextRun.Length))
                {
                    if (nextRun.Start > owner.Count)
                    {
                        break;
                    }
                    var anchorName = owner.GetAnchorFromAddress(-1, nextRun.Start);
                    if (string.IsNullOrEmpty(anchorName))
                    {
                        continue;
                    }
                    if ((nextRun.Start - Start) % ElementLength != 0)
                    {
                        break;
                    }
                }
                var byteLength   = 0;
                var elementCount = 0;
                while (Start + byteLength + ElementLength <= nextRun.Start && DataMatchesElementFormat(owner, Start + byteLength, ElementContent, flags, nextRun))
                {
                    byteLength += ElementLength;
                    elementCount++;
                    if (elementCount == 100)
                    {
                        flags |= FormatMatchFlags.AllowJunkAfterText;
                    }
                }
                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.
                FormatString    += ElementCount;
            }
            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;
        }