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; } } }