Open4
漢字でGO!雑記
目標
- システムで問題を解析して回答を推測
前提
- RPGツクール開発経験:なし
- JavaScript開発経験:趣味程度
- HTML5開発経験:趣味程度
- 画像解析経験:趣味程度
アプローチ
- 全問題の画像と回答を紐づけ
- 問題画面を画像解析し、問題文字列領域を抽出して全問題の画像と類似度を比較
- 類似度が高い順に回答候補を提示
補足
- 全問題の画像解析理由
使用フォントであるセイビタカナワB
が有料により、フォントからのレンダリングは不採用としたため。 またHTML5で書かれた(?)ゲーム画面内のDOM要素をどうすれば取れるか知らないため。
ソースコード
以下から最新版を取得。
画像はrpgmvp
形式。暗号化された画像で、System.json
のencryptionKey
を用いて復号する。
本システムではwww/data/System.json
に配置されている。
問題画像はwww/img/pictures
以下にあり、Lv**_****.rpgmvp
というファイル名で格納されている。
なお、****
が0000
の場合はダミー画像となっていた。
RPGツクールでは以下で復号化している模様。
rpg_core.js
・・・
Decrypter._headerlength = 16;
Decrypter._xhrOk = 400;
Decrypter._encryptionKey = "";
・・・
Decrypter.decryptArrayBuffer = function(arrayBuffer) {
・・・
var view = new DataView(arrayBuffer);
this.readEncryptionkey();
if (arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer);
for (i = 0; i < this._headerlength; i++) {
byteArray[i] = byteArray[i] ^ parseInt(Decrypter._encryptionKey[i], 16);
view.setUint8(i, byteArray[i]);
}
}
・・・
以下処理を参考にPythonで一括復号化を試みる。
rpgmv
形式の一括デコードスクリプト
既定値は本システムに特化
rpgmvdecoder.py
import binascii
import json
import re
import traceback
from argparse import ArgumentParser, Namespace
from pathlib import Path
from re import Match, Pattern
from typing import Any
ARGS_FILES: str = "files_root_dir"
ARGS_SYSTEM_JSON: str = "system_json_file"
ARGS_OUTPUT: str = "output_dir"
DEFAULT_FILES_ROOT_DIR: str = "www/img/pictures"
DEFAULT_SYSTEM_JSON_PATH: str = "www/data/System.json"
DEFAULT_FILES_GLOB_PATTERN: str = "**/Lv*.rpgmv*"
DEFAULT_FILE_NAME_PATTERN: Pattern = r"(Lv\d+)_\d+"
OUTPUT_SUBDIR: str = "decoded"
def decrypt_rpgmv(file: Path, key: bytearray) -> bytearray:
encrypted_data: bytes = file.read_bytes()
data_body: bytes = encrypted_data[16:]
decoded_header: bytearray = bytearray(
a ^ b for a, b in zip(*map(bytearray, [data_body[:16], key]))
)
return decoded_header + data_body[16:]
def output_name(file: Path) -> Path:
return file.with_suffix(
".png"
if file.suffix == ".rpgmvp"
else (
".m4a"
if file.suffix == ".rpgmvm"
else ".ogg" if file.suffix == ".rpgmvo" else f"{file.suffix}.bin"
)
)
def arg_to_path(args: Namespace, arg_name: str) -> Path:
target: str = getattr(args, arg_name)
return (
(Path(args.root_dir) / target)
if not args.root_dir.startswith("/")
and not args.root_dir.startswith("\\")
and ":" not in args.root_dir
else Path(target)
)
def main(args: Namespace) -> None:
try:
print("decode start")
files_root_dir: Path = arg_to_path(args, ARGS_FILES)
output_root_dir: Path = arg_to_path(args, ARGS_OUTPUT)
system_json: dict[str, str | Any] = json.loads(
arg_to_path(args, ARGS_SYSTEM_JSON).read_text(encoding="utf-8")
)
encrypted_key: bytearray = bytearray(
binascii.unhexlify(system_json["encryptionKey"].encode())
)
file_name_pattern: Pattern = re.compile(args.name_pattern)
for source in files_root_dir.glob(args.glob_pattern):
name_matched: Match = file_name_pattern.match(source.name)
if not isinstance(name_matched, Match):
continue
subdir: str = name_matched.group(1)
output: Path = output_name(
output_root_dir
/ f"{OUTPUT_SUBDIR}{'' if not isinstance(subdir, str) else ('/' + subdir)}/{source.name}"
)
already_exist: bool = output.exists()
try:
output.parent.mkdir(parents=True, exist_ok=True)
output.write_bytes(decrypt_rpgmv(source, encrypted_key))
print("decoded", source, "->", output)
except Exception:
traceback.print_exc()
if not already_exist and output.is_file():
output.unlink()
print("decode end")
except Exception:
traceback.print_exc()
if __name__ == "__main__":
arg_parser: ArgumentParser = ArgumentParser(description="rpgmv file decoder")
arg_parser.add_argument("-d", "--root_dir", default=".", help="source root dir")
arg_parser.add_argument(
"-f", f"--{ARGS_FILES}", default=DEFAULT_FILES_ROOT_DIR, help="files root dir"
)
arg_parser.add_argument(
"-s",
f"--{ARGS_SYSTEM_JSON}",
default=DEFAULT_SYSTEM_JSON_PATH,
help="System.json path",
)
arg_parser.add_argument(
"-g",
"--glob_pattern",
default=DEFAULT_FILES_GLOB_PATTERN,
help="files glob pattern",
)
arg_parser.add_argument(
"-n",
"--name_pattern",
default=DEFAULT_FILE_NAME_PATTERN,
help="file name pattern",
)
arg_parser.add_argument(
"-o", f"--{ARGS_OUTPUT}", default=DEFAULT_FILES_ROOT_DIR, help="output root dir"
)
main(arg_parser.parse_args())
回答データはwww/excelData
以下にLv**.txt
で保存されている。
形式は独自フォーマットのため自前パーサーを組む必要あり。