冴えないModの作り方 ~Beat Saber~

こんちわ、デンパ時計です。Modの作り方の流れ書いていきます。
英語ですがこっちの方が詳しいし親切です。

なお、製作に関して何らかの損害を受けた場合、こちらは一切関与しませんのですべて自己責任で製作してください。

用意する物

・Microsoftアカウント(無料)
・GitHubアカウント(無料)
・Git for Windows(無料)
・Visual Studio 2022(無料)
・dnSpy(無料)
・すでにMod他のが入ってるBeat Saber(Steam版、OculusPC版のどっちか 3000円くらい)
・気合(金で解決できない)

・Visual Studio Code(無料、必須じゃないのでお好みで)

セットアップ

1.Microsoftアカウントを作る
ここ読んで気合で作ってきてください

2.GitHubアカウントを作る
ここ読んで気合で作ってきてください

3.Git for Windowsをインストールする
ここから気合でインストールしてください

4.Beat Saberを買う
できるならSteam版を買って欲しい。Oculus版にはないDLLがあったりして後々困るから。(僕はOculus版だけど)
あとセールとか来たことないからみんな定価で買ってる。
買ったら気合で適当なModを入れる。

5.Visual Studio2022をインストールする
ここから丁寧に解説するよ。

ダウンロードページからセットアップ実行ファイルを持ってくる。

画像1

Visual Studioはエディションによって料金がかかったりかからなかったりします。
今回は個人利用なので無料のComunity 2022を選びましょう。

では落としてきたセットアップ実行ファイル(VisualStudioSetup.exe)を使ってインストーラーをインストールしましょう

いやなに言ってんの

実はこれ本体ではなくインストーラーのインストーラーなんです。
ややこしいですね。

画像2

なのでセットアップ中のタイトルもVisual Studio Installerになってますね

画像3

インストールが終わるとこんな画面になって何をインストールするか選べます。

チェックつけるのは2つ
・.NET デスクトップ開発
・Unityによるゲーム開発

画像4

画像5

チェック入れたらインストールボタンを押して今度こそ本体をインストールしましょう。

画像6

めちゃ長いので先にdnSpyをダウンロードしておきましょう。
dnSpy-net-win64.zipで大丈夫です。

画像7

中はこんな感じになってるんで好きなとこに展開してください。

Visual Studioのダウンロードが終わったらこんな感じの画面になると思います。

画像8

ではそのまま閉じてください
何故ならまだセットアップが終わっていないからです!

続きましてMod製作に必要な拡張機能をインストールしていきます。

まずはUnityModdingTools.Templates.BeatSaber
ページに飛んだらDownloadをポチーしてください。

画像9


BeatSaberModdingTools.vsixという名前のファイルがダウンロードできたはずなのでダブルクリックします。

画像10

Installぽちったら勝手にインストールされます。

画像11

画像12

これで一つ目の拡張機能がインストールされました。
はい、察しのいい方は気づいたかもしれませんが1個だけじゃないんですね。

続きまして、BeatSaberModdingTools.Tasksを落としてきます。
これも一緒です。画面に従ってぽちぽちボタン押してインストールしましょう。

画像13

画像14

画像15

はい、お疲れ様でした。ここまでで必要なセットアップはほぼ完了です。

Mod製作

というわけでVisual Studioを起動しましょう。

画像16

スタートメニューにVisual Studio 2022ってやつがあるんでクリック。

画像17

新しいプロジェクトの作成

画像18

ここでプロジェクトのテンプレートを選びます。
とりあえず上の検索欄にBeatとか打ち込むと絞り込めるはずです。

画像19

作るModによって変わるんですけど今回は「BSIPA4 Plugin(Disablable)」で行きましょう。

画像20

ここで作るModの名前を決めます。

そういえば何作るか言ってませんでしたね。
今回作るのは精度に応じてSSとかAとかの表示を変えるModです。

なので名前を「RankTextChanger」とかにしときましょうか。
名前を入力したらフレームワークのコンボボックスで4.8を選択します。

画像21

では作成をぽちー!

画像22

というわけで初期画面がこちら。
サインインしろって言われたら作成したMicrosoftアカウントでサインインしておきましょう。
Comunity エディションを使う条件としてサインインが必須なので。

まずはBeat SaberのインストールフォルダをVisual Studioにわからせましょう。
ちなみに、このあとの設定は初回のみでOKですのでこの後でほかのModを作る際は必要ないです。

画像23

