using System; using System.Buffers.Binary; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Server.Network; /// /// Handles outgoing packet compression for the network. /// public static class NetworkCompression { // UO packets may not exceed 64kb in length public const int BufferSize = 0x10000; // Optimal compression ratio is 2 / 8; worst compression ratio is 11 / 8 private const int MinimalCodeLength = 2; // Fixed overhead, in bits, per compression call private const int TerminalCodeLength = 4; // If our input exceeds this length, we cannot possibly compress it within the buffer private const int DefiniteOverflow = (BufferSize * 8 - TerminalCodeLength) / MinimalCodeLength; // Packed Table: High 16 bits = Length, Low 16 bits = Value private static readonly uint[] _packedHuffmanTable = new uint[257]; static NetworkCompression() { ReadOnlySpan rawTable = [ 0x2, 0x000, 0x5, 0x01F, 0x6, 0x022, 0x7, 0x034, 0x7, 0x075, 0x6, 0x028, 0x6, 0x03B, 0x7, 0x032, 0x8, 0x0E0, 0x8, 0x062, 0x7, 0x056, 0x8, 0x079, 0x9, 0x19D, 0x8, 0x097, 0x6, 0x02A, 0x7, 0x057, 0x8, 0x071, 0x8, 0x05B, 0x9, 0x1CC, 0x8, 0x0A7, 0x7, 0x025, 0x7, 0x04F, 0x8, 0x066, 0x8, 0x07D, 0x9, 0x191, 0x9, 0x1CE, 0x7, 0x03F, 0x9, 0x090, 0x8, 0x059, 0x8, 0x07B, 0x8, 0x091, 0x8, 0x0C6, 0x6, 0x02D, 0x9, 0x186, 0x8, 0x06F, 0x9, 0x093, 0xA, 0x1CC, 0x8, 0x05A, 0xA, 0x1AE, 0xA, 0x1C0, 0x9, 0x148, 0x9, 0x14A, 0x9, 0x082, 0xA, 0x19F, 0x9, 0x171, 0x9, 0x120, 0x9, 0x0E7, 0xA, 0x1F3, 0x9, 0x14B, 0x9, 0x100, 0x9, 0x190, 0x6, 0x013, 0x9, 0x161, 0x9, 0x125, 0x9, 0x133, 0x9, 0x195, 0x9, 0x173, 0x9, 0x1CA, 0x9, 0x086, 0x9, 0x1E9, 0x9, 0x0DB, 0x9, 0x1EC, 0x9, 0x08B, 0x9, 0x085, 0x5, 0x00A, 0x8, 0x096, 0x8, 0x09C, 0x9, 0x1C3, 0x9, 0x19C, 0x9, 0x08F, 0x9, 0x18F, 0x9, 0x091, 0x9, 0x087, 0x9, 0x0C6, 0x9, 0x177, 0x9, 0x089, 0x9, 0x0D6, 0x9, 0x08C, 0x9, 0x1EE, 0x9, 0x1EB, 0x9, 0x084, 0x9, 0x164, 0x9, 0x175, 0x9, 0x1CD, 0x8, 0x05E, 0x9, 0x088, 0x9, 0x12B, 0x9, 0x172, 0x9, 0x10A, 0x9, 0x08D, 0x9, 0x13A, 0x9, 0x11C, 0xA, 0x1E1, 0xA, 0x1E0, 0x9, 0x187, 0xA, 0x1DC, 0xA, 0x1DF, 0x7, 0x074, 0x9, 0x19F, 0x8, 0x08D, 0x8, 0x0E4, 0x7, 0x079, 0x9, 0x0EA, 0x9, 0x0E1, 0x8, 0x040, 0x7, 0x041, 0x9, 0x10B, 0x9, 0x0B0, 0x8, 0x06A, 0x8, 0x0C1, 0x7, 0x071, 0x7, 0x078, 0x8, 0x0B1, 0x9, 0x14C, 0x7, 0x043, 0x8, 0x076, 0x7, 0x066, 0x7, 0x04D, 0x9, 0x08A, 0x6, 0x02F, 0x8, 0x0C9, 0x9, 0x0CE, 0x9, 0x149, 0x9, 0x160, 0xA, 0x1BA, 0xA, 0x19E, 0xA, 0x39F, 0x9, 0x0E5, 0x9, 0x194, 0x9, 0x184, 0x9, 0x126, 0x7, 0x030, 0x8, 0x06C, 0x9, 0x121, 0x9, 0x1E8, 0xA, 0x1C1, 0xA, 0x11D, 0xA, 0x163, 0xA, 0x385, 0xA, 0x3DB, 0xA, 0x17D, 0xA, 0x106, 0xA, 0x397, 0xA, 0x24E, 0x7, 0x02E, 0x8, 0x098, 0xA, 0x33C, 0xA, 0x32E, 0xA, 0x1E9, 0x9, 0x0BF, 0xA, 0x3DF, 0xA, 0x1DD, 0xA, 0x32D, 0xA, 0x2ED, 0xA, 0x30B, 0xA, 0x107, 0xA, 0x2E8, 0xA, 0x3DE, 0xA, 0x125, 0xA, 0x1E8, 0x9, 0x0E9, 0xA, 0x1CD, 0xA, 0x1B5, 0x9, 0x165, 0xA, 0x232, 0xA, 0x2E1, 0xB, 0x3AE, 0xB, 0x3C6, 0xB, 0x3E2, 0xA, 0x205, 0xA, 0x29A, 0xA, 0x248, 0xA, 0x2CD, 0xA, 0x23B, 0xB, 0x3C5, 0xA, 0x251, 0xA, 0x2E9, 0xA, 0x252, 0x9, 0x1EA, 0xB, 0x3A0, 0xB, 0x391, 0xA, 0x23C, 0xB, 0x392, 0xB, 0x3D5, 0xA, 0x233, 0xA, 0x2CC, 0xB, 0x390, 0xA, 0x1BB, 0xB, 0x3A1, 0xB, 0x3C4, 0xA, 0x211, 0xA, 0x203, 0x9, 0x12A, 0xA, 0x231, 0xB, 0x3E0, 0xA, 0x29B, 0xB, 0x3D7, 0xA, 0x202, 0xB, 0x3AD, 0xA, 0x213, 0xA, 0x253, 0xA, 0x32C, 0xA, 0x23D, 0xA, 0x23F, 0xA, 0x32F, 0xA, 0x11C, 0xA, 0x384, 0xA, 0x31C, 0xA, 0x17C, 0xA, 0x30A, 0xA, 0x2E0, 0xA, 0x276, 0xA, 0x250, 0xB, 0x3E3, 0xA, 0x396, 0xA, 0x18F, 0xA, 0x204, 0xA, 0x206, 0xA, 0x230, 0xA, 0x265, 0xA, 0x212, 0xA, 0x23E, 0xB, 0x3AC, 0xB, 0x393, 0xB, 0x3E1, 0xA, 0x1DE, 0xB, 0x3D6, 0xA, 0x31D, 0xB, 0x3E5, 0xB, 0x3E4, 0xA, 0x207, 0xB, 0x3C7, 0xA, 0x277, 0xB, 0x3D4, 0x8, 0x0C0, 0xA, 0x162, 0xA, 0x3DA, 0xA, 0x124, 0xA, 0x1B4, 0xA, 0x264, 0xA, 0x33D, 0xA, 0x1D1, 0xA, 0x1AF, 0xA, 0x39E, 0xA, 0x24F, 0xB, 0x373, 0xA, 0x249, 0xB, 0x372, 0x9, 0x167, 0xA, 0x210, 0xA, 0x23A, 0xA, 0x1B8, 0xB, 0x3AF, 0xA, 0x18E, 0xA, 0x2EC, 0x7, 0x062, 0x4, 0x00D ]; for (var i = 0; i < 257; i++) { var len = (uint)rawTable[i * 2]; var val = (uint)rawTable[i * 2 + 1]; _packedHuffmanTable[i] = (len << 16) | val; } } public static int Compress(ReadOnlySpan input, Span output) { if (input.Length > DefiniteOverflow) { return 0; } ref var inputRef = ref MemoryMarshal.GetReference(input); ref var outputRef = ref MemoryMarshal.GetReference(output); ref var tableRef = ref MemoryMarshal.GetReference(_packedHuffmanTable.AsSpan()); nuint i = 0; ulong bitValue = 0; var bitCount = 0; nuint outputIdx = 0; var safeOutputLength = (nuint)output.Length - 4; var unrolledLimit = (nuint)input.Length - 1; // Unrolled 2x hot loop using native integer indices while (i < unrolledLimit) { var b1 = Unsafe.Add(ref inputRef, i); var entry1 = Unsafe.Add(ref tableRef, b1); var len1 = (int)(entry1 >> 16); ulong val1 = entry1 & 0xFFFF; bitValue = (bitValue << len1) | val1; bitCount += len1; var b2 = Unsafe.Add(ref inputRef, i + 1); var entry2 = Unsafe.Add(ref tableRef, b2); var len2 = (int)(entry2 >> 16); ulong val2 = entry2 & 0xFFFF; bitValue = (bitValue << len2) | val2; bitCount += len2; if (bitCount >= 32) { if (outputIdx > safeOutputLength) { return 0; } bitCount -= 32; var word = (uint)(bitValue >> bitCount); Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputRef, outputIdx), BinaryPrimitives.ReverseEndianness(word)); outputIdx += 4; } i += 2; } if (i < (nuint)input.Length) { var b = Unsafe.Add(ref inputRef, i); var entry = Unsafe.Add(ref tableRef, b); var len = (int)(entry >> 16); ulong val = entry & 0xFFFF; bitValue = (bitValue << len) | val; bitCount += len; if (bitCount >= 32) { if (outputIdx > safeOutputLength) { return 0; } bitCount -= 32; var word = (uint)(bitValue >> bitCount); Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputRef, outputIdx), BinaryPrimitives.ReverseEndianness(word)); outputIdx += 4; } } // Terminal code logic var termEntry = Unsafe.Add(ref tableRef, 256); var termLen = (int)(termEntry >> 16); ulong termVal = termEntry & 0xFFFF; bitValue = (bitValue << termLen) | termVal; bitCount += termLen; var remainder = bitCount & 7; if (remainder != 0) { var padding = 8 - remainder; bitValue <<= padding; bitCount += padding; } bitValue <<= 64 - bitCount; while (bitCount > 0) { if (outputIdx >= (nuint)output.Length) { return 0; } Unsafe.Add(ref outputRef, outputIdx++) = (byte)(bitValue >> 56); bitValue <<= 8; bitCount -= 8; } return (int)outputIdx; } }