Files
BlockSpace-v1.5.0b/Assets/Scripts/Assembly-CSharp/BlockSpace/Worlds/WorldGroupsCodec.cs
T
2026-06-01 17:19:55 +02:00

673 lines
16 KiB
C#

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<WorldGroupRecord> groupRecords;
public List<WorldNodeRecord> nodeRecords;
public List<WorldWireRecord> 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<VoxelGroup> groups)
{
return EncodeGzip(groups, null, null);
}
public static byte[] EncodeGzip(IEnumerable<VoxelGroup> groups, IEnumerable<WorldNodeRecord> nodes, IEnumerable<WorldWireRecord> wires)
{
List<WorldGroupRecord> list = BuildGroupRecords(groups);
List<WorldNodeRecord> list2 = ((nodes != null) ? new List<WorldNodeRecord>(nodes) : new List<WorldNodeRecord>());
List<WorldWireRecord> list3 = ((wires != null) ? new List<WorldWireRecord>(wires) : new List<WorldWireRecord>());
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<WorldGroupRecord> records)
{
WorldSaveData saveData;
bool result = TryDecodeGzip(gzipBytes, out saveData);
records = saveData.groupRecords ?? new List<WorldGroupRecord>();
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<WorldGroupRecord>(),
nodeRecords = new List<WorldNodeRecord>(),
wireRecords = new List<WorldWireRecord>()
};
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<WorldGroupRecord> BuildGroupRecords(IEnumerable<VoxelGroup> groups)
{
List<WorldGroupRecord> list = new List<WorldGroupRecord>();
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<WorldNodeRecord> 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<WorldWireRecord> 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>();
}
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.");
}
}
}