広告枠を募集中 ご相談ください

Skyrim Script Extender(SKSE)とゲームエンジンのフック機構:Papyrus仮想マシンの制約、ネイティブDLLコード注入とAddress Libraryによるバージョン依存解消

365Finds Editorial
Wiki 記事 11,430 本収録

あなたの AI で 365 Wiki を使う

下のプロンプトをコピーして、普段使っている AI に貼り付け、そのまま質問してください。

完全版 Skill をダウンロード

まずはプロンプトでお試しください。よく使う場合は完全版 365Wiki Skill をインストールすると、毎回貼り付ける必要がありません。普段使う AI がなければ、Gemini や Microsoft Copilot のウェブ版でも使えます。

はじめに:BethesdaゲームのMod構造とゲームエンジンの壁

10年以上にわたり、Bethesda Game Studiosが開発したCreation Engineは——『The Elder Scrolls V: Skyrim』や『Fallout 4』の駆動エンジンとして——PCゲーム史上最も活発で巨大なコミュニティModエコシステムを育んできました。数万人に及ぶクリエイターの手により、バニラのゲームは現代的なアクション戦闘、美麗な動的天候シェーダー、洗練されたユーザーインターフェース、高精度な物理シミュレーションを備えた作品へと生まれ変わっています。

しかし、公式エンジン自体は外部からの機能拡張に対して極めて保守的な制限を課しています。公式Mod制作ツール「Creation Kit」が採用するスクリプト言語 Papyrus は、メモリ破壊を防ぐサンドボックス化されたインタープリタ言語です。安全性に優れる反面、Papyrusは厳しい実行速度制限(タイムスライス)を受け、物理メモリ領域への直接アクセスや、マウス・キーボードの低レベル制御、高度なUI連携APIを直接呼び出すことができません。

この制約を突破するためにリバースエンジニアリングコミュニティが生み出したのが、Skyrim Script Extender(SKSE) です。SKSEは実行時にゲームプロセスのメモリ空間へ自身を注入(インジェクション)し、ゲームエンジン内部をフックすることで、Papyrus仮想マシンに数百ものネイティブマシン語命令を追加し、外部C++ DLLプラグインの直接実行を可能にします。

SKSE、Papyrus VM、そして動的メモリアドレス解決の連携メカニズムを理解することは、ゲームの強制終了(CTD)の解決、セーブデータの肥大化防止、そして公式アップデートによるMod破損を乗り切るための必須知識です。

Papyrus仮想マシンの構造:イベントループ、バイトコードとタイムスライス制限

BethesdaはPapyrusを設計する際、実行速度よりも「ゲーム全体の動作安定性」を最優先事項としました:

[Modスクリプトソース (.psc)] ──> Papyrusコンパイラ ──> バイナリバイトコード (.pex)
                                                           │
                                                           ▼
[Creation Engine メインループ]                      [Papyrus 仮想マシン (VM)]
   フレーム1: 描画 -> 物理演算 -> ゲームロジック ───> 割り当て実行時間枠 (~1.2 ms)
   フレーム2: 描画 -> 物理演算 -> ゲームロジック ───> バイトコード命令を順次実行
                                                   (時間切れ時は中断して次フレームへ)

1. バイトコードとサンドボックス隔離環境

Mod制作者が記述した .psc テキストコードは、コンパイルされてバイナリ形式の .pex バイトコードへと変換されます。エンジンはこのバイトコードをCPUで直接ネイティブ実行するのではなく、内部のPapyrus仮想マシン(VM)上で逐次解釈して実行します:

2. 協調型マルチタスクとフレーム単位の実行時間枠

Modスクリプトの暴走によってゲーム画面の描画フレームレートが低下するのを防ぐため、Papyrusは「協調型マルチタスク」方式を採用しています:

SKSEの深層:プロセス注入、メモリフックとネイティブ関数登録

SKSEは、実行中の SkyrimSE.exe(Windows PE形式バイナリ)のメモリ空間を直接書き換えることで、エンジンの制約を無効化します。

