Retas Age of Empires III untuk mengubah pengaturan kualitas shader

Awal Mei 2020 - jika Anda seperti saya, maka karantina membuat Anda mengulang game yang belum diluncurkan selama bertahun-tahun.

Dan jika Anda bahkan lebih seperti saya, maka di suatu tempat Anda mungkin memiliki disk Age of Empires 3. tergeletak di sekitar. Mungkin Anda sedang bermain di Mac, mungkin Anda belum meng-upgrade ke Catalina dan ingin memerintah Morgan Black.

Jadi, Anda memulai permainan, masuk ke menu utama, dan segera perhatikan bahwa ada sesuatu yang salah ... Menu ini tampak menjijikkan .


Jika Anda bertanya-tanya apa sebenarnya โ€œmenjijikkan,โ€ maka perhatikan airnya. Segala sesuatu yang lain juga mengerikan, tetapi kurang jelas.


Jadi, Anda masuk ke opsi, naikkan semua parameter ke maksimum ... Tapi gamenya masih jelek.

Anda melihat bahwa opsi " Kualitas Shader " curiga dikunci ke " Rendah ."

Mencoba No. 1 - parameter hack


Pada titik ini, Anda mulai mencari folder game, yang seharusnya dibuat di suatu tempat di folder Documents, karena game itu pada tahun 2005, dan kemudian semua orang melakukannya.

Apa yang kita cari Tentu saja, file tempat parameter disimpan. Antarmuka menu tidak memungkinkan kita untuk mengubah pengaturan, tetapi kami rumit, kan?

Kami menemukan XML yang diperlukan, karena gim ini 2005 dan itu, tentu saja, XML, tempat kami menemukan opsi " optiongrfxshaderquality ", yang disetel ke 0. Tampaknya kami sedang mencarinya, jadi kami meningkatkan nilainya menjadi 100, karena tidak ada banyak kualitas. Di sini saya setuju dengan Anda.


Anda juga mungkin memperhatikan bahwa ini adalah penggunaan XML yang buruk. Untungnya, ia tidak memiliki manfaat.

Puas dengan diri kita sendiri, kami meluncurkan game. Sayangnya, tidak ada yang berubah. Melihat lagi pada file XML, kita melihat bahwa parameter kembali diatur ke 0. Ensemble Studios (semoga tenang) terlihat dengan jijik sama sekali kecerdikan Anda.

Setelah itu, kita bisa menyerah . Ini adalah Mac, oleh karena itu, tambalan yang ditulis oleh pengguna tidak mungkin ditemukan (pada kenyataannya, saya memeriksa - ada semua jenis solusi yang meragukan yang mungkin berhasil). Semuanya tentang grafis. Tampaknya untuk memperbaiki masalah akan sulit.

Tapi kami tidak akan menyerah. Karena kita ingat seperti apa seharusnya usia 3. Kita ingat bagaimana kita mengagumi air bergelombang yang indah ini. Efek partikel ini. Ini kapal-kapal besar yang tidak cocok dengan bingkai. Dan, hanya saja kamera diperbesar, apa yang baru saja saya pikirkan?

Mencoba No. 1 - meretas data


Di bagian paling awal folder di Dokumen ada file log. Gim ini menulis kartu grafis di sana dan melaporkan bahwa ia telah memilih pengaturan "generik dx7". Dikatakan bahwa Anda telah menginstal Intel GMA 950, yang sebenarnya ada di komputer sebelumnya.

Tetapi Anda memiliki Intel Iris Graphics 6100. Ada yang salah. Anda membuat asumsi - gim menentukan kemampuan grafis komputer, membandingkannya dengan basis data kartu grafis alih-alih memeriksa kemampuan kartu itu sendiri. Karena itulah yang dilakukan pengembang AAA, dan jika Anda bekerja pada RTS open-source , maka Anda tahu bagaimana ini terjadi.


Isi file log. Jika Anda merasa lucu bahwa Intel ditetapkan sebagai 0x8086, maka Anda telah memilih artikel yang sesuai.

Secara kebetulan, Anda menemukan catatan tempel lama yang mengkonfirmasi kekhawatiran Anda. Anda melihat ke dalam folder GameData, tetapi hanya ada file .bar dan .xmb yang sebagian besar tidak dapat dibaca. Anda mencari grep " Intel " dan " dx7 ". Tidak ada yang terjadi. Hapus beberapa file ... Tidak ada yang terjadi. Tapi Anda tahu bahwa game AAA dapat menyimpan beberapa sumber daya di tempat-tempat aneh, dan ini adalah port dari game Windows di Mac, jadi semuanya bisa sangat aneh di sini.

Pada akhirnya, Anda menemukan file "render.bar"; jika Anda memindahkannya, game akan mogok saat dimulai. Tidak terlalu jelas, tapi tidak buruk. Kami membuka file dalam Hex Fiend , karena program ini biasanya memungkinkan Anda mempelajari sesuatu tentang file biner. Dan ternyata demikian. Bagian dari data ini adalah teks yang dapat dibaca. Beberapa dari mereka adalah shader. Tetapi sebagian besar data anehnya dibagi oleh ruang kosong ...


Hex Fiend Interface Ini minimalis, tetapi berhasil. Byte pertama adalah huruf "ESPN", yang kemungkinan besar adalah header .bar.

Anda berseru: "Jelas, ini UTF-16!" Gim ini dibuat untuk Windows, jadi logis jika ia menyimpan teks dalam UTF-16, menghabiskan hampir setengah dari file data 30 megabyte. Lagi pula, Windows menyukai UTF-16 (walaupun seharusnya tidak ), jadi mengapa tidak menggunakan pengkodean ini dan ASCII shader dalam satu file. Itu ide yang logis!

(Sebenarnya, saya butuh waktu sekitar satu hari untuk mengetahuinya)

Hex Fiend memiliki opsi untuk mengurai byte seperti UTF-16, jadi sekali lagi kami mencari string grep dx7. Ada beberapa entri. Kami menggantinya dengan garis dx9, yang juga ditemukan dalam data, menyimpan file dan meluncurkan game.

Ya Log lagi tidak menampilkan "dx9". Ini mungkin diatur di tempat lain.

Mari kita coba yang lain. Misalkan gim mengenali kartu grafis, dan pengidentifikasi heksadesimalnya disimpan dalam log (dalam kasus saya, 0x8086 adalah Intel, dan juga 0x2773). Kami mencari "8086" di Hex Fiend, kami menemukan sesuatu, dan bagian dari teks terlihat menjanjikan. Intel 9 Series adalah seri GMA 950, dan Usia 3 mungkin tidak bekerja dengan baik.


Beberapa angka seperti pengidentifikasi kartu (2582, 2592); mungkin jika Anda menggantinya, maka sesuatu akan berubah. Kami membuat salinan file, jadi Anda bisa mencoba.

Sayangnya, ini juga jalan buntu. Sebenarnya, kami mempelajari file yang salah. Untungnya, saya dapat memberitahu Anda ini karena saya menghabiskan banyak waktu untuk itu dan mencoba terlalu banyak. Sialan ini butuh sekitar 20 jam. Dan ini belum termasuk waktu untuk semua tangkapan layar ... File yang

diinginkan disebut "DataP.bar", dan jika kita dengan bijak mengganti ID, kita akan melihat sesuatu yang sama sekali baru di log:


Saya tidak mengerti mengapa "generik DX7" ini tidak sama seperti pada tangkapan layar di atas.

Tentu saja, dalam permainan kita masih dibatasi oleh pengaturan "Rendah", yaitu, log tidak berbohong. Kami berharap untuk Medium, tapi sayangnya .

Tampaknya lebih banyak dari data peretasan tidak dapat dicapai. Sudah waktunya untuk mendapatkan alat dengan serius.

Mencoba No. 2 - peretasan


Jadi, jika tidak ada yang berhasil, kita memiliki satu hal lagi yang tersisa: untuk mengubah file yang dapat dieksekusi itu sendiri (yaitu, seperti yang mereka katakan pada tahun 2005, "crack" itu). Tampaknya hari ini hanya disebut peretasan, tetapi istilah ini telah dipertahankan di bidang pembajakan game (tentu saja, saya pribadi tidak pernah melakukan ini).

Kami beralih ke bagian paling menarik dari artikel (ya, Anda sudah membaca ribuan kata, tapi bukankah itu penasaran?).

Disassembler Hopper


Pada tahap ini, alat kami akan menjadi disassembler: aplikasi yang menerima kode biner dari file yang dapat dieksekusi dan mengubahnya menjadi kode assembler yang dapat dibaca (ha ha). Yang paling populer di antaranya adalah IDA Pro, tetapi harganya sangat mahal dan saya tidak yakin apakah itu berfungsi pada Mac, jadi saya akan menggunakan Hopper . Ini tidak gratis (biaya $ 90), tetapi dapat digunakan secara bebas selama 30 menit sekaligus dengan hampir semua fungsi yang diperlukan.

Saya tidak akan menjelaskan antarmuka secara rinci, karena semuanya tersusun dengan baik dalam tutorial Hopper , tetapi saya akan memberi tahu Anda apa yang akan saya lakukan dan mencoba mengambil tangkapan layar yang cukup sehingga Anda dapat mempelajari sesuatu.

Pertama, Anda perlu mengatakan: saya butuh lima hari untuk menyelesaikannya, dan banyak upaya yang gagal dilakukan. Pada dasarnya, saya akan berbicara tentang langkah-langkah sukses, jadi itu akan tampak ajaib. Tetapi itu sulit . Jangan khawatir jika Anda memiliki masalah.

Dan satu lagi catatan: "cracking" paling sering ilegal dan / atau dilakukan untuk tujuan ilegal. Saya menunjukkan contoh yang agak jarang dari penggunaan yang cukup sah, yang pada saat yang sama sepenuhnya "topi putih", jadi semuanya beres di sini. Saya akan berasumsi bahwa, sebenarnya, ini ilegal / bertentangan dengan perjanjian pengguna akhir, tetapi, jelas, ini adalah kasus force majeure . Bagaimanapun, bayar untuk apa yang Anda suka, dan hanya untuk itu, karena anti-konsumerisme itu keren.




Seret aplikasi Age 3 untuk membukanya. Cukup pilih "x86" alih-alih powerPC, karena gim ini adalah sesuatu 2005.

Tahap 1 - mencari fragmen yang diinginkan


Anehnya, ini mungkin yang paling sulit. Kami memiliki kode yang dibongkar, tetapi kami tidak tahu ke mana harus mencari. Ya, beberapa game memiliki simbol debugging, sehingga Anda dapat membaca nama fungsi, tetapi tidak dalam kasus kami. Hopper memberi kita nama seperti "sub_a8cbb6", yang harus kita tangani sendiri.

Untungnya, kami memiliki kepala mulai: file log kami . Sangat mungkin bahwa string literal digunakan saat menulis ke log, yaitu, ASCII di-flash dalam file yang dapat dieksekusi. Kami dapat mencarinya dengan grep, karena kami tidak akan menghapusnya dengan kompilasi apa pun (namun, mereka dapat disembunyikan dengan kebingungan kode. Untungnya, ini tidak terjadi.). Menemukan literal string grep biasanya adalah hal pertama yang mereka lakukan ketika membongkar sebuah program. Jadi, mari kita masukkan "vendor yang ditemukan" di kotak pencarian Hopper dan ia akan menemukan sesuatu:


Oh ya, string literal, saya memujanya.

Ini sendiri tidak terlalu memajukan kami. Tetapi karena alamat "string literal" ini bersandi keras, kita dapat menemukan tautan ke sana. Hopper menyorotnya dalam warna hijau di sebelah kanan: "DATA XREF = sub_1d5908 + 1252." Mengklik dua kali pada garis, kita akan beralih ke pahlawan kita - prosedur sub_1d5908 .


Di sini kita menemukan kode assembler. Diyakini sulit untuk dibaca, tetapi tidak. Hal yang paling sulit adalah memahaminya.

Secara default, Hopper menggunakan "Intel syntax", yaitu operan pertama adalah penerima, dan yang kedua adalah sumber. Pada baris yang disorot kita lihat mov dword [esp+0x1DC8+var_1DC4], aXmlRenderConfi. Mari kita lakukan.

Baris pertama kami di assembler


movAdalah tim MOV yang dikenal dengan T86-lengkap . Ini memindahkan (sebenarnya menyalin) data dari A ke B, yang pada dasarnya berarti membaca A dan menulisnya ke B. Berdasarkan uraian di atas, aXmlRenderConfiini adalah A, dan dword [esp+0x1DC8+var_1DC4]ini adalah B, penerima. Mari kita lihat lebih dekat.

aXmlRenderConfi- ini adalah nilai yang Hopper membantu kami. Ini sebenarnya adalah alias untuk alamat memori string literal yang kami klik beberapa detik yang lalu. Jika Anda membagi jendela dan melihat kode dalam mode Hex (yang merupakan mode yang disukai untuk peretas), kita akan melihat di sana 88 18 83 00.


Hopper dengan mudah menyoroti fragmen terpilih yang sama di kedua jendela.

Jika nilai "flip" benar, maka kita akan mendapatkan 0x00831888 - alamat memori dari string literal (dalam salah satu tangkapan layar yang ditunjukkan di atas, bahkan disorot dengan warna kuning). Jadi, satu teka-teki terpecahkan: kode mengeksekusi MOV, yaitu menulis alamat 0x00831888.

Mengapa ini ditulis sebagai 88 18 83 00, bukan bagaimana 00 83 18 88? Ini terjadi karena urutan byte - topik yang agak membingungkan yang biasanya berdampak kecil. Ini adalah salah satu hal yang membuat orang mengatakan bahwa "komputer tidak berfungsi." Bagi kami, ini berarti bahwa masalah ini akan terjadi di semua angka yang akan kami gunakan hari ini.

Anda mungkin memperhatikan bahwa saya mengatakan "tuliskan alamatnya", dan itu benar: kami tidak terlalu peduli dengan isinya, yang ternyata berupa string literal dengan nol byte di bagian akhir. Kami menuliskan alamat karena nanti kode akan merujuk ke alamat ini. Ini adalah sebuah pointer.

Bagaimana dengan dword [esp+0x1DC8+var_1DC4]? Semuanya lebih rumit di sini. dwordartinya kita bekerja dengan "kata ganda (double-words)", yaitu, dengan 16 * 2 bit. Artinya, kami menyalin 32 bit. Ingat: alamat kami adalah0x00831888, yaitu, 8 karakter heksadesimal, dan setiap karakter heksadesimal dapat memiliki 16 nilai, yaitu 4 bit data. Jadi kita mendapatkan 8 * 4 = 32. Omong-omong, notasi "32-bit" untuk sistem komputer datang dari sini. Jika kita menggunakan 64 bit, maka alamat memori akan ditulis sebagai 0x001122334455667788, akan dua kali lebih panjang dan 2 ^ 32 kali lebih besar, dan kita harus menyalin 16 byte. Jadi kita akan memiliki lebih banyak memori, tetapi menyalin pointer akan membutuhkan kerja dua kali lebih banyak, dan karena itu 64 bit sebenarnya tidak dua kali lebih cepat dari 32 bit. Ngomong-ngomong, penjelasan yang lebih rinci tentang istilah "kata" dapat ditemukan di sini . Karena itu, tentu saja, lebih rumit daripada penjelasan saya.

Jadi, bagaimana dengan bagian dalam tanda kurung? Tanda kurung berarti kita menghitung apa yang ada di dalamnya, dan menganggapnya sebagai penunjuk. Oleh karena itu, perintah MOV akan menulis ke alamat memori yang diperoleh dari perhitungan. Mari kita lihat lagi secara lebih detail (dan jangan khawatir, ketika Anda selesai dengan ini, Anda pada dasarnya akan tahu cara membaca kode assembler dengan sintaks Intel).

espitu adalah register yang di assembler adalah yang paling dekat dengan variabel. Ada banyak register dalam arsitektur x86, dan register yang berbeda memiliki cara aplikasi yang berbeda, tetapi kami tidak akan terlalu peduli. Pelajari lebih lanjut tentang ini di sini.. Perlu diketahui bahwa ada register khusus untuk angka floating point, serta "bendera" yang digunakan untuk perbandingan dan operasi serupa. Yang lainnya hanyalah angka heksadesimal yang Hopper ulang sedikit untuk membantu kami. var_1DC4- ini -0x1DC4, kita bisa melihatnya di awal prosedur, atau di panel kanan. Ini berarti bahwa hasil perhitungan [esp+0x1DC8+var_1DC4]akan [esp + 0x1DC8 โ€” 0x1DC4]sama dengan [esp + 4]. Intinya, ini berarti "ambil nilai 32-bit dari register ESP, tambahkan 4, dan interpretasikan hasilnya sebagai alamat memori."

Jadi, kami ulangi: kami menulis alamat string literal di "esp + 4". Ini informasi yang benar, tetapi sama sekali tidak berguna. Apa artinya ini?

Ini adalah kesulitan dalam membaca kode assembly: di parsing apa yang harus dilakukan. Di sini kita memiliki awal: kita tahu bahwa dia kemungkinan besar menulis sesuatu ke log. Oleh karena itu, kita dapat mengharapkan panggilan ke fungsi yang menulis ke log. Mari kita cari dia.


Pada tangkapan layar di atas, ada beberapa panggilan serupa, serta perintah callyang memanggil prosedur. Hopper biasanya membicarakan hal ini (saya tidak tahu mengapa dia tidak melakukannya di sini), tetapi pada dasarnya, kami memberikan argumen untuk memanggil fungsi. Jika Anda mengeklik panggilan subrutin yang berbeda, kami menemukan sesuatu yang disebut imp___jump_table___Z22StringVPrintfExWorkerAPcmmPS_PmmPKcS_. Ini adalah nama yang diterjemahkan dari fungsi, dan bagian "Printf" sudah cukup untuk memahami bahwa itu benar-benar menghasilkan sesuatu. Kami tidak peduli bagaimana dia melakukannya.

Ambil langkah mundur


Pada tahap ini, kami sepenuhnya menyimpang dari apa yang kami lakukan: kami mencoba meyakinkan permainan bahwa kartu grafis 2015 kami cukup kuat untuk meluncurkan permainan 2005. Mari kita simpulkan. Kami menemukan tempat di mana log sedang direkam. Jika kita beruntung, maka di sini adalah kode yang memeriksa parameter dan kemampuan kartu grafis. Untungnya, kami benar-benar beruntung (ada kemungkinan besar kalau tidak, artikel ini tidak akan).

Untuk mendapatkan gambaran besar, kita akan menggunakan fungsi Grafik Alur Kontrol Hopper, yang memecah kode menjadi blok kecil dan menggambar panah yang menunjukkan transisi yang berbeda. Ini jauh lebih mudah dipahami daripada di assembler biasa, di mana seringkali semuanya berantakan.


Panggilan ke entri log berada di tengah fungsi yang sangat panjang, karena ini, sekali lagi, biasanya terjadi di game AAA. Di sini kita harus mencari literal string dan "petunjuk" Hopper lainnya, berharap menemukan sesuatu. Pada awal prosedur adalah sisa dari logging, dan di bawah ini ada bagian yang menarik tentang parameter "memaksa dx7" dan "Sangat Tinggi", serta masalah dengan versi pixel shaders ... yang kita miliki.

Log dari data "diretas" kami melaporkan bahwa kami memiliki pixel shaders versi 0.0, dan mereka tidak kompatibel dengan parameter "Tinggi". Kode ini terletak di sudut kiri bawah fungsi kami, dan jika Anda melihat lebih dekat, Anda dapat melihat sesuatu yang aneh (disorot oleh pelayan Anda yang rendah hati): ini adalah kode yang sama empat kali diulang untuk parameter "Sangat Tinggi", "Tinggi", "Sedang" dan "Rendah". Dan ya, saya butuh beberapa jam untuk menemukan ini.


Saya menyoroti dengan warna biru blok di mana "pixel shader versi 0.0 High" ditampilkan dalam log. Saya menyoroti bagian yang serupa dengan warna-warna indah lainnya.

Satu-satunya perbedaan adalah bahwa kita menulis "0x0", "0x1", "0x2" dan "0x3" di tempat yang berbeda. Ini mencurigakan mirip dengan file parameter yang kami periksa di atas dan parameter qualitygrfxshaderquality (Anda mungkin belum memahami ini. Tapi, percayalah).

Pada tahap ini, kita dapat mencoba untuk pergi dengan cara yang berbeda, yang saya lakukan. Tapi saya akan singkat - kami akan melakukan yang paling penting: cari tahu mengapa verifikasi piksel shader gagal. Mari kita mencari cabang. Blok output ke log adalah linear, yang berarti harus berada di suatu tempat di atas.


Penggemar SEMVER, lihat format versi lebih lanjut: angka floating point.

Dan pada kenyataannya, tepat di atasnya ada jbperintah transisi (pikirkan "goto"). Ini adalah " lompatan bersyarat ", dan kondisinya adalah " jika kurang ." Assembler "ifs" bekerja melalui flag register , yang secara singkat saya bicarakan di atas. Cukup hanya untuk mengetahui bahwa kita perlu melihat perintah di atas: kemungkinan besar, ia menentukan benderanya. Seringkali ini adalah perintah cmpatau test, tetapi digunakan di sini ucomiss. Ini barbarisme. Tapi dia dengan mudah googles: ucomiss . Ini adalah perintah perbandingan titik mengambang, dan ini menjelaskan mengapa kode memilikixmm0: Ini adalah daftar nomor floating point. Ini logis: log melaporkan bahwa versi pixel shaders kami adalah "0,0", yang merupakan nilai floating-point. Jadi apa yang terletak di alamat memori 0x89034c? Data heksadesimal memiliki tampilan 00 00 00 40dan dapat membingungkan. Tetapi kita tahu bahwa kita harus mengharapkan angka floating point, jadi mari kita menafsirkan data seperti itu: itu artinya 2.0. Yang merupakan nilai floating point logis untuk versi pixel shader yang sebenarnya digunakan Microsoft dalam Bahasa Shading Tingkat Tinggidi mana DirectX shader ditulis. Dan Age 3 adalah game DirectX, meskipun port di Mac menggunakan OpenGL. Adalah logis juga bahwa pada tahun 2005 pixel shaders versi 2.0 diperlukan untuk mengaktifkan parameter Tinggi, jadi kami bergerak ke arah yang benar.

Bagaimana cara kita memperbaikinya? Instruksi perbandingan ini melakukan perbandingan dengan nilai di xmm0yang diberikan pada baris di atas: movss xmm0, dword [eax+0xA2A8]. MOVSS adalah perintah "pindah" khusus untuk register floating point, dan kami menulis 32 bit dari alamat, yang merupakan hasil dari evaluasi eax + 0xA2A8.

Agaknya, nilai ini bukan 2,0, melainkan 0,0. Mari kita perbaiki.

Melakukan peretasan nyata


Akhirnya, kami siap untuk menuju ke parameter permainan Tinggi. Kami ingin menyusuri jalur "merah" dan melewati semua logika dengan "versi pixel shaders" yang salah. Ini dapat dilakukan dengan berhasil melewati perbandingan, yaitu dengan membalik kondisi transisi, tetapi kami memiliki satu trik lagi: kode tersebut disusun sedemikian rupa sehingga ketika menghapus transisi kita akan pergi dengan cara "merah". Mari kita ganti transisi dengan nopoperasi yang tidak menghasilkan apa-apa (tidak mungkin menghapus atau menambah data ke file yang dapat dieksekusi karena offset akan terjadi dan semuanya akan rusak).

Saya sarankan keluar dari CFG untuk ini, karena kalau tidak Hopper mulai menjadi gila. Jika semuanya dilakukan dengan benar, maka di jendela Hex kita akan melihat garis merah.



Merah menunjukkan perubahan yang telah kami buat. Pada titik ini, jika Anda membayar Hopper, Anda cukup menyimpan file. Tetapi saya tidak membayar, jadi saya akan membuka file yang dapat dieksekusi di Hex Fiend, menemukan area yang diinginkan (dengan menyalin area dari Hopper ke tab Temukan di dalam Hex Fiend) dan mengubahnya secara manual. Hati-hati di sini, Anda dapat merusak semuanya, jadi saya sarankan menyalin file executable sumber di suatu tempat.

Setelah melakukan ini, mulai permainan, buka opsi ... Dan sorak-sorai - kita dapat memilih "Tinggi". Tapi kita tidak bisa memilih "sedang". Dan kita tidak bisa menggunakan parameter tinggi dari bayangan. Namun demikian, kami membuat kemajuan!


Lihat saja pantulan menakjubkan di dalam air!

Perangkat tambahan


Bahkan, Anda dapat memperbaikinya dengan cara terbaik. Solusi kami berfungsi, tetapi kami dapat membuatnya agar kondisinya terpenuhi dengan benar: membuat permainan berpikir bahwa kartu grafis kami sebenarnya memiliki shader versi 2.0.

