2022-11-25 01:42:17 +00:00
using System.Collections.Generic ;
2020-01-20 06:29:50 +00:00
using System.Linq ;
2023-01-22 04:02:33 +00:00
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
namespace PKHeX.Core ;
public sealed class SCBlockCompare
2020-01-20 06:29:50 +00:00
{
2022-06-18 18:04:24 +00:00
private readonly List < string > AddedKeys = new ( ) ;
private readonly List < string > RemovedKeys = new ( ) ;
private readonly List < string > TypesChanged = new ( ) ;
private readonly List < string > ValueChanged = new ( ) ;
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
private readonly Dictionary < uint , string > KeyNames ;
private string GetKeyName ( uint key ) = > KeyNames . TryGetValue ( key , out var value ) ? value : $"{key:X8}" ;
2020-05-26 22:58:48 +00:00
2022-06-18 18:04:24 +00:00
public SCBlockCompare ( SCBlockAccessor s1 , SCBlockAccessor s2 , IEnumerable < string > extraKeyNames )
{
var b1 = s1 . BlockInfo ;
var b2 = s2 . BlockInfo ;
KeyNames = GetKeyNames ( s1 , b1 , b2 ) ;
SCBlockMetadata . AddExtraKeyNames ( KeyNames , extraKeyNames ) ;
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
var hs1 = new HashSet < uint > ( b1 . Select ( z = > z . Key ) ) ;
var hs2 = new HashSet < uint > ( b2 . Select ( z = > z . Key ) ) ;
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
LoadAddRemove ( s1 , s2 , hs1 , hs2 ) ;
hs1 . IntersectWith ( hs2 ) ;
LoadChanged ( s1 , s2 , hs1 ) ;
}
2020-05-26 22:58:48 +00:00
2022-06-18 18:04:24 +00:00
private void LoadAddRemove ( SCBlockAccessor s1 , SCBlockAccessor s2 , ICollection < uint > hs1 , IEnumerable < uint > hs2 )
{
var unique = new HashSet < uint > ( hs1 ) ;
unique . SymmetricExceptWith ( hs2 ) ;
foreach ( var k in unique )
2020-05-26 22:58:48 +00:00
{
2022-06-18 18:04:24 +00:00
var name = GetKeyName ( k ) ;
if ( hs1 . Contains ( k ) )
2020-01-20 06:29:50 +00:00
{
2022-06-18 18:04:24 +00:00
var b = s1 . GetBlock ( k ) ;
RemovedKeys . Add ( $"{name} - {b.Type}" ) ;
}
else
{
var b = s2 . GetBlock ( k ) ;
AddedKeys . Add ( $"{name} - {b.Type}" ) ;
2020-01-20 06:29:50 +00:00
}
2020-05-26 22:58:48 +00:00
}
2022-06-18 18:04:24 +00:00
}
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
private void LoadChanged ( SCBlockAccessor s1 , SCBlockAccessor s2 , IEnumerable < uint > shared )
{
foreach ( var k in shared )
2020-05-26 22:58:48 +00:00
{
2022-06-18 18:04:24 +00:00
var x1 = s1 . GetBlock ( k ) ;
var x2 = s2 . GetBlock ( k ) ;
var name = GetKeyName ( x1 . Key ) ;
if ( x1 . Type ! = x2 . Type )
2020-01-20 06:29:50 +00:00
{
2022-06-18 18:04:24 +00:00
TypesChanged . Add ( $"{name} - {x1.Type} => {x2.Type}" ) ;
continue ;
}
if ( x1 . Data . Length ! = x2 . Data . Length )
{
ValueChanged . Add ( $"{name} - Length: {x1.Data.Length} => {x2.Data.Length}" ) ;
continue ;
2020-01-20 06:29:50 +00:00
}
2022-06-18 18:04:24 +00:00
if ( x1 . Data . Length = = 0 )
continue ;
2020-01-24 07:19:38 +00:00
2022-06-18 18:04:24 +00:00
if ( x1 . Type is SCTypeCode . Object or SCTypeCode . Array )
2020-01-24 07:19:38 +00:00
{
2022-06-18 18:04:24 +00:00
if ( ! x1 . Data . SequenceEqual ( x2 . Data ) )
2022-11-25 01:42:17 +00:00
ValueChanged . Add ( $"Bytes Changed: Length: {x1.Data.Length} {name}" ) ;
2022-06-18 18:04:24 +00:00
continue ;
2020-01-24 07:19:38 +00:00
}
2022-06-18 18:04:24 +00:00
var val1 = x1 . GetValue ( ) ;
var val2 = x2 . GetValue ( ) ;
if ( Equals ( val1 , val2 ) )
continue ;
if ( val1 is ulong u1 & & val2 is ulong u2 )
ValueChanged . Add ( $"{name} - {u1:X8} => {u2:x8}" ) ;
else
ValueChanged . Add ( $"{name} - {val1} => {val2}" ) ;
}
}
2020-01-20 06:29:50 +00:00
2022-06-18 18:04:24 +00:00
private static Dictionary < uint , string > GetKeyNames ( SCBlockAccessor s1 , IEnumerable < SCBlock > b1 , IEnumerable < SCBlock > b2 )
{
var aType = s1 . GetType ( ) ;
var b1n = aType . GetAllPropertiesOfType < IDataIndirect > ( s1 ) ;
var names = aType . GetAllConstantsOfType < uint > ( ) ;
2023-01-22 04:02:33 +00:00
ReplaceLabels ( b1n , b1 ) ;
ReplaceLabels ( b1n , b2 ) ;
2020-01-20 06:29:50 +00:00
2023-01-22 04:02:33 +00:00
// Replace all const name labels with explicit block property names if they exist.
// Since our Block classes do not retain the u32 key they originated from, we need to compare the buffers to see if they match.
// Could have just checked ContainsKey then indexed in, but I wanted to play with the higher performance API method to get the bucket and mutate directly.
void ReplaceLabels ( Dictionary < IDataIndirect , string > list , IEnumerable < SCBlock > blocks )
2022-06-18 18:04:24 +00:00
{
foreach ( var b in blocks )
2020-01-20 06:29:50 +00:00
{
2022-06-18 18:04:24 +00:00
var match = list . FirstOrDefault ( z = > ReferenceEquals ( z . Key . Data , b . Data ) ) ;
2023-01-22 04:02:33 +00:00
if ( match . Value is not { } x )
continue ;
ref var exist = ref CollectionsMarshal . GetValueRefOrNullRef ( names , b . Key ) ;
if ( ! Unsafe . IsNullRef ( ref exist ) )
exist = x ;
2020-01-20 06:29:50 +00:00
}
}
2022-06-18 18:04:24 +00:00
return names ;
}
public IReadOnlyList < string > Summary ( )
{
var result = new List < string > ( ) ;
AddIfPresent ( result , AddedKeys , "Blocks Added:" ) ;
AddIfPresent ( result , RemovedKeys , "Blocks Removed:" ) ;
AddIfPresent ( result , TypesChanged , "BlockType Changed:" ) ;
2022-11-25 01:42:17 +00:00
AddIfPresent ( result , ValueChanged , "Value Changed:" , true ) ;
2022-06-18 18:04:24 +00:00
return result ;
2022-11-25 01:42:17 +00:00
static void AddIfPresent ( List < string > result , ICollection < string > list , string hdr , bool sort = false )
2022-06-18 18:04:24 +00:00
{
if ( list . Count = = 0 )
return ;
result . Add ( hdr ) ;
2022-11-25 01:42:17 +00:00
if ( ! sort )
result . AddRange ( list ) ;
else
result . AddRange ( list . OrderBy ( z = > z ) ) ;
2022-06-18 18:04:24 +00:00
result . Add ( string . Empty ) ;
}
2020-01-20 06:29:50 +00:00
}
2022-06-18 18:04:24 +00:00
}