asl で利用するためのゲーム内の値を asl で監視するためには、その値が書き込まれているメモリアドレスのポインタパスを特定する必要があります。
値を検索してポインタスキャンをするのがポインタパス特定の基本的な方法ですが、Unity 製ゲームの場合は構造解析機能を利用することで効率よくポインタパスの特定を行うことができます。
もくじ
ご注意
この記事は不正行為を推奨するものではなく、あくまで Autosplitter を作るための知識を広めるための記事であることをご承知ください。
アドレスを特定する際に値の書き換えを行うことでアドレスとゲーム内の値の対応を確かめる方法もありますが、個人的には値の書き換えは回避したほうが良いと考えているため、この記事では値の書き換えについては触れません。
割と我流な方法でやっているため、もっと効率の良い方法があるかもしれません。
関連リンク
asl の全体的な説明
この記事はポインタパスを特定する方法のみを説明した記事です。 asl の全体的な説明や基本的な作り方などをまとめた記事もありますので、よろしければこちらもご覧ください。
実演動画
構造解析機能を使ってポインタパスを調べる部分の実演をした動画を公開しておりますので、よろしければこちらも参考としてご覧ください。
全体の流れ
asl で使えそうな変数を含むクラスを探して、そのクラスのポインタパスを特定して、そのクラス内の変数のポインタパスを特定する、というのが大まかな流れです。
各工程
- 欲しい変数を含むクラスの探索
- クラスのアドレスを特定
- クラス内の 1 つの変数を選んでポインタスキャン
- クラス内の 1 つの変数のポインタパスを特定
- クラスのポインタパスを特定
- クラス内の任意の変数のポインタパスを特定
うまくいかなかったら前の工程に戻る
もしも、当たりを付けた変数の挙動が期待したものではなかったり、ポインタスキャンで絞り込めなかったりと、途中でうまくいかなかった場合は前の工程に戻ってやり直すこともあります。
基本的にほとんどの工程で前に戻る可能性があったり根気のいる作業を要求されるため、とにかく粘り強く頑張ってください。
手順ごとの説明
1. 欲しい変数を含むクラスの探索
この工程でやること
asl で監視したい変数を含むクラスを探します。
構造解析機能によってゲームプログラムのクラス一覧を知ることができ、それらのクラス内の変数名も分かります。
変数名から、asl で監視したい値を持っているであろう変数を探します。
また、この工程でわかるのは変数の名前までですが、後の工程でその変数の値も分かるようになるため、値の挙動が期待と違っていた場合はまたこの工程からやり直します。
ゲームプロセスのオープン
ゲームと Cheat Engine の両方を起動して、Cheat Engine の "Open Process" から Process List を開いて、ゲームを選択して "Open" を押します。
プロセスを開くことができていれば、画面上部にプロセス名が表示されます。
プロセスというのは、ゲームを動かすためにメモリ上に展開されたプログラムなどのことです。
クラス一覧からめぼしい変数を探す
メニューバーから "Mono" -> "Dissect mono" を選択して、開いた Mono dissector 画面から欲しい変数を含むクラスを探します。
画面内の ">" を押すとツリーの下の階層が開きます。Unity 製ゲームでは "Assembly-CSharp" を開いた階層にあるのがクラスで、クラスを開いた階層にある "fields" の中にあるのがそのクラスの変数です。
探し方のポイント
- 全てのクラスを片っ端から開いてそれっぽい名前の変数を見つける、くらいのつもりで根気よく。
- 変数名で当たりを付けて検索しても良いです。
- 特に、あるクラスが他のクラス内の変数として含まれている場合などは検索を使わないと見つけにくいです。
- クラス名に "+<>" が含まれているものは、ここでは無視しても良いらしいです。
補足
- 次の手順まで進めるとその変数の現在の値を知ることができるので、その値や値の変動を見て、自分が欲しいと思っていた変数なのかを確認できます。
あるいは現在の値を見ながら、asl に使えそうな変数を探すこともできます。 - この記事では変数と呼んでいますが、正確にはクラスのメンバとしての変数で「フィールド」と言います。
- static fields と fields の 2 種類の変数がありますが、static~ についてはここでは詳しくは説明しません。変数の区分が違うくらいに考えておいてください。
- static fields の中に欲しい変数がある場合は「static な変数のポインタパスを特定したい場合」をご確認ください。
ここでは Main クラスにある CurrentState が asl に使えそうな名前をした変数だったとして説明を進めます。
この先の工程でこの変数の値の挙動を確認します。
その結果、期待した通りの挙動であれば先に進み、期待した挙動ではなかった場合は他の変数を探します。
2. クラスのアドレスを特定
この工程でやること
クラスの実体があるメモリアドレスを特定します。
ゲームプログラムが実行されているということは、メモリ上のどこかにクラスの実体が書き込まれてる状態ということです。
クラスによって実体の数が 1 つだったり複数あったりしますが、いずれの場合でも 1 つに絞り込みます。
この工程で特定する「クラスの実体があるメモリアドレス」は基本的には変動するアドレスで、ゲーム画面を切り替えたり、ゲームを再起動したりすると変わってしまいます。
値を確認しながらクラスの実体を探す
Mono dissector で欲しい変数が含まれている「クラス」を選択してから右クリックして "Data Structure" -> "Dissect Structure" を選択して Sturucture dissect 画面を開きます。
この画面は、そのクラスのアドレスを指定すれば、メンバの変数ごとに現在の値が表示されます。
クラスのアドレス候補を探してからこの画面に戻ってくるので、開いたらそのままにしておいて次に進みます。
一旦、Mono dissector 画面に戻って、先ほど選択したクラスで右クリックして "Find instances of class" を選択して Instances of **** 画面を開きます。
この画面は、メモリ上にクラスの実体が作られていればそのアドレスの候補が表示されます。
候補の中から正しそうなものを 1 つ選び、そのクラスのアドレスを Ctrl+C でコピーします。
クラスの実体の選び方のポイント
- 大抵の場合はアドレスの候補が複数表示されるので、その中から正しいアドレスに絞り込む必要があります。
- 何も表示されない場合は、ゲームを操作してクラスの実体が作られるであろう場面まで進めてから開きなおします。
- どの候補も正しくなさそうな場合は、まだクラスの実体が作られていない可能性があります。ゲームを操作してクラスの実体が作られるであろう場面まで進めてから開きなおします。
先に開いていた Sturucture dissect 画面の Group 1 の真下にあるテキストボックスにコピーしたアドレスを張り付けると、クラスの変数に対応した値が表示されます。
欲しい変数に限らず、そのクラスに含まれる全ての変数の値の整合性を見て、そのアドレスの候補が正しいアドレスなのかを判断します。
整合性が確認できたアドレスが、そのクラスの実体のアドレスです。
整合性の確認の例
- bool 型の変数が bool 値になっている・いない
- 使われているハズの変数のポインタが 0 になっている・いない
- ゲームを操作すると値が変わる・変わらない
- アイテムなどの数量がゲーム画面内の数量と一致している・していない
正しいクラスの実体の探し方のポイント
- 全てのアドレス候補をコピペする、くらいのつもりで根気よく。
- 役割を推測できる変数は全て判断材料に使うつもりで。
- Instances of **** 画面のアドレス候補の ">" を押して開くと変数名と開いた時の値が表示されるので、ここで判断することもできます。
- Sturucture dissect 画面は一定時間ごとに自動更新されるため、ゲームを操作しながら値の変化を確認する場合にはこちらの画面で見ると良いです。
- 単に現在のアドレスと値の関係を見るだけなら、"Mono" -> ".NET Info" で開く画面を使ってもよいです。
ここまで進められたら、欲しい変数のオフセット値をメモしておきます。
Sturucture dissect 画面の変数名の左にある 16 進数がその変数のオフセット値で、クラス先頭からの相対位置のようなものです。
この例では CurrentState の左にある 00EC がこの変数のオフセット値です。先頭の連続した 0 は省略できるので、EC とメモしておけば大丈夫です。
3. クラス内の変数をポインタスキャン
この工程でやること
クラス内の変数を 1 つ選んでポインタスキャンを行います。
ここで選ぶ変数はあくまでポインタスキャンのために利用する変数であり、ポインタスキャンをするのに都合の良い変数を選ぶことが望ましいです。
クラス内の変数であればよいので、もちろん、asl で監視したい変数そのものをポインタスキャンしても大丈夫です。
スキャン用の変数を探す
その変数がコレクションだったり他のクラスだったりする場合は、Sturucture dissect 画面の変数名の左にある > 部分を開くことができます。
これを利用して、ポインタスキャンに都合のよい変数を探します。
探し方のポイント
- 意味を理解できる値が入っている。
- その値の正誤が判断できる。
- ツリーを複数階層開いた場所にある変数だと望ましい。(そうでなくても大丈夫)
- 階層が深すぎるとスキャンの設定を調整する必要があるため、適度な深さだとよい。
ポインタスキャン用の変数のオフセット値はこの先の工程で必要になるので、階層順にメモしておきます。
オフセット値を入力するとき、先頭の連続した 0 は省略できます。
ここでは player.CurrentLife に決めたとします。
オフセット値は player が 0020 で、CurrentLife が 0084 です。先頭の連続した 0 は省略できるので、順番に 20 と 84 をメモしておきます。
最初のポインタスキャン
ポインタスキャン用の変数を選択してから右クリックして "Add to address list" を選択して、Cheat Engine 本体の address list に追加します。
Cheat Engine 本体の address list に追加した、ポインタスキャン用の変数のアドレスを右クリックして "Pointer scan for this address" を選ぶと Pointer scan 画面と Pointerscanner scanoptions 画面が開くので、Pointerscanner scanoptions 画面で検索条件の設定を行います。
"Pointers must end with specific offsets" にチェックを入れて、下の階層のオフセット値からになるように逆順に入力します。
基本的な設定項目については通常のポインタスキャンと同じなので、各自で調べてください。初期値のままでも割と何とかなる印象。
"OK" ボタンを押すとダイアログが開くので、ポインタパス候補の一覧を保存するためのファイル名を入力して決定します。
ここで入力するファイル名は「変数名+通し番号」のようにしておくと良いと思います。
このポインタスキャンはこの先の工程で何度か繰り返し行い、時には 1 つ前のスキャン結果に戻りたい場合もあるため、通し番号を付けておくと便利です。
最初のポインタスキャンでは、その変数のポインタパス候補が大量にリストアップされると思います。(Pointer Paths の数字が候補の数)
ここから、ポインタパス候補の絞り込みを行います。
4. クラス内の変数のポインタパスを特定
この工程でやること
ゲームを再起動しながらクラス内の変数のポインタスキャンの結果を絞り込んでいき、そのポインタパスを特定します。
このポインタスキャン用の変数のポインタパスが特定できてしまえば、ゴールが見えてきます。
クラスのアドレスを意図的に変更
Mono dissector 画面、Sturucture dissect 画面、Instances of GameController 画面を閉じて、クラスの実体があるアドレスを変えるためにゲームをいったん終了します。
構造解析機能を利用しているとゲームが終了できない場合があり、そのような時は私はタスクマネージャから終了しています。
ゲームを再起動して、ゲームのプロセスを開きます。
ゲームプロセスを開いた時に address list をそのまま維持するかを聞いてきますが、Yes でも No でも問題ありません。
再起動時のポイント
- ゲームを再起動したら "Open Process" を忘れずに!
- Cheat Engine 本体と Pointer scan 画面は閉じないこと。
Pointer scan 画面は前の状態から継続して作業する必要があるので、もし閉じてしまった場合は本体の address list にある適当なアドレスを右クリックして "Pointer scan for this address" を選びます。
自動的に開く設定画面を閉じてから "File" -> "Opne" で名前を付けて保存してあるファイルを開けば OK。
再スキャン
クラスが実体化するであろう所までゲームを進めて、先ほどのポインタスキャン用の変数のポインタパス一覧を再スキャンします。
ポインタスキャン用の変数の値が分かっている状態であるなら、値を使って再スキャンができます。
Pointer scan 画面で "Pointer scanner" -> "Rescan memory" を選択して、開いたダイアログの先頭にある "Value to find" にチェックを入れます。
現在の変数の値を入力して再スキャンします。Pointer Paths の数が減っているのが分かるかと思います。
変数の値が分からない状態だったなら、最初のポインタスキャンをするまでと同じようにして、クラスの実体のアドレスを調べ、ポインタスキャン用の変数のアドレスを調べ、その変数のアドレスを使って再スキャンします。
ゲームを再起動して変数の値を使って再スキャン、という作業を数回繰り返します。
ゲームを再起動したら "Open Process" を忘れずに! そして根気よく。
再スキャン時のポイント
- "Open Process" を忘れずに!
- 再スキャン結果が 0 件になったら、まず "Open Process" を疑いましょう。
- それでも 0 件になるなら、前の工程に戻って検索条件を変更して最初のポインタスキャンからやり直しましょう。
- それでもまだ 0 件になるなら、スキャン用の変数を変えてみましょう。
ポインタパスを決定
最初のスキャン結果の件数にもよりますが、Pointer Paths の数が 4 桁になるかならないくらいまで減ってきたら、ポインタリストの "Offset 6" の部分を押してオフセットの個数が少ない順にソートします。
オフセットの個数が少ないアドレスの中から選んでダブルクリックして Cheat Engine 本体の address list に追加します
これがポインタスキャン用に選んだ変数のポインタパスです。
ポインタパス決定時のポイント
- Pointer scan 画面で表示される現在の値はリアルタイムには更新されません。address list に追加して値の挙動を観察しましょう。
- ポインタパスを特定できたと思っていても、次の日には使えなくなっていることがあります。
- ポインタパスを決定する前に 1 日は寝かしてから、改めて再スキャンするようにしましょう。
5. クラスのポインタパスの特定
この工程でやること
ポインタスキャン用の変数のポインタパスとオフセット値から、そのクラスのポインタパスを計算します。
クラスメンバの変数のポインタパスからオフセット値の分だけ前にあるアドレスが、このクラスの先頭のポインタパスです。
クラスのポインタパスを計算
ひとつ前の工程で address list に追加したアドレスの Address 列の部分をダブルクリックしてアドレスの設定画面を開きます。
ポインタスキャンに使った変数のオフセット値の分だけ末尾のオフセット値を戻します。基本的には 0 になるかと思います。
下の階層の変数を使ってスキャンしていた場合は、階層分だけオフセットを削除することを忘れずに。
これがクラスのアドレス(正確にはクラスの先頭アドレス)です。分かりやすくするために、Description をクラス名に変更しておくとよいです。
この例では Main.player.CurrentLife のポインタパスを特定したので、ここから Main のポインタパスが分かります。
6. 欲しい変数のポインタパスを特定
この工程でやること
元々の目的である、asl で監視したい値に対してのポインタパスを特定します。
クラスのポインタパスを起点としてオフセット値を設定すれば、クラス内の変数のポインタパスになります。
クラスのポインタパスを使って任意の変数のポインタパスを計算
address list で、ポインタパスを特定したクラスのアドレスをコピペします。
貼り付けの際に Paste table entries という画面が開くので、何もしないでそのまま Paste ボタンを押します。
コピーしたアドレスの Address 列の部分をダブルクリックしてアドレス設定画面を開きます。
オフセット値の末尾を欲しかった変数のオフセット値に書き換え、分かりやすくするために Description に変数名を入力します。
これでようやく目的の変数のポインタパスを特定することができました。
ポインタスキャン用に address list に追加した変数のアドレスはもう役目を果たしたので、asl で使わないのであればリストから削除して OK です。
最後に、address list を保存しておきましょう。
static な変数のポインタパスを特定したい場合
static な変数の場合は手順 2. の Instances of **** 画面では対応できないため、そのクラスの static 変数の先頭アドレスを他の方法で見つける必要があります。
"Mono" -> ".NET Info" で開く画面を使えば static な変数のアドレスが分かるため、Mono dissector などからそのオフセット値が分かれば先頭アドレスを計算できるかと思います。
Sturucture dissect Static Data 画面を開いて、Group 1 の真下にあるテキストボックスに先頭アドレスを入力して、ここから先は static でない変数の場合と同じです。
0 件のコメント:
コメントを投稿