我有一些文件*.bli、*.ilg、*.pix 等。这些文件来自技术文档LinkOne,并使用旧的C/Win32 压缩库crush32.dll 进行压缩。
信息可以在这里找到 逆向工程文件格式 - ImageLink
我找到了 c# 代码来解压缩它,但它不起作用。我做错了什么?我试着阅读 s001.bli,没关系,比从结果得到“真”,仅此而已。decompressedPixelData 为空,大小为 0。
我通过 Visual Studio 2012 在 VirtualBox 中模拟的 Windows 7 X86 上运行代码。rush32.dll 库似乎只适用于 x86,尽管在 win10 x64 上结果完全相同。
文件和 dll:https : //drive.google.com/file/d/1GipwXL9ikog1YVu46uqxLfuuY1Rk-5Pc/view
代码:
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
#region Public main
[STAThread]
static void Main(string[] args)
{
byte[] Data = File.ReadAllBytes("s001.bli");
int CompressedLength = Data.Length;
int UncompressedLength = 0;
byte[] decompressedPixelData = new byte[UncompressedLength];
bool result = Crush32.Decompress(Data, 15, decompressedPixelData, 0, (int)UncompressedLength, CompressedLength);
string s = "{" +
string.Join(", ",
decompressedPixelData.Select(x => string.Format("0x{0}", x.ToString("X")))
) + "}";
Console.WriteLine(s);
Console.ReadKey(true);
}
#endregion
}
}