using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Linq; namespace PKHeX.Core; /// /// Provides reflection utility for manipulating blocks, providing block names and value wrapping. /// public sealed class SCBlockMetadata { private readonly Dictionary BlockList; private readonly Dictionary ValueList; private readonly SCBlockAccessor Accessor; /// /// Creates a new instance of by loading properties and constants declared via reflection. /// public SCBlockMetadata(SCBlockAccessor accessor, IEnumerable extraKeyNames, params string[] exclusions) { var aType = accessor.GetType(); BlockList = aType.GetAllPropertiesOfType(accessor); ValueList = aType.GetAllConstantsOfType(); AddExtraKeyNames(ValueList, extraKeyNames); if (exclusions.Length > 0) ValueList = ValueList.Where(z => !exclusions.Any(z.Value.Contains)).ToDictionary(z => z.Key, z => z.Value); Accessor = accessor; } /// /// Returns a list of block details, ordered by their type and . /// public IEnumerable GetSortedBlockKeyList() => Accessor.BlockInfo .Select((z, i) => new ComboItem(GetBlockHint(z, i), (int)z.Key)) .OrderBy(z => !(z.Text.Length != 0 && z.Text[0] == '*')) .ThenBy(z => GetSortKey(z)); /// /// Loads names from an external file to the requested list. /// /// Tab separated text file expected. /// Currently loaded list of block names /// Tab separated key-value pair list of block names. public static void AddExtraKeyNames(Dictionary names, IEnumerable lines) { foreach (ReadOnlySpan line in lines) { var split = line.IndexOf('\t'); if (split < 0) continue; var hex = line[..split]; if (!ulong.TryParse(hex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out var value)) continue; var name = line[(split + 1)..].ToString(); names.TryAdd((uint)value, name); } } private static string GetSortKey(in ComboItem item) { var text = item.Text; if (text.Length != 0 && text[0] == '*') return text; // key:X8, " - ", "####", " ", type return text[(8 + 3 + 4 + 1)..]; } private string GetBlockHint(SCBlock z, int index) { var blockName = GetBlockName(z, out _); var isBool = z.Type.IsBoolean(); var type = (isBool ? "Bool" : z.Type.ToString()); if (blockName != null) return $"*{type} {blockName}"; var result = $"{z.Key:X8} - {index:0000} {type}"; if (z.Type is SCTypeCode.Object or SCTypeCode.Array) result += $" 0x{z.Data.Length:X3}"; else if (!isBool) result += $" {z.GetValue()}"; return result; } /// /// Searches the to see if a named Save Block originate from the requested . If no block exists, the logic will check for a named stored-value. /// /// Block requesting the name of /// Block that shares the same backing byte array; null if none. /// Name of the block indicating the purpose that it serves in-game. public string? GetBlockName(SCBlock block, out IDataIndirect? saveBlock) { // See if we have a Block object for this block if (block.Data.Length != 0) { var obj = BlockList.FirstOrDefault(z => ReferenceEquals(z.Key.Data, block.Data)); if (obj is not (null, null)) { saveBlock = obj.Key; return obj.Value; } } // See if it's a single-value declaration if (ValueList.TryGetValue(block.Key, out var blockName)) { saveBlock = null; return blockName; } saveBlock = null; return null; } /// /// Returns an object that wraps the block with a Value property to get/set via a PropertyGrid/etc control. /// /// Returns null if no wrapping is supported. public static object? GetEditableBlockObject(SCBlock block) => block.Type switch { SCTypeCode.Byte => new WrappedValueView(block, block.GetValue()), SCTypeCode.UInt16 => new WrappedValueView(block, block.GetValue()), SCTypeCode.UInt32 => new WrappedValueView(block, block.GetValue()), SCTypeCode.UInt64 => new WrappedValueView(block, block.GetValue()), SCTypeCode.SByte => new WrappedValueView(block, block.GetValue()), SCTypeCode.Int16 => new WrappedValueView(block, block.GetValue()), SCTypeCode.Int32 => new WrappedValueView(block, block.GetValue()), SCTypeCode.Int64 => new WrappedValueView(block, block.GetValue()), SCTypeCode.Single => new WrappedValueView(block, block.GetValue()), SCTypeCode.Double => new WrappedValueView(block, block.GetValue()), _ => null, }; private sealed class WrappedValueView where T : struct { private readonly SCBlock Parent; private T _value; [Description("Stored Value for this Block")] public T Value { get => _value; set => Parent.SetValue(_value = value); } // ReSharper disable once UnusedMember.Local [Description("Type of Value this Block stores")] public string ValueType => typeof(T).Name; public WrappedValueView(SCBlock block, object currentValue) { Parent = block; _value = (T)Convert.ChangeType(currentValue, typeof(T)); } } }