mirror of
https://github.com/MobileTooling/MobilePackageGen.git
synced 2026-07-27 12:49:05 -07:00
171 lines
6.3 KiB
C#
171 lines
6.3 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Reactive.Linq;
|
|
using ZLibDotNet;
|
|
|
|
namespace BlockCompressor;
|
|
|
|
public static class Compressed
|
|
{
|
|
/// <summary>
|
|
/// Default block size (64KB)
|
|
/// </summary>
|
|
public const int DefaultBlockSize = 64 * 1024; // 64KB
|
|
|
|
/// <summary>
|
|
/// Compresses an observable stream of byte arrays into blocks using zlib's deflate algorithm
|
|
/// configured in the style of makemsix/makeappx
|
|
/// </summary>
|
|
public static IObservable<DeflateBlock> Blocks(
|
|
IObservable<byte[]> input,
|
|
int compressionLevel = 6,
|
|
int uncompressedBlockSize = DefaultBlockSize)
|
|
{
|
|
return Observable.Create<DeflateBlock>(observer =>
|
|
{
|
|
// Use ArrayPool to reduce GC pressure
|
|
byte[] rentedBuffer = ArrayPool<byte>.Shared.Rent(uncompressedBlockSize * 2);
|
|
int bytesInBuffer = 0;
|
|
|
|
var wrapper = new ZStreamWrapper();
|
|
|
|
var subscription = input.Subscribe(
|
|
onNext: incomingBytes =>
|
|
{
|
|
try
|
|
{
|
|
// Check if we need a larger buffer
|
|
if (bytesInBuffer + incomingBytes.Length > rentedBuffer.Length)
|
|
{
|
|
// Get a new buffer of appropriate size
|
|
int newSize = Math.Max(rentedBuffer.Length * 2, bytesInBuffer + incomingBytes.Length);
|
|
byte[] newBuffer = ArrayPool<byte>.Shared.Rent(newSize);
|
|
|
|
// Copy existing data to new buffer
|
|
Buffer.BlockCopy(rentedBuffer, 0, newBuffer, 0, bytesInBuffer);
|
|
|
|
// Return old buffer to pool
|
|
ArrayPool<byte>.Shared.Return(rentedBuffer);
|
|
|
|
// Use new buffer
|
|
rentedBuffer = newBuffer;
|
|
}
|
|
|
|
// Copy incoming bytes to our buffer
|
|
Buffer.BlockCopy(incomingBytes, 0, rentedBuffer, bytesInBuffer, incomingBytes.Length);
|
|
bytesInBuffer += incomingBytes.Length;
|
|
|
|
// Process complete blocks
|
|
ProcessCompleteBlocks();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
observer.OnError(ex);
|
|
}
|
|
},
|
|
onError: ex =>
|
|
{
|
|
try
|
|
{
|
|
ArrayPool<byte>.Shared.Return(rentedBuffer);
|
|
}
|
|
finally
|
|
{
|
|
observer.OnError(ex);
|
|
}
|
|
},
|
|
onCompleted: () =>
|
|
{
|
|
try
|
|
{
|
|
// Process any remaining bytes if we have some
|
|
if (bytesInBuffer > 0)
|
|
{
|
|
ProcessBlock(rentedBuffer.AsSpan(0, bytesInBuffer).ToArray());
|
|
}
|
|
|
|
// Process the final zlib block
|
|
byte[] finalData = wrapper.DeflateFinish();
|
|
|
|
finalData = [.. finalData, .. new byte[] { 0x03, 0x00 }];
|
|
|
|
if (finalData.Length > 0)
|
|
{
|
|
var finalBlock = new DeflateBlock
|
|
{
|
|
CompressedData = finalData,
|
|
OriginalData = [],
|
|
};
|
|
|
|
observer.OnNext(finalBlock);
|
|
}
|
|
|
|
observer.OnCompleted();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
observer.OnError(ex);
|
|
}
|
|
finally
|
|
{
|
|
// Return the buffer to the pool
|
|
ArrayPool<byte>.Shared.Return(rentedBuffer);
|
|
}
|
|
});
|
|
|
|
// Local function to process all complete blocks
|
|
void ProcessCompleteBlocks()
|
|
{
|
|
// Process as many complete blocks as we can
|
|
while (bytesInBuffer >= uncompressedBlockSize)
|
|
{
|
|
// Create a block from the buffer
|
|
byte[] blockData = new byte[uncompressedBlockSize];
|
|
Buffer.BlockCopy(rentedBuffer, 0, blockData, 0, uncompressedBlockSize);
|
|
|
|
// Process it
|
|
ProcessBlock(blockData);
|
|
|
|
// Move remaining data to the start of the buffer
|
|
if (bytesInBuffer > uncompressedBlockSize)
|
|
{
|
|
Buffer.BlockCopy(
|
|
rentedBuffer,
|
|
uncompressedBlockSize,
|
|
rentedBuffer,
|
|
0,
|
|
bytesInBuffer - uncompressedBlockSize);
|
|
}
|
|
|
|
bytesInBuffer -= uncompressedBlockSize;
|
|
}
|
|
}
|
|
|
|
// Local function to process a single block
|
|
void ProcessBlock(byte[] blockData)
|
|
{
|
|
try
|
|
{
|
|
// Compress the block using our wrapper
|
|
byte[] compressedData = wrapper.Deflate(
|
|
blockData,
|
|
compressionLevel);
|
|
|
|
// Create and emit the compressed block
|
|
var block = new DeflateBlock
|
|
{
|
|
CompressedData = compressedData,
|
|
OriginalData = blockData,
|
|
};
|
|
observer.OnNext(block);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
observer.OnError(ex);
|
|
}
|
|
}
|
|
|
|
return subscription;
|
|
});
|
|
}
|
|
} |