using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.IO; 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; public SCBlockMetadata(SCBlockAccessor accessor) { var aType = accessor.GetType(); BlockList = aType.GetAllPropertiesOfType(accessor); ValueList = aType.GetAllConstantsOfType(); AddExtraKeyNames(ValueList); Accessor = accessor; } public IEnumerable GetSortedBlockKeyList() { var list = Accessor.BlockInfo .Select((z, i) => new ComboItem(GetBlockHint(z, i), (int)z.Key)) .OrderBy(z => !z.Text.StartsWith("*")) .ThenBy(z => GetSortKey(z)); return list; } public static void AddExtraKeyNames(IDictionary names, string extra = "SCBlocks.txt") { if (!File.Exists(extra)) return; var lines = File.ReadLines(extra); foreach (var line in lines) { var split = line.IndexOf('\t'); if (split < 0) continue; var hex = line.Substring(0, split); if (!ulong.TryParse(hex, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out var value)) continue; var name = line.Substring(split + 1); if (!names.ContainsKey((uint)value)) names[(uint)value] = name; } } private static string GetSortKey(in ComboItem item) { var text = item.Text; if (text.StartsWith("*")) return text; // key:X8, " - ", "####", " ", type return text.Substring(8 + 3 + 4 + 1); } private string GetBlockHint(SCBlock z, int i) { 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} - {i:0000} {type}"; if (z.Type == SCTypeCode.Object || z.Type == SCTypeCode.Array) result += $" 0x{z.Data.Length:X3}"; else if (!isBool) result += $" {z.GetValue()}"; return result; } public string? GetBlockName(SCBlock block, out SaveBlock? 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.Key != 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) { return 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 #pragma warning disable CA1822 // do not make this static, we want it to show up in a property grid as a readonly value [Description("Type of Value this Block stores")] public string ValueType => typeof(T).Name; #pragma warning restore CA1822 public WrappedValueView(SCBlock block, object currentValue) { Parent = block; _value = (T)Convert.ChangeType(currentValue, typeof(T)); } } } }