- Reorganize project structure: Port/ → DotNet/, VC/, C++/ - Add comprehensive cross-platform build automation - Windows: build_all.bat, build.bat files for all components - Linux/macOS: build_all.sh, build.sh files for all components - Update all build scripts with correct folder paths - Create test automation scripts (test_all.bat/sh) - Update documentation to reflect new structure - Maintain 100% roundtrip accuracy for test5.exi (pure EXI) - Support both Windows MSBuild and Linux GCC compilation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
237 lines
6.8 KiB
C#
237 lines
6.8 KiB
C#
/*
|
|
* Copyright (C) 2007-2024 C# Port
|
|
* Original Copyright (C) 2007-2018 Siemens AG
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
namespace V2GDecoderNet.EXI
|
|
{
|
|
/// <summary>
|
|
/// Bit output stream for writing EXI encoded data
|
|
/// </summary>
|
|
public class BitOutputStream
|
|
{
|
|
private byte[] _buffer;
|
|
private int _position;
|
|
private int _bitPosition;
|
|
private int _capacity;
|
|
|
|
public BitOutputStream(int capacity = EXIConstants.BUFFER_SIZE)
|
|
{
|
|
_capacity = capacity;
|
|
_buffer = new byte[capacity];
|
|
_position = 0;
|
|
_bitPosition = 0;
|
|
}
|
|
|
|
public int Position => _position;
|
|
public int BitPosition => _bitPosition;
|
|
public int Capacity => _capacity;
|
|
|
|
/// <summary>
|
|
/// Write a single bit
|
|
/// </summary>
|
|
/// <param name="bit">Bit value (0 or 1)</param>
|
|
public void WriteBit(int bit)
|
|
{
|
|
if (bit != 0 && bit != 1)
|
|
throw new ArgumentException("Bit value must be 0 or 1", nameof(bit));
|
|
|
|
EnsureCapacity(_position + 1);
|
|
|
|
if (bit == 1)
|
|
{
|
|
_buffer[_position] |= (byte)(1 << (7 - _bitPosition));
|
|
}
|
|
|
|
_bitPosition++;
|
|
if (_bitPosition == 8)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write multiple bits from unsigned integer
|
|
/// </summary>
|
|
/// <param name="value">Value to write</param>
|
|
/// <param name="numBits">Number of bits to write (1-32)</param>
|
|
public void WriteBits(uint value, int numBits)
|
|
{
|
|
if (numBits < 1 || numBits > 32)
|
|
throw new ArgumentException("Number of bits must be between 1 and 32", nameof(numBits));
|
|
|
|
for (int i = numBits - 1; i >= 0; i--)
|
|
{
|
|
int bit = (int)((value >> i) & 1);
|
|
WriteBit(bit);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write unsigned integer using EXI encoding
|
|
/// </summary>
|
|
/// <param name="value">Unsigned integer value</param>
|
|
public void WriteUnsignedInteger(uint value)
|
|
{
|
|
if (value == 0)
|
|
{
|
|
WriteByte(0);
|
|
return;
|
|
}
|
|
|
|
// Calculate number of bytes needed
|
|
var bytes = new List<byte>();
|
|
|
|
while (value > 0)
|
|
{
|
|
byte currentByte = (byte)(value & 0x7F);
|
|
value >>= 7;
|
|
|
|
if (value > 0)
|
|
currentByte |= 0x80; // Set continuation bit
|
|
|
|
bytes.Add(currentByte);
|
|
}
|
|
|
|
// Write bytes in reverse order (big-endian)
|
|
for (int i = bytes.Count - 1; i >= 0; i--)
|
|
{
|
|
WriteByte(bytes[i]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write signed integer using EXI encoding
|
|
/// </summary>
|
|
/// <param name="value">Signed integer value</param>
|
|
public void WriteInteger(int value)
|
|
{
|
|
// Encode sign in LSB, shift value
|
|
uint unsignedValue;
|
|
if (value < 0)
|
|
{
|
|
unsignedValue = ((uint)(-value) << 1) | 1;
|
|
}
|
|
else
|
|
{
|
|
unsignedValue = (uint)value << 1;
|
|
}
|
|
|
|
WriteUnsignedInteger(unsignedValue);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write a byte aligned to byte boundary
|
|
/// </summary>
|
|
/// <param name="value">Byte value</param>
|
|
public void WriteByte(byte value)
|
|
{
|
|
// Align to byte boundary
|
|
if (_bitPosition != 0)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
|
|
EnsureCapacity(_position + 1);
|
|
_buffer[_position++] = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write multiple bytes
|
|
/// </summary>
|
|
/// <param name="data">Byte array to write</param>
|
|
public void WriteBytes(byte[] data)
|
|
{
|
|
if (data == null || data.Length == 0)
|
|
return;
|
|
|
|
// Align to byte boundary
|
|
if (_bitPosition != 0)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
|
|
EnsureCapacity(_position + data.Length);
|
|
Array.Copy(data, 0, _buffer, _position, data.Length);
|
|
_position += data.Length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Align to next byte boundary
|
|
/// </summary>
|
|
public void AlignToByteBank()
|
|
{
|
|
if (_bitPosition != 0)
|
|
{
|
|
_bitPosition = 0;
|
|
_position++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the written data as byte array
|
|
/// </summary>
|
|
/// <returns>Byte array containing written data</returns>
|
|
public byte[] ToArray()
|
|
{
|
|
int length = _position + (_bitPosition > 0 ? 1 : 0);
|
|
var result = new byte[length];
|
|
Array.Copy(_buffer, result, length);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the current buffer length in bytes
|
|
/// </summary>
|
|
/// <returns>Length in bytes</returns>
|
|
public int GetLength()
|
|
{
|
|
return _position + (_bitPosition > 0 ? 1 : 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reset the stream position to beginning
|
|
/// </summary>
|
|
public void Reset()
|
|
{
|
|
_position = 0;
|
|
_bitPosition = 0;
|
|
Array.Clear(_buffer, 0, _buffer.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensure buffer has enough capacity
|
|
/// </summary>
|
|
/// <param name="requiredSize">Required size in bytes</param>
|
|
private void EnsureCapacity(int requiredSize)
|
|
{
|
|
if (requiredSize > _capacity)
|
|
{
|
|
int newCapacity = Math.Max(_capacity * 2, requiredSize);
|
|
var newBuffer = new byte[newCapacity];
|
|
Array.Copy(_buffer, newBuffer, _position);
|
|
_buffer = newBuffer;
|
|
_capacity = newCapacity;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get current buffer usage statistics
|
|
/// </summary>
|
|
/// <returns>Usage information</returns>
|
|
public (int UsedBytes, int TotalCapacity, double UsagePercentage) GetUsageStats()
|
|
{
|
|
int usedBytes = GetLength();
|
|
double usage = (double)usedBytes / _capacity * 100.0;
|
|
return (usedBytes, _capacity, usage);
|
|
}
|
|
}
|
|
} |