コードの自作の目的と意味
IPSwitchでいろいろなコードを試している人はたくさんいるように思います。
中にはコードを使うだけで楽しい人もいるかも知れませんが、「自分でもコードを見つけたい」と思っている人もいるかも知れません。
というのは、コードを見つければそのコードはくだらないものであったとしても自身の名前が残る可能性があるからです。
ぼくが見つけたのは金イクラドロップ数変更とサーモンランでのスペシャル使用回数変更のコードなので、たまに動画の紹介で名前が載っていることがありますね。
ぼくより先にコード見つけた人がいるかも知れませんが、こういうのは公開しないと名前が載りません。
実際のところコードはチーム内以外では非公開だったので「なんで勝手に動画化されてるん?」っていう気はしないでもないですが、ちゃんとクレジットがついていてコード自体は公開されていないことを考えると最低限のモラルを持っている人は多いように思います。
自作しやすいコード
コードの中にも自作しやすいものとそうでないものが存在します。
最も簡単なのは実行ファイル内で定義されている値を変更するもので、これはとてつもなく簡単です。
基本的には必要なパラメータが記述されているアドレスを探すだけの作業になります。
当然、「実行ファイルのどこにそんな定数が載っているのか」という話になるのですが、 “ELF String Table” という箇所があるのでそこを見れば簡単に見つけることができます。
どのあたりにそれらのテーブルがあるかは下のテーブルに載せておくので参考にしてください。
Ver | ELF(IDA) | NSO(IDA) |
3.1.0 | 37106C4 | 71037106C4 |
4.7.0 | 23C57E4 | 71023C57E4 |
“Shooter_Normal_H” でテキスト検索をすれば割りと早く見つけられると思います。
これらは文字列なのでソースコードのコメントが削除されたVer3.1.0以降のNSOであっても比較的読みやすいはずです。
ここから自分がいじりたいと思うパラメータを検索する必要があります。
パラメータ名はどこかで見たことがあるものばかりだと思うので、見つけること自体は簡単だと思います。
以下にパラメータ名の一例を挙げておきます。
文字列 | 意味 |
Coop | サーモンラン |
SpecialCost | スペシャル必要量 |
InkConsume | インク消費量 |
Player | イカちゃん |
Npc_ | NPC |
State | メッセージなど |
とにかくパラメータは多すぎるので、全部書ききることはできません。
値を0にするコード
先程、最も書きやすいコードは実行ファイル内で定義されている変数を変更するものだと述べました。
それらのうち、その値を0にするコードは最も書きやすいです。
これを100や1000にするのは意外と難しいのです。
例えば、スペシャル必要量を0にするコードを書いてみましょう。
GHIDRAでELFを分析する場合、メモリ使用量がデフォルトだと1024MBしか使えないためオーバーフローしてしまいます。
4096MBを指定すれば最後まで実行できたので、大きめに設定してください。
ghidraRun.bat
set MAXMEM=4096M call "%~dp0support\launch.bat" bg Ghidra "%MAXMEM%" "" ghidra.GhidraRun %*
GHIDRAを起動させるためのバッチファイル内で最大メモリ使用量が指定できるので、変更しましょう。

アドレスは7103711e8aで、その右を見るとXREF[2]の記述から7100038e98でこの値が使われていることがわかります。
ダブルクリックするとこのアドレスにとぶことができるので、実際にそのサブルーチンを見てみましょう。
サブルーチンというのは、関数(メソッド)内で動く関数みたいなイメージです。

ここのサブルーチンの上五行の命令を覚えておきましょう。
オフセット
逆アセンブラの種類と解析するファイルの種類によってELFをIDAで解析したときに比べてアドレスがズレるのでその値を覚えて置かなければいけません。
逆アセンブラ | アドレス | オフセット |
IDA Pro (ELF) | 847A0 | 0 |
IDA Pro (NSO) | 71000847A0 | +0x710000000 |
GHIDRA (ELF) | 1847A0 | +0x000100000 |
GHIDRA (NSO) | 7100038E8C | — |
これを見るとELFをGHIDRAで逆アセンブルしたときはきれいにアドレスがズレるのですが、NSOを直接解析するとオフセットがおかしな値になってしまうことがわかります。
ここで上手い解決法が見つからなかったので、アドレスのズレを上手く固定できる設定を見つけた方は教えてください。
以後、記事ではIDA ProでELFを解析したときのアドレス(IPSwitchで直接指定できるアドレス)に変換したものを記述します。
自身の環境で同じサブルーチンを参照したい場合は、ここのオフセット値を加えたものを使用してください。
アセンブラを理解する
loc_847A0 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_19A32AC
この五行がスペシャル必要量を設定しているサブルーチンになります。
で、そのサブルーチンを実行したときのレジスタの動きはだいたいこんな感じ(自信がないので間違ってたら指摘ください)
で、細かいことはさておいて最終的にはX1レジスタに入っているアドレスが指し示すメモリの値がSpecialCostとして使われます。

