誰でもできるコード開発 #2
はじめに
今回の内容は以下の記事の続きになります。
この記事を読むにあたって必ず目を通して理解しておいてください。
0 以外の値に上書きしたい
さて、前回はスペシャルコストを 0 にするコードについて学びました。
スペシャルコストを決定する関数は 5.4.0 においてはloc_864DC
であり、それは以下のアセンブラで与えられました。
000864DC LDR X1, [SP,#0x6C0+var_660]
000864E0 ADRP X2, #aSpecialcost@PAGE ; "SpecialCost"
000864E4 SUB X0, X29, #-var_C8
000864E8 ADD X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
000864EC STR X19, [SP,#0x6C0+src]
000864F0 BL sub_19E4678
このとき、BL sub_19E4678
というのは BL が返り値を持つサブルーチンであり、単に X1 レジスタに保存されているアドレスに値を入れればその値がまさにスペシャルコストになりました。
サブルーチン 19E4678 は X1 にスペシャル必要数のデータが保存されているメモリのアドレスをコピーする。
つまり、sub_19E4678
には分岐する必要がなかったのでここの命令を上書きしてしまって良かったわけです。
000864DC LDR X1, [SP,#0x6C0+var_660]
000864E0 ADRP X2, #aSpecialcost@PAGE ; "SpecialCost"
000864E4 SUB X0, X29, #-var_C8
000864E8 ADD X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
000864EC STR X19, [SP,#0x6C0+src]
STR WZR, [X1] // 0 を代入に変更
「レジスタが持つアドレスが指し示すメモリの値を 0 にする」という命令は通常は二命令ないと実装できないのですが、ARM64 には読み込むと常に 0 を返すゼロレジスタと呼ばれる便利なものがあるので上のように一行で実装することができました。
では、0 ではない別の値にしたい場合はどうすればよいでしょうか?
::: tip 0 以外にする必要はあるか
わざわざスペシャルコストを 0 以外の別の値にしたがる人はいないと思いますが、ここでは技術的に可能かどうかだけを解説しています。
:::
以下は X1 レジスタがもつアドレスのメモリの値を 255 にするアセンブラです。
MOV X19, #255
STR X19, [X1]
このように、0 以外の何かの値をメモリに代入しようとすれば最低でも二命令必要になります。ちなみに ARM の命令は一命令で 16 ビット(65535)までの代入に対応しているので任意の 32 ビットの値をレジスタに保存するためにはレジスタにコピーするだけで 命令必要になります。
それをメモリに保存しようとすれば更に追加で一命令必要なので合計三命令です。
スプラトゥーンでは実際に 64 ビットの値を扱うことはほとんどないので「3 命令あれば好きな値をメモリに入れることができる」とおぼえておくと良いでしょう。
これでスペシャルコストの命令を上書きすれば全てのブキのスペシャルコストを 255 にすることができます。
::: tip X19 レジスタを指定した意味
今回はたまたま X19 レジスタを指定していますが、影響がないならどんなレジスタを指定しても構いません。ちなみに ARM64 は X30 までのレジスタが扱えます。
:::
上書きできる命令を探す
アセンブラでこれを実装するのは先程も言ったように少なくとも二命令が必要になります。
つまり、BL 命令以外のどれかを更に上書きする必要が発生するということです。
ここで大事なのは「上書きしても動作に問題のない命令はどれか」ということを正しく理解することなのです。
今回の場合はたまたま BL 命令の一つ前の命令であるSTR X19, [SP,#0x6C0+var_468]
も上書きしても動作に問題がありませんでした。
今回はたまたまうまくいきましたが「常に BL 命令の一つ前の命令は潰してしまっても問題ない」というわけではないことに気をつけてください。
アセンブラから ARM64 の機械語に変換するのはOnline ARM to HEX Converterが非常に便利なのでぜひ使わせていただきましょう。
スペシャルコストを 255 にするコードは以下のようになります。
// Special Cost 255 [tkgling]
@disabled
000864EC F31F80D2 // MOV X19, #255
000864F0 330000B9 // STR X19, [X1]
一行目のコードがの F31F80D2 がレジスタに 255 を代入しているので、ここの値を変えればいくらでも好きな値に設定できます。
分岐先命令を上書き
さて、今回は特定の値を代入する命令も高々二行で書くことができたので置き換えても問題がない命令を見つけて目的のコードを書くことができました。
しかし、もしもどの命令も置き換えることができなかったときはどうすればいいのでしょう?
そういうときは BL 命令自体を上書きするのではなく、BL で分岐した先の命令を変えてしまえば良いことになります。
000864DC LDR X1, [SP,#0x6C0+var_660]
000864E0 ADRP X2, #aSpecialcost@PAGE ; "SpecialCost"
000864E4 SUB X0, X29, #-var_C8
000864E8 ADD X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
000864EC STR X19, [SP,#0x6C0+src]
000864F0 BL sub_19E4678
019E4678 STR X21, [SP,#-0x10+var_20]!
019E467C STP X20, X19, [SP,#0x20+var_10]
019E4680 STP X29, X30, [SP,#0x20+var_s0]
019E4684 ADD X29, SP, #0x20
019E4688 MOV X21, X0
019E468C ADD X0, SP, #0x20+var_18
019E4690 MOV X20, X2
019E4694 MOV X19, X1
019E4698 BL sub_19E5030
019E469C ADD X1, SP, #0x20+var_18
019E46A0 MOV X0, X21
019E46A4 MOV X2, X20
019E46A8 BL sub_19E406C
019E46AC TBZ W0, #0, loc_19E46EC
019E46B0 ADD X0, SP, #0x20+var_18
019E46B4 BL sub_19E505C
019E46B8 AND W8, W0, #0xFF
019E46BC CMP W8, #0xFF
019E46C0 B.EQ loc_19E46EC
019E46C4 ADD X0, SP, #0x20+var_18
019E46C8 BL sub_19E505C
019E46CC AND W8, W0, #0xFF
019E46D0 CMP W8, #0xD1
019E46D4 B.NE loc_19E46EC
019E46D8 ADD X0, SP, #0x20+var_18
019E46DC BL sub_19E5064
019E46E0 STR W0, [X19]
019E46E4 MOV W0, #1
019E46E8 B loc_19E46F0
019E46EC MOV W0, WZR
019E46F0 LDP X29, X30, [SP,#0x20+var_s0]
019E46F4 LDP X20, X19, [SP,#0x20+var_10]
019E46F8 LDR X21, [SP+0x20+var_20],#0x30
019E46FC RET
::: warning sub_19E4678 を上書きするキケン性
このサブルーチンをスキップしてもバグが発生しないということは、このサブルーチン内の命令は全部 NOP(何もしない)にしても構わないということですが、それはあくまでもloc_847A0
から呼ばれた場合にはスキップしても問題ないということです。
sub_19E4678
内でリターンする値を変更することはsub_19E4678
を呼び出している他の全てのサブルーチンに対しても本来とは異なる値を返すことになります。
可能な限り、呼び出し先のサブルーチンの内容を変更するのはやめましょう。
:::
ただしここまでの手法を使ってできるのは「本来 X という値が読み込まれていたところに Y を代入する」という操作だけです。
「本来 X という値が読み込まれていたところに 2X を代入する(例えば攻撃力を倍にするなど)」はもう少し複雑な命令を書く必要があります。
できること | できないこと |
---|---|
好きな定数を代入する | 本来の値を参照する |
値をゼロクリア | 条件分岐 |
関数のスキップ |
本来の値を参照することができないということは、例えばサーモンランのクマブキアンロックのためにCoopAddition
の値を変更する必要があるのですが、クマブキだけを開放するといった細かいことはできないということです。
そういう処理にする場合には複数の命令が必要なので、書き換えられる命令が一行しかない場合にはそういった柔軟な対応ができません。
実際には使えないヒーローモードのブキも含めて全てのブキが使用可能になってしまいます。
一般的に ExeFS の改造でできるのは大雑把で大胆な変更なので、細かいところを調整したいのであれば LFS を利用して直接 BPRM ファイルを変数してしまうほうが楽だと思います。
ブキやスペシャルの性能をめちゃくちゃにしたりとか、そういう系のチートがこれに該当します。
記事は以上。