[IPSwitch] 誰でもできるコード開発 #9

Hack

インスタンスをコールするコード

IPSwitch形式のコードには単純に命令を上書きするものと、インスタンスを読み込んで中身を書き換えるものがあります。

前者の一例を挙げるとスペシャルウエポンの塗りポイントを0にするようなものが考えられます。

// Special Cost 0 [tkgling] [3.1.0]
@disabled
000847B4 3F0000F9 // STR WZR, [X1]

本来パラメータファイルから必要な塗りポイントを取得してレジスタに格納している命令が0x000864E8に書かれているのですが、それを上書きするパッチになります。

ゼロレジスタ(XZR)という読み込むと必ず0を返す特別なレジスタを使ってX1レジスタが保持するメモリアドレスに0というデータを書き込みます。X1レジスタはそれぞれのブキのスペシャルウエポン発動に必要な塗りポイントのパラメータを格納する場所なので、このパッチがあれば必要塗りポイントが0になるという仕組みです。

このパッチはバージョンを問わず常に命令部3F0000F9の値は変わらず、ゲームのバージョンによって実行ファイル内の関数のアドレスがズレるだけなので「アドレスだけを移植すれば動作するコード」ということができるわけです。

それに対してインスタンスを参照するコードは命令部にプログラムのアドレスを参照する内容が書かれているので、アドレス部だけを変えても動作させることはできません。

インスタンス参照コード

// Got 9999 Power Eggs by Signal [tkgling] [3.1.0]
@enabled
00E797FC 60970190 // ADRP    X0, #0x32EC000
00E79800 00DC46F9 // LDR     X0, [X0, #0xDB8]
00E79804 000040F9 // LDR     X0, [X0]         // X0 = PlayerDirector
00E79808 01B841F9 // LDR     X1, [X0, #0x370] // X1 = mTotalBankedPowerIkuraNum
00E7980C 21084091 // ADD     X1, X1, #0x2000  // X1 = X1 + 8192
00E79810 213C1C91 // ADD     X1, X1, #0x70F   // X1 = X1 + 1807
00E79814 01B801F9 // STR     X1, [X0, #0x370] // mTotalBankedPowerIkuraNum = X1
00E79818 C0035FD6 // RET                      // Return

これはカモンかナイスを押せば赤イクラ取得数が9999になるコードですが、最初の二行のコードがPlayerDirectorのインスタンスを読み込む内容になっています。

で、ここが大事なところでインスタンスのアドレスはスプラトゥーンの実行ファイル内にあり、バージョンが上がるとここの値も変わってしまうということです。つまり「アドレスと命令(命令内のアドレス)の両方を書き換えないと動かないコード」になるわけです。

バージョン3.1.0以降ではプログラム内のデバッグシンボルが削除されているので、インスタンスを探すのはめんどくさかったりします。

誰かがwikiみたいなのつくってインスタンスのまとめつくってくれたら楽なんですけどね。まあめんどくさいから誰もしないと思います。

インスタンスを探そう

インスタンスとは要はクラスを実体化したものですので、クラスの数だけインスタンスの種類があることになります。

例えば、サーモンランに関するインスタンスはこれだけあります。

クラス3.1.0意味
Game::Coop::RewardConfig04157FB0
Game::Coop::RuleConfig04158008パラメータを設定する
Game::Coop::LevelsConfig04160E00詳細なパラメータを設定する
Game::Coop::Setting04160E08キケン度やステージなどの設定を司る
Game::VictoryClamDirector04162050
Game::Coop::CameraHolder04164DF0
Game::Coop::GraphMgr04164E40GraphNodeを司る?
Game::Coop::ItemDirector04165738
Game::Coop::EnemyDirector04165740シャケを司るクラス
Game::Coop::PlayerDirector04165DB8サーモンランのプレイヤー情報を司る
Game::Coop::EventDirector04167BC0夜イベントなどの情報を司る
Game::Coop::SeaSurfaceMgr04167C20潮位の変化を司る
Game::Coop::GuideDirector04167E18
Game::Coop::Moderator04168C78クマサンの挙動を司る
Game::Coop::ResultPlayReport04169050リザルトデータを司る