レジスタはこのあとも何度も更新されてしまうので、ここの値を変えることに意味はありません。
では、メモリのアドレスXXXのデータが持つ値を変更するにはどのようなアセンブラを書けば良いでしょうか?
もちろん、直接メモリに値を代入するアセンブラが書ければよいのですが、それはできません。
何故なら、メモリに値を代入するためには必ずレジスタからコピーしなければいけないからです。
つまり、データのコピー先としてメモリのアドレスを直接指定することは不可能です。
ではどうすればいいのかというと、メモリのアドレスを保持しているレジスタを利用するのです。
上の例の場合は、X1レジスタが参照先のメモリのアドレスを保持しているのでこれが利用できます。
MOV X20, #0 STR X20, [X1]
例えばこのように書けばアドレスXXXに保存されているデータの値を0に上書きすることができます。
MOV X20, X1と書いてしまうと、参照したいメモリのアドレス自体が0に更新されてしまって、データを正しく読み込むことができなくなってしまいます。
ただし、これでは二行も使ってしまうのでできれば一行で済ませたいところです。
そこで、読み込むと必ず0を返すゼロレジスタを利用します。
STR WZR, [X1]
こう書けばX1の値を0にするコードが一行で書けてしまいます。
コードの実装
X1レジスタが指し示すアドレスの値を0にするアセンブラは “STR WZR, [X1]” と書くことができました。
そして、本来こういった動作はサブルーチン19A32ACで行われます。
つまり、サブルーチンに入らずに単にX1レジスタの値を変更してしまえば期待通りの動作をするはずです。
よって、サブルーチン19A32ACに分岐するための命令 “BL sub_19A32AC” 自体を書き換えてしまいましょう。
// Before 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_19A32AC // After 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]
“BL sub_19A32AC” の命令が書かれているアドレスは “847B4” ですので、これを “STR WZR, [X1]” という命令で上書きします。
これはこのままではアセンブラですので、ARM64が解釈できる機械語に治す必要があります。
以下のサイトで簡単に変換できるので試してみましょう。


このとき出力されるARM HEXの値をコピーします。
これがARM64が解釈できる “STR WZR, [X1]” という命令になります。
// Special Cost 0 [tkgling] @disabled 000847B4 340000F9
最終的に、このようなコードが得られたら正解です。
応用
実は今のサブルーチンは各ブキのパラメータが載っているbprmファイルを読み込んでその値を読み取るという関数でした。
ブキのパラメータはアップデータでコロコロと変わるので、実行ファイルに書くよりもパラメータ名だけ定義しておいて外部ファイルから読み込んだほうが合理的というわけです。
そして、今回のサブルーチンと全く同じ構造を持ったサブルーチンは多数あります。
LDR X1, [SP,#0xXXX] ADRP X2, #XYZ ; Parameter SUB X0, X29, #-var_C8 ADD X2, X2, #XYZ ; Parameter STR X19, [SP,#0xXXX] BL sub_ABCDEFG
つまり、こういうサブルーチンであるなら、BLの命令を上書きしてパラメータの値を0にすることは簡単だということです。
基本的にはX1レジスタが指し示す値を変更すれば反映されるはずです。
最後に
IPSwitchのコード開発第一回は少々長くなりましたが、パラメータ名が実行ファイル内で定義されている値を0に変更するコードの書き方を学習しました。
次回はある特定の値に変更する方法を学びたいと思います。
記事は以上。
自身を天才と信じて疑わないマッドサイエンティスト。二つ上の姉は大英図書館特殊工作部勤務、額の十字架の疵は彼女につけられた。
コメント
atmosphereの標準機能でもチートコードが使えますけど、アドレス値さえ分かればIPSwitchと同じことができると思っていいでしょうか?
例えば「Special Cost 0」のようなコードだとどんな表記になりますか?(分かればでいいです)
仕組みが違うと思うので多分そのままコピペしても動作しませんよね
そういえばatmosphereにも0.8.5からチート機能が搭載されていましたね(すっかり忘れていました)
https://github.com/WerWolv/EdiZon_CheatsConfigsAndScripts/blob/master/Cheats/0100F8F0000A2000/cheats/25e4de94c55dc7a3.txt
EdiZon用のSplatoon2のコードはこれしか見つからなかったのですが、パット見た感じはIPSwitch – EdiZonでコードのフォーマットが違うようです。
ただ、根本的な仕組みは同じだと思われるので何らかの変換法則があるのだと思います。
昔から使われてるチートコードと同じなのでこっちのほうが見慣れてるんですよね 簡単に変換できるなら変換したいですね
メモリ使用量を指定する方法を教えて欲しいです
ghidraRun.batで “;set MAXMEM=1024M” みたいな表記があると思うので、そこのセミコロンを外して好きな値にしてください。
一応、記事内に追記しておくのでそちらも参考にしてください。
3.1.0のデバックメニューパッチも作れたり出来ますか?
できると思いますが、3.1.0のNSPを持っていない(改造に手を出した頃には既に最新が4.0.0でした)ので動作確認できません。
よって、特に移植する予定はありません。
コードの数字をテキストファイルに入れれば使えるのですか?
最近hack初めたばかりなので…すいません
使い方はここに書いてあるのでご一読ください。
https://tkgstrator.work/?p=16126
読んでもなおわからないことがあればまたコメントください。
3号のマントみたいにモデルハックのようなコードの作り方を教えてください
モデルハックのようなコードをつくりたいのであれば、最初からモデルハックしたほうが楽だと思います。
開発のメリットが薄いですし、なによりぼく自身がそのようなコードを知らないので解説できません。
ならモデルハックの開発方法を教えて欲しいのですが
申し訳ないのですが、モデルハックについては詳しくないので解説できません。
3Dモデルを編集するだけだと思うので、調べればやり方は見つかると思われます。
わかりました
// Special Cost 0 [tkgling]
@disabled
000847B4 340000F9
↑
これはどこからきているのですか?
340000F9はアセンブラでARM HEXで分かったんですけど、
000847B4は分かりません。
確かにわかりにくかったですね。
近日中に書き直してわかりやすくしようと思います。
で、細かいことはさておいて最終的にはX1レジスタに入っているアドレスが指し示すメモリの値がSpecialCostとして使われます。
とありますが、
なぜX1だとわかるんでしょか?
記事を書いたのがかなり前でちょっと忘れてしまったのですが、BL sub_19A32ACというサブルーチンがスペシャルコストのポインタを使って[X1]に読み込んだ値を保存しておくような関数になっていたからだと思います。