Skip to content

Instantly share code, notes, and snippets.

@redheadgektor
Last active January 29, 2024 22:18
Show Gist options
  • Save redheadgektor/4d6a7c568470c46c5679c9c7bddd4e65 to your computer and use it in GitHub Desktop.
Save redheadgektor/4d6a7c568470c46c5679c9c7bddd4e65 to your computer and use it in GitHub Desktop.
Bit-packing tool
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine.UIElements;
/* Bits & Buffers */
public unsafe partial class BitStream : IDisposable
{
private byte[] buffer;
private int position;
private int bitPosition;
public int Size { get; private set; }
public bool AutoResize = true;
public int AutoResizeCount = 1024;
public bool BitAligned
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (bitPosition & 7) == 0;
}
public BitStream(int capacity)
{
if (capacity <= 0)
{
capacity = 1;
}
buffer = new byte[capacity];
Reset(true);
}
public BitStream(byte[] data)
{
SetData(data);
}
public byte this[int index]
{
get
{
if (index < 0 || index >= buffer.Length)
throw new ArgumentException("Invalid byte position.");
return buffer[index];
}
}
public void SetBitPosition(int newPosition)
{
if (newPosition < 0)
{
throw new ArgumentOutOfRangeException("Bit position cannot be negative.");
}
int newBytePosition = newPosition / 8;
int newBitPosition = newPosition % 8;
position = newBytePosition;
bitPosition = newBitPosition;
}
public int GetBitPosition()
{
return (position * 8) + bitPosition;
}
public int GetSizeFromBitPosition()
{
return (GetBitPosition() + 7) / 8;
}
public int GetPosition()
{
return position;
}
public byte[] Data
{
get { return buffer; }
}
public byte* GetDataPointer(int offset = 0)
{
fixed (byte* ptr = &buffer[offset])
{
return ptr;
}
}
public byte* GetDataPointerAtPosition(int offset = 0)
{
fixed (byte* ptr = &buffer[position + offset])
{
return ptr;
}
}
public void SetData(byte[] data)
{
buffer = data;
Size = data.Length;
}
public void SetData(byte* data, int size)
{
SetBitPosition(0);
CheckAndResize(size);
fixed (byte* ptr = &buffer[0])
{
Buffer.MemoryCopy(data, ptr, size, size);
}
Size = size;
}
public int AvailableBits => Size * 8;
public int RemainBits => AvailableBits - GetBitPosition();
public int RemainBytes => RemainBits / 8;
private void CheckAndResize(int size)
{
var target = position + size;
var need = target - Size;
if (need > 0)
{
Size += need;
}
if (Size > buffer.Length)
{
if (!AutoResize)
{
Size = buffer.Length;
throw new OutOfMemoryException("AutoResize disabled!");
}
Array.Resize(ref buffer, Size + AutoResizeCount);
}
}
public static bool IsLittleEndian()
{
unsafe
{
int testValue = 1;
byte* testBytes = (byte*)&testValue;
return (*testBytes == 1);
}
}
public void SkipBits(int bitCount)
{
int bitsToSkip = bitCount;
int newPosition = GetBitPosition() + bitsToSkip;
SetBitPosition(newPosition);
}
public void SkipBytes(int byteCount)
{
int bitsToSkip = byteCount * 8;
SkipBits(bitsToSkip);
}
public void Skip<T>()
where T : unmanaged
{
SkipBytes(sizeof(T));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBit(bool bit)
{
if (bitPosition == 8)
{
bitPosition = 0;
position++;
CheckAndResize(1);
}
int offset = bitPosition & 7;
int pos = position;
++bitPosition;
buffer[pos] = (byte)(
bit ? (buffer[pos] & ~(1 << offset)) | (1 << offset) : (buffer[pos] & ~(1 << offset))
);
}
[StructLayout(LayoutKind.Explicit)]
private struct FloatUnion
{
[FieldOffset(0)]
public float fValue;
[FieldOffset(0)]
public ulong lValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBits(float value, int bitCount = 32)
{
var union = new FloatUnion() { fValue = value };
WriteBits(union.lValue, bitCount);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBits(ulong value, int bitCount = 32)
{
if (bitCount < 1 || bitCount > 64)
{
throw new ArgumentException("Bit count must be between 1 and 64.");
}
for (int i = 0; i < bitCount; i++)
{
WriteBit((value & (1UL << i)) != 0);
}
}
public void WriteBits(long value, int bitCount = 32) => WriteBits((ulong)value, bitCount);
public float ReadBitsFloat(int bitCount = 32)
{
var union = new FloatUnion() { lValue = ReadBits(bitCount) };
return union.fValue;
}
public ulong ReadBits(int bitCount = 32)
{
if (bitCount < 1 || bitCount > 64)
{
throw new ArgumentException("Bit count must be between 1 and 64.");
}
ulong result = 0;
for (int i = 0; i < bitCount; i++)
{
var bit = ReadBit();
result |= (bit ? 1UL : 0UL) << i;
}
return result;
}
public bool ReadBit()
{
if (bitPosition == 8)
{
bitPosition = 0;
position++;
}
if (position >= buffer.Length)
{
throw new EndOfStreamException("BitStream buffer is end");
}
var offset = bitPosition & 7;
var bit = (buffer[position] & (1 << offset)) != 0;
bitPosition++;
return bit;
}
public void Reset(bool resetSize = false, bool resetBuffer = false)
{
position = 0;
bitPosition = 0;
if (resetSize)
{
Size = 0;
}
if (resetBuffer)
{
Size = 0;
Array.Resize(ref buffer, 0);
}
}
}
/* Dump to file */
public unsafe partial class BitStream : IDisposable
{
public enum Compressor
{
GZip,
Deflate
}
public bool DumpToFile(
string path,
bool compress = false,
Compressor compressor = Compressor.GZip,
bool allBuffer = false
)
{
bool result = false;
try
{
var dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
if (compress)
{
if (compressor == Compressor.GZip)
{
fs.WriteByte((byte)'G');
fs.WriteByte((byte)'z');
using (var cstream = new GZipStream(fs, CompressionMode.Compress))
{
cstream.Write(buffer, 0, Size);
cstream.Close();
result = true;
return true;
}
}
else
{
fs.WriteByte((byte)'D');
fs.WriteByte((byte)'e');
fs.WriteByte((byte)'f');
using (var cstream = new DeflateStream(fs, CompressionMode.Compress))
{
cstream.Write(buffer, 0, Size);
cstream.Close();
result = true;
return true;
}
}
}
fs.Write(buffer, 0, Size);
fs.Close();
return true;
}
}
catch
{
result = false;
}
return result;
}
public bool ReadFromFile(
string path,
bool compressed = false,
Compressor compressor = Compressor.GZip
)
{
bool result = false;
try
{
if (File.Exists(path))
{
using (
var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)
)
{
//force decompress if has compression header (see DumpToFile method)
if (!compressed)
{
if (fs.Length >= 3)
{
if (fs.ReadByte() == (byte)'G' && fs.ReadByte() == (byte)'z')
{
compressor = Compressor.GZip;
compressed = true;
}
fs.Position = 0;
if (
fs.ReadByte() == (byte)'D'
&& fs.ReadByte() == (byte)'e'
&& fs.ReadByte() == (byte)'f'
)
{
compressor = Compressor.Deflate;
compressed = true;
}
fs.Position = 0;
}
}
if (compressed)
{
if (compressor == Compressor.GZip)
{
using (var dstream = new GZipStream(fs, CompressionMode.Decompress))
{
CheckAndResize((int)fs.Length);
Size = dstream.Read(buffer, 0, (int)fs.Length);
return true;
}
}
else
{
using (var dstream = new DeflateStream(fs, CompressionMode.Decompress))
{
CheckAndResize((int)fs.Length);
Size = dstream.Read(buffer, 0, (int)fs.Length);
return true;
}
}
}
CheckAndResize((int)fs.Length);
fs.Read(buffer, 0, (int)fs.Length);
}
result = true;
}
}
catch
{
result = false;
}
return result;
}
}
/* Pool */
public unsafe partial class BitStream : IDisposable
{
private static Stack<BitStream> Pool = new Stack<BitStream>();
public bool IsPooled { get; private set; } = false;
public static BitStream Get(int size = 1)
{
BitStream bs = Pool.Count > 0 ? Pool.Pop() : null;
if (bs == null)
{
bs = new BitStream(size);
bs.IsPooled = true;
}
bs.Reset(true);
bs.CheckAndResize(size);
return bs;
}
public static BitStream Get(byte[] data)
{
BitStream bs = Pool.Count > 0 ? Pool.Pop() : null;
if (bs == null)
{
bs = new BitStream(data);
bs.IsPooled = true;
}
bs.Reset();
bs.SetData(data);
return bs;
}
public static BitStream Get(byte* data, int size)
{
BitStream bs = Pool.Count > 0 ? Pool.Pop() : null;
if (bs == null)
{
bs = new BitStream(size);
bs.IsPooled = true;
}
bs.Reset();
bs.SetData(data, size);
return bs;
}
void IDisposable.Dispose()
{
if (IsPooled)
{
Pool.Push(this);
}
}
}
/* Unmanaged */
public unsafe partial class BitStream : IDisposable
{
public void Write<T>(T value)
where T : unmanaged
{
unsafe
{
var ptr = (byte*)(&value);
for (int i = 0; i < sizeof(T); i++)
{
WriteBits(ptr[i], 8);
}
}
}
public T Read<T>()
where T : unmanaged
{
unsafe
{
T value = default(T);
var ptr = (byte*)(&value);
for (int i = 0; i < sizeof(T); i++)
{
ptr[i] = (byte)ReadBits(8);
}
return value;
}
}
}
/* Arrays */
public unsafe partial class BitStream : IDisposable
{
public void Write<T>(T[] value)
where T : unmanaged
{
Write((ushort)value.Length);
for (int i = 0; i < value.Length; i++)
{
Write(value[i]);
}
}
public T[] ReadArray<T>()
where T : unmanaged
{
var sz = Read<ushort>();
var array = new T[sz];
for (int i = 0; i < sz; i++)
{
array[i] = Read<T>();
}
return array;
}
public void Write<T>(T[] value, int size)
where T : unmanaged
{
for (int i = 0; i < size; i++)
{
Write(value[i]);
}
}
public T[] ReadArray<T>(int size)
where T : unmanaged
{
var array = new T[size];
for (int i = 0; i < array.Length; i++)
{
array[i] = Read<T>();
}
return array;
}
}
/* Strings */
public unsafe partial class BitStream : IDisposable
{
UTF8Encoding encoding;
private UTF8Encoding GetOrCreateUTF8Encoder()
{
if (encoding == null)
{
encoding = new UTF8Encoding(false, false);
}
return encoding;
}
/* UTF8 */
public void WriteStringU(string str)
{
var bytes = GetOrCreateUTF8Encoder().GetBytes(str);
Write(bytes, bytes.Length);
Write((byte)0);
}
public string ReadStringU()
{
var bytes = new List<byte>();
byte b;
while ((b = Read<byte>()) != 0)
{
bytes.Add(b);
}
string str = GetOrCreateUTF8Encoder().GetString(bytes.ToArray());
return str;
}
/* ANSI */
public void WriteStringA(string str)
{
foreach (char c in str)
{
Write((byte)c);
}
Write((byte)'\0');
}
public string ReadStringA()
{
var chars = new List<char>();
char c;
while ((c = (char)Read<byte>()) != '\0')
{
chars.Add(c);
}
return new string(chars.ToArray());
}
}
/* Pointers */
public unsafe partial class BitStream : IDisposable
{
public void Write(byte* ptr, int size)
{
for (int i = 0; i < size; i++)
{
WriteBits(ptr[i], 8);
}
}
}
/* Scramble */
public unsafe partial class BitStream : IDisposable
{
int scrambleStartPosition = -1;
public void ScrambleStart()
{
scrambleStartPosition = GetPosition();
}
public void ScrambleEnd(int seed = 0)
{
if (scrambleStartPosition == -1)
{
throw new InvalidOperationException($"{nameof(ScrambleStart)} not called!");
}
var pos = GetPosition();
for (int i = scrambleStartPosition; i < pos; i++)
{
buffer[i] ^= (byte)(i ^ seed);
}
scrambleStartPosition = -1;
}
public void ScrambleBuffer(int seed = 0)
{
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] ^= (byte)(i ^ seed);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment