namespace Age.Engine.Sys4; /// /// Encoder for Eushully's 4 KiB-ring LZSS stream. Tokens are grouped under an LSB-first flag byte; /// one bits are literals and zero bits are 12-bit ring offsets plus a four-bit length-minus-three. /// public static class LzssEncoder { private const int RingSize = 0x1000; private const int RingMask = RingSize - 1; private const int InitialRingPosition = 0xfee; private const int MaxMatchLength = 18; private const int CandidateLimit = 128; /// /// Compress . When the encoded stream would not be smaller, return the /// original bytes; the native container marks that case by storing equal source/stored lengths. /// public static byte[] EncodeOrVerbatim(ReadOnlySpan source) { if (source.IsEmpty) return []; var output = new List(source.Length); var positionsByPrefix = new Dictionary>(); int position = 0; while (position < source.Length) { int flagIndex = output.Count; output.Add(0); byte flags = 0; for (int bit = 0; bit < 8 && position < source.Length; bit++) { (int candidate, int length) = FindMatch(source, position, positionsByPrefix); if (length >= 3) { int ringOffset = (InitialRingPosition + candidate) & RingMask; output.Add((byte)ringOffset); output.Add((byte)(((ringOffset >> 4) & 0xf0) | (length - 3))); AddPositions(source, position, length, positionsByPrefix); position += length; } else { flags |= (byte)(1 << bit); output.Add(source[position]); AddPositions(source, position, 1, positionsByPrefix); position++; } } output[flagIndex] = flags; } return output.Count < source.Length ? output.ToArray() : source.ToArray(); } private static (int Candidate, int Length) FindMatch( ReadOnlySpan source, int position, Dictionary> positionsByPrefix) { if (position > source.Length - 3) return (-1, 0); int key = PrefixKey(source, position); if (!positionsByPrefix.TryGetValue(key, out var candidates)) return (-1, 0); int oldestAllowed = position - RingSize; while (candidates.First is { } first && first.Value < oldestAllowed) candidates.RemoveFirst(); int bestCandidate = -1, bestLength = 0, inspected = 0; for (LinkedListNode? node = candidates.Last; node is not null && inspected < CandidateLimit; node = node.Previous, inspected++) { int candidate = node.Value; int distance = position - candidate; if (distance <= 0 || distance > RingSize) continue; int limit = Math.Min(MaxMatchLength, source.Length - position); int length = 0; while (length < limit && source[position + length] == source[candidate + (length % distance)]) length++; if (length > bestLength) { bestCandidate = candidate; bestLength = length; if (length == MaxMatchLength) break; } } return (bestCandidate, bestLength); } private static void AddPositions( ReadOnlySpan source, int start, int count, Dictionary> positionsByPrefix) { int end = Math.Min(start + count, source.Length - 2); for (int position = start; position < end; position++) { int key = PrefixKey(source, position); if (!positionsByPrefix.TryGetValue(key, out var positions)) { positions = new LinkedList(); positionsByPrefix.Add(key, positions); } positions.AddLast(position); } } private static int PrefixKey(ReadOnlySpan source, int position) => source[position] | source[position + 1] << 8 | source[position + 2] << 16; }