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