Files
EnglewoodLAB/Assets/ADL-Plugins/LZ4/Plugins/LZ4.cs

636 lines
25 KiB
C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class LZ4
{
#if !UNITY_WEBPLAYER || UNITY_EDITOR
internal static bool isle = BitConverter.IsLittleEndian;
#if UNITY_5_4_OR_NEWER
#if (UNITY_ANDROID || UNITY_STANDALONE_LINUX || UNITY_WEBGL) && !UNITY_EDITOR || UNITY_EDITOR_LINUX
private const string libname = "lz4";
#elif UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX
private const string libname = "liblz4";
#endif
#else
#if (UNITY_ANDROID || UNITY_STANDALONE_LINUX || UNITY_WEBGL) && !UNITY_EDITOR
private const string libname = "lz4";
#endif
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX
private const string libname = "liblz4";
#endif
#endif
#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_ANDROID || UNITY_STANDALONE_LINUX
#if (!UNITY_WEBGL || UNITY_EDITOR)
#if (UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_ANDROID || UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX)&& !UNITY_EDITOR_WIN
[DllImport(libname, EntryPoint = "z4setPermissions")]
internal static extern int z4setPermissions(string filePath, string _user, string _group, string _other);
#endif
[DllImport(libname, EntryPoint = "LZ4DecompressFile"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
internal static extern int LZ4DecompressFile(string inFile, string outFile, IntPtr bytes, IntPtr FileBuffer, int fileBufferLength);
[DllImport(libname, EntryPoint = "LZ4CompressFile"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
internal static extern int LZ4CompressFile(string inFile, string outFile, int level, IntPtr percentage, ref float rate);
#endif
[DllImport(libname, EntryPoint = "LZ4releaseBuffer"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
public static extern void LZ4releaseBuffer(IntPtr buffer);
[DllImport(libname, EntryPoint = "LZ4Create_Buffer"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
public static extern IntPtr LZ4Create_Buffer(int size);
[DllImport(libname, EntryPoint = "LZ4AddTo_Buffer"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
private static extern void LZ4AddTo_Buffer(IntPtr destination, int offset, IntPtr buffer, int len);
[DllImport(libname, EntryPoint = "LZ4CompressBuffer"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
internal static extern IntPtr LZ4CompressBuffer(IntPtr buffer, int bufferLength, ref int v, int level);
[DllImport(libname, EntryPoint = "LZ4DecompressBuffer"
#if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
, CallingConvention = CallingConvention.Cdecl
#endif
)]
internal static extern int LZ4DecompressBuffer(IntPtr buffer, IntPtr outbuffer, int bufferLength);
#endif
#if (UNITY_IOS || UNITY_TVOS || UNITY_IPHONE || UNITY_WEBGL) && !UNITY_EDITOR
#if (UNITY_IOS || UNITY_IPHONE) && !UNITY_WEBGL
[DllImport("__Internal")]
private static extern int z4setPermissions(string filePath, string _user, string _group, string _other);
[DllImport("__Internal")]
private static extern int LZ4DecompressFile(string inFile, string outFile, IntPtr bytes, IntPtr FileBuffer, int fileBufferLength);
[DllImport("__Internal")]
private static extern int LZ4CompressFile(string inFile, string outFile, int level, IntPtr percentage, ref float rate);
#endif
#if (UNITY_IOS || UNITY_TVOS || UNITY_IPHONE || UNITY_WEBGL)
[DllImport("__Internal")]
public static extern void LZ4releaseBuffer(IntPtr buffer);
[DllImport("__Internal")]
public static extern IntPtr LZ4Create_Buffer(int size);
[DllImport("__Internal")]
internal static extern void LZ4AddTo_Buffer(IntPtr destination, int offset, IntPtr buffer, int len);
[DllImport("__Internal")]
private static extern IntPtr LZ4CompressBuffer(IntPtr buffer, int bufferLength, ref int v, int level);
[DllImport("__Internal")]
private static extern int LZ4DecompressBuffer(IntPtr buffer, IntPtr outbuffer, int bufferLength);
#endif
#endif
//Helper function
internal static GCHandle gcA(object o) {
return GCHandle.Alloc(o, GCHandleType.Pinned);
}
#if ((!UNITY_WEBGL && !UNITY_TVOS) || UNITY_EDITOR)
// set permissions of a file in user, group, other.
// Each string should contain any or all chars of "rwx".
// returns 0 on success
public static int setFilePermissions(string filePath, string _user, string _group, string _other){
#if (UNITY_STANDALONE_OSX || UNITY_STANDALONE_LINUX || UNITY_ANDROID || UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX || UNITY_IOS || UNITY_TVOS || UNITY_IPHONE) && !UNITY_EDITOR_WIN
return z4setPermissions(filePath, _user, _group, _other);
#else
return -1;
#endif
}
//Helper function
private static bool checkObject(object fileBuffer, string filePath, ref GCHandle fbuf, ref IntPtr fileBufferPointer, ref int fileBufferLength) {
if(fileBuffer is byte[]) { byte[] tempBuf = (byte[])fileBuffer; fbuf = gcA(tempBuf); fileBufferPointer = fbuf.AddrOfPinnedObject(); fileBufferLength = tempBuf.Length; return true; }
if(fileBuffer is IntPtr) { fileBufferPointer = (IntPtr)fileBuffer; fileBufferLength = Convert.ToInt32(filePath); }
return false;
}
//Compress a file to LZ4.
//
//Full paths to the files should be provided.
//level: level of compression (1 - 9).
//returns: rate of compression.
//progress: provide a single item float array to get the progress of the compression in real time. (only when called from a thread/task)
//
public static float compress(string inFile, string outFile, int level, float[] progress) {
if (level < 1) level = 1;
if (level > 9) level = 9;
float rate = 0;
if(progress == null) progress = new float[1];
progress[0] = 0;
GCHandle ibuf = GCHandle.Alloc(progress, GCHandleType.Pinned);
int res = LZ4CompressFile(@inFile, @outFile, level, ibuf.AddrOfPinnedObject(), ref rate);
ibuf.Free();
if (res != 0) return -1;
return rate;
}
//Decompress an LZ4 file.
//
//Full paths to the files should be provided.
//returns: 0 on success.
//bytes: provide a single item ulong array to get the bytes currently decompressed in real time. (only when called from a thread/task)
// fileBuffer : A buffer that holds an LZ4 file. When assigned the function will decompress from this buffer and will ignore the filePath. (iOS, Android, MacOSX, Linux)
// : It can be a byte[] buffer or a native IntPtr buffer (downloaded using the helper function: downloadLZ4FileNative)
// : When an IntPtr is used as the input buffer, the size of it must be passed to the function as a string with the inFile parameter!
//
public static int decompress(string inFile, string outFile, ulong[] bytes, object fileBuffer = null) {
if(bytes == null) bytes = new ulong[1];
bytes[0] = 0;
int res = 0;
GCHandle ibuf = GCHandle.Alloc(bytes , GCHandleType.Pinned);
#if (UNITY_IPHONE || UNITY_IOS || UNITY_STANDALONE_OSX || UNITY_ANDROID || UNITY_STANDALONE_LINUX || UNITY_EDITOR) && !UNITY_EDITOR_WIN
if(fileBuffer != null) {
int fileBufferLength = 0;
IntPtr fileBufferPointer = IntPtr.Zero;
GCHandle fbuf = gcA(null);
bool managed = checkObject(fileBuffer, inFile, ref fbuf, ref fileBufferPointer, ref fileBufferLength);
if (!managed && fileBufferLength == 0) { Debug.Log("Please provide a valid native buffer size as a string in filePath"); return -5; }
res = LZ4DecompressFile(null, @outFile, ibuf.AddrOfPinnedObject(), fileBufferPointer, fileBufferLength);
fbuf.Free();
ibuf.Free();
return res;
}
#endif
res =LZ4DecompressFile(@inFile, @outFile, ibuf.AddrOfPinnedObject(), IntPtr.Zero, 0);
ibuf.Free();
return res;
}
#endif
//Compress a byte buffer in LZ4 format.
//
//inBuffer: the uncompressed buffer.
//outBuffer: a referenced buffer that will be resized to fit the lz4 compressed data.
//level: level of compression (1 - 9).
//includeSize: include the uncompressed size of the buffer in the resulted compressed one because lz4 does not include this.
//returns true on success
//
public static bool compressBuffer(byte[] inBuffer, ref byte[] outBuffer, int level, bool includeSize = true) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
IntPtr ptr;
int res = 0, size = 0;
byte[] bsiz = null;
//if the uncompressed size of the buffer should be included. This is a hack since lz4 lib does not support this.
if (includeSize){
bsiz = new byte[4];
size = 4;
bsiz = BitConverter.GetBytes(inBuffer.Length);
if (!isle) Array.Reverse(bsiz);
}
if (level < 1) level = 1;
if (level > 9) level = 9;
ptr = LZ4CompressBuffer(cbuf.AddrOfPinnedObject(), inBuffer.Length, ref res, level);
cbuf.Free();
if (res == 0 || ptr == IntPtr.Zero) { LZ4releaseBuffer(ptr); return false; }
System.Array.Resize(ref outBuffer, res + size);
//add the uncompressed size to the buffer
if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[i+res] = bsiz[i]; /*Debug.Log(BitConverter.ToInt32(bsiz, 0));*/ }
Marshal.Copy(ptr, outBuffer, 0, res );
LZ4releaseBuffer(ptr);
bsiz = null;
return true;
}
//Compress a byte buffer in LZ4 format and return a new buffer compressed.
//
//inBuffer: the uncompressed buffer.
//level: level of compression (1 - 9).
//includeSize: include the uncompressed size of the buffer in the resulted compressed one because lz4 does not include this.
//returns: a new buffer with the compressed data.
//
public static byte[] compressBuffer(byte[] inBuffer, int level, bool includeSize = true) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
IntPtr ptr;
int res = 0, size = 0;
byte[] bsiz = null;
//if the uncompressed size of the buffer should be included. This is a hack since lz4 lib does not support this.
if (includeSize){
bsiz = new byte[4];
size = 4;
bsiz = BitConverter.GetBytes(inBuffer.Length);
if (!isle) Array.Reverse(bsiz);
}
if (level < 1) level = 1;
if (level > 9) level = 9;
ptr = LZ4CompressBuffer(cbuf.AddrOfPinnedObject(), inBuffer.Length, ref res, level);
cbuf.Free();
if (res == 0 || ptr == IntPtr.Zero) { LZ4releaseBuffer(ptr); return null; }
byte[] outBuffer = new byte[res + size];
//add the uncompressed size to the buffer
if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[i + res] = bsiz[i]; /*Debug.Log(BitConverter.ToInt32(bsiz, 0));*/ }
Marshal.Copy(ptr, outBuffer, 0, res);
LZ4releaseBuffer(ptr);
bsiz = null;
return outBuffer;
}
//Compress a byte buffer in LZ4 format at a specific position of a fixed size outBuffer
//
//inBuffer: the uncompressed buffer.
//outBuffer: a referenced buffer of fixed size that could have already some lz4 compressed buffers stored.
//outBufferPartialIndex: the position at which the compressed data will be written to.
//level: level of compression (1 - 9).
//
//returns compressed size (+4 bytes if footer is used)
//
public static int compressBufferPartialFixed (byte[] inBuffer, ref byte[] outBuffer,int outBufferPartialIndex, int level, bool includeSize = true) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
IntPtr ptr;
int res = 0, size = 0;
byte[] bsiz = null;
//if the uncompressed size of the buffer should be included. This is a hack since lz4 lib does not support this.
if (includeSize){
bsiz = new byte[4];
size = 4;
bsiz = BitConverter.GetBytes(inBuffer.Length);
if (!isle) Array.Reverse(bsiz);
}
if (level < 1) level = 1;
if (level > 9) level = 9;
ptr = LZ4CompressBuffer(cbuf.AddrOfPinnedObject(), inBuffer.Length, ref res, level);
cbuf.Free();
if (res == 0 || ptr == IntPtr.Zero) { LZ4releaseBuffer(ptr); return 0; }
//add the uncompressed size to the buffer
if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[outBufferPartialIndex + res + i ] = bsiz[i]; }
Marshal.Copy(ptr, outBuffer, outBufferPartialIndex, res );
LZ4releaseBuffer(ptr);
bsiz = null;
return res + size;
}
//compressedBufferSize: compressed size of the buffer to be decompressed
//partialIndex: position of an lz4 compressed buffer
//
//returns the uncompressed size
public static int decompressBufferPartialFixed (byte[] inBuffer, ref byte[] outBuffer, int partialIndex , int compressedBufferSize, bool safe = true, bool useFooter = true, int customLength = 0) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
int uncompressedSize = 0;
//be carefull with this. You must know exactly where your compressed data lies in the inBuffer
int res2 = partialIndex + compressedBufferSize;
//if the hacked in LZ4 footer will be used to extract the uncompressed size of the buffer. If the buffer does not have a footer
//provide the known uncompressed size through the customLength integer.
if (useFooter){
res2 -= 4;
uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
}
else{
uncompressedSize = customLength;
}
//Check if the uncompressed size is bigger then the size of the fixed buffer. Then:
//1. write only the data that fit in it.
//2. or return a negative number.
//It depends on if we set the safe flag to true or not.
if(uncompressedSize > outBuffer.Length) {
if(safe) return -101; else uncompressedSize = outBuffer.Length;
}
GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
IntPtr ptrPartial;
ptrPartial = new IntPtr(cbuf.AddrOfPinnedObject().ToInt64() + partialIndex);
//res should be the compressed size
LZ4DecompressBuffer(ptrPartial, obuf.AddrOfPinnedObject(), uncompressedSize);
cbuf.Free();
obuf.Free();
return uncompressedSize;
}
//Decompress an lz4 compressed buffer to a referenced buffer.
//
//inBuffer: the lz4 compressed buffer
//outBuffer: a referenced buffer that will be resized to store the uncompressed data.
//useFooter: if the input Buffer has the uncompressed size info.
//customLength: provide the uncompressed size of the compressed buffer. Not needed if the usefooter is used!
//returns true on success
//
public static bool decompressBuffer(byte[] inBuffer, ref byte[] outBuffer, bool useFooter = true, int customLength = 0) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
int uncompressedSize = 0, res2 = inBuffer.Length;
//if the hacked in LZ4 footer will be used to extract the uncompressed size of the buffer. If the buffer does not have a footer
//provide the known uncompressed size through the customLength integer.
if (useFooter){
res2 -= 4;
uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
}
else{
uncompressedSize = customLength;
}
System.Array.Resize(ref outBuffer, uncompressedSize);
GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
//res should be the compressed size
int res = LZ4DecompressBuffer(cbuf.AddrOfPinnedObject(), obuf.AddrOfPinnedObject(), uncompressedSize);
cbuf.Free();
obuf.Free();
if (res != res2) { /*Debug.Log("ERROR: " + res.ToString());*/ return false; }
return true;
}
//Decompress an lz4 compressed buffer to a referenced fixed size buffer.
//
//inBuffer: the lz4 compressed buffer
//outBuffer: a referenced fixed size buffer where the data will get decompressed
//usefooter: if the input Buffer has the uncompressed size info.
//customLength: provide the uncompressed size of the compressed buffer. Not needed if the usefooter is used!
//returns uncompressedSize
//
public static int decompressBufferFixed(byte[] inBuffer, ref byte[] outBuffer, bool safe = true, bool useFooter = true, int customLength = 0) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
int uncompressedSize = 0, res2 = inBuffer.Length;
//if the hacked in LZ4 footer will be used to extract the uncompressed size of the buffer. If the buffer does not have a footer
//provide the known uncompressed size through the customLength integer.
if (useFooter){
res2 -= 4;
uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
}
else{
uncompressedSize = customLength;
}
//Check if the uncompressed size is bigger then the size of the fixed buffer. Then:
//1. write only the data that fit in it.
//2. or return a negative number.
//It depends on if we set the safe flag to true or not.
if(uncompressedSize > outBuffer.Length) {
if(safe) return -101; else uncompressedSize = outBuffer.Length;
}
GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
//res should be the compressed size
int res = LZ4DecompressBuffer(cbuf.AddrOfPinnedObject(), obuf.AddrOfPinnedObject(), uncompressedSize);
cbuf.Free();
obuf.Free();
if(safe) {
if (res != res2) { /*Debug.Log("ERROR: " + res.ToString());*/ return -101; }
}
return uncompressedSize;
}
//Decompress an lz4 compressed buffer to a new buffer.
//
//inBuffer: the lz4 compressed buffer
//useFooter: if the input Buffer has the uncompressed size info.
//customLength: provide the uncompressed size of the compressed buffer. Not needed if the usefooter is used!
//returns: a new buffer with the uncompressed data.
//
public static byte[] decompressBuffer(byte[] inBuffer, bool useFooter = true, int customLength = 0) {
GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
int uncompressedSize = 0, res2 = inBuffer.Length;
//if the hacked in LZ4 footer will be used to extract the uncompressed size of the buffer. If the buffer does not have a footer
//provide the known uncompressed size through the customLength integer.
if (useFooter)
{
res2 -= 4;
uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
}
else
{
uncompressedSize = customLength;
}
byte[] outBuffer = new byte[uncompressedSize];
GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);
//res should be the compressed size
int res = LZ4DecompressBuffer(cbuf.AddrOfPinnedObject(), obuf.AddrOfPinnedObject(), uncompressedSize);
cbuf.Free();
obuf.Free();
if (res != res2) { /*Debug.Log("ERROR: " + res.ToString());*/ return null; }
return outBuffer;
}
// A reusable native memory pointer for downloading files.
public static IntPtr nativeBuffer = IntPtr.Zero;
public static bool nativeBufferIsBeingUsed = false;
public static int nativeOffset = 0;
// A Coroutine to dowload a file to a native/unmaged memory buffer.
// You can call it for an IntPtr.
//
//
// This function can only be called for one file at a time. Don't use it to call multiple files at once.
//
// This is useful to avoid memory spikes when downloading large files and intend to decompress from memory.
// With the old method, a copy of the downloaded file to memory would be produced by pinning the buffer to memory.
// Now with this method, it is downloaded to memory and can be manipulated with no memory spikes.
//
// In any case, if you don't need the created in-Memory file, you should use the LZ4.LZ4releaseBuffer function to free the memory!
//
// Parameters:
//
// url: The url of the file you want to download to a native memory buffer.
// downloadDone: Informs a bool that the download of the file to memory is done.
// pointer: An IntPtr for a native memory buffer
// fileSize: The size of the downloaded file will be returned here.
public static IEnumerator downloadLZ4FileNative(string url, Action<bool> downloadDone, Action<IntPtr> pointer = null, Action<int> fileSize = null) {
// Get the file lenght first, so we create a correct size native memory buffer.
UnityWebRequest wr = UnityWebRequest.Head(url);
nativeBufferIsBeingUsed = true;
yield return wr.SendWebRequest();
string size = wr.GetResponseHeader("Content-Length");
nativeBufferIsBeingUsed = false;
#if UNITY_2020_1_OR_NEWER
if (wr.result == UnityWebRequest.Result.ConnectionError || wr.result == UnityWebRequest.Result.ProtocolError) {
#else
if (wr.isNetworkError || wr.isHttpError) {
#endif
Debug.LogError("Error While Getting Length: " + wr.error);
} else {
if (!nativeBufferIsBeingUsed) {
//get the size of the zip
int zipSize = Convert.ToInt32(size);
// If the zip size is larger then 0
if (zipSize > 0) {
nativeBuffer = LZ4Create_Buffer(zipSize);
nativeBufferIsBeingUsed = true;
// buffer for the download
byte[] bytes = new byte[2048];
nativeOffset = 0;
using (UnityWebRequest wwwSK = UnityWebRequest.Get(url)) {
// Here we call our custom webrequest function to download our archive to a native memory buffer.
wwwSK.downloadHandler = new CustomWebRequest4(bytes);
yield return wwwSK.SendWebRequest();
if (wwwSK.error != null) {
Debug.Log(wwwSK.error);
} else {
downloadDone(true);
if(pointer != null) { pointer(nativeBuffer); fileSize(zipSize); }
//reset intermediate buffer params.
nativeBufferIsBeingUsed = false;
nativeOffset = 0;
nativeBuffer = IntPtr.Zero;
//Debug.Log("Custom download done");
}
}
}
} else { Debug.LogError("Native buffer is being used, or not yet freed!"); }
}
}
// A custom WebRequest Override to download data to a native-unmanaged memory buffer.
public class CustomWebRequest4 : DownloadHandlerScript {
public CustomWebRequest4()
: base()
{
}
public CustomWebRequest4(byte[] buffer)
: base(buffer)
{
}
protected override byte[] GetData() { return null; }
protected override bool ReceiveData(byte[] bytesFromServer, int dataLength) {
if (bytesFromServer == null || bytesFromServer.Length < 1) {
Debug.Log("CustomWebRequest: Received a null/empty buffer");
return false;
}
var pbuf = gcA(bytesFromServer);
//Process byteFromServer
LZ4AddTo_Buffer(nativeBuffer, nativeOffset, pbuf.AddrOfPinnedObject(), dataLength );
nativeOffset += dataLength;
pbuf.Free();
return true;
}
// Use the below functions only when needed. You get the same functionality from the main coroutine.
/*
// If all data has been received from the server
protected override void CompleteContent()
{
//Debug.Log(Download Complete.");
}
// If a Content-Length header is received from the server.
protected override void ReceiveContentLength(int fileLength)
{
//Debug.Log("ReceiveContentLength: " + fileLength);
}
*/
}
#endif
}