PKHeX/PKHeX.Core/Saves/Encryption/SwishCrypto/SCBlock.cs
Kurt 066aedfc21 Separate scblock ctor's
No longer need to set byte[] twice
Add value-setter checks for bad mutations (size change, bool1<->bool2 only)
2021-06-03 12:35:39 -07:00

176 lines
6.5 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
namespace PKHeX.Core
{
/// <summary>
/// Block of <see cref="Data"/> obtained from a <see cref="SwishCrypto"/> encrypted block storage binary.
/// </summary>
public sealed class SCBlock
{
/// <summary>
/// Used to encrypt the rest of the block.
/// </summary>
public readonly uint Key;
/// <summary>
/// What kind of block is it?
/// </summary>
public SCTypeCode Type { get; private set; }
/// <summary>
/// For <see cref="SCTypeCode.Array"/>: What kind of array is it?
/// </summary>
public readonly SCTypeCode SubType;
/// <summary>
/// Decrypted data for this block.
/// </summary>
public readonly byte[] Data;
/// <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);
}
internal SCBlock(uint key, SCTypeCode type) : this(key, type, Array.Empty<byte>())
{
}
internal SCBlock(uint key, SCTypeCode type, byte[] arr)
{
Key = key;
Type = type;
Data = arr;
}
internal SCBlock(uint key, byte[] arr, SCTypeCode subType)
{
Key = key;
Type = SCTypeCode.Array;
Data = arr;
SubType = subType;
}
public bool HasValue() => Type > SCTypeCode.Array;
public object GetValue() => Type.GetValue(Data);
public void SetValue(object value) => Type.SetValue(Data, value);
public SCBlock Clone()
{
if (Data.Length == 0)
return new SCBlock(Key, Type);
if (SubType == 0)
return new SCBlock(Key, Type, (byte[]) Data.Clone());
return new SCBlock(Key, (byte[])Data.Clone(), SubType);
}
/// <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>
public static SCBlock ReadFromOffset(byte[] data, ref int offset)
{
// Create block, parse its key.
var key = BitConverter.ToUInt32(data, offset);
offset += 4;
var xk = new SCXorShift32(key);
// Parse the block's type
//var block =
var type = (SCTypeCode)(data[offset++] ^ xk.Next());
switch (type)
{
case SCTypeCode.Bool1:
case SCTypeCode.Bool2:
case SCTypeCode.Bool3:
// Block types are empty, and have no extra data.
Debug.Assert(type != SCTypeCode.Bool3); // invalid type, haven't seen it used yet
return new SCBlock(key, type);
case SCTypeCode.Object: // Cast raw bytes to Object
{
var num_bytes = BitConverter.ToUInt32(data, offset) ^ xk.Next32();
offset += 4;
var arr = new byte[num_bytes];
for (int i = 0; i < arr.Length; i++)
arr[i] = (byte)(data[offset++] ^ xk.Next());
return new SCBlock(key, type, arr);
}
case SCTypeCode.Array: // Cast raw bytes to SubType[]
{
var num_entries = BitConverter.ToUInt32(data, offset) ^ xk.Next32();
offset += 4;
var sub = (SCTypeCode)(data[offset++] ^ xk.Next());
var num_bytes = num_entries * sub.GetTypeSize();
var arr = new byte[num_bytes];
for (int i = 0; i < arr.Length; i++)
arr[i] = (byte)(data[offset++] ^ xk.Next());
#if DEBUG
Debug.Assert(sub > SCTypeCode.Array || Array.TrueForAll(arr, z => z <= 1));
#endif
return new SCBlock(key, arr, sub);
}
default: // Single Value Storage
{
var num_bytes = type.GetTypeSize();
var arr = new byte[num_bytes];
for (int i = 0; i < arr.Length; i++)
arr[i] = (byte)(data[offset++] ^ xk.Next());
return new SCBlock(key, type, arr);
}
}
}
}
}