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で保存されている。
形式は独自フォーマットのため自前パーサーを組む必要あり。