Seperti yang kita ingat, game memuat nilai di alamat eax+0xA2A8. Anda dapat menggunakan Hopper untuk mengetahui apa yang semula direkam di sana. Kita perlu menemukan perintah di mana 0xA2A8 digunakan sebagai operan penerima (saya memilih 0xA2A8 karena ini adalah nilai yang cukup spesifik, dan kami tidak dapat memastikan bahwa register yang sama tidak digunakan di tempat lain). Di sini lampu latar Hopper sangat berguna bagi kami, karena kami cukup mengklik 0xA2A8 dan mencari bagian kuning di CFG.


Kemenangan kami sedikit lebih tinggi: seperti yang diharapkan, kami menulis nilai semacam register floating-point. Saya akui bahwa saya tidak terlalu mengerti apa yang terjadi sebelum ini (tebakan terbaik saya: permainan memeriksa kemungkinan kompresi tekstur), tetapi ini tidak terlalu penting. Mari kita menulis "2.0" dan melanjutkan pekerjaan.

Untuk melakukan ini, kita akan menggunakan "Assemble instruksi" dari aplikasi Hopper, dan kemudian melakukan manipulasi yang sama di Hex Fiend.




Kami menggunakan MOV daripada MOVSS karena kami menulis nilai biasa, dan bukan nilai khusus register floating-point. Selain itu, itu dalam urutan byte langsung, yaitu, terbalik.

Anda akan melihat bahwa sesuatu yang aneh sedang terjadi: kami "memakan" tim jb. Itu terjadi karena perintah baru membutuhkan lebih banyak byte daripada yang sebelumnya, dan seperti yang saya katakan, kita tidak dapat menambah atau menghapus data dari yang dapat dieksekusi untuk menghemat offset. Karena itu, Hopper tidak punya pilihan selain menghancurkan tim jb. Ini bisa menjadi masalah, dan kita bisa menghilangkannya dengan memindahkan perintah (setelah semua, kita tidak perlu lagi menulis nilai xmm3). Ini akan bekerja di sini, karena bagaimanapun, kami melakukan percabangan di jalan yang benar. Beruntung.

Jika Anda memulai permainan ... maka tidak ada yang akan berubah banyak. Saya menyarankan bahwa seri Intel 9 tidak cukup cepat, sehingga permainan mengeluh. Ini bukan masalah, karena pada kenyataannya kami berjuang untuk "Sangat Tinggi".

Apa kartu yang paling kuat di sekitar tahun 2004? NVIDIA GeforceFX 6800. Mari menipu kita untuk meyakinkan gim yang telah kita pasang.

Masuk kedalam lubang kelinci


Ya, ini adalah bagian yang sama sekali baru.

Kita tahu bahwa permainan menggunakan kode Hex untuk merujuk ke kartu grafis, dan bahwa ia menerima informasi dari file data. Kita tahu bahwa ini harus terjadi dalam fungsi yang kita pelajari (setidaknya itu akan aneh jika tidak). Jadi kita dapat menemukan kode yang melakukan ini. Tapi itu masih bisa sulit, karena kita tidak tahu persis apa yang harus dicari.

Kami memiliki satu petunjuk: opsi berperilaku aneh, ada Rendah / Tinggi, tetapi tidak ada Medium. Jadi mungkin ada kode lain yang menangani semua ini "Sangat Tinggi", "Tinggi", "Sedang" dan "Rendah". Dan dia sebenarnya, dalam fungsi yang sama. Dia jauh lebih awal di CFG, dan sepertinya menarik.


Kita dapat mengasumsikan bahwa data mentah disimpan dalam beberapa jenis XML, karena sangat mungkin, karena ada beberapa file data lain yang merupakan XML. Dan XML pada dasarnya adalah sekelompok petunjuk dan atribut. Oleh karena itu, kode di sini kemungkinan besar memeriksa beberapa atribut XML (dan pada kenyataannya, di bawah ini adalah "yang diharapkan dari ID, Sangat Tinggi ...", yang sangat mirip dengan memeriksa kebenaran file). Kita dapat membayangkan struktur XML serupa di mana nilai Boolean menentukan kualitas shader:


Perlu mempertimbangkan bahwa ini adalah tebakan yang sangat sewenang-wenang. Semuanya mungkin terlihat sedikit berbeda.

Bagian yang sangat menarik ditampilkan di sisi kiri CFG (untuk Anda itu bisa di sebelah kanan, tetapi ditampilkan di sebelah kiri di tangkapan layar): kita melihat yang sama var_CCyang digunakan dalam kode yang menulis ID perangkat ke log. Artinya ebp+var_CC, mungkin merujuk pada ID perangkat kami, 0x2773. Yang aneh adalah bahwa tidak ada string literal di "cek" pertama, tetapi ini adalah bug Hopper: alamat 0x0089da8e berisi data heksadesimal 69006400, yang dalam UTF-16 singkatan dari "id" (pada kenyataannya, mungkin Hopper tidak dapat memahami ini karena UTF16). Dan kami hanya mencari ID.

