2020-01-19 08:55:10 +00:00
using System ;
using System.Diagnostics ;
using System.IO ;
2022-01-03 05:35:59 +00:00
using static System . Buffers . Binary . BinaryPrimitives ;
2020-01-19 08:55:10 +00:00
namespace PKHeX.Core
{
/// <summary>
/// Block of <see cref="Data"/> obtained from a <see cref="SwishCrypto"/> encrypted block storage binary.
/// </summary>
2020-06-27 18:38:21 +00:00
public sealed class SCBlock
2020-01-19 08:55:10 +00:00
{
/// <summary>
/// Used to encrypt the rest of the block.
/// </summary>
public readonly uint Key ;
/// <summary>
/// What kind of block is it?
/// </summary>
2021-06-03 19:35:39 +00:00
public SCTypeCode Type { get ; private set ; }
2020-01-19 08:55:10 +00:00
/// <summary>
/// For <see cref="SCTypeCode.Array"/>: What kind of array is it?
/// </summary>
2021-06-03 19:35:39 +00:00
public readonly SCTypeCode SubType ;
2020-01-19 08:55:10 +00:00
/// <summary>
/// Decrypted data for this block.
/// </summary>
2021-06-03 19:35:39 +00:00
public readonly byte [ ] Data ;
2020-01-19 08:55:10 +00:00
2021-06-03 19:35:39 +00:00
/// <summary>
/// Changes the block's Boolean type. Will throw if the old / new <see cref="Type"/> is not boolean.
/// </summary>
/// <param name="value">New boolean type to set.</param>
/// <remarks>Will throw if the requested block state changes are incorrect.</remarks>
public void ChangeBooleanType ( SCTypeCode value )
{
if ( Type is not ( SCTypeCode . Bool1 or SCTypeCode . Bool2 ) | | value is not ( SCTypeCode . Bool1 or SCTypeCode . Bool2 ) )
throw new InvalidOperationException ( $"Cannot change {Type} to {value}." ) ;
Type = value ;
}
/// <summary>
/// Replaces the current <see cref="Data"/> with a same-sized array <see cref="value"/>.
/// </summary>
/// <param name="value">New array to load (copy from).</param>
/// <remarks>Will throw if the requested block state changes are incorrect.</remarks>
public void ChangeData ( ReadOnlySpan < byte > value )
{
if ( value . Length ! = Data . Length )
throw new InvalidOperationException ( $"Cannot change size of {Type} block from {Data.Length} to {value.Length}." ) ;
value . CopyTo ( Data ) ;
}
2022-02-10 19:08:30 +00:00
/// <summary>
/// Creates a new block reference to indicate a boolean value via the <see cref="type"/> (no data).
/// </summary>
/// <param name="key">Hash key</param>
/// <param name="type">Value the block has</param>
2021-06-03 19:35:39 +00:00
internal SCBlock ( uint key , SCTypeCode type ) : this ( key , type , Array . Empty < byte > ( ) )
{
}
2022-02-10 19:08:30 +00:00
/// <summary>
/// Creates a new block reference to indicate an object or single primitive value.
/// </summary>
/// <param name="key">Hash key</param>
/// <param name="type">Type of data that can be read</param>
/// <param name="arr">Backing byte array to interpret as a typed value</param>
2021-06-03 19:35:39 +00:00
internal SCBlock ( uint key , SCTypeCode type , byte [ ] arr )
{
Key = key ;
Type = type ;
Data = arr ;
}
2022-02-10 19:08:30 +00:00
/// <summary>
/// Creates a new block reference to indicate an array of primitive values.
/// </summary>
/// <param name="key">Hash key</param>
/// <param name="arr">Backing byte array to read primitives from</param>
/// <param name="subType">Primitive value type</param>
2021-06-03 19:35:39 +00:00
internal SCBlock ( uint key , byte [ ] arr , SCTypeCode subType )
{
Key = key ;
Type = SCTypeCode . Array ;
Data = arr ;
SubType = subType ;
}
2020-01-19 08:55:10 +00:00
2022-02-10 19:08:30 +00:00
/// <summary> Indiciates if the block represents a single primitive value. </summary>
2020-01-19 08:55:10 +00:00
public bool HasValue ( ) = > Type > SCTypeCode . Array ;
2022-02-10 19:08:30 +00:00
/// <summary> Returns a boxed reference to a single primitive value. Throws an exception if the block does not represent a single primitive value. </summary>
2020-01-19 08:55:10 +00:00
public object GetValue ( ) = > Type . GetValue ( Data ) ;
2022-02-10 19:08:30 +00:00
/// <summary> Sets a boxed primitive value to the block data. Throws an exception if the block does not represent a single primitive value, or if the primitive type does not match. </summary>
/// <param name="value">Boxed primitive value to be set to the block</param>
2020-01-19 08:55:10 +00:00
public void SetValue ( object value ) = > Type . SetValue ( Data , value ) ;
2022-02-10 19:08:30 +00:00
/// <summary>
/// Creates a deep copy of the block.
/// </summary>
2020-01-19 08:55:10 +00:00
public SCBlock Clone ( )
{
2021-06-03 19:35:39 +00:00
if ( Data . Length = = 0 )
return new SCBlock ( Key , Type ) ;
2022-01-03 05:35:59 +00:00
var clone = Data . AsSpan ( ) . ToArray ( ) ;
2021-06-03 19:35:39 +00:00
if ( SubType = = 0 )
2022-01-03 05:35:59 +00:00
return new SCBlock ( Key , Type , clone ) ;
return new SCBlock ( Key , clone , SubType ) ;
2020-01-19 08:55:10 +00:00
}
/// <summary>
/// Encrypts the <see cref="Data"/> according to the <see cref="Type"/> and <see cref="SubType"/>.
/// </summary>
public void WriteBlock ( BinaryWriter bw )
{
var xk = new SCXorShift32 ( Key ) ;
bw . Write ( Key ) ;
bw . Write ( ( byte ) ( ( byte ) Type ^ xk . Next ( ) ) ) ;
if ( Type = = SCTypeCode . Object )
{
bw . Write ( ( uint ) Data . Length ^ xk . Next32 ( ) ) ;
}
else if ( Type = = SCTypeCode . Array )
{
var entries = ( uint ) ( Data . Length / SubType . GetTypeSize ( ) ) ;
bw . Write ( entries ^ xk . Next32 ( ) ) ;
bw . Write ( ( byte ) ( ( byte ) SubType ^ xk . Next ( ) ) ) ;
}
foreach ( var b in Data )
bw . Write ( ( byte ) ( b ^ xk . Next ( ) ) ) ;
}
/// <summary>
/// Reads a new <see cref="SCBlock"/> object from the <see cref="data"/>, determining the <see cref="Type"/> and <see cref="SubType"/> during read.
/// </summary>
/// <param name="data">Decrypted data</param>
/// <param name="offset">Offset the block is to be read from (modified to offset by the amount of bytes consumed).</param>
/// <returns>New object containing all info for the block.</returns>
2022-01-03 05:35:59 +00:00
public static SCBlock ReadFromOffset ( ReadOnlySpan < byte > data , ref int offset )
2020-01-19 08:55:10 +00:00
{
2022-02-10 19:08:30 +00:00
// Get key, initialize xorshift to decrypt
2022-01-03 05:35:59 +00:00
var key = ReadUInt32LittleEndian ( data [ offset . . ] ) ;
2020-01-19 08:55:10 +00:00
offset + = 4 ;
var xk = new SCXorShift32 ( key ) ;
// Parse the block's type
2021-06-03 19:35:39 +00:00
var type = ( SCTypeCode ) ( data [ offset + + ] ^ xk . Next ( ) ) ;
2020-01-19 08:55:10 +00:00
2021-06-03 19:35:39 +00:00
switch ( type )
2020-01-19 08:55:10 +00:00
{
case SCTypeCode . Bool1 :
case SCTypeCode . Bool2 :
case SCTypeCode . Bool3 :
// Block types are empty, and have no extra data.
2021-06-03 19:35:39 +00:00
Debug . Assert ( type ! = SCTypeCode . Bool3 ) ; // invalid type, haven't seen it used yet
return new SCBlock ( key , type ) ;
2020-01-19 08:55:10 +00:00
case SCTypeCode . Object : // Cast raw bytes to Object
{
2022-01-03 05:35:59 +00:00
var num_bytes = ReadInt32LittleEndian ( data [ offset . . ] ) ^ ( int ) xk . Next32 ( ) ;
2020-01-19 08:55:10 +00:00
offset + = 4 ;
2022-01-03 05:35:59 +00:00
var arr = data . Slice ( offset , num_bytes ) . ToArray ( ) ;
offset + = num_bytes ;
2020-01-19 08:55:10 +00:00
for ( int i = 0 ; i < arr . Length ; i + + )
2022-01-03 05:35:59 +00:00
arr [ i ] ^ = ( byte ) xk . Next ( ) ;
2021-06-03 19:35:39 +00:00
return new SCBlock ( key , type , arr ) ;
2020-01-19 08:55:10 +00:00
}
case SCTypeCode . Array : // Cast raw bytes to SubType[]
{
2022-01-03 05:35:59 +00:00
var num_entries = ReadInt32LittleEndian ( data [ offset . . ] ) ^ ( int ) xk . Next32 ( ) ;
2020-01-19 08:55:10 +00:00
offset + = 4 ;
2021-06-03 19:35:39 +00:00
var sub = ( SCTypeCode ) ( data [ offset + + ] ^ xk . Next ( ) ) ;
2020-01-19 08:55:10 +00:00
2021-06-03 19:35:39 +00:00
var num_bytes = num_entries * sub . GetTypeSize ( ) ;
2022-01-03 05:35:59 +00:00
var arr = data . Slice ( offset , num_bytes ) . ToArray ( ) ;
offset + = num_bytes ;
2020-01-19 08:55:10 +00:00
for ( int i = 0 ; i < arr . Length ; i + + )
2022-01-03 05:35:59 +00:00
arr [ i ] ^ = ( byte ) xk . Next ( ) ;
2020-01-19 08:55:10 +00:00
#if DEBUG
2022-02-05 01:35:15 +00:00
Debug . Assert ( sub > SCTypeCode . Array | | ( sub = = SCTypeCode . Bool3 & & Array . TrueForAll ( arr , z = > z < = 2 ) ) | | Array . TrueForAll ( arr , z = > z < = 1 ) ) ;
2020-01-19 08:55:10 +00:00
#endif
2021-06-03 19:35:39 +00:00
return new SCBlock ( key , arr , sub ) ;
2020-01-19 08:55:10 +00:00
}
default : // Single Value Storage
{
2021-06-03 19:35:39 +00:00
var num_bytes = type . GetTypeSize ( ) ;
2022-01-03 05:35:59 +00:00
var arr = data . Slice ( offset , num_bytes ) . ToArray ( ) ;
offset + = num_bytes ;
2020-01-19 08:55:10 +00:00
for ( int i = 0 ; i < arr . Length ; i + + )
2022-01-03 05:35:59 +00:00
arr [ i ] ^ = ( byte ) xk . Next ( ) ;
2021-06-03 19:35:39 +00:00
return new SCBlock ( key , type , arr ) ;
2020-01-19 08:55:10 +00:00
}
}
}
2022-02-26 00:10:49 +00:00
public void CopyFrom ( SCBlock other )
{
if ( Type . IsBoolean ( ) )
ChangeBooleanType ( other . Type ) ;
else
ChangeData ( other . Data ) ;
}
2020-01-19 08:55:10 +00:00
}
2021-01-07 23:34:26 +00:00
}