using System; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; namespace SmartNav.Model.Navigation; public static class WaterCacheFormat { public const uint FileMagic = 1129469015u; public const int FileFormatVersion = 1; public const string FileName = "water-cache.bin"; private const int MaxTerritoryCount = 65536; private const int ClassBytes = 65536; private const int SurfaceBytes = 131072; private const int PayloadBytes = 196608; public static void Write(Stream stream, string gameVersion, IReadOnlyList maps) { ArgumentNullException.ThrowIfNull(stream, "stream"); ArgumentNullException.ThrowIfNull(gameVersion, "gameVersion"); ArgumentNullException.ThrowIfNull(maps, "maps"); List list = new List(maps.Count); foreach (TerritoryWaterMap map in maps) { if (map.HasWater) { list.Add(map); } } list.Sort((TerritoryWaterMap left, TerritoryWaterMap right) => left.TerritoryId.CompareTo(right.TerritoryId)); using BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); binaryWriter.Write(1129469015u); binaryWriter.Write(1); binaryWriter.Write(gameVersion); binaryWriter.Write(list.Count); foreach (TerritoryWaterMap item in list) { byte[] array = new byte[196608]; item.Classes.CopyTo(array.AsSpan(0, 65536)); ReadOnlySpan surfaceYRaw = item.SurfaceYRaw; for (int num = 0; num < surfaceYRaw.Length; num++) { BinaryPrimitives.WriteUInt16LittleEndian(array.AsSpan(65536 + num * 2), surfaceYRaw[num]); } using MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(array); } binaryWriter.Write(item.TerritoryId); binaryWriter.Write((int)memoryStream.Length); binaryWriter.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); } } public static (string GameVersion, List Maps) Read(Stream stream) { ArgumentNullException.ThrowIfNull(stream, "stream"); using BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); uint num = binaryReader.ReadUInt32(); if (num != 1129469015) { throw new InvalidDataException($"water cache has magic 0x{num:X8}, expected 0x{1129469015:X8}"); } int num2 = binaryReader.ReadInt32(); if (num2 != 1) { throw new InvalidDataException($"water cache has format version {num2}, expected {1}"); } string item = binaryReader.ReadString(); int num3 = binaryReader.ReadInt32(); if (num3 < 0 || num3 > 65536) { throw new InvalidDataException($"water cache declares {num3} territories"); } List list = new List(num3); for (int i = 0; i < num3; i++) { ushort num4 = binaryReader.ReadUInt16(); int num5 = binaryReader.ReadInt32(); if (num5 < 0) { throw new InvalidDataException($"territory {num4} declares {num5} compressed bytes"); } byte[] array = binaryReader.ReadBytes(num5); if (array.Length != num5) { throw new InvalidDataException($"territory {num4} is truncated: {array.Length} of {num5} bytes"); } byte[] array2 = new byte[196608]; using (DeflateStream deflateStream = new DeflateStream(new MemoryStream(array, writable: false), CompressionMode.Decompress)) { deflateStream.ReadExactly(array2); } byte[] subArray = array2[..65536]; byte[] array3 = subArray; foreach (byte b in array3) { if (b > 2) { throw new InvalidDataException($"territory {num4} has cell class {b}, expected 0..{2}"); } } ushort[] array4 = new ushort[65536]; for (int k = 0; k < array4.Length; k++) { array4[k] = BinaryPrimitives.ReadUInt16LittleEndian(array2.AsSpan(65536 + k * 2)); } list.Add(new TerritoryWaterMap(num4, subArray, array4)); } return (GameVersion: item, Maps: list); } }