Kamu tahu apa? Mari kita jalankan debugger dan periksa semuanya dalam kenyataan.

Game debugging


Buka jendela terminal dan jalankan lldb (cukup ketik lldb dan tekan enter). Pertama kita harus memberitahu LLDB untuk mengikuti Usia 3, dan kemudian kita bisa memulai permainan dengan menelepon run. Permainan dimulai, dan kami tidak mendapatkan apa pun yang berguna.


Sekarang kita perlu menambahkan breakpoint: untuk mengkonfirmasi asumsi kita, kita ingin menghentikan eksekusi ketika kita sampai pada kode yang ditunjukkan di atas. Kami tidak memiliki kode sumber, tetapi itu tidak masalah: kami dapat menetapkan breakpoint di alamat memori. Dan kami memiliki alamat dalam kodenya. Mari kita tambahkan berhenti untuk panggilan MOV yang menerima "id", alamatnya adalah 0x001d5e68. Untuk menambahkan breakpoint, cukup masukkan br set -a 001D5E68. Setelah memulai permainan, itu berhenti dan LLDB menunjukkan kode yang dibongkar (dalam sintaks AT&T, bukan Intel, jadi semua operan terbalik, tetapi kami melihat bahwa ini adalah kode yang sama). Anda mungkin memperhatikan bahwa LLDB dalam hal ini sebenarnya lebih pintar daripada Hopper; dia memberi tahu kami bahwa kami sedang melakukan operasi yang terkait dengan XML dan output printf.



Merasa seperti seorang hacker?

Untuk melanjutkan, mari kita ambil "instruksi langkah". Perintah singkat untuk ini
adalah ni. Mengulangi beberapa kali, kami perhatikan bahwa kami kembali ke tempat kami mulai. Ini adalah sebuah siklus! Dan ini sepenuhnya logis: kita mungkin beralih ke beberapa jenis XML. Faktanya, Hopper menunjukkan ini kepada kita - jika kita mengikuti CFG, kita akan melihat siklus (kemungkinan).


Ini berarti:if (*(ebp+var_CC) == *(ebp+var_28)) { *(ebp+var_1D84)=edi }

Kondisi keluarnya adalah nilainya ebp+var_1D84tidak nol. Dan kita melihat parameter kode yang menarik dalam kode yang disorot oleh saya var_CC.

Mari kita tambahkan breakpoint yang satu ini cmpdan kita akan mengetahuinya.



Kiri: atur breakpoint pada CMP dan lanjutkan eksekusi. Di sebelah kanan, kami memantau register dan memori.

Kami melihat register dengan reg r, dan nilai memori dengan mem read $ebp-0x28. eaxdan sebenarnya mengandung 0x2773, dan nilai dalam memori sekarang 0x00A0. Jika kami menjalankannya beberapa kali thread c, maka kami akan melihat bahwa iterasi terjadi pada ID yang berbeda untuk mencari kecocokan. Pada tahap tertentu, nilai-nilai akan bertepatan, loop akan keluar, dan permainan akan dimulai. Sekarang kita tahu cara mengelolanya.

Ingat file log: itu mengidentifikasi perangkat dan pabrikan. Mungkin di suatu tempat ada siklus serupa untuk nama-nama produsen. Dan pada kenyataannya, ini sangat mirip dan terletak di CFG tepat di atas siklus kami. Loop ini sedikit lebih sederhana, dan kami mencari string "vendor".

Kali ini perbandingan dilakukan denganvar_D0. Variabel ini sebenarnya digunakan sebagai ID pabrikan. Jika kita meletakkan breakpoint di sini dan memeriksa semuanya, kita akan melihat 0x8086 yang familier di eax, dan pada saat yang sama, perbandingan dengan 0x10DE sedang terjadi. Mari kita tulis eaxdan lihat apa yang terjadi.



Wow.

Yaitu, 0x10DE adalah NVIDIA. Sebenarnya, orang bisa memahami ini ketika mempelajari file data, tetapi itu tidak terlalu menarik. Sekarang pertanyaannya adalah: apa pengidentifikasi GeforceFX 6800? Kita dapat menggunakan LLDB dan hanya memeriksa setiap pengenal, tetapi itu akan memakan waktu (ada banyak dari mereka, saya mencoba menemukan yang tepat). Jadi mari kita lihat render.bar kali ini.


Misalkan ini adalah "XMB", representasi biner dari XML yang digunakan di Usia 3.

File ini sangat kacau. Tetapi setelah tag Geforce FX 6800, kami melihat beberapa pengidentifikasi. Kemungkinan besar, kita membutuhkan salah satunya. Mari kita coba 00F1.

Cara termudah untuk menguji ini adalah dengan menggunakan LLDB dan mengatur breakpoint yang diinginkan. Saya akan melewatkan langkah ini, tetapi saya akan mengatakan bahwa 00F1 muncul (untungnya).

Pada tahap ini, kita perlu menjawab pertanyaan: "Bagaimana membuat perubahan ini permanen?" Tampaknya akan lebih mudah untuk mengubah nilai var_CCdan var_D0pada 0X00F1 dan 0X10DE. Untuk melakukan ini, kita hanya perlu mendapatkan ruang untuk kode.

