東方リプレイアップローダを作成したい！①

## はじめに

前回は黄昏酒場スコアボードを作成しました。
これで味をしめたので全ての東方リプレイに対応したアップローダを作成したいという願望のもと、作成にあたって考えていること、進捗などを記述していきます。

## モチベーション

前回の黄昏酒場でもあった通り、旧ロイヤルフレア管理人がいなくなったことで大問題が起こった。
これに対して東方民たちは旧ロイヤルフレアの遺産を使用して個人間でリプレイアップローダが乱立した。
しかし旧ロイヤルフレアのアップローダは以下の点からイケてないと言える

- Perlで[^1]書かれていて保守しづらい
- パスワードのハッシュ化がOS依存
- リプレイでなくても任意のファイルをアップロード出来てしまう
- thprac[^2]というツールが使用されたリプレイを投稿できない
- cgiが使用されている
- なんかUIが昔っぽくていやだ

以上の点から新しいリプレイアップローダを作成したい。
ただ日本人は伝統を重んじるタイプが多いので新しいアップローダを作成しても使わないことが予想される。
そこで以下のようにして利用してもらうことを考えた

- プレイ進行の表示
  - 旧ロイヤルフレアは最終スコアしか表示しない
  - 海外ニキたちがリプレイファイル内の各面毎のスコア情報について解析しているのでそれをコピペしてきたい
  - 海外ニキのコードはapache2.0ライセンスなので引用元を記載すれば使用できそう
- 東方の偉い人[^3]に土下座して管理してもらう
  - 毎週みんなのリプレイを見る会をやってらっしゃる方がいる
  - その方の運営する旧ロイヤルフレアアップローダを自分のものに差し替えれば反響ありそう
  - 土下座でも靴舐めでも金銭払いでもなんでもやります

以上のもと東方リプレイアップローダを作成したい

## 海外ニキたちのコード

海外ニキたちがリプレイの中身を解析した、とは言ったがどうやら実際のゲームの中のコードに興味がある[^4]ようで、リプレイファイルの解析実績は整数作品たちに留まるようだ。
主に海外ニキたちは silentselene.net という東方スコアボードを作成するにあたって集中的に研究したようで、そのgithubレポジトリに成果が管理されている。
具体的にどのようなものを作ったかと言うと kaitai-structor というバイナリ解析フレームワーク用のyamlファイルである。

https://github.com/n-rook/thscoreboard/tree/ff066805015cbb8eefe394dc9ea37dfac4622136/ref/threp-ksy

kaitai-structor の使い方は省略する。
ただしこれだけでは動かない。実はユーザデータオフセットの項からユーザデータの項まではLZSS圧縮なるものがされていて直接取り出すことはできない。
そこでなんと32th system[^5]さまがtsadecodeという解凍ライブラリを作成していらっしゃった
https://github.com/32th-System/py-tsadecode.git
パブリックドメインありがたや。

## mongodbの使用

今回は複数の東方作品を扱い、作品指定せずにGETリクエストが来た場合、作品ごちゃ混ぜで投稿順にデータを返す必要がある。
黄昏酒場スコアボードではposgresqlというリレーショナルデータベースを使用していたので今回もそれを流用したいが、この「スキーマぐちゃぐちゃのデータをまとめて返す」ということをposgresql単品でやるのは自分の技術力ではしんどかった。
silentselene.netではここらへんをどのようにしていたかというと、単一のリレーショナルデータベースのテーブルに神霊廟のトランスのカラムも天空璋の季節ゲージのカラムも全て含んだデータを作成し、そこに紅魔郷のデータをいれるのであればそれらを全てNULLにしたデータを入れていた。
自分は流石に無意味なNULLが入ったデータを作りたくないと考えたので、ウェブサイトとして責任を持つべきな情報(削除パスワードや投稿コメントなど)はposgresqlで管理し、リプレイに書かれている情報はmongodbというデータベースで管理させることにした。
mongodbはドキュメントデータベースと呼ばれるデータベースで、JSON形式であればスキーマがデータ毎に異なっていたとしても受け入れてくれる。つまり、あるデータは神霊廟のデータでトランスゲージが書かれていたり、あるデータはスペルカードプラクティスなので各ステージのデータがなかったりしても同じように管理できる。
唯一スキーマについて_idというキーが主キーだと決まっている。このキーは指定しなければ勝手に振られるが、今回はposgresqlに挿入したときの主キーと同じものにした。つまり外部キーであり主キーであるものにした。

ここで1つ疑問が生まれる。データベースといえばトランザクションだが、2種類のデータベースを使っている時点で、2つのデータベース間で整合性が取れなくなると予測されるのではないだろうか？
今回自分はposgresqlでトランザクションを取ってからmongodbにアクセスするとした。よって非同期的にリクエストが来ても必ずmongodbには同時に1リクエストしか行かない。posgresqlをpythonでラッピングするpsycopg3はトランザクション内でエラーをキャッチするとトランザクション丸ごとロールバックする機能が付いている。これにより、posgresqlに挿入するトランザクション中でmongodbへ挿入し、何らかのエラーが起きてロールバックすると最悪mongodbに余計なデータが入る被害で済む。あとはposgresqlでGETしたデータのみmongodbにアクセスすればユーザ目線は整合性が保たれているように見える。
問題は削除時だ。今と同じことを削除時も行うと最悪posgresqlで生きているデータがmongodbに存在しない事が起こる。よって今回は、削除時はposgresqlのデータのみ削除し、cronなどで定期的にposgresqlとmongodbとついでにリプレイファイルがあるファイルシステムの整合性をチェックし、常にそれぞれの集合の積を取るようにした。
これでユーザ目線はある程度の整合性が取れていることが担保されるだろう。

