Skip to content

Instantly share code, notes, and snippets.

@syrinka
Last active August 30, 2022 13:34
Show Gist options
  • Save syrinka/b2c1cd996231da8171bff70e0d10167f to your computer and use it in GitHub Desktop.
Save syrinka/b2c1cd996231da8171bff70e0d10167f to your computer and use it in GitHub Desktop.
Pixel Game Maker MV 引擎资源解密脚本
import os
import click
import struct
import json
from base64 import b64decode
from twofish import Twofish
iv = bytes.fromhex("A0 47 E9 3D 23 0A 4C 62 A7 44 B1 A4 EE 85 7F BA")
def rotr32(x, n):
return (x >> n) | ((x << (32 - n)) & 0xFFFFFFFF)
def rotl32(x, n):
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))
def bytesToWords(data: bytes) -> list[int]:
assert len(data) % 4 == 0
words = []
for i in range(len(data)//4):
words.append(struct.unpack("<L", data[i*4:i*4+4])[0])
return words
def wordsToBytes(words: list[int]) -> bytes:
buf = bytearray()
for word in words:
buf.extend(struct.pack("<L", word))
return bytes(buf)
def decrypt_key(words: list[int]):
assert len(words) == 4 # 16bytes
for _ in range(8):
words[2] = rotl32(words[2], 1) & 0xffffffff
words[3] = rotr32(words[3] & 0xffffffff, 1)
words[0] = rotl32(words[0], 1) & 0xffffffff
words[1] = rotr32(words[1] & 0xffffffff, 1)
[words[0], words[2]] = [words[2], words[0]]
[words[1], words[3]] = [words[3], words[1]]
return words
def xor(a, b):
w = [0 for _ in range(len(a))]
for i in range(len(a)):
w[i] = a[i] ^ b[i]
return bytes(w)
def kc(data: bytes, key: bytes):
key = list(key)
i = 0
h = len(data) - data[3] - 4
while h > 0:
t = (h ^ key[i]) & 0xff
key[i] = 1 if t < 1 else t
h //= 256
i += 1
return bytes(key)
def decrypt_file(ipath, opath, key):
data = open(ipath, "rb").read()
twofish = Twofish(kc(data, key))
try:
file = open(opath, "wb")
except FileNotFoundError:
os.mkdir(os.path.dirname(opath))
file = open(opath, "wb")
xora = iv
cnt = (len(data)-4) // 16
for i in range(cnt):
ct = data[i*16+4:i*16+20]
pt = twofish.decrypt(ct)
file.write(xor(xora, pt))
xora = ct
file.close()
@click.group()
def main():
"""\b
╔═╗╔═╗╔╦╗╔╦╗ ╔═╗┌─┐┌┬┐┌─┐┌─┐
╠═╝║ ╦║║║║║║ ║ │ │ ││├┤ │
╩ ╚═╝╩ ╩╩ ╩ ╚═╝└─┘─┴┘└─┘└─┘
Pixel Game Maker MV Codec @syrinka
\b
使用方法:
1. get-key 获取游戏密钥
2. decrypt 解密需要的资源
pgmm-codec COMMAND --help 查看对应命令的帮助
\b
Thanks to:
https://github.com/blluv/pgmm_decrypt
"""
pass
@main.command('get-key', short_help='获取游戏的密钥')
@click.argument('info',
type=click.Path(exists=True, dir_okay=False, readable=True))
def func(info):
"""
获取游戏的密钥
INFO: 游戏的 info.json 文件路径,该文件通常位于 <游戏根目录/Resources/data> 目录下
\b
使用例:
pgmm-codec get-key Resources/data/info.json
"""
info = json.loads(open(info, "rb").read().decode("utf8"))
data_words = bytesToWords(b64decode(info["key"]))
key = xor(iv, wordsToBytes(decrypt_key(data_words)))
print(f'该游戏密匙为 [{repr(key)[2:-1]}]')
print('请复制 [ ] 内的部分')
@main.command('decrypt', short_help='使用密钥解密文件')
@click.argument('key')
@click.option('-i', '--input',
type=click.Path(exists=True, file_okay=False, readable=True),
required=True,
help='''\b
输入目录,将会尝试解密其下的*所有文件*
请留意当中是否仍有正常文件,解密后很可能打不开''')
@click.option('-o', '--output',
type=click.Path(file_okay=False),
help='输出目录,留空则为 <输入目录>-dec')
@click.option('-q', '--quiet',
is_flag=True)
def func(key, input, output, quiet):
"""
使用密钥解密资源,并输出到指定路径
KEY: 通过 get-key 获得的密钥
\b
使用例:
pgmm-codec decrypt KEY -i Resources/img -o img-dec
"""
scope = {'key': key}
exec(f'key = b"{key}"', scope)
key = scope['key']
if output is None:
output = input + '-dec'
ilen = len(input)
for root, dirs, files in os.walk(input):
if not quiet:
print(f'# 当前目录:{root}')
for file in files:
ipath = os.path.join(root, file)
opath = os.path.join(output, root[ilen+1:], file)
decrypt_file(ipath, opath, key)
if not quiet:
print(file)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment