Cheat Engineでゲーム内の情報を芋づる式に見つけよう!

Cheat Engineには「Dissect data structures」という機能があります。
この機能を使うとゲームを動かしながら、メモリの情報を視覚的にわかりやすく分析でき、値を変更することも可能です。

プレイヤーの体力や弾丸のアドレスを見つければ、そのアドレスの近辺には他の情報もたくさんあります。
なぜこのようなことが起こるかというと、プログラミングでは関連するデータをメモリの特定の場所にまとめて管理しているという特徴があるからです。
C言語でいう構造体のことですね。これはゲームだけに限りません。
なので体力のアドレスを見つければ、弾丸やその他の情報も近くにある可能性が高いということです。
逆もまた然りです。
似たようなツールでReClass.NETがあります。

「Dissect data structures」でデータ構造を分析する

実際にやってみましょう。
使用するゲームはAssault Cubeです。

ここで使用するアドレスはプレイヤーの情報にアクセスするためのベースになるアドレスで、プレイヤーベースと呼ばれるものです。
Assault Cubeの場合、ポインター509b74がプレイヤーベースになります。

Cheat Engineメインウインドウ右下の「Add Address Manually」をクリックします。
Pointerにチェックを入れ、青枠に509b74を入力し、赤枠のアドレスをクリップボードにコピーします。


Cheat Engineメインウインドウにある「Memory View」をクリックします。


メニューバーのToolsから「Dissect data/structures」をクリックします。


コピーしたアドレスを張り付け、「Define new structure」をクリックします。


Structure Nameはなんでも良いので入力し、OKをクリックします。 



これで分析に使用するウインドウが出現しました。 
赤枠がオフセット、緑枠がデータ型、青枠がアドレスとそのアドレスが保持している値になります。
ゲーム内で情報が変化すれば、このウインドウの情報もリアルタイムで更新されます。
試しにオフセットのF8まで下にスクロールしてみましょう。


体力を確認できました。
他にも情報がないかスクロールしながら探していきます。
ゲーム画面と一致する値や、それらしい数字を見つけます。


3つほどありました。オフセット114の50も気になりますね。
ある程度情報を見つけたのでこのくらいにして、新たに設定していきます。
↓画像にある矢印のあたりで右クリックして「Lock」を選択します。


さらにメニューバーのFileから「Add extra address」をクリックします。


左に入力してあるアドレスと同じアドレスを右にも入力します。


そうすると↓画像のようになります。


赤枠のアドレスはロックされていて値は変化しません。ロック時の状態を保っています。
青枠のアドレスはゲーム内の情報が変わると値も更新されます。これで変化が一目瞭然です。
それではゲームに戻り、試しに銃を撃ってみましょう。


一発撃つと、アサルトライフルのマガジンの弾数が20から19になりました。
Dissect data structuresのウインドウを見るとオフセット150の値が20から19に変化しています。
ロック時の状態から値が変化すると画像のように文字が赤色になります。
これで、アサルトライフルのマガジンのオフセットとアドレスを見つけることが出来ました。
次はリロードしてアサルトライフルの残り弾数(予備マガジン)を40から39にしてみます。


見つけることが出来ました。
これでゲームを動かしながら、値を見つけるやり方が分かったと思います。

Cheat Engineのポインタースキャンを使おう!

Cheat Engineのポインタースキャンの使い方を、チュートリアルを用いて解説します。
チュートリアルはメニューバーのHelpから起動できます。



右下のSkipをクリックしてStep8まで進めてください。
Step8はマルチレベルポインターについてです。
ポインタースキャンで見つけたポインターの値を5000で固定し、Change pointerをクリックすれば、Nextボタンが押せるようになり、次のステップに進めます。
4レベルポインターです。
オフセットを4つ持っているポインターのことを4レベルポインターと呼びます。

手順は以下の通り
1.  値を見つける(value 1)
2. value 1のポインターマップを作る(マップ1)
3. Chage pointerをクリックし、ポインターを変更する
4. 再度、値を見つける(value 2)
5. value 2のポインターマップを作る(マップ2)
6. マップ1とマップ2を使いポインタースキャンをする
7. スキャンして見つけたポインターの値を5000に固定する
8. Chage pointerをクリックする

ポインタースキャンの目的は、いつ起動しても使える安定的なポインターを見つけることです。
ポインターマップとは、現在そのプロセスで使われているすべてのポインターのスナップショットのことです。
2つのポインターマップを使ってスキャンすることで作業効率が良くなります。
これは実際のゲームでも同じです。
 

それでは始めていきます。
Value:に現在の値を入力しFirst Scanをクリックします。


Change valueをクリックし、値を変更します。


真ん中のアドレス01921208の値が1912になりました。
これをアドレスリストに追加し、Descriptionを「value 1」に変更します。


value 1の行を右クリックからGenerate pointermapを選択します。


ポインターマップのファイル名を入力します。
ここでは「step8 map1 01921208」にしました。
ポインタースキャン用のフォルダを作り、そこに作成しています。


Change pointerをクリックし、ポインターを変更します。
もう一度value 1の時と同じ手順で値を見つけ、Descriptionを「value 2」にします。
そうするとアドレスリストは下画像のようになっていると思います。


ここからvalue 2の行を右クリックからGenerate pointermapを選択し、2つめのポインターマップを作成します。


2つめのポインターマップ名は「step8 map2 018AD920」にしました。


value 2の行を右クリックからPointer scan for this addressを選択します。


画像赤枠のUse saved pointermapにチェックを入れます。


018AD920に対応するポインターマップは2つ目に作ったポインターマップです。
したがって「step8 map2 018AD920.scandata」を選択し、開くをクリックします。


Compare results with other saved pointermap(s)にチェックを入れ、
画像赤枠のアイコンをクリックして比較するポインターマップを選択します。


マップ1を選択し、開くをクリックします。


プルダウンメニューから01921208=value 1を選択します。
マップ1に01921208=value 1
マップ2に018AD920=value2
となっています。


ポインタースキャンをする準備ができたのでOKをクリックします。
その他にも設定項目がありますが、ここではデフォルトのまま行います。
よく使う設定は以下の4つです。

Max different offsets per node
この数字を大きくすることでヒットするポインターの数が増え、スキャン時間も長くなります。
デフォルトでは3になっていますが、2にするだけで大幅にスキャン時間を減らすことができます。
まずは2から始めるのがおすすめです。
Pointers must end with specific offsets
最終オフセットが分かっている場合に入力することで、検索範囲を絞れます。
Maximum offset value
最大オフセット値を10進数で入力します。
Max level
ポインターパスの深さ、最大レベルを入力します。


ポインタースキャン結果のファイル名を入力します。


ポインタースキャンが完了しました。
見事に1つだけに絞り込まれています。
ダブルクリックしてアドレスリストに追加します。


pointerscan resultがスキャンで見つけたポインターです。
435のあたりをダブルクリックして値を5000に変更し、
Activeにチェックを入れます。


Change pointerをクリックします。


Nextボタンが押せるようになりました!
これでStep8はクリアです。

スキャン結果をソートする

Max levelを5でスキャンした場合、スキャン結果のウインドウで一番右のオフセットが6になっているので、
そこをクリックするとオフセットの少ない順にソートできます。
実際のゲームではポインターが100~1000以上見つかるはずなので、
そのような時に少ない順にソートして上から順に使えるかテストしていけば効率がよいです。

Cheat Engineでゴッドイーター3をチートしよう! スクリプト編

Cheat Engineでよくあるのが、体力や弾丸のアドレスを見つけたのに再起動すると使えないといった問題です。
これは動的に確保されたメモリ領域の場合に起こる現象です。
今回はスクリプトを使ってアドレスを自動検出することで、その問題を解決してみようと思います。
これをしておくと、後々ポインタースキャンをする時に作業が楽になります。
このゲームの場合ミッションが変わるごとにアドレスが変わるようになっています。

この記事の内容はゴッドイーター3だけでなく、他のゲームにも応用できることなのでぜひ読んでみてください。

スクリプトでアドレスを自動検出する


HPのアドレスを見つけた状態から始めていきます。
HPのアドレスを見つける方法は後日追記します。


「Find out what accesses this address」を使用して、HPのアドレスにアクセスしている命令を見つけにいきます。


デバッガーでge3.exeにアタッチするのでYesをクリックします。


アクセスしている命令を見つけました。
左のCountにある数字がすごい勢いでカウントアップされているのを確認できます。
これはその命令が実行されている回数を表しています。
もうデバッグは必要ないので右下のStopをクリックして中止します。


スクリプトを組む命令を決めます。
どの命令でもできると思いますが一番上の命令に組むことにします。
ミッション開始と同時に実行される命令にスクリプトを組めば、スクリプトをオンにした瞬間に狙いの動作を実現できます。
スクリプトを組む命令を選択してShow disassemblerをクリックします。


逆アセンブラウィンドウが表示されました。
対象としている命令はアドレス 7FF74CB28FDA の movss xmm1, [rbx+10] です。
このアドレスは皆さんと異なるかもしれません。
同じゲームでもバージョンによってアドレスがずれている可能性があるからです。


対象の命令の上で右クリックから「Find out what addresses this instruction accesses」を選択します。
この命令がアクセスしているアドレスを見つけてくれます。
大変便利な機能で使う頻度も多いです。


Stopをクリックしてデバッグを中止します。
画像のように3つのアドレスにアクセスしているのを確認できます。
上からプレイヤーのHPのアドレス、hugoのHPのアドレス、zekeのHPのアドレスになっています。
実はこの命令、ブレークポイントを設置してみればわかるのですが、プレイヤー、hugo、zekeのように順々にアクセスしながらループしているため、このままスクリプトを組んでもうまく動作しません。
検出したいのはあくまでプレイヤーのアドレスだけです。
したがって、自分と味方1、味方2を区別できる値を見つける必要があります。


プレイヤーのアドレスを選択後、右クリックから「Find commonalities between addresses」、「Mark selection as group 1」を選択します。
プレイヤーのアドレスをグループ1にその他をグループ2に設定しています。
グループ1を設定すればその他は自動的にグループ2になります。


再度右クリックから「Find commonalities between addresses」、「Scan for commonalities」を選択します。


逆アセンブラ画面に戻り確認するとわかりますが、この命令ではRBXにベースアドレスが格納されているので、RBXの行をダブルクリックします。


Scanをクリックします。


画像のように3つのアドレスが保持している値の相違を一目で確認できるようになりました。
一番左の黒字の数字はオフセットです。
ここでは自分と味方1、味方2を区別できるような値をスクロールしながら探します。
そうするとオフセット150にそれらしい値がありました。


プレイヤーが0、hugoが1、zekeが2になっています。
ここで間違いないでしょう。
この「Find commonalities between addresses」は、例えば銃の打ち合いをするゲームで青チームが1、赤チームが2といった具合で共通する値を探すときにも使用します。
Cheat Engineの機能「Dissect data structure」でも同じことができます。

これでスクリプトを作る準備ができました。

スクリプトを作ろう!


逆アセンブラウインドウのメニューバーからTools、Auto Assembleを選択します。


TemplateからAOB Injectionを選択します。


AOB Injectionのテンプレートが挿入されました。
ここから新たにコードを追加していきます。


// <- の行が変更を加えた箇所です。

Cheat Engineのスクリプトは2つのセクションがあります。
[ENABLE] セクションに書いたコードはスクリプトがオンになった時に実行されます。
[DISABLE]セクションに書いたコードはスクリプトがオフになった時に実行されます。

aobscanmodule    -> プロセス内から指定された16進数コードを探し、そのアドレスを取得する。
label      -> アドレスにラベル名をつける。
alloc      -> allocateの略でメモリを確保する。
dealloc  -> 確保したメモリを開放する。
registersymbol     -> アドレスをシンボルに登録してスクリプト外でも使えるようにする。
unregistersymbol -> 登録したシンボルを解除する。
db -> 指定されたアドレスにバイト配列を書き込む。
          上のスクリプトだとINJECTに F3 0F 10 4B 10 の計5バイトを書き込んでいる。

cmp  -> compareの略で値を比較する。
jne    -> jmp not equalの略で比較した値が同じでなければ指定されたアドレスにジャンプします。
             cmpとセットで使われることが多い。
mov  -> 値をコピーする。

Cheat Engine7.1からの変更点
 dealloc(newmem)
 dealloc(healthBase)
 と従来2行にわたって書かなければならなかったのが
 dealloc(newmem healthBase)
 と間にスペースを入れることで1行で書けるようになりました。
 Cheat Engine7.1未満をお使いの方は各所変換しながらコードを書いてください。

スクリプトの詳細な説明はこちらから


メニューバーのFileからAssign to current cheat tableを選択します。


Cheat Engineのメインウインドウに戻ると Auto Assemble script というスクリプトが追加されているので、画像の赤枠部分をクリックしてスクリプトをオンにしましょう。
スクリプトを再編集したいときは、<script>のあたりをダブルクリックします。

Cheat Engineメインウインドウの右下にある「Add Address Manually」をクリックして手動でアドレスを設定します。


Pointerにチェックを入れます。


スクリプトでシンボルに登録したhealthBaseとオフセットの10を入力します。
TypeをFloatに変え、DescriptionはHPにしときます。


チートテーブルを見ると無事にHPのアドレスを検出できています。

チートテーブルを整理する

ここからする設定は見栄えの問題で必須ではありませんが、やっておくとチートテーブルがすっきりします。


HPのアドレスの行を左クリックしたままスクリプトの行に重ねるようにドラッグ&ドロップします。


スクリプトの行の上で右クリックから、「Group config」、「Hide children when deactivated」を選択します。
これでスクリプトをオフにするとHPのアドレスも自動で隠れるようになります。
Group configには他にも便利な設定があるのでいろいろ試してみてください。

OPのアドレスも検出する


HPとOPのアドレスを見てみるとアドレスが似ていることに気付きます。
これは2つのアドレスの距離が近いことを示しています。
OPのアドレスからHPのアドレスを減算してみると
1AA73AC6340 - 1AA73AC6424 = E4
になり、HPのアドレスからOPのアドレスまでの距離はE4であることが分かりました。
これを利用してついでにOPのアドレスも自動で検出できるように設定します。


HPのアドレスの行を選択し、Ctrl+C、Ctril+Vでアドレスを複製します。
右クリックからコピー、張り付けすることもできます。


Pasteをクリックします。


矢印のあたりをダブルクリックしてアドレスを編集します。


オフセットの10に+E4を付け足し、DescriptionをOPに変更します。


これでHP、OPと、2つのアドレスを検出できるようになりました。
もちろん値を固定することもできます。
ゲームを再起動したり、ミッションを変えても無事検出できることを確認できたら完成です。

スクリプト補足説明

以下のコードはコピペできます。
ピンク字が変更箇所です。
ge3.exe -> 7ff74c4f0000 モジュールのベースアドレス
ge3.exe+638FDA -> 7FF74CB28FDA  スクリプトを組んだ命令
ge3.exe+638FDF -> 7FF74CB28FDF  スクリプトを組んだ命令の次の命令
[ENABLE]

aobscanmodule(INJECT,ge3.exe,F3 0F 10 4B 10 F3 0F 10)  // ge3.exeプロセス内から指定された16進数コードを探しアドレスを取得する
alloc(newmem,$1000,"ge3.exe"+638FDA)  // スクリプト用に1000バイトのメモリを確保する
alloc(healthBase, 8)        // 8バイトのメモリを確保する

label(code)                 // アドレスにラベル名をつける
label(return)               // returnはge3.exe+638FDFを表している

newmem:
  cmp [rbx+150], 0          // プレイヤーのアドレスかそれ以外かを比較
  jne code                  // プレイヤーのアドレスでなければcode:へジャンプする
  mov [healthBase], rbx     // healthBaseにプレイヤーのアドレスをコピーする
code:
  movss xmm1,[rbx+10]       // プレイヤーのアドレス以外ならここにジャンプしてきてコードが実行される
  jmp return                // ge3.exe+638FDFにリターンする

INJECT:                     // INJECT:はアドレスge3.exe+638FDAを示している
  jmp newmem                // ge3.exe+638FDAのmovss xmm1, [rbx+10] を jmp newmem に書き換える
return:                     // ge3.exe+638FDAの次のコードge3.exe+638FDFにリターンする
registersymbol(INJECT healthBase)      // INJECTとhealthBaseをシンボルに登録する

[DISABLE]

INJECT:                    
  db F3 0F 10 4B 10        // 上で書き換えたge3.exe+638FDAのjmp newmemをmovss xmm1, [rbx+10]に戻す 
                           // movss xmm1, [rbx+10]は16進数コードだとF3 0F 10 4B 10になる
                            
unregistersymbol(INJECT healthBase)    // INJECTとhealthBaseをシンボルから解除する
dealloc(newmem healthBase)             // 確保したメモリを開放する(newmemとhealthBase)