673 lines
16 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|