"""Self-contained cocos2d-x XXTEA Lua decryptor. File format (per cocos2d-x xxtea.c + cq2-00 docs): [8-byte sign "4meJnPyl"] [XXTEA ciphertext of zlib-compressed lua source] Key: "34UFXMgtkz" padded to 16 bytes with NULs. Decrypt flow (mirrors cocos exactly): 1. ciphertext bytes -> uint32 LE array (no length append) 2. standard XXTEA decrypt (delta=0x9E3779B9, rounds=6+52//n) 3. last uint32 = real plaintext length -> truncate 4. zlib decompress Usage: uv run python scripts/decrypt_client_lua.py # decrypts key proxies uv run python scripts/decrypt_client_lua.py ALL # decrypt whole stab tree """ import struct import sys import zlib from pathlib import Path SIGN = b"4meJnPyl" KEY = b"34UFXMgtkz".ljust(16, b"\0") DELTA = 0x9E3779B9 STAB = Path(r"D:\ProgramFiles\996box\GameBoxDownload\1\cq\cache_206923\mod_fgcq\stab\scripts") OUT = Path("lua-dec2") def xxtea_decrypt(v: list[int], key: list[int]) -> list[int]: n = len(v) if n < 2: return v # cocos: n<1 returns; n==1 unchanged by loop below effectively rounds = 6 + 52 // n total = (rounds * DELTA) & 0xFFFFFFFF y = v[0] while total != 0: e = (total >> 2) & 3 for p in range(n - 1, 0, -1): z = v[p - 1] mx = (((z >> 5) ^ ((y << 2) & 0xFFFFFFFF)) + ((y >> 3) ^ ((z << 4) & 0xFFFFFFFF))) ^ ((total ^ y) + (key[(p & 3) ^ e] ^ z)) mx &= 0xFFFFFFFF y = v[p] = (v[p] - mx) & 0xFFFFFFFF z = v[n - 1] mx = (((z >> 5) ^ ((y << 2) & 0xFFFFFFFF)) + ((y >> 3) ^ ((z << 4) & 0xFFFFFFFF))) ^ ((total ^ y) + (key[0 ^ e] ^ z)) mx &= 0xFFFFFFFF y = v[0] = (v[0] - mx) & 0xFFFFFFFF total = (total - DELTA) & 0xFFFFFFFF return v def bytes_to_u32(data: bytes, include_length: bool) -> list[int]: # cocos xxtea_to_uint_array: pad tail bytes into last u32 by OR (no truncation) n = (len(data) >> 2) + (0 if (len(data) & 3) == 0 else 1) v = [0] * n for i in range(len(data)): v[i >> 2] |= data[i] << ((i & 3) << 3) if include_length: v.append(len(data)) return v def u32_to_bytes(v: list[int]) -> bytes: return struct.pack(f"<{len(v)}I", *v) def cocos_decrypt(raw: bytes) -> bytes: if raw[:8] != SIGN: raise ValueError(f"bad sign {raw[:8]!r}") cipher = raw[8:] v = bytes_to_u32(cipher, include_length=False) key = bytes_to_u32(KEY, include_length=False) v = xxtea_decrypt(v, key) real_len = v[-1] if real_len <= 0 or real_len > (len(v) - 1) * 4: raise ValueError(f"implausible length {real_len}") plain = u32_to_bytes(v[:-1])[:real_len] # This game's scripts are stored as plain Lua text (no zlib layer). # But tolerate both: if it starts with zlib magic, decompress. if plain[:2] == b"\x78\x9c" or plain[:2] == b"\x78\x01" or plain[:2] == b"\x78\xda": plain = zlib.decompress(plain) return plain def decrypt_file(src: Path, dst: Path) -> bool: try: plain = cocos_decrypt(src.read_bytes()) dst.parent.mkdir(parents=True, exist_ok=True) dst.write_bytes(plain) return True except Exception as e: print(f" FAIL {src.name}: {type(e).__name__}: {e}") return False def main() -> int: mode = sys.argv[1] if len(sys.argv) > 1 else "KEY" if mode.upper() == "ALL": files = list(STAB.rglob("*.lua")) out_root = OUT / "stab" ok = 0 for i, f in enumerate(files): rel = f.relative_to(STAB) if decrypt_file(f, out_root / rel): ok += 1 if i % 200 == 0: print(f" {i}/{len(files)}...") print(f"decrypted {ok}/{len(files)} -> {out_root}") else: targets = [ "game/proxy/remote/AuthProxy.lua", "game/proxy/remote/LoginOtpPassWordProxy.lua", "game/proxy/remote/LoginProxy.lua", "game/proxy/remote/RemoteProxy.lua", "network/networkUtil.lua", "game/proxy/GameEnvironmentProxy.lua", ] for t in targets: src = STAB / t if not src.exists(): print(f" missing: {t}") continue dst = OUT / t if decrypt_file(src, dst): print(f" OK: {t} -> {dst} ({dst.stat().st_size}B)") return 0 if __name__ == "__main__": sys.exit(main())