メインコンテンツまでスキップ

PGMM (Pixel Game Maker MV / アクションツクール MV) のアセット復号

キーの復号

  • キーはgame/Resources/data/info.jsonkeyプロパティの値を使用する

  • まずbase64でエンコードされているのでこれをデコードする

  • 次にバイト順序が入れ替えられているのでそれを直す

 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
  • 最後に初期化ベクトル (Initialization Vector) とxorする
 iv = bytes.fromhex("A0 47 E9 3D 23 0A 4C 62 A7 44 B1 A4 EE 85 7F BA")
key = xor(iv, wordsToBytes(decrypt_key(data_words)))

ファイルの復号

  • まずはファイルごとに固有のキーがあるのでそれを求める
 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)
  • twofishを使って復号する
  • この時も初期化ベクトルを使用する
  • 復号は16byte単位で行われる
 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()