临时加了个暴搜,勉强能用,但是长度那部分还得再看看。
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'))