[skse64_loader.exe 外部ローダー]
       │
       ├─ 1. 中断状態(CREATE_SUSPENDED)でゲームプロセスを生成
       ├─ 2. リモートスレッドで LoadLibraryW を呼び出し skse64.dll をプロセス空間へ注入
       ├─ 3. メモリ保護を解除(VirtualProtect)し、エントリーポイントや仮想関数テーブル(VTable)を書き換え
       └─ 4. メインスレッドを再開(ResumeThread)
               │
               ▼
[SkyrimSE.exe プロセスメモリ空間]
   ├── オリジナルエンジンコード(DirectX描画、Havok物理、オーディオ)
   ├── Papyrus スクリプト仮想マシン
   │     └── [フックされた仮想関数テーブル]: ネイティブ関数登録テーブル(C++マシン語)
   └── SKSE プラグインマネージャー
         └── "Data/SKSE/Plugins/" 内の全サードパーティDLLプラグインをロード

1. ローダーによる中断生成とリモートスレッド注入

プレイヤーが skse64_loader.exe を通じてゲームを起動した際の一連の挙動は以下の通りです:

  1. ローダーはWindows APIの CreateProcess を CREATE_SUSPENDED フラグ付きで呼び出し、プロセスの初期化直後、最初のアセンブリ命令が実行される前にメインスレッドを停止させます。
  2. 対象プロセスのメモリ上に skse64.dll のパスを書き込み、CreateRemoteThread を介して LoadLibraryW を実行させ、SKSEのコアモジュールをプロセス内に読み込ませます。
  3. SKSEは VirtualProtect を用いてゲームコード領域の書き込み禁止(PAGE_EXECUTE_READ)を解除(PAGE_EXECUTE_READWRITE)し、目的の関数先頭にジャンプ命令(トランポリンフック)を埋め込んだ後、ResumeThread でゲームの実行を再開させます。

2. ネイティブC++マシン語関数のバインド

フックが完了すると、SKSEはPapyrus VMの関数登録テーブルを拡張します。Mod制作者がPapyrusスクリプト内でSKSE拡張関数(マウス座標の直接取得や未圧縮データ配列の走査など)を呼び出すと、VMはバイトコードの逐次解釈をスキップし、skse64.dll にコンパイルされた超高速なC++マシン語ルーチンを直接実行します。

バイナリ互換性の危機:公式アプデとAddress Libraryによる解決

かつてスカイリムのMod環境において最大の脅威となっていたのが、Bethesda公式によるゲームの小規模パッチ配信でした。わずかなアップデートであっても、世界中のDLLプラグインが一斉にクラッシュして起動不能に陥っていたためです。

メモリオフセットの変動問題

Bethesdaがゲームのソースコードを数行修正して新しい SkyrimSE.exe をビルドすると:

[スカイリム 1.5.97]                               [スカイリム 1.6.640]
オフセット 0x005A3210: GetPlayerCamera() ──再コンパイル──> オフセット 0x005B48F0: GetPlayerCamera()
       ▲                                                    ▲
       │(ハードコードされたポインタが不正アクセスでCTD!)     │
[旧式 SKSE プラグイン DLL] ───────────────────────────(アクセス違反 0xC0000005)

Address LibraryとCommonLibSSEによるIDマッピング

この脆さを根本から解決するために生み出されたのが、Address Library for SKSE Plugins です:

  1. 統一IDデータベース:ゲームの歴代全バージョンにおける数万個の主要関数に対して、バージョンに依存しない恒久的な一意の識別番号(ID)を付与したデータベースを構築。
  2. 実行時の動的アドレス解決:現代のDLLプラグインはアドレスを直接指定せず、CommonLibSSE を介して実行時にデータベースへ問い合わせを行います:
    // プラグイン実行時にカメラ管理クラスの実アドレスを取得
    uintptr_t targetAddress = AddressLibrary::GetAddress(52147);
    
  3. バージョン更新への耐性:Bethesdaが新しいパッチを配布しても、Address Libraryの単一データベースファイルを更新するだけで済み、Mod制作者がコードを書き直して再配布するのを待つ必要がなくなりました。

