LunaChatを移植した話 (前編)

LunaChatは、Minecraft (Spigotサーバー)用のプラグインで、主にローマ字漢字変換に使われています。身内のサーバーをFabricに移行するにあたり、このプラグインの移植依頼が来たので移植してみました。

# LunaChatの目的と仕様
MinecraftのJava版は、CJK対応が不安定で、チャット入力は多くの場合ローマ字で行われます。ローマ字で送信されたチャットメッセージを漢字に自動で変換するのが、LunaChatの主な機能です。LunaChatは、かな漢字変換以外にもチャンネルチャットなど様々な機能を有していますが、全部の機能を実装するのは大変で、ローマ字漢字変換以外は不要なので、これだけ実装することにしました。

LunaChatのローマ字漢字変換は、主に以下の部分で構成されています:

- 変換不要な場合(漢字やひらがなが含まれている場合)か確認する
- ローマ字からカナに変換
- かな漢字変換

かな漢字変換には様々な手法がありますが(例: MeCabを叩く、OSのIMEに任せる)、LunaChatの場合は「Google翻訳を使用する」という裏技的な方法を使用しています。

# Fabricの基礎知識
Fabricは、MinecraftのMod系統の一つで、Forgeと並んで広く利用されています。主にJava版バージョン1.14以降を対象とし、ほとんどすべてのスナップショット版で利用可能です。

Fabricは、主に「Modローダー」と「API」の2つによって構成されていますが、Fabric APIは多くのMod開発には不要で、多くの場合Modは代わりに「Mixin」という技術を使って、元のソースコードをモンキーパッチするような感覚で開発されています。また、Fabric APIの中身もブロックの追加など簡単なもので、これ以上のことはMod開発者に放り投げています。こうしたことから、ForgeやSpigotと比べFabricの更新は非常に早く(多くの場合でリリースから数時間以内)、スナップショット版での開発には適しています。

また、プラグインをサポートするSpigotと比較すると、Fabricはクライアント側でも使用でき、また動作に最低限必要とはいえないパッチ(例えばアイテム増殖バグの修正)を一切含んでいないという特徴があります。

# 移植
## 環境の整備
以下は、Windows機で作業します。Linux系ではJavaのインストール、Gradle操作等で差異があるかもしれません。

