636 lines
25 KiB
C#
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
|
|
}
|