単なるスキンリソースパックだと思っていたが、実際には DST スキンシステムをほぼ丸ごと再構築していた
最近、すでにメンテナンスが停止され、ソースコードが公開されている『Don't Starve Together』の全スキン mod を見つけた。最初は、この手のプロジェクトの実装方法はどれも似たようなものだろうと思っていた。公式スキンリソースを MOD ディレクトリにパッケージし、いくつかのテーブルを書き換え、最後にゲーム内で「表示させる」だけで終わりだろうと。
しかし、このリポジトリを実際に読み通した後、私の判断は完全に変わった。
これは普通のリソースパックなどではない。より正確に言えば、これはランタイムスキンインジェクションシステムだ。単に公式スキンを持ってきただけではなく、『Don't Starve Together』本来のスキンの動作方式をできる限り再現し、その体系の上にさらに custom_ 名前空間と一連のランタイム Hook を追加している。これにより、ローカルスキンが公式スキンと同様に名前検索、アイコン検索、所有権判定、エンティティ生成、スキン切り替えフローに参加できるようになっている。
実際にやっていることは「スキンの追加」ではなく「スキンシステムの乗っ取り」
このプロジェクトで最も注目すべき点は、公式スキン体系を迂回して完全に独立したロジックを自前で作り上げたわけではないということだ。むしろ、できる限り公式の構造に沿って進み、必要な箇所にのみ自前のランタイム書き換えレイヤーを挿入している。
例えば modmain.lua では、MOD はまず custom_ プレフィックスの判定と除去ロジックを定義している。ここでの要点は単に「名前にプレフィックスを付ける」ことではなく、安定したマッピング関係を確立することにある:
- 公式スキン
wilson_formal - MOD スキン
custom_wilson_formal
このマッピングが確立されれば、後の処理はすべてスムーズに進む。MOD はランタイムで custom_wilson_formal を自身のスキンとして認識でき、公式データを呼び出す必要がある場面では wilson_formal に復元して名前、説明、アイコン、レアリティを検索できる。
つまり、custom_ は装飾的なプレフィックスではなく、システム全体のエントリーポイントなのだ。
公式テキストをコピーしたのではなく、公式の検索能力を奪い取った
このプロジェクトが「なかなか面白い」と感じた本当の理由は、skinloader/skinloader.lua にある一連のプロキシロジックだ。
このファイルは巨大なローカライズテキストテーブルを管理しているわけでもなく、公式のアイコン名やレアリティ文字列を丸ごとコピーしているわけでもない。やっていることはもっと直接的で、検索関数のインターセプトだ。
以下のようなグローバル関数を一律プロキシでラップしている:
GetSkinDescriptionGetSkinNameGetSkinInvIconNameGetModifiedRarityStringForItemGetColorForItem
もし渡されたアイテムに custom_ プレフィックスが付いていれば、まずプレフィックスを除去してから、リクエストを公式関数に戻す。
その結果、MOD は大量の表示レイヤーデータを自前で管理する必要がない。ゲームが custom_wilson_formal を検索するとき、最終的に取得されるのは公式の wilson_formal の名前、説明、アイコン、レアリティロジックそのものだ。
エンジニアリングの観点から言えば、これは非常に典型的かつ賢いやり方だ。結果をコピーするのではなく、公式の「結果を生成する能力」を再利用しているのだ。
「全スキン解放」の最も核心的な部分は、実は所有権の偽装にある
この手のプロジェクトを見て、最初に「スキンリストを表示しているだけでは?」と思う人は多いだろう。しかし、その理解は半分しか正しくない。
本当にこれを成立させているのは UI ではなく、インベントリプロキシレイヤーだ。
skinloader/skinloader.lua では、この MOD はインベントリの所有権に関連する複数のインターフェースを書き換えている。例えば:
InventoryProxy.CheckOwnershipInventoryProxy.CheckOwnershipGetLatestInventoryProxy.GetOwnedItemCountInventoryProxy.GetFullInventory
核心的な考え方は非常にシンプルだ。あるスキンが MOD 自身の SKINS テーブルに登録されていれば、公式ロジックにそれを「所有済み」として扱わせる。
このステップは極めて重要だ。なぜなら DST のスキンシステムは「リソースがローカルにあればそのまま装着できる」というものではなく、途中に所有権チェックのレイヤーが存在するからだ。この MOD が本当に突破しているのは、まさにこの関門なのだ。
つまり、この手のプロジェクトは「スキンファイルを入れれば使える」というものではなく、同時に三つの問題を解決する必要がある:
- スキンデータを登録できるか
- スキンの所有権チェックを騙せるか
- スキンリソースが最終的に正しくレンダリングされるか
この三つのうち一つでも欠ければ成立しない。
生成とスキン切り替えフローまで丸ごと乗っ取っている
もし「所有済み」の段階で止まっていたら、プレイヤーはせいぜい一部の画面でスキン項目を見られるだけで、実際にエンティティに適用しようとすると失敗する可能性がある。
このプロジェクトはそこで止まっていない。さらにランタイムで、より低レベルなスキンフローのいくつかの段階を乗っ取っている:
CreatePrefabSkin(...)SpawnPrefab(...)Sim:ReskinEntity(...)AnimState:GetSkinBuild(...)
これらのポイントがつながって初めて、「スキンの定義から表示まで」の完全なチェーンが構成される。
その意図は実に直接的だ。MOD はリソースをディレクトリに置いてエンジンが偶然見つけるのを待つのではなく、エンティティ生成、スキン切り替え、アニメーションビルド名の解決といった重要なノードで、ゲームに「今使うべきはこの custom_ スキンだ」と明示的に伝えている。
これが、私がこのプロジェクトを素材集ではなく、スキンシステムインジェクターに近いと感じる理由だ。
このリポジトリで最も混乱しやすいのは、三つのレイヤーを先に区別していないこと
このリポジトリで直接 CreatePrefabSkin(...) を検索すると、すぐに頭が混乱する。なぜなら、同じ公式スキンが複数の異なるファイルに同時に登場することが多いからだ。
後になって気づいたのだが、このプロジェクトを理解する最も効果的な方法は、強制的に三つのレイヤーに分けて見ることだ。
第一レイヤーは「ミラーレイヤー」で、主に以下のファイルだ:
このレイヤーの役割は、できる限り公式スキン構造と一致させることだ。ここでは通常まだ公式の命名が使われ、custom_ は付かない。「公式構造のローカルミラー」に近い。
第二レイヤーは「アクティベーションレイヤー」で、以下のファイルだ:
このレイヤーこそが、MOD がランタイムで実際に使用する内容だ。ここのエントリの多くは custom_<official_id> に変換され、assets、build_name_override、init_fn といったランタイムに必要なフィールドが補完される。現在のリポジトリでは、このレイヤーの大量のエントリが groupid = 0825 を共有している。
第三レイヤーは「リソースレイヤー」で、以下のファイルだ:
anim/dynamic/*.zipanim/dynamic/*.dyn
このレイヤーは一見最も直感的だが、実は最初に見るべきではない。なぜなら、リソースレイヤーは結果に過ぎず、ロジックの中心ではないからだ。これらのリソースがいつロードされ、どの名前でマッチングされ、表示できるかどうかを本当に決定しているのは、前述の Lua インジェクションロジックだ。
この三つのレイヤーを分離しない限り、後のほぼすべての判断が混乱する。
この手のプロジェクトで最も危険な誤解は「サフィックスルールを理解した」と思うこと
この手のスキンリポジトリを読んでいると、ある錯覚に陥りやすい。_d、_p、_none がそれぞれ何を意味するか覚えれば、あとは機械的にテンプレートを当てはめればいい、という錯覚だ。
しかし、このリポジトリを読み進めるほど、これが最も間違いやすい考え方だと感じるようになった。
通常の場合:
_dは独立したリソースを持つバリアントを表すことが多い_pはbuild_name_overrideを通じて_dを再利用することが多い_noneはスキンなしのプレースホルダーエントリであることが多い
問題は、これらはあくまで経験則であり、絶対的なルールではないということだ。
新しいスキンをどう組み込むべきかを本当に決定するのは、サフィックスそのものではなく、公式の元の定義に実際に何が書かれているかだ:
assetsがあるかどうか- 別の
buildを再利用しているかどうか init_fnがあるかどうかghost_skinが公式命名なのかcustom_ghost_*なのかpowerup、stage2、stage3のようなバリアントチェーンを持っているかどうか
このステップを飛ばして、「_p っぽく見える」だけでファイルを変更し始めると、十中八九プロジェクトを壊すことになる。
このプロジェクトで最も過小評価されやすいファイルは、実は build.bin だ
ファイル名だけ見ると、リソースの組み込み作業量はそれほど大きくないと思う人が多いだろう。公式の .dyn と .zip をコピーして custom_xxx にリネームすれば終わりではないか、と。
しかし、本当の落とし穴はまさにここにある。
.dyn は比較的シンプルだ。このリポジトリの現時点での結論は、.dyn はアニメーションデータを保存しており、内部に build 名を埋め込んでいないため、多くの場合はそのままコピーしてリネームすれば十分だということだ。
本当に厄介なのは .zip だ。
なぜなら、.zip 内の build.bin には内部 build 名が書き込まれているからだ。MOD がランタイムで custom_backpack_invisible を探しているのに、コピーしてきたリソースパックの内部にはまだ backpack_invisible と書かれていれば、エンジンがマッチングする際に直接食い違いが生じる可能性がある。その結果として現れるのは、最も典型的なあの問題だ:
- スキンが透明になる
- モデルが表示されない
- テクスチャが欠落する
つまり、多くの場合「ファイル名を正しく変更した」だけでは不十分で、build.bin 内の内部名も一緒に変更しなければならない。
この点こそ、多くの門外漢が最も見落としやすいが、実際には成否を決定する箇所だ。
なぜ backpack_invisible の補完事例が代表的なのか
このリポジトリで最近行われた比較的典型的な補完は backpack_invisible だ。これを特別に取り上げる価値があるのは、このスキン自体が特別だからではなく、メンテナンスフロー全体を非常に完全に露呈しているからだ。
このスキンを補完するために、リポジトリでは互いに関連するいくつかの作業が行われた:
scripts/prefabskins.luaでbackpackカテゴリに追加。scripts/prefabs/skinprefabs.luaでミラー定義を補完。scripts/prefabs/kleiskinprefabs.luaでcustom_backpack_invisibleのアクティベーションエントリを追加。- 公式の
.dynをanim/dynamic/custom_backpack_invisible.dynにコピー。 - 公式の
anim_dynamic.zipから対応する.zipを抽出し、build.binの内部名をパッチしてからanim/dynamic/custom_backpack_invisible.zipとして保存。
この事例は三つのことをよく示している:
- 更新は Lua だけを変更するのでもリソースだけをコピーするのでもなく、データレイヤーとリソースレイヤーの両方を同時に変更する必要がある。
- 公式リソースは必ずしも散在するディレクトリにあるわけではなく、
databundleの中から探す必要があるものもある。 - 表示できるかどうかを決定する鍵の一つは、ランタイムの
build名とリソースパック内部のbuild名が一致していなければならないということだ。
この事例を完全に理解すれば、このプロジェクトの後続のほとんどのスキン更新は、実際には同種の問題の異なるバリエーションに過ぎない。
これは「スキンを無料で使う方法」の答えではなく、非常に典型的なエンジニアリングサンプルだ
最終的にこのプロジェクトに対する私の評価は、最初とはまったく異なるものになった。
最初は「全スキン MOD ソースコードパッケージ」だと思っていた。読み終えた後は、DST スキンシステムの非常に典型的なエンジニアリングサンプルとして見るようになった。最も研究に値するのは「公式の所有権をどう迂回するか」ではなく、迂回すると同時に、公式スキンシステム本来のセマンティクスをできる限り壊さなかったことだ。
各スキンを独立したロジックとして乱暴に書くのではなく、公式に既に存在するものをできる限り再利用している:
- 名前と説明の検索
- アイコン検索
- レアリティロジック
- prefab skin データ構造
- リソース再利用関係
本当に追加されたのは、自身が制御しなければならないレイヤーだけだ:custom_ 命名、所有権偽装、ランタイムインジェクション。
したがって、このリポジトリで最も難しいのは Lua が書けるかどうかではなく、変更する際に十分な抑制を保てるかどうかだ。どの部分を公式と一致させるべきか、どの部分がこのプロジェクトが本当に自前で乗っ取る必要がある箇所なのかを、常に把握していなければならない。
今後 DST が新しいスキンを更新した場合でも、最も安定したルートは変わらない。まず公式が何を追加したかを確認し、次に元の定義を読み、それからミラーレイヤー、アクティベーションレイヤー、リソースレイヤーをそれぞれ処理し、最後に build 名、ghost チェーン、各種バリアントチェーンが途切れていないかを検証する。
この観点から見ると、このプロジェクトの本当の価値は「どんな効果を実現したか」ではなく、スキン系 MOD がエンジニアリング上、公式システム自体にどこまで近づけるかを示したことにある。