解码 Z64 (ZB64) 字符串

Decoding Z64 (ZB64) string

我正在努力分解由 NiceLabel 标签制作软件生成的 ZPL 标签定义。在大多数情况下,我不必担心解码 Z64,因为它只是编码图形,我不需要更改基础数据。

但是,出于某种原因,我有一行文本被标签用作图形,可能是由于字体或其他原因。

无论如何,Z64 或 ZB64 字符串是通过使用 LZ77 压缩原始数据并将其编码为 Base64,然后在末尾附加 CRC 来创建的。

测试字符串完整示例:

:Z64:eJztkDFOxDAQRb81hRsULmBtruECyRwpZYpFGLmg5AhwFKMUuYal9CtL26QwHsbe3RMguv3lz9P85wD3/CWaiZ+56OjqWA44cwKIAyfeXXL1sQ7YWqd54czltTge+VOdOQsXFp8TrLUw9KEW3+6pLU4Zk3mC0ataonSEzU8JMywGCiFcue+c8YLGvYcLF5a+68WFhbvtRs5jdmVkWolj96vgXe/it7eucT+0+gxV5N5RrdTveQpevhnxO+BEfRe0xIzc/EbUzkn3lhLSIH6DdFeu+c39Hb7c7vksfrJryB8vu6A4cxE/NjpK1/6LkJZ3+nL1gaLt3D33/Ed+AehfkrY=:6C38

测试字符串目标示例:

eJztkDFOxDAQRb81hRsULmBtruECyRwpZYpFGLmg5AhwFKMUuYal9CtL26QwHsbe3RMguv3lz9P85wD3/CWaiZ+56OjqWA44cwKIAyfeXXL1sQ7YWqd54czltTge+VOdOQsXFp8TrLUw9KEW3+6pLU4Zk3mC0ataonSEzU8JMywGCiFcue+c8YLGvYcLF5a+68WFhbvtRs5jdmVkWolj96vgXe/it7eucT+0+gxV5N5RrdTveQpevhnxO+BEfRe0xIzc/EbUzkn3lhLSIH6DdFeu+c39Hb7c7vksfrJryB8vu6A4cxE/NjpK1/6LkJZ3+nL1gaLt3D33/Ed+AehfkrY=

我要解码/解压的代码:

static string DecompressZb64(string compressedString)
{
    var b64 = SmartWarehouse.Shared.Utils.Parser.ConvertFromBase64(compressedString);
    var encoding = new ASCIIEncoding();
    var inBytes = Encoding.ASCII.GetBytes(b64);
    var outBytes = new byte[inBytes.Length];
    try
    {
        using (var memoryStream = new MemoryStream())
        using (var decompressionStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
        {
            decompressionStream.Read(outBytes, 0, inBytes.Length);
        }

        return encoding.GetString(outBytes);
    }
    catch (Exception e)
    {
        // TODO: DOcument exception
        Console.WriteLine(e.Message);
    }

    return string.Empty;
}

当前异常:

Block length does not match with its complement.

堆栈跟踪:

   at System.IO.Compression.Inflater.DecodeUncompressedBlock(Boolean& end_of_block)
   at System.IO.Compression.Inflater.Decode()
   at System.IO.Compression.Inflater.Inflate(Byte[] bytes, Int32 offset, Int32 length)
   at System.IO.Compression.DeflateStream.Read(Byte[] array, Int32 offset, Int32 count)
   at SmartWarehouse.Tools.Program.DecompressZb64(String compressedString) in C:\Users\[user_dir]\Source\Repos\Handheld.[user].[fork]\SmartWarehouse.Tools\Program.cs:line 511

更新:

我正在调查这个,我发现了这个 SO post it is essentially the same issue. So i did some more research and i found this article on a blog from 2007。其中讨论了一种解决方法,即跳过输入数组中的前 2 个字节,因为这些字节实际上并未包含在 RFC 规范中。

代码更改:

static string DecompressZb64(string compressedString)
{
    var b64 = Convert.FromBase64String(compressedString);
    var encoding = new ASCIIEncoding();
    var outBytes = new byte[b64.Length - 2];
    try
    {
        using (var memoryStream = new MemoryStream(b64))
        {
            memoryStream.ReadByte();
            memoryStream.ReadByte();
            using (var decompressionStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
            {
                decompressionStream.Read(outBytes, 0, b64.Length - 2);
            }
        }

        return encoding.GetString(outBytes);
    }
    catch (Exception e)
    {
        // TODO: DOcument exception
        Console.WriteLine(e.Message);
    }

    return string.Empty;
}

此代码更改实际上不再产生异常,但是它没有正确解压缩并且 returns 这个结果:

"[=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=][=16=]"

这个数据对我来说很好,我从中得到了这张图片:

这是一个单色位图,每个像素一位。

你的代码直到流结束才读取,实际有1280字节的图像数据,然后可以解码为上图。

using System; 
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text; 
 

namespace ZB64Decode
{
public class Program
{
    static void Main(string[] args)
    {
        //this is C# 
        var sb = new StringBuilder();
        sb.AppendLine("^XA");
        sb.AppendLine("^UT1,1");
        sb.AppendLine("^Uh0,300,64,4000,2,0,F");
        sb.AppendLine("^Uh1,300,64,4000,2,0,F");
        sb.AppendLine("^UC1,0,1,0,0");
        sb.AppendLine("^LH0,0");
        sb.AppendLine("^LT0");
        sb.AppendLine("^XZ");
        sb.AppendLine("^XA");
        sb.AppendLine("^PW3543");
        sb.AppendLine("^LL512");
        sb.AppendLine("^FO532,66^GFA,885,9272,76,:Z64:eJztWkFywyAMDMOBI0/gKTyNPC1P6RN6zKFTNxiCY0AgJE0nh+zJTc0GrdYES1wu/wWzHfiVIuLR2YZpB4FJpZH38UcouP6oRHddogqPEd/9f0WyLzyTGqpsVuLUj5tvoxv8tv3gqSa3WCSZxrjIoMgUzpAGoxnW2wZMdIFHG9tOEhQtekVyze7VSzYMQ/3DVIMTRt/sFtcUA0epZ2o28GCUfi3CCOjbDWHVhMaElRyOB1nss3+C7no7rAqf4DsTM+vr+A7VUYw4rTixeqAmqdUf2bKjESrz96LGwlQWNxRvPVHNw1N+2p9wJ30UWfkIfbKT5YQY1X/5gxVinMqtXCui53vjWVmMCEcmHS/EGOT1ecnKYoQudlXra3ONIphZ3Oh14H7rCzrKdDxXrijYLV3w5Socmr50HcixCUhfNBeQvkxIQPoiVOA92AkqPdISacwsImnMSklyiVgiu8FyF6+EnUbEXjk8SS4Rq+YUviuXyCOUH6IP14er5npX37/rmiO5rkqu90K/Q5Jcux0Ud0OekCwvuZ+Q3OdI7r9EDJZJJPerkvtoiUQWY0m+d0i+Dwk4/5BJ8P2R++p+enk3XIf5I3uSdYDX93gKTnUEywvypLdmBVlVgwInk1U1iFUcqibCKVo1AjHqhc1Qanm1W2qsq5FouFZqS677diyw2r7LcL0Vnjax7rTgHsEQQC+CUsDvl+8vpMYCOGTQogEwaALBLZo+hk2gpRbYpM1llnLpx4V6fMsQ0TQM6KaAmQqisC1ijWiyahwZ7jZUVxfZIcb0m1F96x12dnzALRx9GHf8x6cFWmzwkQAPH2EA4AE26PMh0lmTagLkMye+HDG5P4+HUCbVsHGZIqqzMLPb/wCr2VTW:718B");
        sb.AppendLine("^PQ1,0,1,Y^XZ");
        var zpl = sb.ToString();
        
        //extract the Z64-String
        var z64Data = "";
        var bytesPerRow = 0;
        foreach (var item in zpl.Split('^'))
        {
            if (item.StartsWith("GFA"))
            {
                var sp= item.Split(':');
                z64Data = item.Substring(sp[0].Length);
                bytesPerRow = Convert.ToInt32(sp[0].Split(',')[3]);
                break;
            }
        }

        //convert String to Bitmap
        Bitmap decodedBitmap = null;
        if (z64Data.StartsWith(":Z64"))
        {
            var imageData = DecompressZb64(z64Data.Substring(5));

            int width = bytesPerRow * 8;
            int height = imageData.Length / bytesPerRow;

            decodedBitmap = ArrayToBitmap(imageData, width, height, PixelFormat.Format1bppIndexed); 
        }

        Debug.WriteLine(decodedBitmap.Width + ":" + decodedBitmap.Height);
    }
     

    public static Bitmap ArrayToBitmap(byte[] bytes, int width, int height, PixelFormat pixelFormat)
    {
        var image = new Bitmap(width, height, pixelFormat);
        var imageData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
                          ImageLockMode.ReadWrite, pixelFormat);
        try
        {
            Marshal.Copy(bytes, 0, imageData.Scan0, bytes.Length);
        }
        finally
        {
            image.UnlockBits(imageData);
        }
        return image;
    }

    public static byte[] DecompressZb64(string compressedString)
    {
        var b64 = Convert.FromBase64String(compressedString.Split(':')[0]).Skip(2).ToArray();
        return Decompress(b64);
    }

    public static byte[] Decompress(byte[] data)
    {
        byte[] decompressedArray = null;
        try
        {
            using (MemoryStream decompressedStream = new MemoryStream())
            {
                using (MemoryStream compressStream = new MemoryStream(data))
                {
                    using (DeflateStream deflateStream = new DeflateStream(compressStream, CompressionMode.Decompress))
                    {
                        deflateStream.CopyTo(decompressedStream);
                    }
                }
                decompressedArray = decompressedStream.ToArray();
            }
        }
        catch (Exception ex)
        {
            // do something !
        }

        return decompressedArray;
    }
}

}