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

はじめに

今回の内容は以下の記事の続きになります。

[IPSwitch] 誰でもできるコード開発 #1 (opens new window)

この記事を読むにあたって必ず目を通して理解しておいてください。

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
1
2
3
4
5
6

このとき、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 を代入に変更
1
2
3
4
5
6

「レジスタが持つアドレスが指し示すメモリの値を 0 にする」という命令は通常は二命令ないと実装できないのですが、ARM64 には読み込むと常に 0 を返すゼロレジスタと呼ばれる便利なものがあるので上のように一行で実装することができました。

では、0 ではない別の値にしたい場合はどうすればよいでしょうか?

0 以外にする必要はあるか

わざわざスペシャルコストを 0 以外の別の値にしたがる人はいないと思いますが、ここでは技術的に可能かどうかだけを解説しています。

以下は X1 レジスタがもつアドレスのメモリの値を 255 にするアセンブラです。

MOV X19, #255
STR X19, [X1]
1
2

このように、0 以外の何かの値をメモリに代入しようとすれば最低でも二命令必要になります。ちなみに ARM の命令は一命令で 16 ビット(65535)までの代入に対応しているので任意の 32 ビットの値をレジスタに保存するためにはレジスタにコピーするだけで 命令必要になります。

それをメモリに保存しようとすれば更に追加で一命令必要なので合計三命令です。

スプラトゥーンでは実際に 64 ビットの値を扱うことはほとんどないので「3 命令あれば好きな値をメモリに入れることができる」とおぼえておくと良いでしょう。

これでスペシャルコストの命令を上書きすれば全てのブキのスペシャルコストを 255 にすることができます。

X19 レジスタを指定した意味

今回はたまたま X19 レジスタを指定していますが、影響がないならどんなレジスタを指定しても構いません。ちなみに ARM64 は X30 までのレジスタが扱えます。

上書きできる命令を探す

アセンブラでこれを実装するのは先程も言ったように少なくとも二命令が必要になります。

つまり、BL 命令以外のどれかを更に上書きする必要が発生するということです。

ここで大事なのは「上書きしても動作に問題のない命令はどれか」ということを正しく理解することなのです。

今回の場合はたまたま BL 命令の一つ前の命令であるSTR X19, [SP,#0x6C0+var_468]も上書きしても動作に問題がありませんでした。

今回はたまたまうまくいきましたが「常に BL 命令の一つ前の命令は潰してしまっても問題ない」というわけではないことに気をつけてください。

アセンブラから ARM64 の機械語に変換するのはOnline ARM to HEX Converter (opens new window)が非常に便利なのでぜひ使わせていただきましょう。

スペシャルコストを 255 にするコードは以下のようになります。

// Special Cost 255 [tkgling]
@disabled
000864EC F31F80D2 // MOV X19, #255
000864F0 330000B9 // STR X19, [X1]
1
2
3
4

一行目のコードがの 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

sub_19E4678 を上書きするキケン性

このサブルーチンをスキップしてもバグが発生しないということは、このサブルーチン内の命令は全部 NOP(何もしない)にしても構わないということですが、それはあくまでもloc_847A0から呼ばれた場合にはスキップしても問題ないということです。

sub_19E4678内でリターンする値を変更することはsub_19E4678を呼び出している他の全てのサブルーチンに対しても本来とは異なる値を返すことになります。

可能な限り、呼び出し先のサブルーチンの内容を変更するのはやめましょう。

ただしここまでの手法を使ってできるのは「本来 X という値が読み込まれていたところに Y を代入する」という操作だけです。

「本来 X という値が読み込まれていたところに 2X を代入する(例えば攻撃力を倍にするなど)」はもう少し複雑な命令を書く必要があります。

できること できないこと
好きな定数を代入する 本来の値を参照する
値をゼロクリア 条件分岐
関数のスキップ

本来の値を参照することができないということは、例えばサーモンランのクマブキアンロックのためにCoopAdditionの値を変更する必要があるのですが、クマブキだけを開放するといった細かいことはできないということです。

そういう処理にする場合には複数の命令が必要なので、書き換えられる命令が一行しかない場合にはそういった柔軟な対応ができません。

実際には使えないヒーローモードのブキも含めて全てのブキが使用可能になってしまいます。

一般的に ExeFS の改造でできるのは大雑把で大胆な変更なので、細かいところを調整したいのであれば LFS を利用して直接 BPRM ファイルを変数してしまうほうが楽だと思います。

ブキやスペシャルの性能をめちゃくちゃにしたりとか、そういう系のチートがこれに該当します。

記事は以上。