galwiki吧 关注:153贴子:2,084

ai6win引擎的arc封包和akb图片

只看楼主收藏回复

先挖坑。本贴试图通过garbro对ai6win arc和akb的拆包反向推理其封包方式。


IP属地:美国1楼2025-01-09 09:10回复
    不懂


    IP属地:江西来自Android客户端2楼2025-01-09 10:55
    回复
      你要想引流可以教教如何用ai汉化,我的技术都是跟b站大佬DA学的,但他很偏执非常不喜欢ai汉化,我有个朋友去q群问他甚至被拉黑了,而我水平不够教不了这个


      IP属地:河南来自Android客户端3楼2025-01-09 12:34
      收起回复
        首先介绍一般的旮旯封包的结构:
        文件头--文件目录--原始文件内容。
        文件头记录被封包的文件夹的基本信息。
        文件目录一般是文件名--偏移量--文件大小的形式。
        这个听起来比较抽象不好懂,所以我们结合ai6win的arc封包具体分析。


        IP属地:美国4楼2025-01-10 08:25
        回复
          首先我们要在garbro源码里找到ai6win引擎部分代码。直接搜索ai6win,注意搜索选项选整个解决方案
          于是我们找到arcai6win.cs,在当前的GARbro1.544版本下,它应该位于GARbro-1.5.44\ArcFormats\Silky下。源码的网盘链接:
          通过网盘分享的文件:ArcAi6Win.7z
          链接: https://pan.baidu.com/s/1uLcMorX3LYUaovW-_mHlOg?pwd=m5iu 提取码: m5iu
          这就是ai6win引擎的拆包源码。


          IP属地:美国5楼2025-01-10 09:32
          回复
            (注意这张图右上角的搜索选项)
            其中的TryOpen函数为解包函数。如果不信可以在这里下断点,你会发现用garbro解包的时候,只命中这个函数。
            由这个解包过程,得arc封包结构:
            开头4byte,存储一个32位整数(count),是封包里的总文件数目。—最开始说的文件头
            文件目录头紧跟其后。每个目录头占272bytes,前260bytes文件名,字符串。261-264byte为文件大小,32位整数。265-268byte解密后大小,32位整数。269-272byte为文件偏移量,32位整数。
            4+272*count byte后为文件内容。
            因此我们封包的思路也很简单,读入文件然后分别把需要的信息写进目录头和内容。这个unpacked size我不太明白,如果你在vs下断点观察到这俩一样,封包也写成和size相等即可。


            IP属地:美国6楼2025-01-10 10:10
            回复
              为了更好理解,我们以十六进制编辑器打开一个arc文件。这里的示例是
              姬騎士オリヴィア~へ、変態、この変態男! 少しは恥を知りなさい!的layer.arc。
              芝士文件头,封包里有0x0D89 = 3465个文件。为什么不是890D? 这是小端序,高位字节在后而每一个字节中高位在前。

              拆一下,确实有3465个文件。

              芝士文件名。使用了简单的加密让你一眼看不出来原名。

              加密的原理是:
              byte - 文件名长度 - 1 + byte所在位数。(0起)
              l - 9 - 1 + 0 = b (b cdefghijkl)
              u - 9 - 1 + 1 = l
              ....
              最后得出的文件名应该是black,akb,一共九个字。
              芝士文件大小。

              看一下也确实是0x20 = 32bytes。为什么是20?这里又用的是大端序了。

              芝士文件偏移。我们可以在封包的这个位置,0xE6194,找到文件原始内容。

              从这里开始的32位和解包为akb得到的32位完全一样。


              实际上,这种通过偏移寻找文件内容的做法在计算机领域是极为常见的。
              arc包就是这样了,很简单吧


              IP属地:美国7楼2025-01-10 10:49
              回复
                接着我们来说akb格式。
                同样的,我们搜索ai6win找到源码,然后设断点找到解密函数。
                在GARbro1.5.44版本,它应该位于GARbro-1.5.44\ArcFormats\Silky下。
                通过网盘分享的文件:ImageAKB.zip
                链接: https://pan.baidu.com/s/1Bdsa7NXCDdfpIOV-ELAJow?pwd=b9wc 提取码: b9wc

                涉及到图片解密的有多个函数,首先是ReadMetaData,读文件头;
                然后是Read,读文件内容。而其中的Unpack函数是解密akb的核心。


                IP属地:美国8楼2025-01-10 11:00
                回复
                  从ReadMetaData我们可得.akb文件前32位为文件头,结构如下:

                  前四byte是一个标志,大抵是标记这张图是不是差分,不太确定,先不管;
                  5-6byte为图片宽度;
                  7-8byte为图片高度;
                  9-12byte也是一个标志。其中,第12byte的第七位为1,色深(BPP)为32位;否则为24位。
                  32位色深就是一个像素用32位表示,r(红),g(绿),b(蓝),a(透明度)各占八位,24位就没有a。
                  13-16byte不知道。
                  17-20byte为图片X方向偏移。
                  21-24byte为图片Y方向偏移。
                  25-28byte 减去X方向偏移,为图片实际宽度。
                  29-32byte 减去Y方向偏移,为图片实际高度。
                  不知道的其实无所谓,可以照抄原图的属性。


                  IP属地:美国9楼2025-01-10 11:27
                  收起回复
                    然后来用之前的black.akb认识文件头:
                    这是原始图片,可以看到它宽800,高600,色深24位:

                    这是文件签名:

                    宽度,0x320 = 800(虽然事到如今了,但是我还是要说x不是乘号,x代表后面的数是十六进制)

                    高度,0x258 = 600

                    flag,第12byte0xC0 = 1100 0000(二进制),由低位向高位数第七位为1,故为24位色深。

                    其余的几个属性,Xoffset,Yoffset,innerWidth,innerHeight




                    至于为什么这张全黑只有一个文件头,我的猜想是底下的文件内容假如像素数量,小于 宽*高 个,就会以(0,0,0)也就是纯黑填充。所以它不需要写,节省空间。


                    IP属地:美国10楼2025-01-10 17:08
                    回复
                      文件解压的算法是unpack。源码应当位于GARbro-1.5.44\ArcFormats\LzssStream.cs
                      简单语言描述一下
                      (提示:通过右键>转到定义可以迅速转到函数原型,通过步进也可以追溯)
                      文件内容由若干操作组组成。每一个操作组的第一byte是操作数,每个操作组占
                      1 (操作数)+ 16 - 操作数变为二进制后1的个数(例如,0xFC = 11111100,6个)byte。
                      解压前初始化字典,由某位置(不一定是0)开始写入。写满词典后,再次从起始位置开始写入。
                      由操作数最低位开始,若该位为1,则从操作组读入1byte,写入输出流,并写入字典。
                      若该位为0,则从操作组读入2bytes;
                      偏移量为 (第二byte & 0xf0) << 4 | 第一byte,即第二byte高位 在12 - 9 位,第一byte在8-1位合成偏移量
                      向输出流写入由字典偏移量开始,长度为(第二byte低位 + 3 )的byte序列,每写入1byte后也向字典写入。

                      大概就是这么个过程。说真的看源码比我这个说的清楚多了


                      IP属地:美国11楼2025-01-10 17:27
                      回复
                        复原akb文件的思路,最简单的是不压缩。
                        也就是将所有操作组的操作数全部设为0xFF,而后跟8byte原始数据。
                        虽然这样会得到120%的惊人压缩率(
                        但是一定可以保证ai6win引擎能读取我们封回的akb。


                        IP属地:美国12楼2025-01-10 17:30
                        回复
                          但是,事实上在转化成akb时,被压缩的并不是图片的原始rgb。
                          这一点我们可以参照源码的RestoreDelta函数,它位于GARbro-1.5.44\ArcFormats\Silky\ImageAKB.cs
                          由此可以得出,解压后GARbro恢复了所得像素点的差分。

                          那么压缩前,我们就该对像素点进行差分编码。(压缩和解压的所有步骤先后顺序完全颠倒)
                          具体而言,ai6win引擎是这么做的:
                          (tips:bmp图片的像素存储也是“小端序”的。也就是最左边一列的height*bpp/8个像素,存储在bmp最后的height*bpp/8个byte)
                          将所有像素从上到下,从左到右,行遍历,每个像素的三个byte以bgr排列,排成一串。
                          对于第height * bpp + 1 至第 width * height *bpp byte,从最后一个byte开始,减去它前方height * bpp 位置的byte 的值。
                          然后,对于第bpp + 1 至 第height * bpp byte,从第height * bpp byte开始,减去它前方 bpp 位置的byte 的值。
                          第1 至 第 bpp byte 不变。
                          注意这几步是有先后顺序的。


                          IP属地:美国13楼2025-01-10 17:58
                          回复
                            然后对差分后的编码做---额,不压缩封回akb。
                            但在数据不能整除8的时候,会导致错误。(操作数指示还有几byte,而文件已经结束了)
                            那么,我们退而求其次,只压缩一次。
                            已知akb的压缩算法实际上就是把长序列变为两位索引,实现压缩。那么对一个长度为n的序列做压缩,可以压缩掉n-2byte。设数据总量为s,%为取余,
                            则压缩一个长度为s % 8 + 2的序列,我们的数据的byte数就被8整除了。
                            而只压缩一次确实没什么难度...反正我目前用这种方法实现了替换ai6win 引擎游戏的图片。


                            IP属地:美国14楼2025-01-10 21:42
                            收起回复
                              但是,要完全复原压缩算法,就有很多细节了。目前我大概推出这么三点:
                              1.将写入一byte差分编码前,在字典中寻找其开头的最长序列,用字典中序列的起始位置代替序列,以实现最高压缩率;
                              2.写入n个连0时,如果字典开头位置前有足够的0,且字典中没有以连0开头的更长序列,则字典中位置为 (开头位置-n);
                              3.字典是首尾相接的。
                              不过我还是没能完全复原。有更多发现我在这接着写吧。


                              IP属地:美国来自Android客户端15楼2025-01-11 12:25
                              回复