Game::PlayerMgr::sInstance

ナワバリバトルやガチマッチで使うプレイヤーデータを司るクラスです。

チーム変更や、持っているブキやスペシャルを変更したりする場合にはこのクラスを使います。

3.1.05.3.1
アドレス0x041575780x02CFDCF8
検索位置0x005C57580x007605C0
検索バイナリ08 C0 50 39 1F 01 00 71 E0 13 80 9A

このインスタンスの見つけ方ですがGame::Coop::Utl::GetPlayer()という関数がプレイヤーのデータを取得する際にGame::PlayerMgrを呼び出しているので、それを利用します。

// Game::Coop::Utl::GetPlayer(Game::Coop::Utl *__hidden this, int) [3.1.0]
005C5758                 MOV             W8, W0
005C575C                 TBNZ            W8, #0x1F, loc_5C5794
005C5760                 STP             X29, X30, [SP,#-0x10+var_s0]!
005C5764                 MOV             X29, SP
005C5768                 ADRP            X9, #off_4157578@PAGE
005C576C                 LDR             X9, [X9,#off_4157578@PAGEOFF]
005C5770                 LDR             X0, [X9] ; this
005C5774                 MOV             W1, W8  ; unsigned int
005C5778                 BL              _ZNK4Game9PlayerMgr18getAllKindPlayerAtEj
005C577C                 LDP             X29, X30, [SP+var_s0],#0x10
005C5780                 CBZ             X0, locret_5C5790
005C5784                 LDRB            W8, [X0,#0x430]
005C5788                 CMP             W8, #0
005C578C                 CSEL            X0, XZR, X0, NE

見ると0x005C5778で_ZNK4Game9PlayerMgr18getAllKindPlayerAtEjが呼び出されています。これをデマングルするとGame::PlayerMgr::getAllKindPlayerAt(unsigned int) constということがわかります。

Game::Coop::Utlクラス内でGame::PlayerMgrクラスのメソッドが呼びだされているので、呼び出す前に必ずそのクラスのインスタンスを呼び出していなければいけません。となると0x4157578が探していたGame::PlayerMgrクラスのアドレスであることがわかるのです。

つまり、Game::PlayerMgrを探すためには先にGame::Coop::Utl::GetPlayer()のサブルーチンを探せば良いことになります。このサブルーチンは比較的特徴的な命令を持っているので、

005C5784                 LDRB            W8, [X0,#0x430]
005C5788                 CMP             W8, #0
005C578C                 CSEL            X0, XZR, X0, NE

この三つの命令群をバイナリ検索すれば簡単に見つけられます。これをARM64に変換すると08 C0 50 39 1F 01 00 71 E0 13 80 9Aになります。これをバイナリ検索すれば5.3.1の場合0x007605ECがヒットすると思います。

// Game::Coop::Utl::GetPlayer(Game::Coop::Utl *__hidden this, int) [5.3.1]
007605C0                 MOV             W8, W0
007605C4                 TBNZ            W8, #0x1F, loc_7605FC
007605C8                 STP             X29, X30, [SP,#-0x10+var_s0]!
007605CC                 MOV             X29, SP
007605D0                 ADRP            X9, #off_2CFDCF8@PAGE
007605D4                 LDR             X9, [X9,#off_2CFDCF8@PAGEOFF]
007605D8                 LDR             X0, [X9]
007605DC                 MOV             W1, W8
007605E0                 BL              _ZNK4Game9PlayerMgr18getAllKindPlayerAtEj
007605E4                 LDP             X29, X30, [SP+var_s0],#0x10
007605E8                 CBZ             X0, locret_7605F8
007605EC                 LDRB            W8, [X0,#0x430]
007605F0                 CMP             W8, #0
007605F4                 CSEL            X0, XZR, X0, NE

すると0x02CFDCF8が5.3.1におけるGame::PlayerMgrのアドレスだとわかるわけです。

Game::Coop::PlayerDirector

サーモンランで使うプレイヤーデータを司るクラスです。

金イクラ数や赤イクラ数の変更などをする場合にはこのクラスを使います。

3.1.05.3.1
アドレス0x04165DB80x02D0CEE0
検索位置0x005A615C0x0073EC84
検索バイナリF3 03 00 AA 74 22 0D D1 08 41 00 91

このインスタンスはGame::Coop::PlayerDirectorのでデコンストラクタを使って探すのが楽ではないかと思います。

// Game::Coop::PlayerDirector::~PlayerDirector(Game::Coop::PlayerDirector *__hidden this) [3.1.0]
005A6130                 STP             X20, X19, [SP,#-0x10+var_10]!
005A6134                 STP             X29, X30, [SP,#0x10+var_s0]
005A6138                 ADD             X29, SP, #0x10
005A613C                 ADRP            X8, #off_4168FF8@PAGE
005A6140                 LDR             X8, [X8,#off_4168FF8@PAGEOFF]
005A6144                 MOV             X19, X0
005A6148                 SUB             X20, X19, #0x348
005A614C                 ADD             X8, X8, #0x10
005A6150                 STR             X8, [X19]
005A6154                 ADRP            X8, #off_4165DB8@PAGE
005A6158                 LDR             X8, [X8,#off_4165DB8@PAGEOFF]
005A615C                 STR             XZR, [X8]
005A6160                 BL              _ZN4sead9IDisposerD2Ev
005A6164                 ADRP            X8, #off_4156138@PAGE
005A6168                 LDR             X8, [X8,#off_4156138@PAGEOFF]
005A616C                 ADD             X8, X8, #0x10
005A6170                 STR             X8, [X20]
005A6174                 LDP             X29, X30, [SP,#0x10+var_s0]
005A6178                 SUB             X0, X19, #0x230 ; this
005A617C                 LDP             X20, X19, [SP+0x10+var_10],#0x20
005A6180                 B               _ZN4sead3JobD2Ev

何が書いてあるかさっぱりだと思うのですが、上から10行目の0x04165DB8がGame::Coop::PlayerDirectorのアドレスになります。

005A6144                 MOV             X19, X0
005A6148                 SUB             X20, X19, #0x348
005A614C                 ADD             X8, X8, #0x10
005A6150                 STR             X8, [X19]

幸いなことにこのサブルーチンにも特徴的な命令があり、これをバイナリに変換するとF3 03 00 AA 74 22 0D D1 08 41 00 91 68 02 00 F9となります。これは似たようなサブルーチンがいくつかあるので、しっかりと見極めましょう。

5.3.1の場合はバイナリ検索では次の12回のサブルーチンがヒットすると思うのですが、10回目にヒットするところがGame::Coop::PlayerDirectorのデコンストラクタのサブルーチンになります。バージョンによって何回目のサブルーチンなのかは変わる可能性がありますが、候補は6つしかないのでそのときは適当に全部試してみてください。

006E3310, 006E3364
006F3054, 006F30A8
006FF75C, 006FF7B0
0070A74C, 0070A7A0
0073EC30, 0073EC84
01386678, 013866CC

なので0x0073EC84のサブルーチンをチェックします。

// Game::Coop::PlayerDirector::~PlayerDirector(Game::Coop::PlayerDirector *__hidden this) [5.3.1]
0073EC84                 STP             X20, X19, [SP,#var_20]!
0073EC88                 STP             X29, X30, [SP,#0x20+var_10]
0073EC8C                 ADD             X29, SP, #0x20+var_10
0073EC90                 ADRP            X8, #off_2D100B8@PAGE
0073EC94                 LDR             X8, [X8,#off_2D100B8@PAGEOFF]
0073EC98                 MOV             X19, X0
0073EC9C                 SUB             X20, X19, #0x348
0073ECA0                 ADD             X8, X8, #0x10
0073ECA4                 STR             X8, [X19]
0073ECA8                 ADRP            X8, #off_2D0CEE0@PAGE
0073ECAC                 LDR             X8, [X8,#off_2D0CEE0@PAGEOFF]
0073ECB0                 STR             XZR, [X8]
0073ECB4                 BL              sub_171A9C8
0073ECB8                 ADRP            X8, #off_2CFCF90@PAGE
0073ECBC                 LDR             X8, [X8,#off_2CFCF90@PAGEOFF]
0073ECC0                 ADD             X8, X8, #0x10
0073ECC4                 SUB             X0, X19, #0x230
0073ECC8                 STR             X8, [X20]
0073ECCC                 BL              nullsub_198
0073ECD0                 LDP             X29, X30, [SP,#0x20+var_10]
0073ECD4                 MOV             X0, X20 ; void *
0073ECD8                 LDP             X20, X19, [SP+0x20+var_20],#0x20
0073ECDC                 B               _ZdlPv  ; operator delete(void *)

同様に上から10番目の命令を見ればアドレスが0x02D0CEE0であることがわかります。

チーム変更コードを移植しよう

インスタンスのアドレスが分かったので、あとはコードを移植するだけになります。

全てをそのまま使うことはできないのですが、テンプレートがあるのでそれを使えば空いているところに値を入れるだけで移植ができます。

チーム変更コードテンプレート

STP X29, X30, [SP, #-0x10]!
MOV X29, SP
ADRP  X8, #0xXXXXX000
LDR   X8, [X8, #0xYYY]
LDR   X0, [X8]
BL    #0xAAAAAAAA - 0xBBBBBBBB
LDR   X8, [X0, #0x328]
EOR   X8, X8, #1
STR   X8, [X0, #0x328]
LDP X29, X30, [SP], #0x10
RET

チーム変更コードの移植に必要なのは四つの値です。

パラメータ意味
XXXXXGame::PlayerMgrのアドレスを使って計算する
YYYXXXXX000に対するオフセット
Game::PlayerMgrのアドレスを使って計算する
AAAAAAAAGame::PlayerMgr::getControlledPerformerのアドレス
BBBBBBBBこの命令が書かれるアドレス
必要な四つのパラメータ

これらの値を計算するためにはあと二つのアドレスがわからなければいけません。というのも、チーム変更は「自分が操作したいプレイヤー」のチーム情報がわからないといけないからです。Game::PlayerMgrは全てのプレイヤーの情報を管理しているので、そこから「自分のプレイヤー」の情報だけをとってくる必要があります。

これにはGame::PlayerMgr::getControlledPerformerが使えますので、まずはこのサブルーチンを呼び出すことを考えます。サブルーチン呼び出しには「呼び出したいサブルーチンが定義されているアドレス」と「サブルーチンを呼び出すアドレス」の二つが必要です。

よって、まずはGame::PlayerMgr::getControlledPerformerが定義されているアドレスを探しましょう。

Game::PlayerMgr::getControlledPerformer

サブルーチン3.1.05.3.1
Game::PlayerMgr::getControlledPerformer0x00F07B1C0x010E6E38
Game::PlayerMgr::getControlledPerformer

getControlledPerformer()はGame::PlayerMgrクラスのサブルーチンなので先程まで探していたアドレス付近にあります。これもやはり特徴的な命令があるので簡単に見つけられます。

// Game::PlayerMgr::getControlledPerformer(Game::PlayerMgr *__hidden this) [3.1.0]
00F07B1C                 STR             X19, [SP,#-0x10+var_10]!
00F07B20                 STP             X29, X30, [SP,#0x10+var_s0]
00F07B24                 ADD             X29, SP, #0x10
00F07B28                 LDRSW           X8, [X0,#0x5C8]
00F07B2C                 LDR             W9, [X0,#0x624]
00F07B30                 CMP             W9, W8
00F07B34                 B.LE            loc_F07B64
00F07B38                 LDR             X10, [X0,#0x638]
00F07B3C                 LDR             W9, [X0,#0x630]
00F07B40                 ADD             X11, X10, X8,LSL#3
00F07B44                 CMP             W9, W8
00F07B48                 CSEL            X8, X11, X10, HI
00F07B4C                 LDR             X19, [X8]
00F07B50                 CBZ             X19, loc_F07B68
00F07B54                 LDRB            W8, [X19,#0x430]
00F07B58                 CBZ             W8, loc_F07B68
00F07B5C                 BL              _ZN2Lp3Utl31printStackTraceIfLastWarningAddEv
00F07B60                 B               loc_F07B68

バイナリ検索で08 C8 85 B9 09 24 46 B9 3F 01 08 6B 8D 01 00 54とすれば0x010E6E44がヒットすると思います。

// Game::PlayerMgr::getControlledPerformer(Game::PlayerMgr *__hidden this) [5.3.1]
010E6E38                 STR             X19, [SP,#-0x10+var_10]!
010E6E3C                 STP             X29, X30, [SP,#0x10+var_s0]
010E6E40                 ADD             X29, SP, #0x10
010E6E44                 LDRSW           X8, [X0,#0x5C8]
010E6E48                 LDR             W9, [X0,#0x624]
010E6E4C                 CMP             W9, W8
010E6E50                 B.LE            loc_10E6E80
010E6E54                 LDR             X10, [X0,#0x638]
010E6E58                 LDR             W9, [X0,#0x630]
010E6E5C                 ADD             X11, X10, X8,LSL#3
010E6E60                 CMP             W9, W8
010E6E64                 CSEL            X8, X11, X10, HI
010E6E68                 LDR             X19, [X8]
010E6E6C                 CBZ             X19, loc_10E6E84
010E6E70                 LDRB            W8, [X19,#0x430]
010E6E74                 CBZ             W8, loc_10E6E84
010E6E78                 BL              sub_19F8C5C
010E6E7C                 B               loc_10E6E84

するとやはり一発で見つかります。サブルーチンのアドレスというのは「サブルーチンの先頭アドレス」を意味するので、この場合は0x10E6E38ということになります。

これで「呼び出したいサブルーチンのアドレス」は分かったので、次は「呼び出すアドレス」を決めます。

「探す」ではなく「決める」と書いたのは、ここまで分かった情報で「好きなアドレスからGame::PlayerMgrクラスを読み込み、getControlledPerformer()をコールして自分のプレイヤー情報を読み込み、チームを変更する」というコードは書けるからです。しかし、このままでは自分が好きなタイミングでチームを変更することができません。

要するに、ナイスやカモンを押したタイミングでチームを変えたいので、ナイスやカモンの本来の動作をチーム変更コードに上書きしたいわけです。なので、今回はナイスやカモンの挙動のうち、上書きしてもゲームの動作に問題ない箇所を探せば良いことになります。

Game::PlayerCloneHandle::sendSignalEvent

上書きしても大丈夫なナイスやカモンをコールしたときに呼び出されるサブルーチンとしていつも使っているのがGame::PlayerCloneHandle::sendSignalEvent()です。

これは別にこのサブルーチンでなくても他のサブルーチンでも代用できます。

サブルーチン3.1.05.3.1
Game::PlayerCloneHandle::sendSignalEvent0x00E797FC0x0104CA58
Game::PlayerCloneHandle::sendSignalEvent
// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [3.1.0]
00E797FC                 STR             X19, [SP,#var_20]!
00E79800                 STP             X29, X30, [SP,#0x20+var_10]
00E79704                 ADD             X29, SP, #0x20+var_10
00E79708                 STUR            W1, [X29,#-4]
00E7970C                 LDUR            W8, [X29,#-4]
00E79710                 MOV             X19, X0
00E79714                 STRB            W2, [SP,#0x20+var_17]
00E79708                 STRB            W8, [SP,#0x20+var_18]
00E7971C                 BL              _ZNK4Game15CloneHandleBase14isOfflineSceneEv
00E79720                 TBZ             W0, #0, loc_1042848
00E79724                 MOV             W0, #1
00E79728                 B               loc_1042854
00E7972C                 LDR             X0, [X19,#0x10]
00E79730                 ADD             X1, SP, #0x20+var_18
00E79734                 BL              _ZN4Game14PlayerCloneObj21pushPlayerSignalEventERKNS_22PlayerSignalCloneEventE
00E79738                 LDP             X29, X30, [SP,#0x20+var_10]
00E7973C                 AND             W0, W0, #1
00E79740                 LDR             X19, [SP+0x20+var_20],#0x20
00E79744                 RET

これも特徴的な命令があるのでバイナリ検索でFD 43 00 91 A1 C3 1F B8 A8 C3 5F B8 F3 03 00 AAと検索すれば一発で見つかります。

// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [5.3.1]
0104CA58                 STR             X19, [SP,#var_20]!
0104CA5C                 STP             X29, X30, [SP,#0x20+var_10]
0104CA60                 ADD             X29, SP, #0x20+var_10
0104CA64                 STUR            W1, [X29,#-4]
0104CA68                 LDUR            W8, [X29,#-4]
0104CA6C                 MOV             X19, X0
0104CA70                 STRB            W2, [SP,#0x20+var_17]
0104CA74                 STRB            W8, [SP,#0x20+var_18]
0104CA78                 BL              sub_5BC880
0104CA7C                 TBZ             W0, #0, loc_104CA88
0104CA80                 MOV             W0, #1
0104CA84                 B               loc_104CA94
0104CA88                 LDR             X0, [X19,#0x10]
0104CA8C                 ADD             X1, SP, #0x20+var_18
0104CA90                 BL              sub_104E69C
0104CA94                 LDP             X29, X30, [SP,#0x20+var_10]
0104CA98                 AND             W0, W0, #1
0104CA9C                 LDR             X19, [SP+0x20+var_20],#0x20
0104CAA0                 RET

これで命令を呼び出したいアドレスを決めることができました。

テンプレートを完成させよう

さて、ここでGame::PlayerCloneHandle::sendSignalEventの内容全てをテンプレートで上書きします。テンプレートの方が命令が少ないので余った部分には何もしないを意味するNOPを埋めておきます。

埋めていなくてもRET命令があるためここの命令は実行されないのですが、解説ではわかりやすさを重視して入れておきます。

// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [5.3.1]
0104CA58                 STP                 X29, X30, [SP, #-0x10]!
0104CA5C                 MOV                 X29, SP
0104CA60                 ADRP                X8, #0xXXXXX000
0104CA64                 LDR                 X8, [X8, #0xYYY]
0104CA68                 LDR                 X0, [X8]
0104CA6C                 BL                  #0xAAAAAAAA - 0xBBBBBBBB
0104CA70                 LDR                 X8, [X0, #0x328]
0104CA74                 EOR                 X8, X8, #1
0104CA78                 STR                 X8, [X0, #0x328]
0104CA7C                 LDP                 X29, X30, [SP], #0x10
0104CA80                 RET
0104CA84                 NOP
0104CA88                 NOP
0104CA8C                 NOP 
0104CA90                 NOP 
0104CA94                 NOP 
0104CA98                 NOP 
0104CA9C                 NOP 
0104CAA0                 NOP 

あとはこの四つのパラメータを計算したら終わりです。

パラメータ意味
XXXXX000下三桁が0の値
Game::PlayerMgrのアドレスを使って計算する
YYYXXXXX000に対するオフセット
Game::PlayerMgrのアドレスを使って計算する
AAAAAAAAGame::PlayerMgr::getControlledPerformerのアドレス
BBBBBBBBこの命令が書かれるアドレス
必要な四つのパラメータ

これらを計算するのに必要なデータも載せます。

パラメータ求め方
XXXXX計算式は後述01CB1
YYYGame::PlayerMgrのアドレスの下三桁CF8
AAAAAAAAGame::PlayerMgr::getControlledPerformerのアドレス010E6E38
BBBBBBBBBL命令が書かれるアドレス0104CA6C

ここでXXXXX以外の値は簡単にわかります。問題はXXXXXなのですが、これは

Game::PlayerMgrのアドレスの上五桁からこの命令(ADRP)が書かれるアドレスの上五桁を引いたものになります。今回の場合ですと、

Game::PlayerMgr : 02CFDCF8 -> 02CFD
ADRP            : 0104CA60 -> 0104C

XXXXX = 02CFD - 0104C = 01CB1

となり、XXXXX=01CB1となります。

// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [5.3.1]
0104CA58                 STP                 X29, X30, [SP, #-0x10]!
0104CA5C                 MOV                 X29, SP
0104CA60                 ADRP                X8, #0x01CB1000
0104CA64                 LDR                 X8, [X8, #0xCF8]
0104CA68                 LDR                 X0, [X8]
0104CA6C                 BL                  #0x010E6E38 - 0x0104CA6C
0104CA70                 LDR                 X8, [X0, #0x328]
0104CA74                 EOR                 X8, X8, #1
0104CA78                 STR                 X8, [X0, #0x328]
0104CA7C                 LDP                 X29, X30, [SP], #0x10
0104CA80                 RET
0104CA84                 NOP
0104CA88                 NOP
0104CA8C                 NOP 
0104CA90                 NOP 
0104CA94                 NOP 
0104CA98                 NOP 
0104CA9C                 NOP 
0104CAA0                 NOP 

ここまでをまとめるとこうなります。あとはこれをARM to HEX Converterで変換すればIPSwitch形式のコードが得られます。BL命令はまとめて変換するとオフセットがズレるバグがあるので、BL命令の箇所だけは必ず個別に変換してください。

0104CA58 FD7BBFA9
0104CA5C FD030091
0104CA60 88E500B0
0104CA64 087D46F9
0104CA68 000140F9
0104CA6C F3680294 // BL
0104CA70 089441F9
0104CA74 080140D2
0104CA78 089401F9
0104CA7C FD7BC1A8
0104CA80 C0035FD6
0104CA84 1F2003D5
0104CA88 1F2003D5
0104CA8C 1F2003D5
0104CA90 1F2003D5
0104CA94 1F2003D5
0104CA98 1F2003D5
0104CA9C 1F2003D5
0104CAA0 1F2003D5

これだと無意味に長くてわかりにくいのでNOPを消したりして簡易表示すると以下のようになります。

// Swap Team Color by Signal [tkgling]
@disabled
0104CA58 FD7BBFA9FD030091
0104CA60 88E500B0087D46F9
0104CA68 000140F9F3680294
0104CA70 089441F9080140D2
0104CA78 089401F9FD7BC1A8
0104CA80 C0035FD6

動作テストをしてみる

5.3.1で動作するチーム変更コード

今までと同じように普通に動作します。

イクラ個数変更コードを移植しよう

イクラの個数変更コードはチーム変更よりも簡単です。

何故なら、Game::Coop::PlayerDirectorが全てのプレイヤーの情報を持っているため、わざわざgetControlledPerformerのような操作しているプレイヤー情報を取得する必要がないのです。

もちろん、もっと複雑なコードにする場合はBL命令で他のサブルーチンを呼び出す必要があるのでちょっとややこしいことになります。

イクラ個数変更コードテンプレート

ADRP                X0, #0xXXXXX000
LDR                 X0, [X0, #0xYYY]
LDR                 X0, [X0]
MOV                 W1, #0x270F
STR                 W1, [X0, #0x370]
MOV                 W1, #0x3E7
STR                 W1, [X0, #0x378]
STR                 W1, [X0, #0x37C]
RET 

なんでこういうコードになっているかというと、それは以前のコードを見ていただきたいのですが、その記事が若干わかりにくいので簡単に解説。

クラスの構造

こんな感じでGame::Coop::PlayerDirectorクラスは四人分のデータをもっているので、先頭アドレスを調べてそこの値を上書きしてしまう方法が使えます。

自分がホストをしているのであれば、自分の情報は先頭になるので先頭アドレスから0x370ズレたところにデータが入っています。

赤イクラの数を変えたいのであれば0x370を、金イクラの数を変えたいのであれば0x378を変えれば良いとなるわけです。また、保険として0x37Cも変えておけば完璧です。

テンプレートを完成させよう

同じようにsendSignalEventをに上書きをします。

// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [5.3.1]
0104CA58                 ADRP                X0, #0xXXXXX000
0104CA5C                 LDR                 X0, [X0, #0xYYY]
0104CA60                 LDR                 X0, [X0]
0104CA64                 MOV                 W1, #0x270F
0104CA68                 STR                 W1, [X0, #0x370]
0104CA6C                 MOV                 W1, #0x3E7
0104CA70                 STR                 W1, [X0, #0x378]
0104CA74                 STR                 W1, [X0, #0x37C]
0104CA78                 RET
0104CA7C                 NOP
0104CA80                 NOP
0104CA84                 NOP
0104CA88                 NOP
0104CA8C                 NOP 
0104CA90                 NOP 
0104CA94                 NOP 
0104CA98                 NOP 
0104CA9C                 NOP 
0104CAA0                 NOP 
パラメータ意味
XXXXX000下三桁が0の値
Game::Coop::PlayerDirectorのアドレスを使って計算する
YYYXXXXX000に対するオフセット
Game::Coop::PlayerDirectorのアドレスを使って計算する
必要な四つのパラメータ
Game::Coop::PlayerDirector : 02D0CEE0 -> 02CD0
ADRP                       : 0104CA58 -> 0104C

XXXXX = 02CD0 - 0104C = 01CC0

なのでXXXXXは01CC0となり、YYYはEE0となります。

// Game::PlayerCloneHandle::sendSignalEvent(Game::PlayerSignalCloneEvent::Type) [5.3.1]
0104CA58                 ADRP                X0, #0x01CC0000
0104CA5C                 LDR                 X0, [X0, #0xEE0]
0104CA60                 LDR                 X0, [X0]
0104CA64                 MOV                 W1, #0x270F
0104CA68                 STR                 W1, [X0, #0x370]
0104CA6C                 MOV                 W1, #0x3E7
0104CA70                 STR                 W1, [X0, #0x378]
0104CA74                 STR                 W1, [X0, #0x37C]
0104CA78                 RET
0104CA7C                 NOP
0104CA80                 NOP
0104CA84                 NOP
0104CA88                 NOP
0104CA8C                 NOP 
0104CA90                 NOP 
0104CA94                 NOP 
0104CA98                 NOP 
0104CA9C                 NOP 
0104CAA0                 NOP 

これをARM to HEX Converterで変換するとこうなります。

0104CA58 00E60090
0104CA5C 007047F9
0104CA60 000040F9
0104CA64 E1E18452
0104CA68 017003B9
0104CA6C E17C8052
0104CA70 017803B9
0104CA74 017C03B9
0104CA78 C0035FD6
0104CA7C 1F2003D5
0104CA80 1F2003D5
0104CA84 1F2003D5
0104CA88 1F2003D5
0104CA8C 1F2003D5
0104CA90 1F2003D5
0104CA94 1F2003D5
0104CA98 1F2003D5
0104CA9C 1F2003D5
0104CAA0 1F2003D5

NOPの部分は実際には不要なので整形した上で余計なコードを削除すると、

// Got 9999 PowerEggs and 999 GoldenEggs by sendSignal [tkgling]
@disabled
0104CA58 00E60090007047F9
0104CA60 000040F9E1E18452
0104CA68 017003B9E17C8052
0104CA70 017803B9017C03B9
0104CA78 C0035FD6

というコードが得られます。

動作テストをしてみる

上手くイクラを取得することができました。

記事は以上。

コメント

  1. 匿名 より:

    コメント失礼します。5.4.0のチーム変更コードを教えて頂くことは可能でしょうか?

タイトルとURLをコピーしました