一个欲儿的博客

一个欲儿的博客

造梦西游3再续天庭 存档修改
2026-06-27

《再续天庭1.1》的存档解密逻辑主要经历了四个步骤:

第一步,程序读取指定的加密存档文件,并去除其中的换行和空格,然后使用自定义的 Base64 字符表“zYxWvUtSrQpOnMlKjIhGfEdCbAaBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890+/=”对文本进行变体 Base64 解码,将其还原为原始的字节数组;

第二步,程序对该字节数组中的每一个字节进行异或(XOR)运算,固定的异或密钥为十进制的 90(即十六进制的 0x5A),这一步利用了异或运算的自反性来逆转混淆;

第三步,程序进入一个最多执行 10 次的循环,尝试使用 Zlib 库进行多层解压缩,期间会兼顾处理包含或不包含标准标头的压缩流数据,直到无法继续解压、触发异常时自动跳出循环;

第四步,程序进行 AMF3 协议的数据反序列化解析,首先校验第一个字节是否为数字 6(代表字符串对象),验证通过后通过特定的位移运算读取紧随其后的 AMF3 变长整数(U29)以确定 XML 字符串的实际长度,将其右移 1 位得到真正的长度值,并根据该长度截取并使用 UTF-8 编码解码出 XML 明文,最终利用 minidom 库对 XML 进行美化排版并保存为以 .xml 结尾的文本文件。

附上python代码

import os
import zlib
import xml.dom.minidom

# 完美对齐解密出来的自定义 Base64 字符表
CUSTOM_ALPHABET = "zYxWvUtSrQpOnMlKjIhGfEdCbAaBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890+/="
XOR_KEY = 90  # 0x5A

def custom_b64decode(s: str) -> bytearray:
    """变体 Base64 解码逻辑 (函数 o)"""
    s = s.strip().replace("\r", "").replace("\n", "").replace(" ", "")
    data = bytearray()
    for i in range(0, len(s), 4):
        chunk = s[i:i+4]
        c = [CUSTOM_ALPHABET.index(char) if char in CUSTOM_ALPHABET else 64 for char in chunk]
        while len(c) < 4:
            c.append(64)
        b = (c[0] << 2) | ((c[1] & 48) >> 4)
        d = ((c[1] & 15) << 4) | ((c[2] & 60) >> 2)
        g = ((c[2] & 3) << 6) | c[3]
        data.append(b)
        if c[2] != 64: data.append(d)
        if c[3] != 64: data.append(g)
    return data

def custom_b64encode(g: bytearray) -> str:
    """变体 Base64 编码逻辑 (函数 p)"""
    a = ""
    for b in range(0, len(g), 3):
        c = min(3, len(g) - b)
        d = g[b]
        e = g[b + 1] if c > 1 else 0
        f = g[b + 2] if c > 2 else 0
        h = (d & 252) >> 2
        i = (d & 3) << 4 | (e & 240) >> 4
        j = (e & 15) << 2 | (f & 192) >> 6
        k = f & 63
        a += CUSTOM_ALPHABET[h] + CUSTOM_ALPHABET[i]
        a += CUSTOM_ALPHABET[j] if c > 1 else CUSTOM_ALPHABET[64]
        a += CUSTOM_ALPHABET[k] if c > 2 else CUSTOM_ALPHABET[64]
    return a

def read_amf3_integer(data: bytearray, pos: int):
    """AMF3 变长整数 (U29) 读取逻辑 (函数 m)"""
    result = 0
    for i in range(4):
        b = data[pos]
        pos += 1
        if i < 3:
            result = (result << 7) | (b & 127)
            if (b & 128) == 0: break
        else:
            result = (result << 8) | b
    return result, pos

def write_amf3_integer(c: int) -> bytearray:
    """AMF3 变长整数 (U29) 写入逻辑 (函数 n)"""
    a = []
    if c < 128:
        a.push(c) if hasattr(a, 'push') else a.append(c)
    elif c < 16384:
        a.append(c >> 7 & 127 | 128)
        a.append(c & 127)
    elif c < 2097152:
        a.append(c >> 14 & 127 | 128)
        a.append(c >> 7 & 127 | 128)
        a.append(c & 127)
    else:
        a.append(c >> 22 & 127 | 128)
        a.append(c >> 15 & 127 | 128)
        a.append(c >> 8 & 127 | 128)
        a.append(c & 255)
    return bytearray(a)

