「コサック」のリバースエンジニアリングと減速





有名なゲーム「Cossacks:War Again」には、ネットワークゲームの楽しさをゼロにするバグがあります。それは、現代のコンピューターでのゲームプロセスの非人道的なスピードです。 同時に、設定でゲームの速度を変更しても、シングルプレイヤーモードで完全に動作し、ネットワーク上のゲームで何が起こっているかに影響しません。 このトピックは多くのフォーラムで議論されていますが、最も一般的なヒントは次のとおりです。



  1. ゲームが実行されているプロセッサのコアを人工的にロードします
  2. リソースが限られている仮想マシンでゲームを実行する
  3. ローカルネットワークではなく、インターネットで再生します-さらに遅延があります


最初の2つのオプションは、ゲームが遅いという事実につながりますが、ジャークがあります。 音質も低下します。 3番目のオプションは、コメントなしです。



降りる



まず、 Cheat Engineを使用して速度設定を探します。 これは、設定メニューのスライダーの位置に直接比例する特定の乗数、または間隔に反比例する必要があります。 このメモリセルは非常に迅速に配置されます。







この間隔は最大速度で0であり、私にとって快適なゲーム速度は間隔20に対応します。ゲーム中に値を変更すると、すぐにゲームプロセスの速度に影響します。 それでは、ネットワークゲームの内容を見てみましょう。 起動し、Cheat Engineの値を変更しますが、...何もありません。 設定内のスライダーの位置のみが変更されます。 さて、この間隔が処理される場所を見てみましょう。 Cheat Engineは、セルが読み取られる2つの隣接アドレスのみを表示します。







組み込みの逆アセンブラーのリストを見てください。







端的に言えば、間隔は2倍になって何かと比較され、2倍に満たない場合はどこかに戻ることができます。 まあ、私たちはそのような問題のために私たちが愛するタタールプログラムを立ち上げ、1つの長い機能の最後にこの写真を見る:



掘り下げてみると、次のことがわかります。





間違いを探す



間隔との比較のサブプロシージャのどこかにブレークポイントを設定します。 1つのゲームを開始し、すぐにデバッガーに飛びます。 ネットワーク上でゲームを開始します-ブレークポイントは機能しません。 さらに、関数の最初にブレークポイントを配置すると、常に機能します。 インターバルチェックが意識的に実行されないのは、マルチプレイヤーゲームであることがわかります。 残っている唯一のものは小さいです。この関数の原因となる分岐を見つけて、常に必要なサブプロシージャloc_4D1ABFの分岐が常に実行されるように変更します。



下から上に行きます。 まず、サブルーチンloc_4D1A9Cにブレークポイントを設定します。 ビンゴ! シングルプレイヤーゲームでは、word_611B60変数は常に1に等しいため、ジャンプ条件は満たされず、制御は最初にloc_4D1AAAに転送され、そこからサブプロシージャに転送されます。 ネットワークで再生する場合、word_611B60変数は常に2に等しくなります。これにより、loc_4D1AE6と関数の最後にすぐにジャンプします。 ゲームが常にloc_4D1ABFサブプロシージャを使用して制御をブランチに転送するには、 cmp edx、2比較命令をcmp edx、3に置き換えるだけで十分です。 アスファルトの2バイトのように!




それほど単純ではない



ネットワークゲームで速度設定が機能するようになりました。 残念ながら、軟膏のハエは消えませんでした。時間の経過とともに、プレイヤーの1人がスクロールの速度と水のアニメーションの速度を大幅に上げ始めます。 しばらくすると、エフェクトが消え、他のプレーヤーに表示されます。 同時に、残りのゲームプロセスの速度は両方で同じであり、設定で設定された速度に対応します。



この動作の理由を見つけることはできませんでしたが、ProcessMessagesの原因となったloc_4D1AAAサブプロシージャの強い疑いがありました。 どうやらこのゲームはネットワーク経由でプレイするために必要ではなかったようです。 おそらく、このサブプロシージャが回避されたのは、上記の奇妙な振る舞いのためだったのでしょうか? いずれにせよ、ブランチから除外しようとするので、loc_4D1ABFサブプロシージャのみが有用です。



電卓を入手します



だから私たちがする必要があること:



  1. ジャンプをloc_4D1A9C サブプロシージャ内の条件に置き換え、loc_4D1ABF サブプロシージャへの短い直接ジャンプに置き換えます。
  2. loc_4D1ABF サブプロシージャのジャンプバックオフセットを変更して、loc_4D1AAAに入らないようにロックます。
  3. loc_4D1ABF サブプロシージャの直後のブロックにあるloc_4D1AAAへの2番目のジャンプ命令を削除します


最初の段落では、すべてが明確です。短いジャンプの動作コードはEBであり、必要なオフセットは、サブプロシージャloc_4D1A9Cの開始アドレスからジャンプ命令に続くバイトのアドレスを減算することにより決定されます。



3番目の段落はさらに簡単です。ジャンプ命令を2つのnopに置き換えます。



2番目のポイントでは、短いジャンプバックのオフセットを計算する必要があります。 幸いなことに、このアクションのアルゴリズムを明確に説明している記事に出くわしました。次のバイトジャンプ命令のアドレスから宛先アドレスを減算し、 1hを減算してから、数値を(バイトサイズで)バイナリ形式に変換し、それを16進数に逆変換して変換しますシステム。 結果の数値は、必要なオフセットです。



それでは紳士。 パッチ!











結果



同じ最愛のプログラムで変更されたファイルを開き、次の図を参照してください。







ProcessMessagesへの呼び出しを含むブロックは実行されません。 loc_4D1A9Cサブプロシージャでマルチプレーヤーゲームのチェックをオフにした後、GetTickCount呼び出しと間隔との比較を使用して、サブプロシージャに制御が渡されます。 差が間隔より小さい場合、間隔が尊重されるまで、サブプロシージャはそれ自体の先頭にジャンプして戻ります。



これで、ゲームは正常に動作します。 ゲームの速度は、プレーヤーの中で最低の速度に対応し、同期は壊れません。 スクロール速度もカスタマイズ可能です。



あとがき



これはリバースエンジニアリングとアセンブラでの作業における私の最初の経験なので、おそらくこれは最もエレガントなソリューションではありません。 問題の根本は、ゲームのタイミングのベースとなるQueryPerformanceFrequencyおよびQueryPerformanceCounter関数の使用にあります。 これらの関数は、新しいゲームを作成するときに一度呼び出され、その後のすべての計算のトーンをGetTickCountで設定します 。 残念ながら、プログラムのこの部分に適切に影響を与えることができませんでした。 怠けすぎで、上記の関数の呼び出しの周りで奇妙な算術演算が行われていたためです。



UPD:注意深く分析すると、 QPFおよびQPC機能が他の目的に使用されていることがすぐに明らかになります。 結果は要約されており、QPCはその後他の場所では呼び出されません。 結果を格納する変数は、文字列を処理する関数を呼び出す前に使用されます。 したがって、ここでのQPFとQPCは、ランダムマップおよび/またはマップファイル名を作成するプロセスでPRNとしてのみ使用される可能性が高いです。



参照資料





UPD: VRVのリクエストにより、Steamから入手可能な「コサック」のバージョンにもパッチが適用されました。 彼が作成した議論はLCNフォーラムにあります。



Cossacks実行可能ファイルのさまざまなバージョンのパッチャーは、 ここから入手できます



All Articles