拡張機能に「Beat Saber Modding Tools」があるのでSettingを開きます。

画像24

開くとBeat Saber Locationsみたいな項目があるのでなければBeat SaberのインストールフォルダをBrowseから選んであげてください。


画像25

Steamならローカルファイルを閲覧からどこにインストールされてるか一発でわかります。

Oculus版なら詳細を開いて場所にカーソルを持っていくとパスをコピーできます。

画像27

画像27

画像28

「Generate csproj.user on project load.」にチェック入れてOK押して閉じます。

さあ、ここからコードを書いていきます。

まずはmanifestから。
manifestが何なのかはこちらの記事を読んでください。

書かれている項目で大事なのは「dependsOn」です。
ぶっちゃけそれ以外適当でいいです。
までもゲームバージョンとかを合わせておくと丁寧ですよね。
というわけでそれっぽく編集。

{
 "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json",
 "id": "RankTextChanger",
 "name": "RankTextChanger",
 "author": "denpadokei",
 "version": "0.0.1",
 "description": "",
 "gameVersion": "1.21.0",
 "dependsOn": {
   "BSIPA": "^4.2.0"
 }
}

こんな感じになりました。
製作者を意味するauthorとgameVersionだけ書き換えました。

では続いてPluginを開きます。
赤線いっぱいでヤバいですね☆

画像29

これはまだDLLの参照がうまく言ってないのでこうなっています。

画像30

ソリューションエクスプローラーに注目しましょう。
「RankTextChanger」を右クリックします。

画像31

BeatSaberModdingToolsを開くと2項目あるんでどっちも実行。

そうすると、RankTextChanger.csproj.userにBeat Saberのフォルダパスが勝手に入ります。

画像32

ではもう一回Plugin.csに戻って「Ctrl + Shift + B」を押して一回ビルドします。

画像33

そうするとビルドが終わってModがBeat SaberのPluginsにコピーされたことが表示されます。
よくわかんないけど

========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

って出てればいいです。

続いてデバッグ作業です。これめっちゃやるんで覚えてください。
Beat Saberの起動引数に「 --verbose --debug fpfc」を加えましょう。
Steam版なら起動オプションってところですね

画像34


Oculus版はショートカットを作成してリンク先の項目の後ろにくっつけちゃえば大丈夫です。

画像35

画像36

起動するとBeat Saberと一緒に黒いウインドウが出てきますね。
このウインドウはログウインドウといって文字通りログがいっぱい出てきます。

肝心のBeat Saberの方はなんとキーボードのWASDで操作できます。
左クリックとかするとボタンを押すことができます。

ではいったんゲーム内の終了ボタンを押してゲームを終了します。
※もし固まったらログウインドウの中心を右クリックしてください。

インストールフォルダのLogフォルダを開きます。

画像37


_latest.logを開きます。

画像38

そっ閉じしたくなるくらい意味不明な文字がいっぱい並んでますね。

ここで探すのは

[INFO @ 20:16:23 | IPA] Beat Saber
[INFO @ 20:16:23 | IPA] Running on Unity 2019.4.28f1
[INFO @ 20:16:23 | IPA] Game version 1.21.0
[INFO @ 20:16:23 | IPA] -----------------------------
[INFO @ 20:16:23 | IPA] Loading plugins from Plugins and found 58
[INFO @ 20:16:23 | IPA] -----------------------------

の箇所です。

ここに読み込まれたMod一覧があるので

[INFO @ 20:16:23 | IPA] RankTextChanger (RankTextChanger): 0.0.1

があるかを探します。あればOKです。

ない場合はDLLのコピーがうまくいってない可能性があるのでVisual Studioの拡張機能の設定等を見直しましょう。

ではVisual Studioに戻ってコードを書いていきます。

今回はRankの文字を変更するのにHarmonyを使っていきます。
Harmonyって何?ってなりますがHarmonyはHarmonyです。
ライブラリの名前です。

画像39

参照を右クリックします。

画像40


Beat Saber Reference Managerをクリックします

画像41

そうすると、参照するDLLを選ぶ画面が開くのでLibsにある「0Harmony」にチェックを入れてOK

画像42

これでHarmonyが使えるようになります。

次にHarmonyでパッチを当てる関数をdnSpyで探します。

画像43

File→Openで「Beat Saber_Data\Managed」にあるdllを全部選択して開く

画像44

なんかどわっと表示されるので「Main」をクリックして展開していきます。

画像45

なんかそれっぽいのを探します。