セーブデータの構造、孤児スクリプトとPapyrusスタックダンプ

スカイリムのセーブデータ形式(.ess)の根本的な特徴として、セーブが行われた瞬間に稼働していたPapyrus VMの全状態が、そのままセーブファイル内へ直接シリアライズ(保存)されます。

[.ess セーブファイルの内部構造]
  ├── グローバルワールド状態(FormIDレコード、天候、ゲーム内日時)
  ├── プレイヤーのインベントリおよび進行中のクエストフラグ
  └── [Papyrus 仮想マシンの稼働スナップショット]
        ├── 現在実行中の全スクリプトインスタンス
        ├── 変数の実データおよびゲームオブジェクト参照
        └── タイムスライス待機中の中断スレッドコールスタック

プレイ中のスクリプトMod削除が招く致命的な破損

スクリプトを含むModをセーブデータの途中でアンインストールした場合:

  1. ドライブから .pex ファイルが削除されても、.ess セーブデータの中にはそのスクリプトの稼働インスタンスや待機中のイベントがそのまま残留します。
  2. 次回ロード時、Papyrus VMはセーブデータからスクリプトを復元しようと試みますが、対応する .pex が見つからないため、エラーを Papyrus.0.log に大量出力し続けます。
  3. これらは参照先を失った**孤児スクリプト(Orphaned Scripts)**となり、解放されることなく毎フレームVMの処理枠を消費します。時間の経過とともにセーブファイルの肥大化(Save Bloat)やスクリプトラグを招き、最終的にセーブデータが完全に破損します。

スクリプトキューの飽和とスタックダンプ

Modの競合によってスクリプトが無限ループに陥ったり、削除されたオブジェクトを永遠に待ち続けたりすると、そのスレッドは実行権を手放さなくなります。未消化のスクリプト呼び出しがVMの安全許容量(数万件以上)を超えると、ログに大量の**スタックダンプ(Stack Dump)**が出力され、VMの破綻に伴ってゲームは即座にCTDへと至ります。

トラブルシューティング・マトリクス:スクリプト拡張・DLL競合・CTD診断

症状根本的なエンジニアリング原因ログでの判別方法推奨される解決策
起動ボタンを押した直後に即座にCTDするDLLプラグインのバージョンがゲームEXEと不一致skse64.log 末尾にロード失敗のエラーがないか確認該当DLLを更新するか、EXEバージョンに合致したAddress Libraryを導入
MCMメニューに「SKSE64が実行されていません」と表示Steam本体や通常EXEから誤起動しDLLが注入されていないゲーム内で半角/全角キーを押し getskseversion と入力必ず Mod Organizer 2 等のランチャーから skse64_loader.exe 経由で起動
会話の選択肢が出るまで数秒待たされる・動作が重いPapyrus VMの処理タスクがパンクしスクリプトラグが発生Papyrus.0.log に同一スクリプトの大量エラーがないか確認粗悪な常時ポーリングModを特定して排除;FallrimToolsでセーブを清掃
ファストトラベル時やロード扉をくぐった瞬間にCTD注入されたネイティブDLL内でヌルポインタ参照が発生Crash Logger SSE などのクラッシュログを確認スタックトレース最上段のモジュール名を特定し、競合Modを修復
セーブファイルが50MBを超え、ロード時間が異常に長いアンインストールしたModの孤児スクリプトがセーブ内に蓄積FallrimTools ReSaver で該当 .ess ファイルを開いて確認未バインドのスクリプトインスタンスを一括削除;プレイ中のMod削除を禁止

ゲームエンジンのフック機構と仮想マシンの協調構造を理解することで、Mod環境のトラブルシューティングは当て推量から確実な技術診断へと昇華します。365Findsの専門的な知見を活用することで、プレイヤーや開発者はメモリフックやバージョン管理の壁を乗り越え、極めて安定した快適なMod環境を構築できます。

参考文献