Solusi sederhana adalah mengganti salah satu panggilan rekaman log dengan NOP, misalnya, panggilan subsistem. Ini akan membebaskan kita beberapa puluh byte, dan ini bahkan lebih dari yang diperlukan. Mari kita pilih semua ini dan ganti dengan NOP. Maka kita hanya perlu menulis informasi menggunakan variabel MOV: assembler mov dword [ebp+var_CC], 0xF1dan mov dword [ebp+var_D0], 0x10DE.



Sebelum dan sesudahnya

Mari kita jalankan permainan. Akhirnya, kami telah mencapai sesuatu: Anda dapat memilih dari Rendah, Sedang atau Tinggi, serta mengaktifkan parameter Tinggi untuk bayangan.


Tapi kami masih tidak bisa mengaktifkan Sangat Tinggi, dan ini menyedihkan. Apa yang terjadi?


Ah, mengerti.

Ternyata, Geforce FX 6800 harus mendukung versi 3.0 pixel shader, dan kami hanya mengatur versi 2.0. Sekarang mudah bagi kami untuk memperbaikinya: cukup tulis 3.0 floating point ebp+0xA2A8. Nilai ini adalah 0x40400000.

Akhirnya, permainan tidak lagi berubah-ubah, dan kami dapat menikmati pengaturan Sangat Tinggi.


Aneh, tetapi terlihat sangat berbeda. Warnanya jauh kurang cerah, dan kabut yang bergantung pada jangkauan telah muncul. Ada juga masalah kecil konflik bayangan (itu juga di screenshot di atas, ini disebabkan oleh parameter bayangan tinggi, bukan kualitas shader), yang saya belum dapat menyelesaikannya.

Dan ini adalah akhir dari perjalanan kami, terima kasih sudah membaca.

Selain itu, saya akan memberikan contoh bagaimana masing-masing parameter dalam permainan terlihat, karena semua tangkapan layar yang ditemukan online mengerikan atau tidak lengkap.

Grafik pada Medium cukup dapat diterima, tetapi tidak memiliki bayangan. Sejauh yang saya tahu, Sangat Tinggi untuk sebagian besar menambahkan LUT yang sama sekali berbeda (penjelasan yang baik tentang istilah ini dapat ditemukan di bagian Color Grading dari artikel ini.), kabut di kejauhan, dan mungkin bahkan model pencahayaan yang sama sekali berbeda? Gambarannya sangat berbeda, dan agak aneh.





Dari atas ke bawah: Sangat-Tinggi, Tinggi (dengan opsi Bayangan tinggi), Sedang (hanya dengan bayangan diaktifkan), Rendah (dengan bayangan dinonaktifkan).

Tautan bermanfaat lainnya


Blog yang saya tautkan di atas berisi analisis yang luar biasa dari jaringan pipa rendering modern: http://www.adriancourreges.com/blog/

Rate 0 AD adalah game RTS open-source dengan genre yang sama: https://play0ad.com . Dia membutuhkan pengembang.

Jika Anda ingin tahu lebih banyak tentang format XMB, Anda dapat membaca kodenya dalam 0 AD . Saya tidak memeriksa, tetapi saya cukup yakin bahwa ini adalah format yang sama, karena orang yang menulis dekoder XMB untuk Age 3 di Age of Empires heaven juga mengimplementasikan file-file ini dalam kode sumber 0 AD pada tahun 2004. Jadi ini akan menjadi pelajaran yang menyenangkan dalam paleontologi kode. Terima kasih untuk semua pekerjaannya, Ykkrosh.

Saya ingat persis bahwa saya membaca tutorial yang sangat baik tentang penggunaan Hopper, tetapi sekarang saya tidak dapat menemukan tautannya. Jika ada yang tahu apa yang saya bicarakan, lepaskan tautannya.

Pada akhirnya, saya ingin menyebutkan proyek Cosmic Frontier: pembuatan ulang seri Escape Velocity klasik (lebih dari Override, tetapi juga kompatibel dengan Nova). Ini adalah permainan yang luar biasa, dan sangat menyedihkan bahwa hanya sedikit orang yang mengetahuinya. Sekarang pencipta telah meluncurkan kampanye di Kickstarter, dan pengembang utama memiliki blog pengembang yang sangat menarik , yang berbicara tentang penggunaan Hex Fiend untuk membalikkan format data insinyur dari game asli. Ini bacaan yang bagus. Jika Anda pikir artikel saya gila. kemudian di salah satu postingan mereka:

  • Kami meluncurkan emulator Mac klasik untuk menggunakan API lama usang untuk membaca file data yang dienkripsi.
  • Membalikkan enkripsi rekayasa dari kode permainan
  • Mereka menggunakan sistem file hack untuk mengakses data yang sama di Mac modern.

Ya, ini adalah orang-orang yang benar-benar bersemangat, dan saya berharap proyek mereka akan berhasil diselesaikan, karena EV Nova adalah permainan favorit saya.

All Articles