下のSerchを押して「Rank」と入力。
Option Serchを「Class」「SelectedFiles」にします。

画像46

そうするとRankにかかわりそうなクラスが表示されます。
ここで該当関数があるかどうかはModderの経験と勘が問われます。

一旦「RelativeScoreAndImmediateRankCounter」をダブルクリックして開きましょう。

その中に「UpdateRelativeScoreAndImmediateRank」という関数があるかと思います。

そこに「RankModel.GetRankForScore」というものがありますね。

画像47

このRankModelがRankに関わる色々をやってそうなのでダブルクリックして開きます。

そうすると、「GetRankName」という関数がありますね。

画像48

そうです。これが今回の目的の関数です。

では、Visual Studioに戻りましょう。

どうも先ほどの関数は「GamePlayCore.dll」にあるらしいのでHarmonyを追加した手順と同様に参照に追加します。

画像61

まずは「RankTextChangerController.cs」を消します
いらないので。

そうするとPlugin.csで赤線が引かれてるのでそこを消します。

画像49

画像50

画像51

画像52

では続いて、上にあるコメントを解除しましょう。

画像53

画像54

Harmonyでくくられている箇所もコメントを解除しちゃいましょう。

画像55

画像56

そうするとUnpatchAllに赤線が引かれているのでUnpatchSelfに書き換えます。

       /// <summary>
       /// Attempts to remove all the Harmony patches that used our HarmonyId.
       /// </summary>
       internal static void RemoveHarmonyPatches()
       {
           try
           {
               // Removes all patches with this HarmonyId
               //harmony.UnpatchAll(HarmonyId);
               harmony.UnpatchSelf();
           }
           catch (Exception ex)
           {
               Plugin.Log?.Error("Error removing Harmony patches: " + ex.Message);
               Plugin.Log?.Debug(ex);
           }
       }

画像57

いい感じですね。

続いてプロジェクトに「GetRankNamePatch」という名前のクラスを追加します。

画像58

画像59

画像60

はいできました。ではパッチを書いていきましょう。

using HarmonyLib;
using System;

namespace RankTextChanger
{
   [HarmonyPatch(
       typeof(RankModel),
       nameof(RankModel.GetRankName),
       new Type[] { typeof(RankModel.Rank) }
       )]
   internal class GetRankNamePatch
   {
       public static void Postfix(RankModel.Rank rank, ref string __result)
       {
           switch (rank)
           {
               case RankModel.Rank.E:
                   __result = "超悪";
                   break;
               case RankModel.Rank.D:
                   __result = "悪";
                   break;
               case RankModel.Rank.C:
                   __result = "微悪";
                   break;
               case RankModel.Rank.B:
                   __result = "普通";
                   break;
               case RankModel.Rank.A:
                   __result = "可";
                   break;
               case RankModel.Rank.S:
                   __result = "良";
                   break;
               case RankModel.Rank.SS:
                   __result = "超良";
                   break;
               case RankModel.Rank.SSS:
                   __result = "神";
                   break;
               default:
                   break;
           }
       }
   }
}

できました。

一個一個説明すると長くなるのでポイントだけおさえて説明していきます。

まずは

 [HarmonyPatch(
       typeof(RankModel),
       nameof(RankModel.GetRankName),
       new Type[] { typeof(RankModel.Rank) }
       )]

ここは何の関数にパッチを当てるかを指定します。
HarmonyPathまではおまじないです。
typeofはクラスの種類を教えてあげます。今回はRankModelクラスですね。

nameofは関数の名前を教えてあげます。"GetRankName"と書くのと同義です。nameofを使った方がバグに気づきやすくなります。

new Type[]は関数の引数に何が入るかを教えてあげます。
今回はRankModel.Rank一個なのでnew Type[] { typeof(RankModel.Rank) }だけです。
増えるとnew Type[] { typeof(hoge), typeof(hage), typeof(mage) }みたいになります。

続いて

public static void Postfix(RankModel.Rank rank, ref string __result)

Harmonyはメソッドの名前や引数の名前がきっちり決まってないと動かないのでここは自由に変えることができません。
(書き方次第で自由に変えることもできますが初心者向けではない。)

まず「Postfix」というところ。
この名前にするとHarmonyは「指定した関数の実行後に実行するんだな。」と解釈してくれます。

次に「RankModel.Rank rank」はこの関数の引数を取得します。
rankという名前は元関数の引数の名前に合わせてください。

画像63

