2020-01-25 01:57:47 +00:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
2020-05-26 22:58:48 +00:00
using System.Globalization ;
using System.IO ;
2020-01-25 01:57:47 +00:00
using System.Linq ;
namespace PKHeX.Core
{
/// <summary>
/// Provides reflection utility for manipulating blocks, providing block names and value wrapping.
/// </summary>
2020-09-07 20:51:13 +00:00
public sealed class SCBlockMetadata
2020-01-25 01:57:47 +00:00
{
private readonly Dictionary < SaveBlock , string > BlockList ;
private readonly Dictionary < uint , string > ValueList ;
private readonly SCBlockAccessor Accessor ;
2021-01-07 23:34:26 +00:00
/// <summary>
/// Creates a new instance of <see cref="SCBlockMetadata"/> by loading properties and constants declared via reflection.
/// </summary>
2020-01-25 01:57:47 +00:00
public SCBlockMetadata ( SCBlockAccessor accessor )
{
var aType = accessor . GetType ( ) ;
BlockList = aType . GetAllPropertiesOfType < SaveBlock > ( accessor ) ;
ValueList = aType . GetAllConstantsOfType < uint > ( ) ;
2020-05-26 22:58:48 +00:00
AddExtraKeyNames ( ValueList ) ;
2020-01-25 01:57:47 +00:00
Accessor = accessor ;
}
2021-01-07 23:34:26 +00:00
/// <summary>
/// Returns a list of block details, ordered by their type and <see cref="GetSortKey"/>.
/// </summary>
2020-01-25 01:57:47 +00:00
public IEnumerable < ComboItem > 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 ;
}
2021-01-07 23:34:26 +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="extra">Side-loaded list of block names to add to the <see cref="names"/> list.</param>
2020-05-26 22:58:48 +00:00
public static void AddExtraKeyNames ( IDictionary < uint , string > names , string extra = "SCBlocks.txt" )
{
if ( ! File . Exists ( extra ) )
return ;
var lines = File . ReadLines ( extra ) ;
2021-01-07 23:34:26 +00:00
AddExtraKeyNames ( names , lines ) ;
}
/// <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>
public static void AddExtraKeyNames ( IDictionary < uint , string > names , IEnumerable < string > lines )
{
2020-05-26 22:58:48 +00:00
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 ) ;
2021-01-07 23:34:26 +00:00
if ( ! names . ContainsKey ( ( uint ) value ) )
names [ ( uint ) value ] = name ;
2020-05-26 22:58:48 +00:00
}
}
2020-01-25 01:57:47 +00:00
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 _ ) ;
2020-01-29 03:08:26 +00:00
var isBool = z . Type . IsBoolean ( ) ;
var type = ( isBool ? "Bool" : z . Type . ToString ( ) ) ;
2020-01-25 01:57:47 +00:00
if ( blockName ! = null )
return $"*{type} {blockName}" ;
2020-01-29 03:08:26 +00:00
var result = $"{z.Key:X8} - {i:0000} {type}" ;
2020-12-25 20:30:26 +00:00
if ( z . Type is SCTypeCode . Object or SCTypeCode . Array )
2020-01-29 03:08:26 +00:00
result + = $" 0x{z.Data.Length:X3}" ;
else if ( ! isBool )
result + = $" {z.GetValue()}" ;
return result ;
2020-01-25 01:57:47 +00:00
}
2021-01-07 23:34:26 +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>
2020-01-25 01:57:47 +00:00
public string? GetBlockName ( SCBlock block , out SaveBlock ? saveBlock )
{
// See if we have a Block object for this block
2020-09-06 18:21:23 +00:00
if ( block . Data . Length ! = 0 )
2020-01-25 01:57:47 +00:00
{
2020-09-06 18:21:23 +00:00
var obj = BlockList . FirstOrDefault ( z = > ReferenceEquals ( z . Key . Data , block . Data ) ) ;
if ( obj . Key ! = null )
{
saveBlock = obj . Key ;
return obj . Value ;
}
2020-01-25 01:57:47 +00:00
}
// See if it's a single-value declaration
if ( ValueList . TryGetValue ( block . Key , out var blockName ) )
{
saveBlock = null ;
return blockName ;
}
saveBlock = null ;
return null ;
}
/// <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>
2021-01-02 01:08:49 +00:00
public static object? GetEditableBlockObject ( SCBlock block ) = > block . Type switch
2020-01-25 01:57:47 +00:00
{
2021-01-02 01:08:49 +00:00
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
2021-01-02 01:08:49 +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
2021-01-02 01:08:49 +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
2021-01-02 01:08:49 +00:00
_ = > null ,
} ;
2020-01-25 01:57:47 +00:00
2020-09-07 20:51:13 +00:00
private sealed class WrappedValueView < T > where T : struct
2020-01-25 01:57:47 +00:00
{
private readonly SCBlock Parent ;
private T _value ;
[Description("Stored Value for this Block")]
public T Value
{
get = > _value ;
set = > Parent . SetValue ( _value = value ) ;
}
2020-09-09 19:47:24 +00:00
// ReSharper disable once UnusedMember.Local
2020-10-04 01:59:36 +00:00
#pragma warning disable CA1822 // do not make this static, we want it to show up in a property grid as a readonly value
2020-01-25 01:57:47 +00:00
[Description("Type of Value this Block stores")]
public string ValueType = > typeof ( T ) . Name ;
2020-10-04 01:59:36 +00:00
#pragma warning restore CA1822
2020-01-25 01:57:47 +00:00
public WrappedValueView ( SCBlock block , object currentValue )
{
Parent = block ;
_value = ( T ) Convert . ChangeType ( currentValue , typeof ( T ) ) ;
}
}
}
2021-01-07 23:34:26 +00:00
}