-
-
Notifications
You must be signed in to change notification settings - Fork 2
Mod reload ja
English | 日本語
実験的な機能です。 中核 1.2.0(2026-09-17 リリース)で入りました。Mod を作る人向けで、Developer tools がオンのときだけ動きます。
Mod をビルドして、ゲームを閉じて、また起動して、タイトル画面を抜けて、店を開く。これで 1 回試すのに 1 分かかり、それを 1 時間に何十回も繰り返すことになります。Mod の再読み込みを使えば、この手間がなくなります。ビルドすると、ゲームを動かしたまま、新しい DLL が動いている版と入れ替わります。開発者ツールの中の機能(ほかの Mod と一緒に動かす のルール 8)なので、遊ぶ人の目に触れることはありません。
- Mod(プラグイン):できます。 ただし、Mod がそう宣言したときだけです(後述)。Mod がフレームワークに登録するものはすべて GUID を持っているので、古い版をきれいに外せます。
- ライブラリ(フレームワークの Dialogue、Assets など。自作のライブラリも):できません。 ほかの Mod がライブラリの型を直接参照しているので、ライブラリだけ新しくしても、その Mod は古い型に結びついたままになります。ライブラリは再起動が要ります。
- Mono はアセンブリを外せません。 再読み込みでは、新しいビルドを古いものの隣に読み込んで、古いほうが何もしないようにします。古いコードはメモリに残りますが、1 回あたり数百 KB なので、開発中なら問題になりません。
- 遊ぶ人向けの「再起動なしで Mod を入れる・更新する」:できません。 「起動時に一度だけ」を前提に書かれた Mod が壊れますし、下の Direct3D 12 の問題が遊ぶ人に降りかかるからです。遊ぶ人はこれまでどおり再起動します。
- 対象は DLL だけです。テクスチャには専用の読み直しがあり(Assets)、Drag'n Wash Localization の翻訳ファイルにもあります。
- Developer tools をオンにします(Options → Mods → Drag'n Wash ModFramework)。
- Mod が再読み込みに対応していると宣言します。登録時に
ModInfo.Reloadable = trueにするか、プラグインのクラスに[ReloadableMod]属性を付けます。何も言わない Mod は再読み込みされません。 - ビルドしたら、DLL を
<Mod>.dll.newという名前で、入っている DLL の隣に置きます(下の.csprojの例がこれをやってくれます)。Windows では動いている DLL を Mono が掴んでいて上書きできないので、フレームワークは.newから読み直し、次の起動時にプリローダーのパッチャーがそれを本物の DLL にします。上書きできる環境(Linux)では、DLL そのものが変わったときも読み直します。
ファイルが変わらなくなってから 0.5 秒後に Mod が読み直され、ログ(と Console)に Reloaded <Mod> (1st time) from <file>. と出ます。DLL の隣に .pdb があれば、それも一緒に読み込みます。
変更を拾うのは、ゲームの窓が前に出ている間です。バックグラウンドでは Unity がゲームの更新処理を止めるので、その間に届いたビルドは、ゲームに戻ったときに読み直されます。
| 場所 | 内容 |
|---|---|
[Developer] WatchMods(Mods 画面の Drag'n Wash ModFramework、Show advanced settings をオンにすると出る Reload mods when their DLL changes。既定はオン) |
対応を宣言した Mod の DLL か .dll.new が変わったら、自動で読み直す |
Console の mods reload <guid>
|
1 つの Mod を今すぐ読み直す。WatchMods がオフでも使える |
mods watch on、mods watch off
|
WatchMods を切り替える |
mods |
どの Mod が対応していて、それぞれ何回読み直したかを出す |
| Mods 画面 | 読み直した Mod には Reloaded と、このセッションでの回数、動いているのが BepInEx が読み込んだファイルではないことが出る |
<!-- .csproj に。ビルド後に DLL が .dll.new としてゲームへ行き、動いているゲームがそれを読み直します。 -->
<PropertyGroup>
<GameDir>C:\Program Files (x86)\Steam\steamapps\common\Drag'n Wash</GameDir>
</PropertyGroup>
<Target Name="CopyToGame" AfterTargets="Build" Condition="Exists('$(GameDir)')">
<Copy SourceFiles="$(TargetPath)" DestinationFiles="$(GameDir)\BepInEx\plugins\$(AssemblyName)\$(AssemblyName).dll.new" />
</Target>- 先に新しいビルドを確かめます。 バイト列から読み込むのでファイルはロックされず、次のビルドの邪魔をしません。そこから同じ GUID のプラグインを探します。失敗したら理由をログに出して、古い版はそのまま動かします。入れ替えられないのに止めてしまう、ということはありません。
-
古い版を外します。 古い版がフレームワークとライブラリに登録したものを、GUID とアセンブリで外します。Tool window のタブとコマンド、テキストの書き換え、
GameEventsの処理、ライブラリのイベントにつないだ処理、サービス、GameHooksの記録、ModInfoです。Harmony のパッチを外し、プラグインのコンポーネントを破棄するので、OnDestroyが走ります。 -
新しい版を始めます。 BepInEx が古い版を始めたのと同じ場所に置かれ、起動時と同じように
Awakeが走ります。GameEvents.OnGameStartedの処理は、遅れて登録したときと同じようにすぐ走ります。同じ id で Options の行をもう一度足すと、古い行のコールバックを引き継ぎます。
手順 2 より前に失敗したときは、古い版が動き続けます。そのあとで失敗したときはログにそう出て、Mods 画面ではその Mod の動いている版に使えない印が付きます。この場合は再起動が要ります。
古いアセンブリと、ゲームがまだ持っている古い版のオブジェクトは残ります。
新しい版の Awake はゲームの途中で走ります。そこでテクスチャを読み込んだりフォントを準備したりする Mod は、Direct3D 12 が落ちうるまさにその瞬間にアップロードすることになります(Unity UUM-140564)。ネイティブのクラッシュは捕まえられないので、フレームワークは再読み込みの前に印のファイル(BepInEx/config/<中核の GUID>.reload-in-progress)を書き、終わったら消します。次の起動で印が残っていたら、再読み込みでゲームが落ちたということです。そのときはログにそう出て、自分でオンに戻すまで WatchMods はオフになり、Mods 画面では「Automatic mod reload」が使えない機能として出ます。起動オプションに -force-d3d11 を付けて作業すれば、この問題は起きません。
再読み込みに対応すると宣言した Mod は、次を守ってください。
- Harmony の ID は自分の GUID(
new Harmony(MyMod.Guid))。パッチはこれで見つけて外します。 - 自前の static フィールドで抱えず、フレームワーク経由で登録する(
ModFramework.Register、AddTab、AddCommand、AddRewriter、GameEvents、Services、GameOptions)。フレームワークが知らないものは外せません。 - まっさらになるのは、新しいアセンブリ自身の static だけです。古い版がゲームに渡したもの(コルーチン、
DontDestroyOnLoadのオブジェクト、ファイルの監視)はOnDestroyで片付けてください。 -
Awakeに、Direct3D 12 でゲームの途中に走らせて危ないこと(テクスチャのアップロード)を置かない。置くならGameFonts.RuntimeUploadsAreSafeで分ける。
ライブラリはほかの Mod からの登録を抱えているので、Mod が読み直されるときは、その古い登録を外さないといけません。ModReload.Unloading は、Mod の古い版を外す直前に、その GUID とアセンブリを渡して呼ばれます。その Mod があなたのライブラリに登録したものを、GUID で持っているものは GUID で、そうでないものはアセンブリで外してください。
ModReload.Unloading += (guid, assembly) =>
{
MyLibrary.RemoveOwned(guid); // GUID で持っているもの
ModReload.PruneEvent(typeof(MyLibrary), nameof(MyLibrary.Changed), assembly); // static なイベントの処理
};| メンバー | 内容 |
|---|---|
event Action<string, Assembly> Unloading |
Mod の古い版を外す前に呼ばれる。例外を投げた処理はログに出て、ほかの処理は呼ばれる |
Delegate Prune(Delegate handlers, Assembly assembly) |
処理のうち、メソッドか対象がそのアセンブリにあるものを除いたもの |
void PruneEvent(Type type, string eventName, Assembly assembly) |
type の static なイベントに、名前で Prune をその場でかける |
string Reload(string guid) |
次のフレームでの再読み込みを予約する。予約した内容か、できない理由(開発者ツールがオフ、読み込まれていない、対応していない、ライブラリ)を返す |
bool IsReloadable(string guid)、IReadOnlyList<string> ReloadableMods()
|
Mod が対応を宣言したか。宣言した読み込み済みの Mod の一覧 |
int ReloadCount(string guid) |
このセッションで Mod を読み直した回数 |
bool Watching |
WatchMods と開発者ツールがどちらもオンなら true |
どれも中核の DragNWash.ModFramework 名前空間にあります。ReloadableModAttribute もここです。
Players
Mod authors
- Getting started
- Playing well with others (the guide)
- Going online
- Installer
- Mod reload
- Overrides (no code)
- Graphs (no code, makes things happen)
Tools (F1, developer tools)
- Inspector
- Console
- Code graph
- Bridge (AI clients, MCP)
API
日本語
- ホーム
- プレイヤー向け · ランチャー · FAQ · クラッシュレポート
- はじめての Mod · ほかの Mod と一緒に動かす · 外と通信する Mod · インストーラー · Mod の再読み込み · Overrides · Graphs
- Inspector · Console · コードのグラフ · Bridge
- 中核 API · 操作の登録簿 · Text · Dialogue · Tool window · Assets · Flags and saves · GameEvents · SettingMeta
Links
- Repository
- Releases
- Changelog
- Design records: DESIGN · ROADMAP · CONTENT_POLICY