using System; using System.Diagnostics; using System.IO; namespace PKHeX.Core { /// /// Block of obtained from a encrypted block storage binary. /// public sealed class SCBlock { /// /// Used to encrypt the rest of the block. /// public readonly uint Key; /// /// What kind of block is it? /// public SCTypeCode Type { get; private set; } /// /// For : What kind of array is it? /// public readonly SCTypeCode SubType; /// /// Decrypted data for this block. /// public readonly byte[] Data; /// /// Changes the block's Boolean type. Will throw if the old / new is not boolean. /// /// New boolean type to set. /// Will throw if the requested block state changes are incorrect. 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; } /// /// Replaces the current with a same-sized array . /// /// New array to load (copy from). /// Will throw if the requested block state changes are incorrect. public void ChangeData(ReadOnlySpan 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()) { } 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); } /// /// 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 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); } } } } }