DebugMenu
今回はDebugMenuの移植をやりたいと思います。
DebugMenuについてはSplatoon2のwikiが詳しいのでそこから抜粋。
A debug menu in a modern Nintendo game!? BOOYAH! This debug menu exists in all versions of Splatoon 2 from 1.0.0 to 3.2.0, including the Global Testfire and the Splatfest World Premiere demos. Someone tipped Nintendo off about its existence in retail copies, so they removed it from the game in an update. However, we can still play around with it in the demos!
デバッグメニューが最近の任天堂のゲームにあるだって!?やったね!このデバッグメニューは先行試射会や前夜祭を含む1.0.0から3.2.0までの全てのスプラトゥーン2のバージョンに存在します。誰かがこっそりと製品版でのデバッグメニューの存在に気づいて情報を得たので、任天堂はアップデートでゲームからデバッグメニューを削除しました。ただし、デモ版ではまだ遊ぶことができるよ。
先行試射会と前夜祭のDebug PatchはOatmealDome氏が公開しています。
全部で300行くらいあって移植は大変そうな感じがするのですが、後半部分はDebug Menuを日本語から英語に翻訳するコードなので後半の100行はほとんど無視できます。
つまり、200行ほど移植すればいいことになります。これでも結構苦労する作業ですが、3.1.0以前であればサブルーチン名がそのまま残っているので検索は容易です。
DebugMenu(1.0.0)
1.0.0向けのDebugMenuはkhang06(@Khangarood)氏がリリースしているのですが、タイトルがbad dbgsetting patch v2となっており何やら怪しい雰囲気が漂っていますね。
// DbgSetting over ShootingRange
@enabled
01455850 540F1A00
001A0F84 E0FF8152
001A0F94 ABAD2594
0120828C 82030014
0120748C 16000014
01207490 CF000014
012074B4 18010014
012074B8 55010014
012074E0 57020014
012074BC 0E020014
012AF368 E0031FAA
012AF36C 1BFFFF17
すごく簡単な解説
最初から順番に解説していきましょう。
01455850 540F1A00
試し撃ち場をDebugMenuに置換するコードです。
001A0F84 E0FF8152
オペレータIDを開発者モードに切り替えるコードです。
001A0F94 ABAD2594
Cstm::newScene<TitleForShow::Scene>()で遷移する先をTitleForShowからDebugMenuに変更するコードです。
019BCCEC 82030014
019BBEEC 16000014
019BBEF0 CF000014
019BBF14 18010014
019BBF18 55010014
019BBF40 57020014
019BBF1C 0E020014
DebugMenuのテキストを表示するコードです。
01A661A4 E0031FAA
01A661A8 1BFFFF17
純正コントローラではDebugMenuでコントローラが効かないので、それを修正します。
DebugMenu(3.1.0)
今回はKhang06氏の不完全なコードではなく、OatmealDome氏の前夜祭向けのコードを移植することにします。
赤い色がついているのはKhang06氏のコードでは移植されていないものです。
_ZN3Cmn3Def12isDevelopSeqEv
デバッグメニューを起動するためのフラグ管理で、これが常にTrueを返すように変更します。
0を代入するコードになっているので、1を代入するコードに変更します。
// MOV W0, WZR -> MOV W0, #0x1
// Cmn::Def::isDevelopSeq() always returns true
@enabled
0009CF44 20008052
_ZN3Cmn8SoundMgr10initializeEPN4sead4HeapES3_
Lan ModeでクラッシュしないようにCmn:SoundMicを初期化しないように変更します。
Cmn::SoundMic()を初期化するところをNOPで呼び出されないようにします。
// BL #0x8438 -> NOP
// Don't initialize Cmn::SoundMic
@enabled
001C5BC8 1F2003D5
ZN4Cstm8newSceneIN4Shop5SceneEEEPN2Lp3Sys5SceneEiPN4sead4HeapE
新しいシーンの読み込みをデバッグメニュー起動に上書きします。
OatmealDome氏のコードではShopをDebugMenuに上書きしており、Khang06氏のコードではShootingRangeをDebugMenuに上書きしています。
このサブルーチンは二つ命令を置き換える必要があるので一つずつ見ていきます。
// World Premiere
0x162084 MOV W0, #0x390
-> MOV W0, #0x1000
// Splatoon 3.1.0
0x227200 MOV W0, #0x398
-> MOV W0, #0x1000
最初はよくわからない値を0x1000に置き換えます。これはもう先行試射会でも同じ値が使われていたので、この値を使えということなのでしょう。
もう一方はシーン変更ですが、Shopに飛ぶはずの命令をDbgSettingにとばせばいいだけなのでアドレスを調べて差を求めれば終わります。
3.1.0ではDebugSetting::Sceneは0x1238FD0に書かれているので、0x1238FD0 – 0x227210 = 0x1011DC0を計算してジャンプすべきアドレスを求めます。
// World Premiere
0x162084 BL #0xA4BA20
-> BL #0x97DF24
// Splatoon 3.1.0
0x227210 BL #0x10D2684
-> BL #0x1011DC0
あとはこれらをHEXに変換すれば終わりです。
// DbgSetting in Cstm::newScene<Shop::Scene>(sead::Heap*)
@enabled
00227200 00008252
00227210 70474094
でもショップをひらいたらDebugMenuが起動するってめんどくさくない?(心の声)
言われてみれば確かに…
というわけで、Boot後に即座にDebugMenuがひらくようにしてみます。
これは<Shop::Scene>ではなく<TitleForShow::Scene>に対して命令を上書きすれば良いので、以下のようになります。
// DbgSetting in Cstm::newScene<TitleForShow::Scene>(sead::Heap*)
@enabled
-------- E0FF8152
-------- --------
ここはみなさんへの宿題ということで。
_ZN4Cstm8newSceneIN13ThanksForShow5SceneEEEPN2Lp3Sys5SceneEiPN4sead4HeapE
ダミーマッチは自身のシーン関数を持たないのでThanksForShowのnewSceneを書き換えます。
// DummyMatch in Cstm::newScene<ThanksForShow::Scene>(sead::Heap*)
@enabled
00227350 00008252
00227360 07714094
ここ移植ミスってるかも…
// DummyMatch in Cstm::newScene<ThanksForShow::Scene>(sead::Heap*)
@enabled
00227350 E0FF8152
00227360 07714094
うごかなかったらこっちでどうぞ。
_ZN2Lp3Sys15cpAssertProductEi
ブート時に開発者モードを有効化します。
関数に入った瞬間にRETするように命令を変更します。
// MOV W8, W0 -> RET
// Dummy out Lp::Sys::cpAssertProduct()
@enabled
019A7E14 C0035FD6
デバッグメニューのテキスト移植
debug textとpilot textについて行いますが、めんどくさかったのでMeshi(@meshi_0217)氏のコードを参考にしました。
掲載許可をもらっていないので、ここでは紹介しません。
まあでも1.0.0向けのKhang06氏のコードを移植すればいけると思いますよ。
_ZN2Lp3Utl5Limit24getSceneDbgMixCtrl_PilotEv
デバッグコントロールMixではなく標準コントローラを呼び出します。
// SUB SP, SP, #0x10 -> MOV X0, WZR
01A661A4 E0031FAA
OatmealDome氏のコードを見るとLp::Utl::getCtrl(int)にジャンプするコードになっているのでまずはそのサブルーチンのアドレスを調べます。
すると0x1A65E14にあることがわかるので、ジャンプするためのオフセットは以下のように計算できます。
0x1A65E14 – 0x1A661A8 = 0xFFFFFFFFFFFFFC6B
このとき「前にジャンプする」となっているときはこの計算結果より1増やさないといけないので、実際の命令は以下のようになります。
// ADRP X8, #0x26F2000 -> B #0xFFFFFFFFFFFFFC6C
01A661A4 1BFFFF17
たまたまこれは前夜祭と変わらなかったので同じコードが流用できました。
これら二つをまとめると、以下のコードが得られます。
// Redirect Lp::Utl::Limit::getSceneDbgMixCtrl_Pilot() to Lp::Utl::getCtrl()
@enabled
01A661A4 E0031FAA
01A661A8 1BFFFF17
これをやらないとコントローラで操作できないので必須です。
ここ4バイトズレててDebugMenu動かなくて二十分くらい悩んでました…
_ZN10DummyMatch5SceneC2EPN4sead4HeapE
LanModeでのダミーマッチを使うことでサーバが落ちていてもマッチングが行え、BANのリスクがないので安全です。
// B loc_1243B18 -> NOP
// MOV W8, WZR -> MOV W8, #0x1
// Force ChangeLanMatchInBoot to always be true
@enabled
01243B10 1F2003D5
01243B14 28008052
Sample/Actor -> DbgSetting
ここから急に難しくなるので注意して下さい。

