黄昏酒場スコアボード作成④〜バックエンドでのリプレイ解析

## はじめに

[③](https://qiita.com/wefmaika/items/a57ddca2d3e61c138c18)の続きです

## リプレイファイルの解析

リプレイファイルを上げただけでそのプレイ内容を理解するにはリプレイファイルの構造を理解し解析出来なければならない。
旧ロイヤルフレアは `alcoscore.cgi` で全てを賄っていたと先に話したが、 `alcoscore.cgi` はリプレイを解析するための `ALCOReplay.py` というPythonファイルをimportし、そのファイル内で実装されている解析クラスを呼び出すことでリプレイ解析を行っていた。
今回の開発にて旧ロイヤルフレアの解析クラスはとても参考になった。しかしそのまま移植しようにもPythonの記法が古く、バリデーションもせず全てを受け入れる[^1]ものであった。
そのため旧ロイヤルフレアのコードを参考にしつつも、どのような構成のファイルをどのように解析しているのか理解してから、モダンに書き換えるため手を加えようと挑んだ。

## 東方関連のリプレイファイル構成

東方のリプレイファイルは自機の操作シーケンスや乱数のシード値、そしてプレイのメタデータが入っている。
具体的にどのような構成になっているかというと以下の通りである。[^2]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659726/1717cf0b-325f-4caa-8a3f-9d66ad103dd8.png)
～中略～
![スクリーンショット 2025-04-09 223112.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659726/16073499-0984-49c6-be32-01877d83d1f0.png)

伝家の宝刀stirlingバイナリエディタを使用して解析する。
左側がバイナリデータで右側がそのバイナリデータをShift-JISデコードした結果である。

最初の4バイトはいわゆるマジックナンバーである。黄昏酒場なら `al1r` が入る
飛んで `000C` 番地 から2バイトを使って `66 9C` と書かれている。これはリトルエンディアンで `9C66` という値を示していてメタデータが `9C66` 番地から始まることを表している。
スクロールして実際に `9C66` へ行ってみるとここから `USER` という読める文字が書かれていることが分かる。
`0D 0A` が `CRLF` でいわゆる改行を表すということを抑えたうえで `9C66` から1行分読んでみると `USER{なんか空白}リプレイファイル情報` となる。これは黄昏酒場なので何も書かれていないがその他東方作品では作品名が書かれていることがある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659726/8961c808-a82f-47db-9707-9c723a0ad7e2.png)
東方風神録と入っていたり弾幕アマノジャクと入っていたりする。ダブルスポイラーが文花帖と書いてあってth095の文花帖と区別がつかない…

その後はキーバリュー形式でデータが入っている。キーとバリューはスペース(`20`)で区切られている。バリューの末尾に `CRLF` で改行が入っていて、一行ごとにキーバリューがある。
ここで特筆すべきは日付の年が下二桁表記で入っている[^3]ことである。うーんこの。

これさえ分かればサクサク組める。パース時にpydanticのBaseModelを使用してバリデーションを行い、不適切なデータならエラーを返すようにした。

ただしこのメタデータはあくまでIT意識の高いプレイヤーのために作成しているものであって、このメタデータを変更しても別の領域に東方が読む用のメタデータが保存されているらしく、ゲーム画面上では変更されない。

## マジックナンバー

普通の人間は拡張子を使って、『最後に.pngってついてるということは画像ファイルだな！』と判断しているが、実はファイルの中にも先頭にファイルフォーマットが書かれていることがある。例えば `png` ならUTF-8デコードするとバイナリデータの先頭に `.PNG` と書かれている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3659726/53464d9d-f776-444a-9847-9a40e2df5f09.png)
同様に東方のリプレイファイルでもマジックナンバーが設定されており黄昏酒場のリプレイファイルなら必ず先頭に `al1r` と書かれている。
このマジックナンバーには規則性があり、th095の文花帖以降では東方の `t` とナンバリング、そして製品版か体験版かの情報が入る。[^4]
例えば東方風神録製品版であればth10なので `t10r` と入る。製品版、つまり `release` 版なので末尾は `r` である。
対して東方虹龍洞体験版なら `trial` なので `t18t` となる。[^5]

## 次回

リプレイ解析以外の内部処理を作成していく。

## おまけ 花映塚以前のリプレイファイル

th09花映塚以前のマジックナンバーは `T9RP` などの `T` ,ナンバリング, `RP`[^6] で表される。
th09花映塚はキーバリュー形式で保管されているが区切り文字が `09 09` の水平タブ2文字である。
それより前の永夜抄以前はマジックナンバーこそ同じだが解読できる形式ではなくよくわからなかった。
いつか3部作のリプレイを収集するときに頑張ることにする。

## 参考文献
https://qiita.com/forestsource/items/15933888466ba9c3f048

[^1]:旧ロイヤルフレアのリプレイアップローダを昔使用した事がある人なら分かるかもしれないが、東方新作が出た直後にリプレイを上げると作品名こそでないもののスコア等の記録が解析出来た状態で上がっていたのを覚えていないだろうか？
あれはこの後説明するマジックナンバーの解析に失敗してもパースを続行しメタデータを取得していたからである。
幻想郷は全てを受け入れるのである。それはそれは残酷な話なのである。
[^2]:黄昏酒場823万円(全国1位)のリプレイファイルを参照した。
[^3]:つまり2100年に作成されたリプレイは2000年に作成されたプレイと判別がつかないということである。
ZUNは2000年問題から何を学んだんだ…？
と思ったが親切心からリプレイファイルのデータ量を2バイト分圧縮していただいているということである。ZUNに感謝、アーメン。
[^4]:つまり `al1r` は `alcostg` の `1` 作目の `release` 版ってこと…？
2作目期待していいんですか…？？
[^5]:この流れで行くと東方天空璋体験版は `t16t` となりそうだが、実際には `t16r` となっている。ただしメタデータ開始行に `USER{空白}東方天星璋` と書かれるので多分天空璋体験版自体が全体的に手抜きだったからだと思われる。ZUNは2014年に第一子出産で天空璋は2016年の作品なので子育てに忙しすぎたのかもしれない。
[^6]: `RePlay` の `RP` ?