求大佬手游[Omniheroes]还原方法

临时加了个暴搜,勉强能用,但是长度那部分还得再看看。

import struct
import hashlib
import texture2ddecoder
from pathlib import Path
from PIL import Image

def to_int32(n):
    return (n & 0xFFFFFFFF) if n < 0x80000000 else (n & 0xFFFFFFFF) - 0x10000000000

def to_uint32(n):
    return n & 0xFFFFFFFF

def cal_index(t):
    t &= 0xFFFFFFFFFFFFFFFF
    x8 = (t + 0x1B77400) & 0xFFFFFFFFFFFFFFFF
    x10 = x8 >> 10
    x9_umulh = ((x10 * 0x31B5D43AFE99EF) >> 64) & 0xFFFFFFFFFFFFFFFF
    x9_lsr = x9_umulh >> 6
    x8_mod = (x8 - (x9_lsr * 0x5265C00)) & 0xFFFFFFFFFFFFFFFF
    x8_mul = (x8_mod * 0x31B5D43B) & 0xFFFFFFFFFFFFFFFF
    index = x8_mul >> 0x35
    return index

def calculate_decrypt_len(sha256_hash):
    s = []
    v82 = struct.unpack('<8I', sha256_hash)
    for val in v82:
        for j in range(4):
            byte = (val >> (j * 8)) & 0xFF
            s.append(byte >> 4)
            s.append(byte & 0x0F)
    v_stack = [to_int32(x) for x in s]

    v1_vec = [v_stack[i] + v_stack[i+4] + v_stack[i+8] + v_stack[i+12] for i in range(4)]
    v2_vec = [v_stack[i+16] + v_stack[i+20] + v_stack[i+24] + v_stack[i+28] for i in range(4)]
    v3_vec = [v_stack[i+32] + v_stack[i+36] + v_stack[i+40] + v_stack[i+44] for i in range(4)]
    v4_vec = [v_stack[i+48] + v_stack[i+52] + v_stack[i+56] + v_stack[i+60] for i in range(4)]
    
    v40_vec = [
        to_int32(v1_vec[0] + v2_vec[0] + v3_vec[0] + v4_vec[0]),
        to_int32(v1_vec[1] + v2_vec[1] + v3_vec[1] + v4_vec[1]),
        to_int32(v1_vec[2] + v2_vec[2] + v3_vec[2] + v4_vec[2]),
        to_int32(v1_vec[3] + v2_vec[3] + v3_vec[3] + v4_vec[3])
    ]
    v41 = sum(v40_vec)

    v42_base = []
    m = 0xCCCCCCCD
    for val in v40_vec:
        val_s32 = to_int32(val)
        mul_res_64 = val_s32 * m
        shifted = (mul_res_64 >> 32) >> 3
        v42_base.append(to_int32(val_s32 - shifted * 10))

    for i in range(len(v42_base)):
        if v42_base[i] == 0: v42_base[i] = 1

    v18 = to_uint32(v41)
    v44 = (v18 % 10) or 1
    v43 = ((v41 >> 32) % 10) or 1
    v45_part1 = to_int32(v42_base[0] * v42_base[2])
    v45_part2 = to_int32(v42_base[1] * v42_base[3])
    decrypt_len = to_uint32(v43 * v44 * v45_part1 * v45_part2)
    if decrypt_len <= 100: decrypt_len = 100
    return decrypt_len

def brute(enc, ex, b=8):
    ASTC_MAGIC = 0x5CA1AB13
    test_data = bytearray(enc[:b])
    max_idx = len(ex) - 1

    for idx0 in range(max_idx):
        for idx1 in range(max_idx):
            for idx2 in range(max_idx):
                pattern = [(idx0, idx0+1), (idx1, idx1+1), (idx2, idx2+1)]
                test = bytearray(test_data)
                
                for i in range(b):
                    idx_pair = pattern[i % 3]
                    xor_key = (ex[idx_pair[0]] + ex[idx_pair[1]]) & 0xFF
                    test[i] ^= xor_key
                
                if len(test) >= 4 and struct.unpack('<I', test[:4])[0] == ASTC_MAGIC:
                    return pattern
    return None

def dec(input):
    data = bytearray(input)
    t = struct.unpack('<Q', data[6:14])[0]
    KEY_TABLE = ["QIAN-MERCURY", "KUN-VENUS", "ZHEN-EARTH", "XUN-MARS", "KAN-JUPITER", "LI-SATURN", "GEN-URANUS", "DUI-NEPTUNE"]
    
    index = cal_index(t)
    salt = KEY_TABLE[index]
    key_string = f"{salt}{t}"
    print(f"时间戳: {t}")
    print(f"密钥盐: {salt}")
    
    sha256_hash = hashlib.sha256(key_string.encode()).digest()
    payload = data[14:]
    decrypt_len = calculate_decrypt_len(sha256_hash)
    aligned_len = (decrypt_len + 16 - 1) // 16 * 16
    expanded_for_brute = []
    for byte in sha256_hash:
        expanded_for_brute.append((byte >> 4) & 0xF)
        expanded_for_brute.append(byte & 0xF)

    key_pattern = brute(payload, expanded_for_brute)
    print(key_pattern)
    for i in range( min(aligned_len, len(payload))):
        idx_pair = key_pattern[i % 3]
        xor_key = (expanded_for_brute[idx_pair[0]] + expanded_for_brute[idx_pair[1]]) & 0xFF
        payload[i] ^= xor_key
    return bytes(payload)

def conv(data, path):
    header = data[:16]
    magic = struct.unpack('<I', header[:4])[0]
    block_x, block_y = header[4], header[5]
    xsize = int.from_bytes(header[7:10], 'little')
    ysize = int.from_bytes(header[10:13], 'little')
    print(f"{xsize}x{ysize},块{block_x}x{block_y}")
    decoded = texture2ddecoder.decode_astc(data, xsize, ysize, block_x, block_y)
    image = Image.frombytes('RGBA', (xsize, ysize), decoded)
    image.save(path, 'PNG')

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='QDREAM ASTC解密')
    parser.add_argument('i', help='加密ASTC文件')
    args = parser.parse_args()
    with open(args.i, 'rb') as f:
        enc = f.read()

    conv(dec(enc), (Path(args.i).stem + '.png'))
1 个赞