書き換えたいアドレスには「aSampleActorへのアドレス」「定数0x403」「Sample/Actorへのアドレス」の三つのデータが保持されています。
このうち、定数に関しては書き換え後も変わりません。
つまり、aSampleActorへのアドレスをaDbgsettingへのアドレスに書き換えて…
Sample/ActorへのアドレスをCstm::newScene<TitleForShow>に書き換えればいいわけやね。
Cstm::newSceneは既にDbgSettingがひらくように変更してあるのでこれで動作するわけです。
3.1.0 | address |
aSampleActor | 3E1FAB8 |
aDbgsetting | 3E1F820 |
Sample/Actor | 372163F |
Cstm::newScene<TitleForShow::Scene>() | 02272CC |
// 3.1.0 0x01C27E40
B8 FA E1 03 00 00 00 00 03 04 00 00 00 00 00 00
3F 16 72 03 00 00 00 00
->
20 F8 E1 03 00 00 00 00 03 04 00 00 00 00 00 00
CC 72 22 00 00 00 00 00
// Add newScene function for DbgSetting
@enabled
01C27E40 20F8E103
01C27E50 CC722200
Sample/Actor -> FreeTest
やることは先程と似たような感じです。
3.1.0 | address |
aFreeTest | 3E1F538 |
Cstm::newScene<Game::CmnScene>() | 0227080 |
ただ、先程は上書きするアドレスが上書きしても大丈夫そうなサブルーチンだったのですが、今回は微妙に危なそうです。
// 3.1.0 0x01C27E58
C8 FA E1 03 00 00 00 00 03 04 00 00 00 00 00 00
E8 0B 71 03 00 00 00 00
->
38 F5 E1 03 00 00 00 00 03 04 00 00 00 00 00 00
80 70 22 00 00 00 00 00
// Add newScene function for FreeTest
@enabled
01C27E58 C8FAE103
01C27E68 80702200
Sample/Actor -> DummyMatch
3.1.0 | address |
aDummyMatch | 3E1F920 -> 3E1F922 |
Cstm::newScene<ThanksForShow::Scene>() | 0227320 |
aDummyMatchのアドレスは0x3E1F920なのですがOatmealDome氏のコードを見ると2バイトズラしているのでそれに倣ってぼくも2バイトズラします。
// 3.1.0 0x01C27E70
D0 FA E1 03 00 00 00 00 03 04 00 00 00 00 00 00
4C 16 72 03 00 00 00 00
->
D0 FA E1 03 00 00 00 00 03 04 00 00 00 00 00 00
20 73 22 00 00 00 00 00
// Add newScene function for DummyMatch
@enabled
01C27E70 D0FAE103
01C27E80 20732200
完成したコード
テキストのところを紹介していないのでそのままでは動かないのですが、80%くらい完成したものを公開しておきます。
// Cmn::Def::isDevelopSeq() always returns true
@enabled
0009CF44 20008052
// Don't initialize Cmn::SoundMic
@enabled
001C5BC8 1F2003D5
// DbgSetting in Cstm::newScene<Shop::Scene>(sead::Heap*)
@disabled
00227200 00008252
00227210 70474094
// DummyMatch in Cstm::newScene<ThanksForShow::Scene>(sead::Heap*)
@enabled
00227350 E0FF8152
00227360 07714094
// Dummy out Lp::Sys::cpAssertProduct()
@enabled
019A7E14 C0035FD6
// Redirect Lp::Utl::Limit::getSceneDbgMixCtrl_Pilot() to Lp::Utl::getCtrl()
@disabled
01A661A4 E0031FAA
01A661A8 1BFFFF17
// Force ChangeLanMatchInBoot to always be true
@enabled
01243B10 1F2003D5
01243B14 28008052
// Add newScene function for DbgSetting
@enabled
01C27E40 20F8E103
01C27E50 CC722200
// Add newScene function for FreeTest
@enabled
01C27E58 C8FAE103
01C27E68 80702200
// Add newScene function for DummyMatch
@enabled
01C27E70 D0FAE103
01C27E80 20732200
// Lp::Sys::DbgTextWriter::pilotEntryV()
@enabled
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
やってみればわかるのですが、超めんどくさいコードになっています。
起動したらチュートリアルが始まるんだけど!?
っていう意見については仕様です。
// Cmn::Def::isDevelopSeq() always returns true
@enabled
0009CF44 20008052
これがオンになっていると、必ずチュートリアルから始まります。
オフにすればチュートリアルは始まらず、セーブを使うことができるのでめんどくさいかたはそっちでどうぞ。
チュートリアル後にショップに入るとDebugMenuが起動します。

