はじめに

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

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

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

0 以外の値に上書きしたい

さて、前回はスペシャルコストを 0 にするコードについて学びました。

スペシャルコストを決定する関数は Ver5.4.0 においては loc_864DC であり、それは以下のアセンブラで与えられました。

loc_864DC
LDR        X1, [SP,#0x6C0+var_660]
ADRP       X2, #aSpecialcost@PAGE ; "SpecialCost"
SUB        X0, X29, #-var_C8
ADD        X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
STR        X19, [SP,#0x6C0+var_468]
BL         sub_19E4678
1
2
3
4
5
6
7

このとき、BL sub_19E4678 というのは BL が返り値を持つサブルーチンであり、単に X1 レジスタに保存されているアドレスに値を入れればその値がまさにスペシャルコストになりました。

つまり、sub_19E4678 には分岐する必要がなかったのでここの命令を上書きしてしまって良かったわけです。

loc_864DC
LDR        X1, [SP,#0x6C0+var_660]
ADRP       X2, #aSpecialcost@PAGE ; "SpecialCost"
SUB        X0, X29, #-var_C8
ADD        X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
STR        X19, [SP,#0x6C0+var_468]
STR        WZR, [X1] // 0 を代入に変更
1
2
3
4
5
6
7

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

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

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

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

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

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

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

それをメモリに保存しようとすれば更に追加で一命令必要なので合計三命令です。スプラトゥーンでは実際に 64 ビットの値を扱うことはほとんどないので「三命令あれば好きな値をメモリに入れることができる」とおぼえておくと良いでしょう。

これでスペシャルコストの命令を上書きすれば全てのブキのスペシャルコストを 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 で分岐した先の命令を変えてしまえば良いことになります。

LDR        X1, [SP,#0x6C0+var_660]
ADRP       X2, #aSpecialcost@PAGE ; "SpecialCost"
SUB        X0, X29, #-var_C8
ADD        X2, X2, #aSpecialcost@PAGEOFF ; "SpecialCost"
STR        X19, [SP,#0x6C0+var_468]
BL         sub_19E4678
sub_19E4678
STR        X21, [SP,#-0x10+var_20]!
STP        X20, X19, [SP,#0x20+var_10]
STP        X29, X30, [SP,#0x20+var_s0]
ADD        X29, SP, #0x20
MOV        X21, X0
ADD        X0, SP, #0x20+var_18
MOV        X20, X2
MOV        X19, X1
BL         sub_19E5030
ADD        X1, SP, #0x20+var_18
MOV        X0, X21
MOV        X2, X20
BL         sub_19E406C
TBZ        W0, #0, loc_19E46EC
ADD        X0, SP, #0x20+var_18
BL         sub_19E505C
AND        W8, W0, #0xFF
CMP        W8, #0xFF
B.EQ       loc_19E46EC
ADD        X0, SP, #0x20+var_18
BL         sub_19E505C
AND        W8, W0, #0xFF
CMP        W8, #0xD1
B.NE       loc_19E46EC
ADD        X0, SP, #0x20+var_18
BL         sub_19E5064
STR        W0, [X19]
MOV        W0, #1
B          loc_19E46F0
MOV        W0, WZR
LDP        X29, X30, [SP,#0x20+var_s0]
LDP        X20, X19, [SP,#0x20+var_10]
LDR        X21, [SP+0x20+var_20],#0x30
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
42

sub_19E4678 を上書きするキケン性

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

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

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

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

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

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

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

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

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

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

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

記事は以上。