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)); } }
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); }
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; }