何も表示されてないんだけど!?
という意見についてもこれも仕様です。
// Lp::Sys::DbgTextWriter::pilotEntryV()
@enabled
というコードが空っぽなのが原因ですね。先程も言いましたが、Khang06氏のコードを3.1.0向けに移植すればテキストが表示されるようになります。
// Lp::Sys::DbgTextWriter::pilotEntryV() for 1.0.0 [Khang06]
@enabled
0120828C 82030014
0120748C 16000014
01207490 CF000014
012074B4 18010014
012074B8 55010014
012074E0 57020014
012074BC 0E020014
つまり、上のコードを3.1.0向けに移植すればいいということです。
つまり、この記事はこのコードを移植するだけでDebugMenuが使えるようになるというチュートリアルなわけです。
全部は無理でもこれだけならイケるっしょ。
自分で移植しても良かったのですが、今回はMeshi氏が既に移植していたのでそのコードを使わせてもらいました。

ちゃんとテキストが表示されてる!
正直、ここをやるのはめっちゃめんどくさかったので先にしていただいていたのはすごく助かりました。
実際にやってみた
自分はいちいちShopをひらくのがめんどくさかったので、Boot時にDebugMenuが起動するようにしてあります。
// Cmn::Def::isDevelopSeq() always returns true
@enabled
0009CF44 20008052
// Don't initialize Cmn::SoundMic
@enabled
001C5BC8 1F2003D5
// DbgSetting in Cstm::newScene<TitleForShow::Scene>(sead::Heap*)
@enabled
-------- E0FF8152
-------- --------
// DummyMatch in Cstm::newScene<ThanksForShow::Scene>(sead::Heap*)
@enabled
00227350 E0FF8152
00227360 07714094
// Dummy out Lp::Sys::cpAssertProduct()
@enabled
019A7E14 C0035FD6
// Redirect Lp::Utl::Limit::getSceneDbgMixCtrl_Pilot() to Lp::Utl::getCtrl()
@enabled
01A661A4 E0031FAA
01A661A8 1BFFFF17
// Force ChangeLanMatchInBoot to always be true
@enabled
01243B10 1F2003D5
01243B14 28008052
// Add newScene function for DbgSetting
@enabled
01C27E40 20F8E103
01C27E50 CC722200
// Add newScene function for FreeTest
@enabled
01C27E58 C8FAE103
01C27E68 80702200
// Add newScene function for DummyMatch
@enabled
01C27E70 D0FAE103
01C27E80 20732200
// Lp::Sys::DbgTextWriter::pilotEntryV()
@enabled
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
-------- --------
何か間違っていたら連絡ください。記事は以上。
自身を天才と信じて疑わないマッドサイエンティスト。二つ上の姉は大英図書館特殊工作部勤務、額の十字架の疵は彼女につけられた。
コメント
aFreeTestやaDbgSettingのアドレスはどのようにして探し出すのでしょうか?
自分がDebugMenuの開発をしたわけではないのですが旧バージョンにはデバッグシンボルが残っているためシンボル名などで検索したのかと思われます。