def format_xml(xml_str: str) -> str:
    """对 XML 进行美化排版"""
    try:
        dom = xml.dom.minidom.parseString(xml_str)
        return dom.toprettyxml(indent="    ")
    except:
        return xml_str

def compress_xml(xml_str: str) -> str:
    """去除 XML 格式化引入的换行和缩进,还原紧凑字符串"""
    lines = [line.strip() for line in xml_str.splitlines() if line.strip()]
    return "".join(lines)

def cmd_decrypt():
    path = input("\n请输入要解密的存档路径 (如 2.sav): ").strip().strip('"')
    if not os.path.exists(path):
        print("[-] 错误:找不到指定文件。")
        return
    
    try:
        with open(path, "r", encoding="utf-8") as f:
            raw_content = f.read()

        # 1. Base64 还原
        bytes_data = custom_b64decode(raw_content)
        # 2. XOR 90 解密
        for i in range(len(bytes_data)): bytes_data[i] ^= XOR_KEY
        # 3. 多层 Zlib 解压
        for _ in range(10):
            try:
                try: bytes_data = zlib.decompress(bytes_data, -zlib.MAX_WBITS)
                except zlib.error: bytes_data = zlib.decompress(bytes_data)
            except zlib.error: break

        # 4. AMF3 解析
        if bytes_data[0] != 6:
            print("[-] 校验失败:该文件不是合法的 AMF3 存档数据。")
            return
        amf3_value, next_pos = read_amf3_integer(bytes_data, 1)
        str_len = amf3_value >> 1
        xml_bytes = bytes_data[next_pos : next_pos + str_len]
        xml_text = xml_bytes.decode('utf-8')

        # 美化排版并保存
        pretty_xml = format_xml(xml_text)
        out_path = path + ".xml"
        with open(out_path, "w", encoding="utf-8") as out_f:
            out_f.write(pretty_xml)
            
        print(f"[+] 解密成功!明文已格式化保存至: {out_path}")
        print("    你可以直接用记事本打开修改它。")
    except Exception as e:
        print(f"[-] 解密过程中发生异常: {e}")

def cmd_encrypt():
    path = input("\n请输入修改后的 XML 明文路径 (如 2.sav.xml): ").strip().strip('"')
    if not os.path.exists(path):
        print("[-] 错误:找不到指定文件。")
        return
        
    try:
        with open(path, "r", encoding="utf-8") as f:
            xml_content = f.read()

        # 压缩掉美化排版的空格和换行
        clean_xml = compress_xml(xml_content)
        xml_bytes = clean_xml.encode('utf-8')

        # 1. AMF3 序列化打包 (函数 l)
        amf3_len_code = (len(xml_bytes) << 1) | 1
        amf3_len_bytes = write_amf3_integer(amf3_len_code)
        
        amf3_packet = bytearray([6]) + amf3_len_bytes + xml_bytes
        
        # 2. Zlib 压缩
        compressed = zlib.compress(amf3_packet)
        compressed_bytes = bytearray(compressed)

        # 3. XOR 90 加密
        for i in range(len(compressed_bytes)): compressed_bytes[i] ^= XOR_KEY

        # 4. 变体 Base64 编码
        final_string = custom_b64encode(compressed_bytes)

        # 保存为新存档
        out_path = path.replace(".xml", "") if path.endswith(".xml") else path + ".sav"
        if out_path == path: out_path += ".new"
            
        with open(out_path, "w", encoding="utf-8") as out_f:
            out_f.write(final_string)
            
        print(f"[+] 加密成功!新存档已打包输出至: {out_path}")
        print("    你可以将此文件改回游戏要求的名字放回存档目录。")
    except Exception as e:
        print(f"[-] 加密过程中发生异常: {e}")

def main():
    print("=" * 45)
    print("   《再续天庭1.1》存档算法逆向破解工具")
    print("=" * 45)
    while True:
        print("\n[可用指令]  1: 加密打包存档  2: 解密分析存档  0: 退出")
        choice = input("请输入指令数字: ").strip()
        if choice == "1":
            cmd_encrypt()
        elif choice == "2":
            cmd_decrypt()
        elif choice == "0":
            print("退出程序。")
            break
        else:
            print("[-] 无效指令,请重新输入。")

if __name__ == "__main__":
    main()


发表评论: