Last active
January 7, 2025 23:03
-
-
Save syrinka/b2c1cd996231da8171bff70e0d10167f to your computer and use it in GitHub Desktop.
Pixel Game Maker MV 引擎资源解密脚本
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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