2023-01-22 04:02:33 +00:00
using System ;
2020-01-25 01:57:47 +00:00
using System.Collections.Generic ;
using System.ComponentModel ;
2020-05-26 22:58:48 +00:00
using System.Globalization ;
2020-01-25 01:57:47 +00:00
using System.Linq ;
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
/// <summary>
/// Provides reflection utility for manipulating blocks, providing block names and value wrapping.
/// </summary>
public sealed class SCBlockMetadata
2020-01-25 01:57:47 +00:00
{
2022-06-18 18:04:24 +00:00
private readonly Dictionary < IDataIndirect , string > BlockList ;
private readonly Dictionary < uint , string > ValueList ;
private readonly SCBlockAccessor Accessor ;
2020-01-25 01:57:47 +00:00
/// <summary>
2022-06-18 18:04:24 +00:00
/// Creates a new instance of <see cref="SCBlockMetadata"/> by loading properties and constants declared via reflection.
2020-01-25 01:57:47 +00:00
/// </summary>
2022-06-18 18:04:24 +00:00
public SCBlockMetadata ( SCBlockAccessor accessor , IEnumerable < string > extraKeyNames , params string [ ] exclusions )
2020-01-25 01:57:47 +00:00
{
2022-06-18 18:04:24 +00:00
var aType = accessor . GetType ( ) ;
BlockList = aType . GetAllPropertiesOfType < IDataIndirect > ( accessor ) ;
ValueList = aType . GetAllConstantsOfType < uint > ( ) ;
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 ;
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Returns a list of block details, ordered by their type and <see cref="GetSortKey"/>.
/// </summary>
public IEnumerable < ComboItem > 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 ) ) ;
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Loads names from an external file to the requested <see cref="names"/> list.
/// </summary>
/// <remarks>Tab separated text file expected.</remarks>
/// <param name="names">Currently loaded list of block names</param>
/// <param name="lines">Tab separated key-value pair list of block names.</param>
2023-01-22 04:02:33 +00:00
public static void AddExtraKeyNames ( Dictionary < uint , string > names , IEnumerable < string > lines )
2022-06-18 18:04:24 +00:00
{
2023-01-22 04:02:33 +00:00
foreach ( ReadOnlySpan < char > line in lines )
2021-01-07 23:34:26 +00:00
{
2022-06-18 18:04:24 +00:00
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 ;
2023-01-22 04:02:33 +00:00
var name = line [ ( split + 1 ) . . ] . ToString ( ) ;
names . TryAdd ( ( uint ) value , name ) ;
2020-05-26 22:58:48 +00:00
}
2022-06-18 18:04:24 +00:00
}
2020-05-26 22:58:48 +00:00
2022-06-18 18:04:24 +00:00
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 ) . . ] ;
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
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 ;
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Searches the <see cref="BlockList"/> to see if a named Save Block originate from the requested <see cref="block"/>. If no block exists, the logic will check for a named stored-value.
/// </summary>
/// <param name="block">Block requesting the name of</param>
/// <param name="saveBlock">Block that shares the same backing byte array; null if none.</param>
/// <returns>Name of the block indicating the purpose that it serves in-game.</returns>
public string? GetBlockName ( SCBlock block , out IDataIndirect ? saveBlock )
{
// See if we have a Block object for this block
if ( block . Data . Length ! = 0 )
2020-01-25 01:57:47 +00:00
{
2022-06-18 18:04:24 +00:00
var obj = BlockList . FirstOrDefault ( z = > ReferenceEquals ( z . Key . Data , block . Data ) ) ;
2023-01-22 04:02:33 +00:00
if ( obj is not ( null , null ) )
2020-01-25 01:57:47 +00:00
{
2022-06-18 18:04:24 +00:00
saveBlock = obj . Key ;
return obj . Value ;
2020-01-25 01:57:47 +00:00
}
2022-06-18 18:04:24 +00:00
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
// See if it's a single-value declaration
if ( ValueList . TryGetValue ( block . Key , out var blockName ) )
{
2020-01-25 01:57:47 +00:00
saveBlock = null ;
2022-06-18 18:04:24 +00:00
return blockName ;
2020-01-25 01:57:47 +00:00
}
2022-06-18 18:04:24 +00:00
saveBlock = null ;
return null ;
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
/// <summary>
/// Returns an object that wraps the block with a Value property to get/set via a PropertyGrid/etc control.
/// </summary>
/// <returns>Returns null if no wrapping is supported.</returns>
public static object? GetEditableBlockObject ( SCBlock block ) = > block . Type switch
{
SCTypeCode . Byte = > new WrappedValueView < byte > ( block , block . GetValue ( ) ) ,
SCTypeCode . UInt16 = > new WrappedValueView < ushort > ( block , block . GetValue ( ) ) ,
SCTypeCode . UInt32 = > new WrappedValueView < uint > ( block , block . GetValue ( ) ) ,
SCTypeCode . UInt64 = > new WrappedValueView < ulong > ( block , block . GetValue ( ) ) ,
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
SCTypeCode . SByte = > new WrappedValueView < sbyte > ( block , block . GetValue ( ) ) ,
SCTypeCode . Int16 = > new WrappedValueView < short > ( block , block . GetValue ( ) ) ,
SCTypeCode . Int32 = > new WrappedValueView < int > ( block , block . GetValue ( ) ) ,
SCTypeCode . Int64 = > new WrappedValueView < long > ( block , block . GetValue ( ) ) ,
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
SCTypeCode . Single = > new WrappedValueView < float > ( block , block . GetValue ( ) ) ,
SCTypeCode . Double = > new WrappedValueView < double > ( block , block . GetValue ( ) ) ,
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
_ = > null ,
} ;
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
private sealed class WrappedValueView < T > where T : struct
{
private readonly SCBlock Parent ;
private T _value ;
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
[Description("Stored Value for this Block")]
public T Value
{
get = > _value ;
set = > Parent . SetValue ( _value = value ) ;
}
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
// ReSharper disable once UnusedMember.Local
[Description("Type of Value this Block stores")]
public string ValueType = > typeof ( T ) . Name ;
2020-01-25 01:57:47 +00:00
2022-06-18 18:04:24 +00:00
public WrappedValueView ( SCBlock block , object currentValue )
{
Parent = block ;
_value = ( T ) Convert . ChangeType ( currentValue , typeof ( T ) ) ;
2020-01-25 01:57:47 +00:00
}
}
2021-01-07 23:34:26 +00:00
}