using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; using BlockSpace.Voxels; using UnityEngine; namespace BlockSpace.Worlds { public static class WorldGroupsCodec { [Serializable] public struct WorldGroupRecord { public Vector3Int position; public byte packedRotation; public Vector3Int size; public byte[] voxels; } [Serializable] public struct NodePortValueRecord { public string portName; public BSValue value; } [Serializable] public struct WorldNodeRecord { public int nodeId; public string definitionName; public Vector3 position; public Quaternion rotation; public NodePortValueRecord[] inputValues; public NodePortValueRecord[] outputValues; } [Serializable] public struct WorldWireRecord { public int outputNodeId; public string outputPortName; public int inputNodeId; public string inputPortName; } [Serializable] public struct WorldSaveData { public List groupRecords; public List nodeRecords; public List wireRecords; } private static readonly byte[] Magic = Encoding.ASCII.GetBytes("BSW1"); private static readonly byte[] NodesSectionMagic = Encoding.ASCII.GetBytes("BSN1"); private static readonly byte[] WiresSectionMagic = Encoding.ASCII.GetBytes("BSR1"); private const string GroupRuntimeWirePrefix = "__group_runtime__:"; private const string GroupSavedWirePrefix = "__group_saved__:"; public static WorldWireRecord CreateGroupRuntimeWireRecord(int groupId, int inputNodeId, string inputPortName) { return new WorldWireRecord { outputNodeId = 0, outputPortName = "__group_runtime__:" + Mathf.Max(0, groupId), inputNodeId = inputNodeId, inputPortName = inputPortName }; } public static WorldWireRecord CreateGroupSavedWireRecord(int savedGroupIndex, int inputNodeId, string inputPortName) { return new WorldWireRecord { outputNodeId = 0, outputPortName = "__group_saved__:" + Mathf.Max(0, savedGroupIndex), inputNodeId = inputNodeId, inputPortName = inputPortName }; } public static bool TryGetGroupRuntimeWireSourceId(WorldWireRecord record, out int groupId) { return TryParseGroupWireValue(record, "__group_runtime__:", out groupId); } public static bool TryGetGroupSavedWireIndex(WorldWireRecord record, out int savedGroupIndex) { return TryParseGroupWireValue(record, "__group_saved__:", out savedGroupIndex); } public static byte[] EncodeGzip(IEnumerable groups) { return EncodeGzip(groups, null, null); } public static byte[] EncodeGzip(IEnumerable groups, IEnumerable nodes, IEnumerable wires) { List list = BuildGroupRecords(groups); List list2 = ((nodes != null) ? new List(nodes) : new List()); List list3 = ((wires != null) ? new List(wires) : new List()); using MemoryStream memoryStream = new MemoryStream(); memoryStream.Write(Magic, 0, Magic.Length); WriteVarUInt(memoryStream, (uint)list.Count); for (int i = 0; i < list.Count; i++) { WriteGroupRecord(memoryStream, list[i]); } if (list2.Count > 0) { memoryStream.Write(NodesSectionMagic, 0, NodesSectionMagic.Length); WriteVarUInt(memoryStream, (uint)list2.Count); for (int j = 0; j < list2.Count; j++) { WriteNodeRecord(memoryStream, list2[j]); } } if (list3.Count > 0) { memoryStream.Write(WiresSectionMagic, 0, WiresSectionMagic.Length); WriteVarUInt(memoryStream, (uint)list3.Count); for (int k = 0; k < list3.Count; k++) { WriteWireRecord(memoryStream, list3[k]); } } memoryStream.Position = 0L; using MemoryStream memoryStream2 = new MemoryStream(); using (GZipStream destination = new GZipStream(memoryStream2, System.IO.Compression.CompressionLevel.Optimal, leaveOpen: true)) { memoryStream.CopyTo(destination); } return memoryStream2.ToArray(); } public static bool TryDecodeGzip(byte[] gzipBytes, out List records) { WorldSaveData saveData; bool result = TryDecodeGzip(gzipBytes, out saveData); records = saveData.groupRecords ?? new List(); return result; } private static bool TryParseGroupWireValue(WorldWireRecord record, string prefix, out int value) { value = 0; if (record.outputNodeId > 0 || string.IsNullOrEmpty(record.outputPortName) || !record.outputPortName.StartsWith(prefix, StringComparison.Ordinal)) { return false; } if (int.TryParse(record.outputPortName.Substring(prefix.Length), out value)) { return value >= 0; } return false; } public static bool TryDecodeGzip(byte[] gzipBytes, out WorldSaveData saveData) { saveData = new WorldSaveData { groupRecords = new List(), nodeRecords = new List(), wireRecords = new List() }; if (gzipBytes == null || gzipBytes.Length == 0) { return true; } byte[] array; try { array = DecompressGzip(gzipBytes); } catch { return false; } if (array.Length < Magic.Length) { return true; } using MemoryStream memoryStream = new MemoryStream(array); byte[] array2 = new byte[Magic.Length]; if (memoryStream.Read(array2, 0, array2.Length) != array2.Length) { return false; } for (int i = 0; i < Magic.Length; i++) { if (array2[i] != Magic[i]) { return true; } } uint num; try { num = ReadVarUInt(memoryStream); } catch { return false; } for (uint num2 = 0u; num2 < num; num2++) { if (!TryReadGroupRecord(memoryStream, out var record)) { return false; } saveData.groupRecords.Add(record); } string sectionName; while (memoryStream.Position < memoryStream.Length && TryReadSectionHeader(memoryStream, out sectionName)) { if (sectionName == "nodes") { if (!TryReadNodeSection(memoryStream, saveData.nodeRecords)) { return false; } continue; } if (!(sectionName == "wires")) { break; } if (!TryReadWireSection(memoryStream, saveData.wireRecords)) { return false; } } return true; } private static List BuildGroupRecords(IEnumerable groups) { List list = new List(); if (groups == null) { return list; } foreach (VoxelGroup group in groups) { if (!(group == null) && group.size.x > 0 && group.size.y > 0 && group.size.z > 0 && group.voxels != null) { list.Add(new WorldGroupRecord { position = group.position, packedRotation = group.packedRotation, size = group.size, voxels = (byte[])group.voxels.Clone() }); } } return list; } private static void WriteGroupRecord(Stream stream, WorldGroupRecord record) { WriteVarInt(stream, record.position.x); WriteVarInt(stream, record.position.y); WriteVarInt(stream, record.position.z); stream.WriteByte(record.packedRotation); WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.x)); WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.y)); WriteVarUInt(stream, (uint)Mathf.Max(0, record.size.z)); WriteVoxelsRle(stream, record.voxels); } private static bool TryReadGroupRecord(Stream stream, out WorldGroupRecord record) { record = default(WorldGroupRecord); try { int x = ReadVarInt(stream); int y = ReadVarInt(stream); int z = ReadVarInt(stream); int num = stream.ReadByte(); if (num < 0) { return false; } uint num2 = ReadVarUInt(stream); uint num3 = ReadVarUInt(stream); uint num4 = ReadVarUInt(stream); int num5 = (int)(num2 * num3 * num4); if (num5 < 0) { return false; } byte[] voxels = ReadVoxelsRle(stream, num5); record = new WorldGroupRecord { position = new Vector3Int(x, y, z), packedRotation = (byte)num, size = new Vector3Int((int)num2, (int)num3, (int)num4), voxels = voxels }; return true; } catch { return false; } } private static bool TryReadSectionHeader(Stream stream, out string sectionName) { sectionName = string.Empty; if (stream.Position + 4 > stream.Length) { return false; } byte[] array = new byte[4]; if (stream.Read(array, 0, array.Length) != array.Length) { return false; } if (SectionMatches(array, NodesSectionMagic)) { sectionName = "nodes"; return true; } if (SectionMatches(array, WiresSectionMagic)) { sectionName = "wires"; return true; } return false; } private static bool TryReadNodeSection(Stream stream, List records) { try { uint num = ReadVarUInt(stream); for (uint num2 = 0u; num2 < num; num2++) { records.Add(ReadNodeRecord(stream)); } return true; } catch { return false; } } private static bool TryReadWireSection(Stream stream, List records) { try { uint num = ReadVarUInt(stream); for (uint num2 = 0u; num2 < num; num2++) { records.Add(ReadWireRecord(stream)); } return true; } catch { return false; } } private static void WriteNodeRecord(Stream stream, WorldNodeRecord record) { WriteVarInt(stream, record.nodeId); WriteString(stream, record.definitionName); WriteFloat(stream, record.position.x); WriteFloat(stream, record.position.y); WriteFloat(stream, record.position.z); WriteFloat(stream, record.rotation.x); WriteFloat(stream, record.rotation.y); WriteFloat(stream, record.rotation.z); WriteFloat(stream, record.rotation.w); WritePortValueRecords(stream, record.inputValues); WritePortValueRecords(stream, record.outputValues); } private static WorldNodeRecord ReadNodeRecord(Stream stream) { return new WorldNodeRecord { nodeId = ReadVarInt(stream), definitionName = ReadString(stream), position = new Vector3(ReadFloat(stream), ReadFloat(stream), ReadFloat(stream)), rotation = new Quaternion(ReadFloat(stream), ReadFloat(stream), ReadFloat(stream), ReadFloat(stream)), inputValues = ReadPortValueRecords(stream), outputValues = ReadPortValueRecords(stream) }; } private static void WriteWireRecord(Stream stream, WorldWireRecord record) { WriteVarInt(stream, record.outputNodeId); WriteString(stream, record.outputPortName); WriteVarInt(stream, record.inputNodeId); WriteString(stream, record.inputPortName); } private static WorldWireRecord ReadWireRecord(Stream stream) { return new WorldWireRecord { outputNodeId = ReadVarInt(stream), outputPortName = ReadString(stream), inputNodeId = ReadVarInt(stream), inputPortName = ReadString(stream) }; } private static void WritePortValueRecords(Stream stream, NodePortValueRecord[] records) { if (records == null) { WriteVarUInt(stream, 0u); return; } WriteVarUInt(stream, (uint)records.Length); for (int i = 0; i < records.Length; i++) { WriteString(stream, records[i].portName); WriteBSValue(stream, records[i].value); } } private static NodePortValueRecord[] ReadPortValueRecords(Stream stream) { uint num = ReadVarUInt(stream); NodePortValueRecord[] array = new NodePortValueRecord[num]; for (int i = 0; i < num; i++) { array[i] = new NodePortValueRecord { portName = ReadString(stream), value = ReadBSValue(stream) }; } return array; } private static void WriteBSValue(Stream stream, BSValue value) { value = value.Normalize(); stream.WriteByte((byte)value.Kind); switch (value.Kind) { case BSValueKind.Number: WriteDouble(stream, value.Number); break; case BSValueKind.Text: WriteString(stream, value.Text); break; case BSValueKind.Bool: stream.WriteByte((byte)(value.Bool ? 1u : 0u)); break; case BSValueKind.Player: WriteVarInt(stream, value.PlayerId); break; case BSValueKind.Group: WriteVarInt(stream, value.GroupId); break; } } private static BSValue ReadBSValue(Stream stream) { return (BSValueKind)stream.ReadByte() switch { BSValueKind.Number => BSValue.FromNumber(ReadDouble(stream)), BSValueKind.Text => BSValue.FromText(ReadString(stream)), BSValueKind.Bool => BSValue.FromBool(stream.ReadByte() != 0), BSValueKind.Player => BSValue.FromPlayer(ReadVarInt(stream)), BSValueKind.Group => BSValue.FromGroup(ReadVarInt(stream)), _ => BSValue.None, }; } private static bool SectionMatches(byte[] a, byte[] b) { if (a == null || b == null || a.Length != b.Length) { return false; } for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) { return false; } } return true; } private static void WriteVoxelsRle(Stream stream, byte[] voxels) { if (voxels == null || voxels.Length == 0) { return; } int j; for (int i = 0; i < voxels.Length; i += j) { byte b = voxels[i]; j = 1; for (int num = voxels.Length - i; j < num && voxels[i + j] == b; j++) { } stream.WriteByte(b); WriteVarUInt(stream, (uint)j); } } private static byte[] ReadVoxelsRle(Stream stream, int expectedLength) { if (expectedLength <= 0) { return Array.Empty(); } byte[] array = new byte[expectedLength]; int num3; for (int i = 0; i < expectedLength; i += num3) { int num = stream.ReadByte(); if (num < 0) { throw new EndOfStreamException(); } uint num2 = ReadVarUInt(stream); if (num2 == 0) { throw new InvalidDataException("RLE run length was 0."); } int val = expectedLength - i; num3 = (int)Math.Min(num2, (uint)val); for (int j = 0; j < num3; j++) { array[i + j] = (byte)num; } } return array; } private static byte[] DecompressGzip(byte[] gzipBytes) { using MemoryStream stream = new MemoryStream(gzipBytes); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); gZipStream.CopyTo(memoryStream); return memoryStream.ToArray(); } private static void WriteString(Stream stream, string value) { string s = value ?? string.Empty; byte[] bytes = Encoding.UTF8.GetBytes(s); WriteVarUInt(stream, (uint)bytes.Length); stream.Write(bytes, 0, bytes.Length); } private static string ReadString(Stream stream) { uint num = ReadVarUInt(stream); if (num == 0) { return string.Empty; } byte[] array = new byte[num]; if (stream.Read(array, 0, array.Length) != array.Length) { throw new EndOfStreamException(); } return Encoding.UTF8.GetString(array); } private static void WriteFloat(Stream stream, float value) { byte[] bytes = BitConverter.GetBytes(value); stream.Write(bytes, 0, bytes.Length); } private static float ReadFloat(Stream stream) { byte[] array = new byte[4]; if (stream.Read(array, 0, array.Length) != array.Length) { throw new EndOfStreamException(); } return BitConverter.ToSingle(array, 0); } private static void WriteDouble(Stream stream, double value) { byte[] bytes = BitConverter.GetBytes(value); stream.Write(bytes, 0, bytes.Length); } private static double ReadDouble(Stream stream) { byte[] array = new byte[8]; if (stream.Read(array, 0, array.Length) != array.Length) { throw new EndOfStreamException(); } return BitConverter.ToDouble(array, 0); } private static void WriteVarInt(Stream stream, int value) { uint value2 = (uint)((value << 1) ^ (value >> 31)); WriteVarUInt(stream, value2); } private static int ReadVarInt(Stream stream) { uint num = ReadVarUInt(stream); return (int)((num >> 1) ^ (0 - (num & 1))); } private static void WriteVarUInt(Stream stream, uint value) { while (value >= 128) { stream.WriteByte((byte)(value | 0x80)); value >>= 7; } stream.WriteByte((byte)value); } private static uint ReadVarUInt(Stream stream) { uint num = 0u; for (int i = 0; i < 35; i += 7) { int num2 = stream.ReadByte(); if (num2 < 0) { throw new EndOfStreamException(); } num |= (uint)((num2 & 0x7F) << i); if ((num2 & 0x80) == 0) { return num; } } throw new InvalidDataException("VarUInt was too long."); } } }