Распаковка файлов с помощью crush32.dll

120
05 января 2021, 19:50

У меня есть техническая документация, хранящаяся в файлах *.bli, *.ilg, *.pix и т.д. Файлы содержат текстовые данные и графические изображения. И, судя по всему, они сжаты старой библиотекой crush32.dll (https://drive.google.com/file/d/1GipwXL9ikog1YVu46uqxLfuuY1Rk-5Pc/view).

Информация взята отсюда: https://reverseengineering.stackexchange.com/questions/20771/reverse-engineering-file-format-imagelink

Я в интернете нашёл код для распаковки на C#, попытался его использовать, но ничего не вышло: переменная decompressedPixelData ничего не возвращает.

Поскольку библиотека, вроде как, работает только на x32, код я запускал на эмулированной через VirtualBox Win7 x32 в Visual Studio. Возможно я делаю что-то не так?

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace Crush32Wrap
{
    public unsafe static class Crush32
    {
        #region Private PINVOKE signatures
        /// <summary>
        /// Name of legacy crush32.dll file (put it in same folder!)
        /// </summary>
        public const string DLLName = "crush32.dll";
        public const string FName = "s001.bli";
        /// <summary>
        /// The cxERROR for SUCCESS
        /// </summary>
        private const short CX_SUCCESS = 0;
        /// <summary>
        /// Initializes the library
        /// </summary>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxInit();
        /// <summary>
        /// Cleanup
        /// </summary>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxCleanup();
        /// <summary>
        /// Initializes a buffer to buffer operation.
        /// </summary>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBuf2BufInit();
        /// <summary>
        /// Closes a buffer to buffer operation
        /// </summary>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBuf2BufClose();
        /// <summary>
        /// Decrypts data in a buffer.
        /// </summary>
        /// <param name="Buffer">A pointer to the data to be decrypted.</param>
        /// <param name="Length">How many bytes should be decrypted.</param>
        /// <param name="Challenge">The Challenge used to test your password first.</param>
        /// <param name="ExpectedResponse">The ExpectedResponse matching your Challenge and your password.</param>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBufDecrypt(byte* Buffer, int Length, uint Challenge, uint ExpectedResponse);
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBufDecrypt(IntPtr Buffer, int Length, uint Challenge, uint ExpectedResponse);

        /// <summary>
        /// Encrypts data in a buffer.
        /// </summary>
        /// <param name="Buffer">A pointer to the data to be encrypted.</param>
        /// <param name="Length">How many bytes should be encrypted.</param>
        /// <param name="Challenge">The Challenge used to create the returnvalue.</param>
        /// <returns>The "ExpectedResponse" matching your given Challenge and your Password, use this in decryption later.</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern uint cxBufEncrypt(IntPtr Buffer, int Length, uint Challenge);
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern uint cxBufEncrypt(byte* Buffer, int Length, uint Challenge);

        /// <summary>
        /// Sets a password for the encryption and decryption methods.
        /// </summary>
        /// <param name="PtrPasswordString">A pointer to the nullterminated passwordstring in memory.</param>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxSetPassword(IntPtr PtrPasswordString);
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxSetPassword(byte* Password);
        /// <summary>
        /// Compresses the data from the InputBuffer to the OutputBuffer.
        /// </summary>
        /// <param name="InputBuffer">A pointer to read inputdata from.</param>
        /// <param name="OutputBuffer">A pointer to write outputdata to.</param>
        /// <param name="InputLength">How many bytes should be read from input buffer.</param>
        /// <returns>The length of the compressed data.</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern int cxBuf2BufCompress(IntPtr InputBuffer, IntPtr OutputBuffer, int InputLength);
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern int cxBuf2BufCompress(byte* InputBuffer, byte* OutputBuffer, int InputLength);
        /// <summary>
        /// Decompresses the data from the InputBuffer to the OutputBuffer.
        /// </summary>
        /// <param name="InputBuffer">A pointer to read inputdata from.</param>
        /// <param name="OutputBuffer">A pointer to write outputdata to.</param>
        /// <param name="DecompressedLength">The length of the data in decompressed state.</param>
        /// <param name="CompressedLength">The length of the data in compressed state.</param>
        /// <returns>cxERROR statevalue</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBuf2BufExpand(IntPtr InputBuffer, IntPtr OutputBuffer, int DecompressedLength, int CompressedLength);
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        private static extern short cxBuf2BufExpand(byte* InputBuffer, byte* OutputBuffer, int DecompressedLength, int CompressedLength);
        /// <summary>
        /// Creates a CRC32 for the data in the Buffer.
        /// Note: The used algorithm is equal to
        /// http://damieng.com/blog/2006/08/08/calculating_crc32_in_c_and_net
        /// </summary>
        /// <param name="Buffer">A pointer to read the data to CRC.</param>
        /// <param name="Length">How many bytes should be read from pointer.</param>
        /// <returns>The created CRC32</returns>
        [DllImport(DLLName, CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        public static extern uint cxBufCRC32(IntPtr Buffer, int Length);
        #endregion
        #region Public Wrappers
        /// <summary>
        /// Fast decompresses data from managed sourcebuffer to managed targetbuffer using pointers
        /// </summary>
        /// <param name="SourceBuffer">SourceBuffer containing compressed data</param>
        /// <param name="SourceIndex">Cursor in SourceBuffer to start reading</param>
        /// <param name="TargetBuffer">TargetBuffer for decompressed data</param>
        /// <param name="TargetIndex">Cursor in TargetBuffer to start writing</param>
        /// <param name="UncompressedLength">Uncompressed data length</param>
        /// <param name="CompressedLength">Compressed data length</param>
        /// <returns>True if the operation was successful.</returns>
        public static bool Decompress(byte[] SourceBuffer, int SourceIndex, byte[] TargetBuffer, int TargetIndex, int UncompressedLength, int CompressedLength)
        {
            bool isSuccessful = false;
            // init crush32
            if (Crush32.CX_SUCCESS == Crush32.cxInit())
            {
                if (Crush32.CX_SUCCESS == Crush32.cxBuf2BufInit())
                {
                    // pin the managed source & targetbuffers in memory so crush32 can access them
                    // and we can directly use them without marshaling.
                    fixed (byte* ptrSourceBuffer = SourceBuffer, ptrTargetBuffer = TargetBuffer)
                    {
                        // add the offsets to the pointers (they still point to the beginning of the buffer)
                        byte* ptrSourceIndex = ptrSourceBuffer + SourceIndex;
                        byte* ptrTargetIndex = ptrTargetBuffer + TargetIndex;
                        if (Crush32.CX_SUCCESS == Crush32.cxBuf2BufExpand(ptrSourceIndex, ptrTargetIndex, UncompressedLength, CompressedLength))
                            isSuccessful = true;
                    }

                    Crush32.cxBuf2BufClose();
                }
                Crush32.cxCleanup();
            }
            return isSuccessful;
        }
        /// <summary>
        /// Fast compresses data from managed sourcebuffer to managed targetbuffer using pointers
        /// </summary>
        /// <param name="SourceBuffer">SourceBuffer containing data to be compressed</param>
        /// <param name="SourceIndex">Cursor in SourceBuffer to start reading</param>
        /// <param name="TargetBuffer">TargetBuffer for compressed data</param>
        /// <param name="TargetIndex">Cursor in TargetBuffer to start writing</param>
        /// <param name="UncompressedLength">Uncompressed data length in SourceBuffer</param>
        /// <returns>Compressed length</returns>
        public static int Compress(byte[] SourceBuffer, int SourceIndex, byte[] TargetBuffer, int TargetIndex, int UncompressedLength)
        {
            int compressedLength = 0;
            // init crush32
            if (Crush32.CX_SUCCESS == Crush32.cxInit())
            {
                if (Crush32.CX_SUCCESS == Crush32.cxBuf2BufInit())
                {
                    // pin the managed source & targetbuffers in memory so crush32 can access them
                    // and we can directly use them without marshaling.
                    fixed (byte* ptrSourceBuffer = SourceBuffer, ptrTargetBuffer = TargetBuffer)
                    {
                        // add the offsets to the pointers (they still point to the beginning of the buffer)
                        byte* ptrSourceIndex = ptrSourceBuffer + SourceIndex;
                        byte* ptrTargetIndex = ptrTargetBuffer + TargetIndex;
                        // compress
                        compressedLength = Crush32.cxBuf2BufCompress(ptrSourceIndex, ptrTargetIndex, UncompressedLength);
                    }
                    Crush32.cxBuf2BufClose();
                }
                Crush32.cxCleanup();
            }
            return compressedLength;
        }
        /// <summary>
        /// Fast decrypt data in managed buffer using pointer
        /// </summary>
        /// <param name="SourceBuffer">SourceBuffer containing encrypted data</param>
        /// <param name="SourceIndex">Cursor in SourceBuffer to start reading</param>
        /// <param name="Length">Encrypted data length</param>
        /// <param name="Challenge">Challenge to use</param>
        /// <param name="ExpectedResponse">ExpectedRespone to use</param>
        /// <param name="Password">Password to use</param>
        /// <returns>True if the operation was successful.</returns>
        public static bool Decrypt(byte[] SourceBuffer, int SourceIndex, int Length, uint Challenge, uint ExpectedResponse, byte[] Password)
        {
            bool isSuccessful = false;
            // pin the managed sourcebuffer in memory so crush32 can access
            // and we can directly use without marshaling.
            fixed (byte* ptrSourceBuffer = SourceBuffer)
            {
                // add the offsets to the pointers (they still point to the beginning of the buffer)
                byte* ptrSourceIndex = ptrSourceBuffer + SourceIndex;
                isSuccessful = Decrypt(ptrSourceIndex, Length, Challenge, ExpectedResponse, Password);
            }
            return isSuccessful;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="SourceBuffer"></param>
        /// <param name="Length"></param>
        /// <param name="Challenge"></param>
        /// <param name="ExpectedREsponse"></param>
        /// <param name="Password"></param>
        /// <returns></returns>
        public static unsafe bool Decrypt(byte* SourceBuffer, int Length, uint Challenge, uint ExpectedResponse, byte[] Password)
        {
            bool isSuccessful = false;
            // init crush32
            if (Crush32.CX_SUCCESS == Crush32.cxInit())
            {
                if (Crush32.CX_SUCCESS == Crush32.cxBuf2BufInit())
                {
                    // pin the managed sourcebuffer in memory so crush32 can access
                    // and we can directly use without marshaling.
                    fixed (byte* ptrPassword = Password)
                    {
                        // set password && decrypt
                        if (Crush32.CX_SUCCESS == Crush32.cxSetPassword(ptrPassword))
                            if (Crush32.CX_SUCCESS == Crush32.cxBufDecrypt(SourceBuffer, Length, Challenge, ExpectedResponse))
                                isSuccessful = true;
                    }
                    Crush32.cxBuf2BufClose();
                }
                Crush32.cxCleanup();
            }
            return isSuccessful;
        }
        /// <summary>
        /// Fast encrypt data in managed buffer using pointer
        /// </summary>
        /// <param name="SourceBuffer">SourceBuffer containing data to be encrypted</param>
        /// <param name="SourceIndex">Cursor in SourceBuffer to start reading</param>
        /// <param name="Length">Length of data to be encrypted</param>
        /// <param name="Challenge">Challenge to use</param>
        /// <param name="Password">Password to use</param>
        /// <returns>ExpectedResponse for Challenge/Password combination.</returns>
        public static uint Encrypt(byte[] SourceBuffer, int SourceIndex, int Length, uint Challenge, byte[] Password)
        {
            uint expectedResponse = 0;
            // pin the managed sourcebuffer in memory so crush32 can access
            // and we can directly use without marshaling.
            fixed (byte* ptrSourceBuffer = SourceBuffer, ptrPassword = Password)
            {
                // add the offsets to the pointers (they still point to the beginning of the buffer)
                byte* ptrSourceIndex = ptrSourceBuffer + SourceIndex;
                expectedResponse = Encrypt(ptrSourceIndex, Length, Challenge, Password);
            }
            return expectedResponse;
        }
        /// <summary>
        /// Fast encrypt data on unmanaged pointer
        /// </summary>
        /// <param name="SourceBuffer"></param>
        /// <param name="Length"></param>
        /// <param name="Challenge"></param>
        /// <param name="Password"></param>
        /// <returns></returns>
        public static unsafe uint Encrypt(byte* SourceBuffer, int Length, uint Challenge, byte[] Password)
        {
            uint expectedResponse = 0;
            // init crush32
            if (Crush32.CX_SUCCESS == Crush32.cxInit())
            {
                if (Crush32.CX_SUCCESS == Crush32.cxBuf2BufInit())
                {
                    // pin the managed sourcebuffer in memory so crush32 can access
                    // and we can directly use without marshaling.
                    fixed (byte* ptrPassword = Password)
                    {
                        // set password && encrypt
                        if (Crush32.CX_SUCCESS == Crush32.cxSetPassword(ptrPassword))
                            expectedResponse = Crush32.cxBufEncrypt(SourceBuffer, Length, Challenge);
                    }
                    Crush32.cxBuf2BufClose();
                }
                Crush32.cxCleanup();
            }
            return expectedResponse;
        }
        #endregion
    }
}

Запакованные данные:

Выделенно жёлтым - размер запакованных данных. Обведёное синим предположительно заголовок, но может быть и размером распакованных данных. Однако попытки использовать его в коде не увенчались успехом. Вообще, если передавать функции Decompress в размере распакованных данных всё, кроме 0, выпадает ошибка чтения\записи в память\из памяти.

Удалось распаковать данные, но они нечитаемы.

UPD: Разобрался. Всё заработало. Структура файла выглядит так:

В распакованном виде текстовые данные выглядят так:

Выяснилось, что ниже текстовых данных есть что-то вроде структуры таблицы, но как точно оно работает непонятно. Есть блоки, количество которых соответствует количеству строк текстовых данных: Если в данных ничего не менять (или поменять, но сохранить длину данных), запаковать, то файл без проблем читается LinkOne-ом, но если изменить длину данных, то возникает проблема - данные отображаются в LinkOne непредсказуемо.

В LinkOne файл выглядит так:

READ ALSO
Ошибка при передаче sql параметра

Ошибка при передаче sql параметра

Есть код, в котором должен передаться параметр stopWord

144
Поток берет Строку с символами из другого потока и заменяет все гласные буквы на символ &ldquo;-&rdquo;

Поток берет Строку с символами из другого потока и заменяет все гласные буквы на символ “-”

Как можно сделать чтоб поток брал символы с 1го потока , и заменял все гласные буквы на символ “-” и дописывал результат обработки в выходной...

114
Как использовать значение из цикла при обращении к объекту?

Как использовать значение из цикла при обращении к объекту?

Есть 39 лейблов с однотипным названием label_1, label_2

136