using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using FunctionLoader = Packaging.Targets.Native.FunctionLoader; namespace Packaging.Targets.IO { /// /// Provides access to the liblzma API. liblzma is part of the xz suite. /// /// /// You can download pre-built binaries from Windows from https://tukaani.org/xz/. /// /// /// internal static class NativeMethods { /// /// The name of the lzma library. /// /// /// You can fetch liblzma from https://github.com/RomanBelkov/XZ.NET/blob/master/XZ.NET/liblzma.dll /// private const string LibraryName = @"lzma"; private static lzma_stream_decoder_delegate lzma_stream_decoder_ptr; private static lzma_code_delegate lzma_code_ptr; private static lzma_stream_footer_decode_delegate lzma_stream_footer_decode_ptr; private static lzma_index_uncompressed_size_delegate lzma_index_uncompressed_size_ptr; private static lzma_index_buffer_decode_delegate lzma_index_buffer_decode_ptr; private static lzma_index_end_delegate lzma_index_end_ptr; private static lzma_end_delegate lzma_end_ptr; private static lzma_easy_encoder_delegate lzma_easy_encoder_ptr; private static lzma_stream_encoder_mt_delegate lzma_stream_encoder_mt_ptr; private static lzma_stream_buffer_bound_delegate lzma_stream_buffer_bound_ptr; private static lzma_easy_buffer_encode_delegate lzma_easy_buffer_encode_ptr; static NativeMethods() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { if (RuntimeInformation.OSArchitecture != Architecture.X64) { throw new InvalidOperationException(".NET packaging only supports 64-bit Windows processes"); } } var libraryPath = Path.GetDirectoryName(typeof(NativeMethods).GetTypeInfo().Assembly.Location); var lzmaWindowsPath = Path.GetFullPath(Path.Combine(libraryPath, "../../runtimes/win7-x64/native/lzma.dll")); IntPtr library = FunctionLoader.LoadNativeLibrary( new string[] { lzmaWindowsPath, "lzma.dll" }, // lzma.dll is used when running unit tests. new string[] { "liblzma.so.5", "liblzma.so" }, new string[] { "liblzma.dylib" }); if (library == IntPtr.Zero) { throw new FileLoadException("Could not load liblzma. On Linux, make sure you've installed liblzma-dev or an equivalent package."); } lzma_stream_decoder_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_decoder)); lzma_code_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_code)); lzma_stream_footer_decode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_footer_decode)); lzma_index_uncompressed_size_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_uncompressed_size)); lzma_index_buffer_decode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_buffer_decode)); lzma_index_end_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_index_end)); lzma_end_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_end)); lzma_easy_encoder_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_easy_encoder)); lzma_stream_encoder_mt_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_encoder_mt), throwOnError: false); lzma_stream_buffer_bound_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_stream_buffer_bound)); lzma_easy_buffer_encode_ptr = FunctionLoader.LoadFunctionDelegate(library, nameof(lzma_easy_buffer_encode)); } private delegate LzmaResult lzma_stream_decoder_delegate(ref LzmaStream stream, ulong memLimit, LzmaDecodeFlags flags); private unsafe delegate LzmaResult lzma_easy_buffer_encode_delegate(uint preset, LzmaCheck check, void* allocator, byte[] @in, UIntPtr in_size, byte[] @out, UIntPtr* out_pos, UIntPtr out_size); private delegate UIntPtr lzma_stream_buffer_bound_delegate(UIntPtr uncompressed_size); private delegate LzmaResult lzma_stream_encoder_mt_delegate(ref LzmaStream stream, ref LzmaMT mt); private delegate LzmaResult lzma_easy_encoder_delegate(ref LzmaStream stream, uint preset, LzmaCheck check); private delegate void lzma_end_delegate(ref LzmaStream stream); private delegate void lzma_index_end_delegate(IntPtr i, IntPtr allocator); private delegate uint lzma_index_buffer_decode_delegate(ref IntPtr i, ref ulong memLimit, IntPtr allocator, byte[] indexBuffer, ref uint inPosition, ulong inSize); private delegate ulong lzma_index_uncompressed_size_delegate(IntPtr i); private delegate LzmaResult lzma_stream_footer_decode_delegate(ref LzmaStreamFlags options, byte[] inp); private delegate LzmaResult lzma_code_delegate(ref LzmaStream stream, LzmaAction action); /// /// Gets a value indicating whether the underlying native library supports multithreading. /// public static bool SupportsMultiThreading { get { return lzma_stream_encoder_mt_ptr != null; } } /// /// Initialize .xz Stream decoder /// /// /// Pointer to properly prepared /// /// /// Memory usage limit as bytes. Use UINT64_MAX /// to effectively disable the limiter. /// /// /// Bitwise-or of zero or more of the decoder flags: /// , , /// , . /// /// /// : Initialization was successful, /// : Cannot allocate memory, /// : Unsupported flags, /// . /// /// public static LzmaResult lzma_stream_decoder(ref LzmaStream stream, ulong memLimit, LzmaDecodeFlags flags) => lzma_stream_decoder_ptr(ref stream, memLimit, flags); /// /// Encode or decode data /// /// /// The for which to read the data. /// /// /// The action to perform. /// /// /// A value. /// /// /// /// Once the has been successfully initialized (e.g. with /// lzma_stream_encoder()), the actual encoding or decoding is done /// using this function.The application has to update , /// , , and to pass input /// to and get output from liblzma. /// /// /// See the description of the coder-specific initialization function to find /// out what values are supported by the coder. /// /// public static LzmaResult lzma_code(ref LzmaStream stream, LzmaAction action) => lzma_code_ptr(ref stream, action); /// /// Decode Stream Footer /// /// /// Target for the decoded Stream Header options. /// /// /// Beginning of the input buffer of /// LZMA_STREAM_HEADER_SIZE bytes. /// /// /// : Decoding was successful. /// : Magic bytes don't match, thus the given buffer cannot be Stream Footer. /// : CRC32 doesn't match, thus the Stream Footer is corrupt. /// : Unsupported options are present in Stream Footer. /// /// /// If Stream Header was already decoded successfully, but /// decoding Stream Footer returns , the /// application should probably report some other error message /// than "file format not recognized", since the file more likely /// is corrupt(possibly truncated). Stream decoder in liblzma /// uses in this situation. /// /// public static LzmaResult lzma_stream_footer_decode(ref LzmaStreamFlags options, byte[] inp) => lzma_stream_footer_decode_ptr(ref options, inp); /// /// Get the uncompressed size of the file /// /// public static ulong lzma_index_uncompressed_size(IntPtr i) => lzma_index_uncompressed_size_ptr(i); /// /// Single-call .xz Index decoder /// /// If decoding succeeds, will point to a new lzma_index, which the application has to /// later free with lzma_index_end(). If an error occurs, will be . The old value of /// is always ignored and thus doesn't need to be initialized by the caller. /// /// /// Pointer to how much memory the resulting lzma_index is allowed to require. The value /// pointed by this pointer is modified if and only if is returned. /// /// /// Pointer to lzma_allocator, or to use malloc() /// /// /// Beginning of the input buffer /// /// /// The next byte will be read from in[*in_pos]. *in_pos is updated only if decoding succeeds. /// /// /// Size of the input buffer; the first byte that won't be read is in[in_size]. /// /// /// : Decoding was successful. /// , /// : Memory usage limit was reached. The minimum required memlimit value was stored to* memlimit. /// , /// . /// /// public static uint lzma_index_buffer_decode(ref IntPtr i, ref ulong memLimit, IntPtr allocator, byte[] indexBuffer, ref uint inPosition, ulong inSize) => lzma_index_buffer_decode_ptr(ref i, ref memLimit, allocator, indexBuffer, ref inPosition, inSize); /// /// Deallocate lzma_index /// /// /// The index to deallocate /// /// /// The allocated used to allocate the memory. /// /// /// If is , this does nothing. /// /// public static void lzma_index_end(IntPtr i, IntPtr allocator) => lzma_index_end_ptr(i, allocator); /// /// Free memory allocated for the coder data structures /// /// /// Pointer to lzma_stream that is at least initialized with LZMA_STREAM_INIT. /// /// /// After , is guaranteed to be . /// No other members of the structure are touched. /// zlib indicates an error if application end()s unfinished stream structure. /// liblzma doesn't do this, and assumes that /// application knows what it is doing. /// /// public static void lzma_end(ref LzmaStream stream) => lzma_end_ptr(ref stream); /// /// /// Initialize .xz Stream encoder using a preset number. /// /// /// This function is intended for those who just want to use the basic features if liblzma(that is, most developers out there). /// /// /// If initialization fails(return value is not LZMA_OK), all the memory allocated for *strm by liblzma is always freed.Thus, there is no need to call lzma_end() after failed initialization. /// /// If initialization succeeds, use lzma_code() to do the actual encoding.Valid values for `action' (the second argument of lzma_code()) are LZMA_RUN, LZMA_SYNC_FLUSH, LZMA_FULL_FLUSH, and LZMA_FINISH. In future, there may be compression levels or flags that don't support LZMA_SYNC_FLUSH. /// /// /// /// Pointer to lzma_stream that is at least initialized with LZMA_STREAM_INIT. /// /// /// Compression preset to use. A preset consist of level number and zero or more flags. Usually flags aren't used, so preset is simply a number [0, 9] which match the options -0 ... -9 of the xz command line tool. Additional flags can be be set using bitwise-or with the preset level number, e.g. 6 | LZMA_PRESET_EXTREME. /// /// /// Integrity check type to use. See check.h for available checks. The xz command line tool defaults to LZMA_CHECK_CRC64, which is a good choice if you are unsure. LZMA_CHECK_CRC32 is good too as long as the uncompressed file is not many gigabytes. /// /// /// /// - LZMA_OK: Initialization succeeded. Use lzma_code() to encode your data. /// /// - LZMA_MEM_ERROR: Memory allocation failed. /// /// /// - LZMA_OPTIONS_ERROR: The given compression preset is not supported by this build of liblzma. /// /// /// - LZMA_UNSUPPORTED_CHECK: The given check type is not supported by this liblzma build. /// /// /// - LZMA_PROG_ERROR: One or more of the parameters have values that will never be valid. For example, strm == NULL. /// /// public static LzmaResult lzma_easy_encoder(ref LzmaStream stream, uint preset, LzmaCheck check) => lzma_easy_encoder_ptr(ref stream, preset, check); /// /// /// Initialize multithreaded .xz Stream encoder /// /// /// This provides the functionality of lzma_easy_encoder() and /// lzma_stream_encoder() as a single function for multithreaded use. /// /// /// The supported actions for lzma_code() are LZMA_RUN, LZMA_FULL_FLUSH, /// LZMA_FULL_BARRIER, and LZMA_FINISH. Support for LZMA_SYNC_FLUSH might be /// added in the future. /// /// /// /// Pointer to properly prepared lzma_stream /// /// /// Pointer to multithreaded compression options /// /// /// A value which indicates success or failure. /// public static LzmaResult lzma_stream_encoder_mt(ref LzmaStream stream, ref LzmaMT mt) { if (SupportsMultiThreading) { return lzma_stream_encoder_mt_ptr(ref stream, ref mt); } else { throw new PlatformNotSupportedException("lzma_stream_encoder_mt is not supported on this platform. Check SupportsMultiThreading to see whether you can use this functionality."); } } /// /// /// Calculate output buffer size for single-call Stream encoder /// /// /// When trying to compress uncompressible data, the encoded size will be slightly bigger than the input data.This function calculates how much output buffer space is required to be sure that lzma_stream_buffer_encode() doesn't return LZMA_BUF_ERROR. /// /// /// The calculated value is not exact, but it is guaranteed to be big enough.The actual maximum output space required may be slightly smaller (up to about 100 bytes). This should not be a problem in practice. /// /// /// If the calculated maximum size doesn't fit into size_t or would make the Stream grow past LZMA_VLI_MAX (which should never happen in practice), zero is returned to indicate the error. /// /// /// /// The uncompressed size. /// /// /// The buffer output size. /// /// /// The limit calculated by this function applies only to single-call encoding. Multi-call encoding may (and probably will) have larger maximum expansion when encoding uncompressible data. Currently there is no function to calculate the maximum expansion of multi-call encoding. /// public static UIntPtr lzma_stream_buffer_bound(UIntPtr uncompressed_size) => lzma_stream_buffer_bound_ptr(uncompressed_size); /// /// Single-call .xz Stream encoding using a preset number. /// /// /// Compression preset to use. See the description in lzma_easy_encoder(). /// /// /// Type of the integrity check to calculate from uncompressed data. /// /// /// lzma_allocator for custom allocator functions. Set to NULL to use malloc() and free(). /// /// /// Beginning of the input buffer /// /// /// Size of the input buffer /// /// /// Beginning of the output buffer /// /// /// The next byte will be written to out[*out_pos]. *out_pos is updated only if encoding succeeds. /// /// /// Size of the out buffer; the first byte into which no data is written to is out[out_size]. /// /// /// /// - LZMA_OK: Encoding was successful. /// /// /// - LZMA_BUF_ERROR: Not enough output buffer space. /// /// /// - LZMA_OPTIONS_ERROR /// /// /// - LZMA_MEM_ERROR /// /// /// - LZMA_DATA_ERROR /// /// /// - LZMA_PROG_ERROR /// /// /// /// The maximum required output buffer size can be calculated with lzma_stream_buffer_bound() /// public static unsafe LzmaResult lzma_easy_buffer_encode(uint preset, LzmaCheck check, void* allocator, byte[] @in, UIntPtr in_size, byte[] @out, UIntPtr* out_pos, UIntPtr out_size) => lzma_easy_buffer_encode_ptr(preset, check, allocator, @in, in_size, @out, out_pos, out_size); } }