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