## リプレイ大会主催との交渉

ある程度見通しが立ったところで東方リプレイ大会主催のところを見に行った。
するとどうやら東方新作のリプレイを対応させたいが自分の動かしてる旧ロイヤルフレアのcgiの保守が難しいとのことだった。
なるほど2025年の春例大祭で錦上京の体験版が出たので対応させたいが難しい、しかもゆくゆくは2025年の夏コミで製品版も出るのでこちらも対応させなければならなそうだ。
ここで自分は閃いた。
取り急ぎ錦上京体験版対応の旧ロイヤルフレアcgiを提供[^6]するが、製品版が出る頃には自分が作成中のリプレイアップローダのみ錦上京に対応させればユーザが増えそうだな…
この激ヤバ人質危険思想を頭に入れつつ、主催に現状のリプレイアップローダの危険性を説明した後、夏コミ付近を目処にリプレイアップローダを作成しようと決意した。

## ドメイン移行計画

リプレイ大会主催はwww.example.com(example.comにアクセスするとここにリダイレクトされる)というエーペックスドメインでサービスを動かしており、URLのパスで各サービスを区切っていた。
なるほど旧ロイヤルフレアのアップローダは単一のindex.htmlとupload.cgiのみを使用するのでこの区切り方でも成立してしまう。
しかし不健全であることは事実だ。普通であればドメインがサービスを表し、パスはサービスに紐付く各画面を意味する。ドメイン自体が単一のサービスを提供するならまだしも複数サービスを単一のドメインで提供するのはあまりよろしくない。

ドメインは呪いだ。一度公開するとそのページでブックマークする人がいる。そのドメインを捨てるとブックマークした人は何故かアクセスできないという状態になる。しかもその捨てられたドメインが他の人に渡ってそのドメインでマルウェアを配布され始めたら目も当てられない。
ある人はドメインを取得することを「ドメインを産む」と表現した。ドメインを取得することは子供を産むことと同じで責任が伴う。ドメインを捨てることは子供を捨てることに等しい。倫理的に考えてやってはいけない。キラキラネームも勿論付けてはならない。

下手なドメインを使用し始めるとこの呪いがかかる。しかしもう運用し始めてしまったあとである。そしてリプレイ大会主催の看板でアップローダを運用する都合上、自分はこのドメインで運用することになる。
そして自分(とchatgptは)は考えた。
まずinfo.example.comにホームページを置く。ここにサービス一覧を置いた。これはそもそも論自分の作るサービスはSEO対策に自信がないので、SEO対策がきちんとしたhtmlテンプレートを用いて外部リンクを紐付ければ必然とユーザも集まるだろうという魂胆である。ここで海外ニキのsilentselene.netも外部リンクに載せておくと彼らのサービスの知名度が上がるだろう。前回作成した黄昏酒場スコアボードも載せておいた。
その後uploader.example.comあたりで自分のサービスを運用開始し、example.comの旧ロイヤルフレアと同時運用する。これでユーザをゆっくり移行させる。
そして時期を見てexample.comでのアップローダを閉鎖させたあと、info.example.comにあったホームページをexample.comに移行、info.example.comにアクセスするとexample.comにリダイレクトさせるようにする。
これできちんとドメインでサービスの階層構造が出来ていて健全な状態になり、以前のドメインをブクマしていた人はホームページに飛ばされ始め、新しいアップローダをブクマし始めるだろう。

## 次回

一旦長くなってきたのでここまでとする。
サービスのコアな部分は目処がついたので、次は運用に関することを書くと思われる


[^1]: なんでワイの大学時代の研究室の言語はPerlだったんだ…？
[^2]: 練習用ツールで、各面のプラクティスをパワーや残機などのパラメータをいじった状態でプレイ出来る。これによって出来たリプレイはプレイ時の初期パラメータがリプレイの末尾にJSON形式で記載される。リプレイファイルの仕様的に、末尾にはプレイした人のコメントが書けることになっているのでゲーム本編に影響しない。旧ロイヤルフレアは投稿時のコメントをリプレイ末尾に記載する謎機能が付いているのでここで干渉する。
[^3]: 今回の記事は許可もらって書いている
[^4]: ゲームコードについての研究はとてもすごい。東方製作者ZUNの作成した東方用スクリプト言語を使用して攻撃パターンを作成し、実際に東方ゲームに組み込めたりする。謎技術過ぎるだろ。
[^5]: thpracより前のspoilerAL+SSGの時代から東方の解析に携わってた方
[^6]: 錦上京体験版はリプレイ再生に関するバグが多く、結局自分が送りつけた体験版対応cgiは使われてない。太田順也くんさぁ…