【手游】皇室战争 Clash Royale 美术资源加密分析

0x00 CR的图片资源是在SC文件夹中 一般为XXX_tex.sc

sc文件是一个压缩文件,它是使用了修改的LZMA压缩算法进行压缩的(和老版的COC资源压缩方法类似)


标准的LZMA压缩文件的文件头由5+8个字节组成,其中前5个字节中存储了压缩方式,字典大小等信息,后8个字节为压缩前的文件大小;COC所使用的修改后的LZMA算法,其文件头由5+4个字节组成,跟标准算法相比,仅仅是将表示压缩前的文件大小那8个字节,改成了4个字节

如果要使用标准LZMA算法的解压工具能正确解压COC中的资源,只要在第9个字节后面插入4个0字节,

如上图SC文件,其前26个字节是SC文件头信息, 紧接着的13个字节如下:

0x5D 0x00 0x00 0x04 0x00 0x59 0xD9 0x94 0x00 0x00 0x00 0xB1 0xBA

在第9个字节后插入4个0字节,得到如下格式:

0x5D 0x00 0x00 0x04 0x00 0x59 0xD9 0x94 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xB1 0xBA

然后可以用LZMA类库 进行解压缩


0x01 解压后的文件我们暂且称为scf文件


scf文件前5个字节是文件信息,它的结构如下:

类型1长度1数据块1
类型2长度2数据块2
.........
类型n长度n数据块n

其中:类型为1个字节,标识数据块是何类型;长度为4个字节,表示随后的数据块的字节长度。

 

类型1是纹理(texture),类型2是shape,类型3是movie_clip.....这里只研究Tuxture,其他的类型感兴趣的可自己研究

纹理(texture)其数据块结构如下:

纹理格式纹理宽度纹理高度纹理数据

其中:格式为1个字节,宽度为2个字节,高度为2个字节。

格式为0是RGBA8888格式(32位)

格式为2是RGBA4444格式(16位)

格式为4是RGB565格式(16位)

格式为6是LA88格式(16位)


我用Python 写了个提取脚本ExtractRes.py,里面的LA88灰度算法我不是太确定(但影响也不是太大),

用法 python ExtractRes.py xxx/sc (这里的sc是资源文件夹)

注意安装PyLZMA 和 Pillow 这两个库

PyLZMA https://pypi.python.org/pypi/pylzma

Pillow https://pypi.python.org/pypi/Pillow/


# 皇室战争图片资源提取 By:BlueEffie
# 需要安装2个库
# PyLZMA https://pypi.python.org/pypi/pylzma
# Pillow https://pypi.python.org/pypi/Pillow/

import os
import sys
import pylzma
import struct
import PIL.Image

if len(sys.argv) != 2:
    print('Usage: ./ExtractRes.py [SC_Directory]')
    exit(0)


class PixelType:
    rgba8888 = 0
    rgba4444 = 2
    rgb565 = 4
    la88 = 6


def extract_sc_files(path):
    for root, dirs, files in os.walk(path):
        for name in files:
            if name.endswith('.sc'):
                lzma_decompress(root, name)


# 解压缩
def lzma_decompress(path, name):
    data = os.path.join(path, name)
    filename, exc = os.path.splitext(name)
    with open(data, 'rb') as fin:
        fin.seek(26, 0)
        header = fin.read(5)
        raw_size = fin.read(4)
        size = struct.unpack('<I', raw_size)[0]
        obj = pylzma.decompressobj(maxlength=size)
        decompress_data = obj.decompress(header)
        while True:
            chunk = fin.read(4096)
            if not chunk:
                break
            decompress_data += obj.decompress(chunk)
        try:
            obj.flush()
        except SystemError as e:
            print(e)
        parse_file(decompress_data, path, filename)


# 文件解析
def parse_file(data, path, filename):
    index = 0
    while index + 5 <= len(data):
        (tag_type, tag_length) = struct.unpack('<BI', data[index:index + 5])

        if tag_type == 1:
            parse_data = data[index + 5:index + tag_length + 5]
            parse_texture(parse_data, path, filename + '_' + str(index))

            index += tag_length + 5
        else:
            break


# 图片解析
def parse_texture(data, path, filename):
    (bmp_type, width, height) = struct.unpack_from('<BHH', data)
    pixel_size = get_pixel_size(bmp_type)

    img = PIL.Image.new('RGBA', (width, height))
    pixels = []
    for o in range(0, len(data) - 5, pixel_size):
        pixels.append(convert_pixel(data[o + 5:o + 5 + pixel_size], bmp_type))
    img.putdata(pixels)
    img.save('{0}/{1}.png'.format(path, filename), 'PNG')


# 获取图片像素位数
def get_pixel_size(bmp_type):
    if bmp_type == PixelType.rgba8888:
        return 4
    elif bmp_type == PixelType.rgba4444:
        return 2
    elif bmp_type == PixelType.rgb565:
        return 2
    elif bmp_type == PixelType.la88:
        return 2
    else:
        raise Exception('unknown pixel size')


# 图片格式转换
def convert_pixel(px, bmp_type):
    if bmp_type == PixelType.rgba8888:
        if len(px) == 4:
            r, g, b, a = struct.unpack('4B', px)
            return r, g, b, a
        elif len(px) == 3:
            r, g, b = struct.unpack('3B', px)
            return r, g, b, 0
        else:
            return 0, 0, 0, 0
    elif bmp_type == PixelType.rgba4444:
        x, = struct.unpack('<H', px)
        r, g, b, a = (((x >> 12) & 15) << 4, ((x >> 8) & 15) << 4, ((x >> 4) & 15) << 4, ((x >> 0) & 15) << 4)
        return r, g, b, a
    elif bmp_type == PixelType.rgb565:
        x, = struct.unpack('<H', px)
        return ((x >> 11) & 31) << 3, ((x >> 5) & 63) << 2, (x & 31) << 3
    elif bmp_type == PixelType.la88:
        x, = struct.unpack('<H', px)
        return x, x, x
    else:
        raise Exception('unknown pixel type')


if __name__ == '__main__':
    extract_sc_files(sys.argv[1])


  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值