using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;


public class brotli{

#if !UNITY_WEBPLAYER  || UNITY_EDITOR


#if UNITY_5_4_OR_NEWER
	#if (UNITY_ANDROID || UNITY_STANDALONE_LINUX) && !UNITY_EDITOR || UNITY_EDITOR_LINUX
		private const string libname = "brotli";
	#elif UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX
		private const string libname = "libbrotli";
	#endif
#else
	#if (UNITY_ANDROID || UNITY_STANDALONE_LINUX) && !UNITY_EDITOR
		private const string libname = "brotli";
	#endif
	#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX
		private const string libname = "libbrotli";
	#endif
#endif

#if UNITY_EDITOR || UNITY_STANDALONE_WIN || UNITY_STANDALONE_OSX || UNITY_ANDROID || UNITY_STANDALONE_LINUX
	#if (UNITY_STANDALONE_OSX  || UNITY_STANDALONE_LINUX || UNITY_ANDROID || UNITY_EDITOR_OSX || UNITY_EDITOR_LINUX)&& !UNITY_EDITOR_WIN
			[DllImport(libname, EntryPoint = "setPermissions")]
			internal static extern int setPermissions(string filePath, string _user, string _group, string _other);
		#endif

        [DllImport(libname, EntryPoint = "brCompress"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
		internal static extern int brCompress(string inFile, string outFile, IntPtr proc, int quality, int lgwin, int lgblock, int mode);

        [DllImport(libname, EntryPoint = "brDecompresss"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
		internal static extern int brDecompresss(string inFile, string outFile, IntPtr proc, IntPtr FileBuffer, int fileBufferLength);

        [DllImport(libname, EntryPoint = "brReleaseBuffer"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        public static extern void brReleaseBuffer(IntPtr buffer);

        [DllImport(libname, EntryPoint = "brCreate_Buffer"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		    , CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        public static extern IntPtr brCreate_Buffer(int size);

        [DllImport(libname, EntryPoint = "brAddTo_Buffer"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		    , CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        private static extern void brAddTo_Buffer(IntPtr destination, int offset, IntPtr buffer, int len);

        [DllImport(libname, EntryPoint = "brCompressBuffer"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        internal static extern IntPtr brCompressBuffer( int bufferLength, IntPtr buffer, IntPtr encodedSize, IntPtr proc, int quality, int lgwin, int lgblock, int mode);

		//this will work on small files with one meta block
        [DllImport(libname, EntryPoint = "brGetDecodedSize"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        internal static extern int brGetDecodedSize(int bufferLength, IntPtr buffer);

        [DllImport(libname, EntryPoint = "brDecompressBuffer"
        #if (UNITY_STANDALONE_WIN && ENABLE_IL2CPP) || UNITY_ANDROID
		, CallingConvention = CallingConvention.Cdecl
        #endif
        )]
        internal static extern int brDecompressBuffer(int bufferLength, IntPtr buffer, int outLength, IntPtr outbuffer);
#endif

#if (UNITY_IOS || UNITY_TVOS || UNITY_IPHONE || UNITY_WEBGL) && !UNITY_EDITOR

#if !UNITY_TVOS && !UNITY_WEBGL
		[DllImport("__Internal")]
		internal static extern int setPermissions(string filePath, string _user, string _group, string _other);
		[DllImport("__Internal")]
		internal static extern int brCompress( string inFile, string outFile, IntPtr proc, int quality, int lgwin, int lgblock, int mode);
        [DllImport("__Internal")]
		internal static extern int brDecompresss(string inFile, string outFile, IntPtr proc, IntPtr FileBuffer, int fileBufferLength);
        
#endif
        #if !UNITY_WEBGL
        [DllImport("__Internal")]
        public static extern IntPtr brCreate_Buffer(int size);
        [DllImport("__Internal")]
        internal static extern void brAddTo_Buffer(IntPtr destination, int offset, IntPtr buffer, int len);
        [DllImport("__Internal")]
        internal static extern IntPtr brCompressBuffer( int bufferLength, IntPtr buffer, IntPtr encodedSize, IntPtr proc, int quality, int lgwin, int lgblock, int mode);
        #endif

        [DllImport("__Internal")]
		public static extern void brReleaseBuffer(IntPtr buffer);
		//this will work on small files with one meta block
		[DllImport("__Internal")]
		internal static extern int brGetDecodedSize(int bufferLength, IntPtr buffer);
        [DllImport("__Internal")]
        internal static extern int brDecompressBuffer(int bufferLength, IntPtr buffer,  int outLength, IntPtr outbuffer);

#endif

#if (!UNITY_TVOS && !UNITY_WEBGL) || 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_IPHONE) && !UNITY_EDITOR_WIN
            if(!File.Exists(filePath)) return -1;
			return setPermissions(filePath, _user, _group, _other);
#else
			return -1;
#endif
	}


    //Helper functions
    internal static GCHandle gcA(object o) {
        return GCHandle.Alloc(o, GCHandleType.Pinned);
    }

    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 brotli format.
    //
    // Full paths to the files should be provided.
	// inFile:		The input file
	// outFile:		The output file
	// proc:		A single item ulong array to provide progress of compression
	//
    // quality:    (0  - 11) quality of compression (0 = faster/bigger - 11 = slower/smaller).
	//
	// Base 2 logarithm of the sliding window size. Range is 10 to 24.
	// lgwin  :    (10 - 24) memory used for compression (higher numbers use more ram)
	//
	// Base 2 logarithm of the maximum input block size. Range is 16 to 24.
	// If set to 0, the value will be set based on the quality.
	// lgblock:    0 for auto or 16-24
	// mode   :	  (0  -  2) 0 = default, 1 = utf8 text, 2 = woff 2.0 font
    //
	// error codes:	 1  : OK
	//				-1  : compression failed
	//				-2  : not enough memory
	//				-3  : could not close in file
	//				-4  : could not close out file
    //              -5  : no input file found
	//
    public static int compressFile(string inFile, string outFile,  ulong[] proc, int quality = 9, int lgwin = 19, int lgblock = 0, int mode = 0) {
        if(!File.Exists(inFile)) return -5;
        if (quality < 0) quality = 1; if (quality > 11) quality = 11;
		if (lgwin < 10) lgwin = 10; if(lgwin > 24) lgwin = 24;
        if(proc == null) proc = new ulong[1];
		GCHandle cbuf = GCHandle.Alloc(proc, GCHandleType.Pinned);
        int res = brCompress( @inFile, @outFile,cbuf.AddrOfPinnedObject(), quality, lgwin, lgblock, mode);
		cbuf.Free();
		return res;
    }

    // Decompress a brotli file.
    //
    // Full paths to the files should be provided.
	// inFile:		The input file
	// outFile:		The output file
	// proc:		A single item ulong array to provide progress of decompression
	// FileBuffer:	A buffer that holds a brotli file. When assigned the function will read from this buffer and will ignore the filePath. (Linux, iOS, Android, MacOSX)
    // returns: 1 on success.
	//
	// error codes:	 1  : OK
	//				-1  : failed to write output
	//				-2  : corrupt input
	//				-3  : could not close in file
	//				-4  : could not close out file
    //              -5  : no input file found
	//
    public static int decompressFile(string inFile, string outFile, ulong[] proc, object fileBuffer = null) {
        if(fileBuffer == null && !File.Exists(inFile)) return -5;
        if(proc == null) proc = new ulong[1];
		GCHandle cbuf = GCHandle.Alloc(proc, GCHandleType.Pinned);
		int res;

        #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 = brDecompresss(null, @outFile, cbuf.AddrOfPinnedObject(), fileBufferPointer, fileBufferLength);

				fbuf.Free();
                cbuf.Free();
				return res;
			}
        #endif

		res = brDecompresss(@inFile, @outFile, cbuf.AddrOfPinnedObject(), IntPtr.Zero, 0);
		cbuf.Free();
		return res;
    }

#endif



    // Get the uncompressed size of a brotli buffer.
    // This will work only on small buffers with one metablock. Otherwise use the includeSize/hasFooter flags with the buffer functions.
    //
    // inBuffer:	the input buffer that stores a brotli compressed buffer.
    public static int getDecodedSize(byte[] inBuffer)
    {
        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
        int res = brGetDecodedSize(inBuffer.Length, cbuf.AddrOfPinnedObject());
        cbuf.Free();
        return res;
    }

#if !UNITY_WEBGL || UNITY_EDITOR
    // Compress a byte buffer in brotli format.
    //
    // inBuffer:     the uncompressed buffer.
    // outBuffer:    a referenced buffer that will store the compressed data. (it should be large enough to store it.)
    // proc:		 A single item referenced ulong array to provide progress of compression
    //
    // includeSize:  include the uncompressed size of the buffer in the resulted compressed one because brotli does not support it for larger then 1 metablock.
    //
    // quality:    (0  - 11) quality of compression (0 = faster/bigger - 11 = slower/smaller).
    //
    // Base 2 logarithm of the sliding window size. Range is 10 to 24.
    // lgwin  :    (10 - 24) memory used for compression (higher numbers use more ram)
    //
    // Base 2 logarithm of the maximum input block size. Range is 16 to 24.
    // If set to 0, the value will be set based on the quality.
    // lgblock:    0 for auto or 16-24
    // mode   :	  (0  -  2) 0 = default, 1 = utf8 text, 2 = woff 2.0 font
    // returns true on success
    //
    public static bool compressBuffer(byte[] inBuffer, ref byte[] outBuffer, ulong[] proc, bool includeSize = false, int quality = 9, int lgwin = 19, int lgblock = 0, int mode = 0) {
	    if (quality < 0) quality = 1; if (quality > 11) quality = 11;
		if (lgwin < 10) lgwin = 10; if(lgwin > 24) lgwin = 24;

        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
        IntPtr ptr;

        int  size = 0;
		byte[] bsiz = null;
		int[] esiz = new int[1];//the compressed size
		GCHandle ebuf = GCHandle.Alloc(esiz, GCHandleType.Pinned);

        //if the uncompressed size of the buffer should be included. This is a hack since brotli lib does not support this on larger buffers.
        if (includeSize) {
			bsiz = new byte[4];
            size = 4;
            bsiz = BitConverter.GetBytes(inBuffer.Length);
            if (!BitConverter.IsLittleEndian) Array.Reverse(bsiz);
        }

        if(proc == null) proc = new ulong[1];
		GCHandle pbuf = GCHandle.Alloc(proc, GCHandleType.Pinned);

        ptr = brCompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), ebuf.AddrOfPinnedObject(), pbuf.AddrOfPinnedObject(), quality, lgwin, lgblock, mode);

        cbuf.Free(); ebuf.Free(); pbuf.Free();

        if (ptr == IntPtr.Zero) { brReleaseBuffer(ptr); esiz = null; bsiz = null; return false; }

        System.Array.Resize(ref outBuffer, esiz[0] + size);

		//add the uncompressed size to the buffer
        if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[i + esiz[0]] = bsiz[i]; }

        Marshal.Copy( ptr, outBuffer, 0, esiz[0] );

        brReleaseBuffer(ptr);
		esiz = null;
		bsiz = null;

        return true;
    }

	// same as above only this function returns a new created buffer with the compressed data.
	//
    public static byte[] compressBuffer(byte[] inBuffer,  int[] proc, bool includeSize = false, int quality = 9, int lgwin = 19, int lgblock = 0, int mode = 0) {
	    if (quality < 0) quality = 1; if (quality > 11) quality = 11;
		if (lgwin < 10) lgwin = 10; if(lgwin > 24) lgwin = 24;

        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
        IntPtr ptr;

        int  size = 0;
		byte[] bsiz = null;
		int[] esiz = new int[1];//the compressed size
		GCHandle ebuf = GCHandle.Alloc(esiz, GCHandleType.Pinned);

        // if the uncompressed size of the buffer should be included. This is a hack since brotli lib does not support this on larger buffers.
        if (includeSize) {
			bsiz = new byte[4];
            size = 4;
            bsiz = BitConverter.GetBytes(inBuffer.Length);
            if (!BitConverter.IsLittleEndian) Array.Reverse(bsiz);
        }

        if(proc == null) proc = new int[1];
		GCHandle pbuf = GCHandle.Alloc(proc, GCHandleType.Pinned);

        ptr = brCompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), ebuf.AddrOfPinnedObject(), pbuf.AddrOfPinnedObject(), quality, lgwin, lgblock, mode);

        cbuf.Free(); ebuf.Free(); pbuf.Free();

        if (ptr == IntPtr.Zero) { brReleaseBuffer(ptr); esiz = null; bsiz = null; return null; }

		byte[] outBuffer = new byte[esiz[0] + size];

		//add the uncompressed size to the buffer
        if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[i + esiz[0]] = bsiz[i]; }

        Marshal.Copy( ptr, outBuffer, 0, esiz[0] );

        brReleaseBuffer(ptr);
		esiz = null;
		bsiz = null;

        return outBuffer;
    }

	// Same as above, only this time the compressed buffer is written in a fixed size buffer.
	// The compressed size in bytes is returned. If includeSize is true, then 4 extra bytes are written at the end of the buffer.
	// The fixed size buffer should be larger then the compressed size returned.
    //
    public static int compressBuffer(byte[] inBuffer, byte[] outBuffer, int[] proc, bool includeSize = false, int quality = 9, int lgwin = 19, int lgblock = 0, int mode = 0) {
	    if (quality < 0) quality = 1; if (quality > 11) quality = 11;
		if (lgwin < 10) lgwin = 10; if(lgwin > 24) lgwin = 24;

        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
        IntPtr ptr;

        int  size = 0;
        byte[] bsiz = null;
		int res = 0;
		int[] esiz = new int[1];//the compressed size
		GCHandle ebuf = GCHandle.Alloc(esiz, GCHandleType.Pinned);

        // if the uncompressed size of the buffer should be included. This is a hack since brotli lib does not support this on larger buffers.
        if (includeSize) {
			bsiz = new byte[4];
            size = 4;
            bsiz = BitConverter.GetBytes(inBuffer.Length);
            if (!BitConverter.IsLittleEndian) Array.Reverse(bsiz);
        }

        if(proc == null) proc = new int[1];
		GCHandle pbuf = GCHandle.Alloc(proc, GCHandleType.Pinned);

        ptr = brCompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), ebuf.AddrOfPinnedObject(), pbuf.AddrOfPinnedObject(), quality, lgwin, lgblock, mode);

        cbuf.Free(); ebuf.Free(); pbuf.Free();
		res = esiz[0];

        if (ptr == IntPtr.Zero || outBuffer.Length < (esiz[0] + size)) { brReleaseBuffer(ptr); esiz = null; bsiz = null; return 0; }

        Marshal.Copy( ptr, outBuffer, 0, esiz[0] );

        if (includeSize) { for (int i = 0; i < 4; i++) outBuffer[i + esiz[0]] = bsiz[i]; }

        brReleaseBuffer(ptr);
		esiz = null;
        bsiz = null;

        return res + size;
    }

#endif

    // Decompress a brotli compressed buffer to a referenced buffer.
    //
    // inBuffer:            the brotli 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.
    // unCompressedSize:    if unCompressedSize is > 0 then this is the uncompressed size that will be used. Useful when decompressing brotli buffers created from servers.
    //
    // returns true on success
    //
    public static bool decompressBuffer(byte[] inBuffer, ref byte[] outBuffer, bool useFooter = false, int unCompressedSize = 0) {
        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
		int uncompressedSize = 0, res2 = inBuffer.Length;

        if (unCompressedSize == 0) { 
            if (useFooter) {
                res2 -= 4;
                uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
            } else {
			    //use the brotli native method to get the uncompressed size (this will work on buffers with one metablock)
                uncompressedSize = getDecodedSize(inBuffer); 
            }
        } else {
            uncompressedSize = unCompressedSize;
        }

        System.Array.Resize(ref outBuffer, uncompressedSize);

        GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);

        int res = brDecompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), uncompressedSize, obuf.AddrOfPinnedObject());

        cbuf.Free();
        obuf.Free();

        if(res == 1) return true; else return false;
    }

	// same as above only this time the uncompressed data is returned in a new created buffer
	//
    public static byte[] decompressBuffer(byte[] inBuffer, bool useFooter = false, int unCompressedSize = 0) {
        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
		int uncompressedSize = 0, res2 = inBuffer.Length;

        if (unCompressedSize == 0) {
            if (useFooter) {
                res2 -= 4;
                uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
            } else {
			    //use the brotli native method to get the uncompressed size (this will work on buffers with one metablock)
                uncompressedSize = getDecodedSize(inBuffer); 
            }
        } else {
            uncompressedSize = unCompressedSize;
        }

        byte[] outBuffer = new byte[uncompressedSize];

        GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);

        int res = brDecompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), uncompressedSize, obuf.AddrOfPinnedObject());

        cbuf.Free();
        obuf.Free();

        if(res == 1) return outBuffer; else return null;
    }


	// same as above only the decompressed data will be stored in a fixed size outBuffer.
	// make sure the fixed buffer is big enough to store the data.
	//
	// returns: uncompressed size in bytes.
	//
    public static int decompressBuffer(byte[] inBuffer, byte[] outBuffer, bool useFooter = false, int unCompressedSize = 0) {
        GCHandle cbuf = GCHandle.Alloc(inBuffer, GCHandleType.Pinned);
		int uncompressedSize = 0, res2 = inBuffer.Length;

        if (unCompressedSize == 0) {
            if (useFooter) {
                res2 -= 4;
                uncompressedSize = (int)BitConverter.ToInt32(inBuffer, res2);
            } else {
                //use the brotli native method to get the uncompressed size (this will work on buffers with one metablock)
                uncompressedSize = getDecodedSize(inBuffer);
            }
        } else {
            uncompressedSize = unCompressedSize;
        }

        GCHandle obuf = GCHandle.Alloc(outBuffer, GCHandleType.Pinned);

        int res = brDecompressBuffer(inBuffer.Length, cbuf.AddrOfPinnedObject(), uncompressedSize, obuf.AddrOfPinnedObject());

        cbuf.Free();
        obuf.Free();

        if(res == 1) return uncompressedSize; else return 0;
    }

    #if !UNITY_WEBGL && !UNITY_TVOS
    // 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 brotli.brReleaseBuffer 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 downloadBrFileNative(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 = brCreate_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 CustomWebRequest5(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 CustomWebRequest5 : DownloadHandlerScript {

        public CustomWebRequest5()
            : base()
        {
        }

        public CustomWebRequest5(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
            brAddTo_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
#endif
}

