解码游戏中的RPGMV图片

兴趣是最好的老师!

昨天下午随意发现了一款游戏。晚上打开玩了一下,发现画风很好,但是我又没有时间耗费玩通关。所以想要将游戏的图片都保留下来看看。

发现这个游戏文件夹下有data目录,而且还有img/picture目录,为什么关注这个目录呢,因为这个picture目录是img下面文件大小最大的目录,这个一般就是真实的游戏图片目录了。

picture目录下的文件都是.png_的后缀,我用lmhex打开一个文件看了下,编码类似如下:

![[Pasted image 20260509154948.png]]

52 50 47 4D 56 00 00 00 00 03 01 00 00 00 00 00 A4 D1 F7 E0 E1 64 37 B8 34 05 21 8C 6B 57 CD CB 00 00 02 40 00 00 01 80 08 06 00 00 00 45 9A DD 1F 00 00 20 00 49 44 41 54 78 DA EC 9D 07 58 16 C7 D6 C7 AD

这里的52 50 47 4D 56用ASCII码解析就是RPGMV格式的magic number。

然后编码中还有IDAT的字样,这个是PNG图片块。说明图片内容是明文的。

很好,确定了真实的文件格式了。这个文件大概率是使用RPG maker加密过的PNG图片了。

现在我要想办法解密它。

通过搜索,这种加密方式通常采用的是: png+异或+key的方式,而且默认RPGMV只对PNG文件的前16个字节做XOR。还有我需要知道异或用的key是什么。 同样的搜索下知道RPGMV的key一般存在system.json文件里面,这个一下子就找到了,在游戏目录下面的data文件夹下的system.json,打开搜索下encryption,直接就找到了key。

这下好了。写一个简单的代码尝试解压一个文件看看能不能成功。

#2d81b9a7ec6e2db234052181221f8999

from pathlib import Path
import sys

# =========================================
# 用法:
#
# python decrypt_rpgmv.py input.rpgmvp output.png KEY
#
# 例子:
#
# python decrypt_rpgmv.py Actor1.rpgmvp Actor1.png \
# 00112233445566778899AABBCCDDEEFF
#
# =========================================


PNG_MAGIC = bytes([
    0x89, 0x50, 0x4E, 0x47,
    0x0D, 0x0A, 0x1A, 0x0A
])

RPGMV_HEADER_SIZE = 16


def decrypt_rpgmvp(input_file, output_file, key_hex):
    key = bytes.fromhex(key_hex)

    with open(input_file, "rb") as f:
        data = bytearray(f.read())

    # 检查文件头
    if data[:4] != b"RPGM":
        print("[-] Not a RPGMV encrypted file")
        return

    print("[+] RPGMV header detected")

    # 去掉16字节头
    encrypted_data = data[RPGMV_HEADER_SIZE:]

    # 只解密前16字节
    decrypt_len = min(16, len(encrypted_data), len(key))

    for i in range(decrypt_len):
        encrypted_data[i] ^= key[i]

    # PNG 文件头检查
    if encrypted_data[:8] == PNG_MAGIC:
        print("[+] PNG header restored successfully")
    else:
        print("[!] Warning: PNG header mismatch")
        print("    key may be incorrect")

    with open(output_file, "wb") as f:
        f.write(encrypted_data)

    print(f"[+] Saved to: {output_file}")


def main():
    if len(sys.argv) != 4:
        print("Usage:")
        print("python decrypt_rpgmv.py input.rpgmvp output.png KEY")
        return

    input_file = sys.argv[1]
    output_file = sys.argv[2]
    key_hex = sys.argv[3]

    decrypt_rpgmvp(input_file, output_file, key_hex)


if __name__ == "__main__":
    main()

试了下,果然可以解码.png_文件到可以打开的图片文件。

剩下来就是解密整个picture文件夹了,这个文件夹有1.5GB大小,需要写个批处理了。见下面完整代码:

from pathlib import Path
import sys

RPGMV_HEADER_SIZE = 16

PNG_MAGIC = bytes([
    0x89, 0x50, 0x4E, 0x47,
    0x0D, 0x0A, 0x1A, 0x0A
])


def is_rpgmv(path):
    try:
        with open(path, "rb") as f:
            return f.read(4) == b"RPGM"
    except:
        return False


def decrypt_file(src, dst, key):

    with open(src, "rb") as f:
        data = bytearray(f.read())

    encrypted = data[RPGMV_HEADER_SIZE:]

    for i in range(min(16, len(encrypted), len(key))):
        encrypted[i] ^= key[i]

    if encrypted[:8] != PNG_MAGIC:
        print(f"[WARN] PNG header mismatch: {src.name}")

    dst.parent.mkdir(parents=True, exist_ok=True)

    with open(dst, "wb") as f:
        f.write(encrypted)


def main():

    if len(sys.argv) != 4:
        print("Usage:")
        print("python batch_decrypt_any.py input_dir output_dir KEY")
        return

    input_dir = Path(sys.argv[1]).resolve()
    output_dir = Path(sys.argv[2]).resolve()
    key = bytes.fromhex(sys.argv[3])

    files = list(input_dir.rglob("*"))

    total = 0
    ok = 0

    for file in files:

        if not file.is_file():
            continue

        if not is_rpgmv(file):
            continue

        total += 1

        try:
            rel = file.relative_to(input_dir)

            # 去掉最后一个 "_" 并改成 png
            name = rel.name
            if name.endswith("_"):
                name = name[:-1]

            out = output_dir / rel.parent / name

            if out.suffix == "":
                out = out.with_suffix(".png")

            decrypt_file(file, out, key)

            print(f"[OK] {rel}")
            ok += 1

        except Exception as e:
            print(f"[FAIL] {file}")
            print(e)

    print()
    print("====== RESULT ======")
    print("Detected RPGMV files:", total)
    print("Decrypted:", ok)


if __name__ == "__main__":
    main()

![[Pasted image 20260509155026.png]]

然后我就可以在输出的文件夹里面挨个的欣赏美图了。

| 访问量:
Table of Contents