Gitをインストールします。また、Java 16(1.17.1用の開発に必要な最低バージョン)かJava 17の64ビット版を、OpenJDKのビルドの一種[Eclipse Temurin](https://adoptium.net/)の配布サイトからダウンロード・インストールします。`PATH`環境変数と`JAVA_HOME`環境変数を設定します。また、デコンパイル過程で使うためJava 8も同ウェブサイトからインストールしますが、こちらは環境変数の設定をしません。デコンパイラーとして、[Java Decompiler](https://java-decompiler.github.io/)のjarファイルをダウンロードし、Java 8で開くようショートカットを作ります。

```
"C:\(Java 8のインストール先)\bin\javaw.exe" -jar "C:\Users\user\Downloads\jd-gui-1.6.6-min.jar"
```

最後に、MinecraftのModを開発するために便利なランチャーを設定します。Java版のランチャーには、公式のもののほかに、サードパーティーのものが複数あります。今回は、メモリ使用量が公式のものに比べて特段に低く、Modサポートが良いことから、MultiMCランチャーを使います。

[公式ウェブサイト](https://multimc.org/)からダウンロードし、解凍して実行すると、画面が現れます。「Settings」(設定)をクリックして「Language」タブを表示し、日本語を選択します。その後「アカウント」タブで、アカウントを追加できます。Microsoftアカウントを使用してログインする場合は「Add Microsoft」を押し、リンクをクリックしてMultiMCの画面上に現れたコードを入力し、指示に従います。Microsoftアカウントに移行していない場合は、「Add Mojang」をクリックして、メールアドレスとパスワードでログインします。

設定を閉じて、「インスタンスの追加」をクリックします。「名前」に「Mod開発」などと適当な値を入れ、バージョン1.17.1を選択しOKを押します。アイコンが追加されるので、右クリックし「インスタンスの編集」を押します。「Fabricをインストール」ボタンが画面右に現れるので、押してOKを押します。これでMultiMCのセットアップは完了です。

IDEは、個人のお好みで選んでください。(nanoやサクラエディタなどのテキストエディターでも十分です。) IntelliJ IDEAを使用している場合は、[Minecraft Development](https://plugins.jetbrains.com/plugin/8327-minecraft-development)プラグインのインストールをおすすめします。

## 使いまわしできるコード
「漢字やひらがなが含まれているかの確認」と「ローマ字からカナに変換」のコードは、LunaChatのものを使い回しできます。

## コードを読む
現在、Fabricには、チャット関連のAPIは存在しません。そのため、チャット内容を変換する部分は、Mixinを使って書く必要があります。一番簡単な方法は、Minecraftをデコンパイルし、ソースコードを読むことです。ただし、配布されているMinecraftのjarファイルは、難読化処理が行われているため、これを解読する必要があります。そのためのファイルが、Fabricにより[CC0で提供されている](https://github.com/FabricMC/yarn)ため、まずGitでダウンロードします。

```sh
$ git clone --depth 1 --branch 1.17.1 https://github.com/FabricMC/yarn.git
```

その後、難読化処理を外します。大量のメモリ(最低2GB)を使用するので、注意してください。Gradleデーモンは今後しばらく出番がないため切っておきます。

```sh
$ cd yarn
$ gradlew --no-daemon mapNamedJar
```

ファイルがいくつか生成されますが、デコンパイルしたいのは`1.17.1-named.jar`です。Java Decompilerを開き、このファイルをドロップします。

![1.17.1-named.jar内のファイル構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/237877/fb66a677-1011-89d5-8a9b-fc2c78c5118b.png)

このファイル構造は、解読ファイルのライセンスの都合上ForgeやSpigotが使用しているものと異なっていますが、そこは今回は問題ないので無視しましょう。

Minecraftは数千個のクラスファイルから構成されていますが、解読されたときにパッケージが割り当てられたため、ある程度分かりやすくなっています。`net.minecraft`以下では、`block`、`entity`、`item`といった自明なものから(`CandleBlock`や`CompassItem`を探してみましょう)、NBTを定義する`nbt`パッケージ、パケットやその暗号化と圧縮を扱う`network`パッケージ、サーバー側でのチェストの画面などを管理する`screen`パッケージといったものまでさまざまあります。また、クライアント側にのみ存在するレンダリングなどのコードは`client`パッケージに、サーバー側に関係するコードは`server`パッケージにまとめられています。

チャットの処理はサーバー側がパケットを受信したときに行いたいので、`server`パッケージを見てみましょう。その中の`network`パッケージには、`ServerPlayNetworkHandler`というクラスがあります。このクラスは、(その名の通り)サーバーでパケットが受信されたときのハンドラーを定義しています。`chat`で検索すると、`onGameMessage`というイベントハンドラーと、その中で呼び出されている`handleMessage`メソッドが見つかりました。`handleMessage`の中にはコマンドを実行するコードや、チャットメッセージをプレイヤーに送信するコードがあります。注目したいのは、1211行目の`PlayerManager#broadcast`メソッドの呼び出しです。ここを乗っ取ってみましょう。

## Modの開発を始める
といっても、まず実際のModの開発を始めなければ何もできません。「[fabric-example-mod](https://github.com/FabricMC/fabric-example-mod)」というテンプレートが提供されているので、これを使ってみます。GitHub上の「Use this template」ボタンを押して自分のアカウントにコピーします。

```sh
$ git clone https://github.com/apple502j/kanaify.git
$ cd kanaify
```
次に、いくつかファイルを更新します。

- `gradle.properties`では、`maven_group`を適当な値(私はGitHub PagesのURL、`io.github.apple502j`を使用)に設定し、`archives_base_name`にMod ID(以下参照。ここでは`kanaify`としました)を設定します。
- `LICENSE`ファイルは削除するか、好みのライセンスを選んで置き換えます。LGPLか、Apache 2.0ライセンスを使用するのが無難です。
- `README.md`は、後で更新しても、いま更新しても大丈夫です。
- `src/main/java`以下のフォルダーとファイルは(後で書きなおすので)削除します。
- `src/main/resources/modid.mod.json`のファイル名中、`modid`を自分のMod IDに変更します。また、ファイル中`net.fabricmc.example.mixin`を`(maven_groupの値).(Mod ID).mixin`に変更します。
- `fabric.mod.json`は大事なファイルなので、次の一章を割いて解説します。
- `assets/modid/icon.png`は、フォルダーを改名して、必要に応じてアイコンを置き換えます。サイズは縦横128px。

## fabric.mod.json
このファイルは、Modに関する情報を書くものです。

- `id`は、Mod IDです。半角英小文字で始まり、その後は半角英小文字、数字、ハイフン、アンダースコアを利用できます。最大で64文字です。
- `name`は、ユーザーが多くの場合に目にする、Modの名前です。
- `description`は、Modの簡単な説明です。
- `authors`は、Modの制作者名の配列です。個人開発の場合は`["ユーザー名"]`などで大丈夫です。
- `contact`では、`sources`にGitHubリポジトリのURLを、`homepage`にMod配布ページや作成者のウェブサイトなどへのリンクを書くことができますが、任意なので、`"contact"`キーごと消して大丈夫です。
- `license`は、ライセンスの識別子、たとえば`LGPL-3.0`や`Apache-2.0`です。
- `icon`には、さっき作ったアイコンの`fabric.mod.json`からの相対パスを指定します。多くの場合は`assets/(Mod ID)/icon.png`です。
- `depends`では、依存関係を指定します。`"fabric"`とはFabric APIのことで、今回は使わないため消しておきます。その他は変更する必要はありません。
- `suggests`は不要なので消しておきます。
- 残りは後で埋めます。

では、続きは明日の後編で!