続いて「ref string __result」は元関数の実行結果を取得します。
なのでここに「SS」とか「A」とかが最初から入ってるわけですね。
__resultという名前以外では実行結果を取得できないのでここも名前は固定です。

refは参照渡しといって色々説明がめんどくさいのですが、中の値を書き換えたらメソッドの外に出てもそれが影響受けるくらいに考えてください。

           switch (rank)
           {
               case RankModel.Rank.E:
                   __result = "超悪";
                   break;
               case RankModel.Rank.D:
                   __result = "悪";
                   break;
               case RankModel.Rank.C:
                   __result = "微悪";
                   break;
               case RankModel.Rank.B:
                   __result = "普通";
                   break;
               case RankModel.Rank.A:
                   __result = "可";
                   break;
               case RankModel.Rank.S:
                   __result = "良";
                   break;
               case RankModel.Rank.SS:
                   __result = "超良";
                   break;
               case RankModel.Rank.SSS:
                   __result = "神";
                   break;
               default:
                   break;
           }

さて、本処理です。

まず引数のrankを使ってswitchで場合分けします。
その後ランクに応じて書き換えたい文字を結果の__resultに入れます。

以上です。

画像62

こんな感じになってればいいです。

ではPlugin.csに戻ってパッチを当てる関数のコメントを解除しましょう。

画像64

画像65

はい、では「Ctrl + Shit + B」でビルドしましょう。

画像66

めでたくビルドが通ったら、デスクトップモードでBeat Saberを起動して適当な曲を再生しましょう。

画像67

画像68

うまく変わってたら成功です。
お疲れ様でした。

GitHubで管理する

ここまで作ったコードやdllを配布するときはGitHubが便利です。
Gitは色々コマンドがあって全部理解するのは難しいですが、Visual Studioがほとんど自動でやってくれるので初心者でも簡単な機能なら問題なく使えます。

まず右下にある「ソース管理に追加」を押しましょう。

画像69

そしてそこにあるGitを押すとGit管理画面が開きます。

画像70

説明に簡単な説明を入力します。
外部に公開する場合はプライベートリポジトリのチェックを外します。
そして「作成とプッシュ」を押します。

画像71

すると自分のページのリポジトリの一覧に「RankTextChanger」が出てくるはずです。

アクセスすると今まで書いたコードが閲覧できます。

画像72

Readmeの書き方はめんどくさいので割愛します。

さて、続いてほかの人に配布する用のDLLファイルを作成します。
Visual Studioに戻りまして、ビルドオプションをDebugからReleaseに切り替えます。

画像73

切り替えたら「Ctrl + Shit + B」でビルドします。

画像74

そうすると今までのログと違って「~.zip」みたいなものが書かれてるはずです。

1>  Target: BSMT_ZipRelease
1>     ZipDir: Zipping Directory "bin\Release\Artifact" to "bin\Release\zip\RankTextChanger-0.0.1-bs1.21.0-56e70f5.zip"
1>  Target: BSMT_CopyToPlugins
1>     Copying 'bin\Release\RankTextChanger.dll' to 'D:\Oculus\Software\hyperbolic-magnetism-beat-saber\Plugins\RankTextChanger.dll'.
1>     Copying 'bin\Release\RankTextChanger.pdb' to 'D:\Oculus\Software\hyperbolic-magnetism-beat-saber\Plugins\RankTextChanger.pdb'.
========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ==========

勝手にdllをzipにしてくれる便利機能がついてるんですね。

で、どこにできたかというとプロジェクトフォルダのReleaseフォルダの下にいい感じにできてます。

まず、プロジェクトを右クリックしてエクスプローラーフォルダーで開くを押します。

画像75


画像76

画像77

画像78

画像79

「bin\Release\zip」に完成したzipが置いてあるのが分かると思います。

後はGitHubにアップロードして終わりです。

画像80

右側にちっちゃくCreate new releaseがあるので押します。

画像81

画像82

うわ、あめりか語や…

いじるとこそんなに多くないので安心してください。

まずChoose a tagを押してModのバージョンである0.0.1を入力します。

画像83

下のいかにもファイルを添付できそうなところにzipを持っていきます。

画像84

画像85

タイトルを適当に書きます。自分はzipの名前をコピペしてます。

画像86

説明欄はめんどいので「Auto-generate release notes」を押して自動生成に任せます。

画像87

画像88

画像89

画面下の「Publish release」を押して終わりです。
お疲れ様でした。

画像90

今回作ったModのリポジトリ