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