Reverse Engineering Fantastic Dizzy

Fantastic Dizzy adalah game platformer puzzle yang dibuat pada tahun 1991 oleh Codemasters. Dia adalah bagian dari Seri Dizzy . Terlepas dari kenyataan bahwa seri Dizzy masih populer, dan itu menciptakan game amatir ( Dizzy Age ), tampaknya tidak ada yang terlibat dalam pengembangan terbalik dari game asli.


Saya menulis beberapa alat sederhana untuk mengekstrak, melihat, dan mengemas sumber daya dari gim asli. Alat diposting di GitHub .

Membongkar EXE


File biner PCDIZZY.EXE dikemas dalam format Microsoft EXEPack . Meskipun ada banyak alat Linux yang dapat mendekompresi executable tersebut, tidak ada satupun yang mendukung versi yang digunakan untuk Fantastic Dizzy. Oleh karena itu, untuk membongkar file yang dapat dieksekusi, saya menggunakan UNP versi DOS. Setelah membongkar file yang dapat dieksekusi, itu bisa diunduh ke IDA. Mudahnya, versi file biner yang belum dibongkar masih bekerja dengan baik, sehingga bisa didebug menggunakan debugger DOSBox.

File data


Ada dua file data dalam game: DIZZY.NDX dan DIZZY.RES. Ekstensi, serta ukuran file, memberi kami petunjuk tentang apa yang mungkin dikandungnya. File NDX adalah sekitar 8 KB, dan file RES sekitar 800 KB. Karena game ini ditulis dalam C, kita dapat mencari panggilan fopen di IDA untuk melihat di mana file data terbuka. Dalam game DOS yang ditulis assembler, untuk ini Anda perlu mencari instruksi int 21j (untuk membuka file ah = 3d). Biner Dizzy berisi fungsi pembungkus di sekitar fopen yang memungkinkan Anda menentukan nama utama dan ekstensi file. Ini membawa kita ke blok kode berikut:


Ini memuat file DIZZY.RES dan DIZZY.NDX, dan juga menyimpan pointer file dalam variabel global. Ketika merekayasa balik binari DOS, muncul masalah yang menjengkelkan: register di dalamnya 16-bit, tetapi pointer dalam beberapa kasus bisa 32-bit. Di sini pointer FILE * berukuran 32 bit dan dikembalikan dari do_open_file ke ax: dx. Perhatikan bahwa string juga merupakan pointer 32-bit, dan dizzy_basename diteruskan ke fungsi panggilan pada stack (dan analisis otomatis IDA yang membingungkan ini - itu dianggap sebagai argumen mode untuk fopen).

Dengan mencari kemunculan g_dizzy_res / ndx di xrefs, Anda dapat menemukan di mana file dibaca. Debugger DOSBox berguna pada titik ini, karena ada kemungkinan besar banyak operasi pembacaan file acak, dan menggunakan IDA untuk menentukan offset baca akan menjadi proses yang cukup monoton. Panduan yang baik untuk membangun dan menggunakan debugger DOSBox dapat ditemukan di sini .

Ketika menggunakan IDA dan debugger DOSBox bersama-sama, menjadi jelas bahwa file NDX digunakan sebagai indeks untuk file RES. Setiap entri dalam file NDX membutuhkan 16 byte; ini menyimpan pengidentifikasi fragmen, ukurannya dan offset dalam file RES. Melihat bagaimana data RES dibaca, Anda dapat melihat bahwa flag flag diperiksa pertama kali dalam file NDX. Jika bit 0x80 tidak diatur, maka data dibaca langsung dari file RES, jika tidak, jalur kode yang lebih kompleks dijalankan. Bendera diatur untuk sebagian besar fragmen, sehingga dengan tingkat probabilitas tinggi kita dapat mengasumsikan bahwa itu menunjukkan semacam kompresi yang digunakan untuk fragmen-fragmen ini.

Kompresi


Jalur kompresi dimulai dengan membaca dari dasar fragmen RES dua kata 32-bit yang menunjukkan ukuran awal dan akhir, dan kemudian fungsi dekompresi disebut. Pada tahun 1991, pengodean panjang run sederhana (RLE) dan kompresi kamus sangat populer, seperti berbagai algoritma Liv-Zempel. Awal siklus pembongkaran terlihat seperti ini:


Token untuk membongkar diperoleh menggunakan fungsi get_next_token, yang membaca bagian selanjutnya dari sumber data di ax: dx dengan shift oleh cl. Register cl digunakan sebagai posisi bit shift, kembali ke nol setelah mencapai delapan. Di awal siklus, token dibaca dan bit yang lebih rendah diperiksa. Jika flag diatur, maka kodenya sederhana:


Itu hanya menyimpan byte saat ini, menerima token berikutnya dan terus bekerja. Jika flag dihapus, jalur kode yang lebih panjang dipilih, yang berakhir dengan instruksi rep movsb. Ini menunjukkan bahwa beberapa jenis kamus digunakan dalam kompresi.

Algoritma kompresi menarik karena beberapa alasan. Pertama, ia menggunakan pengodean panjang bit variabel. Nilai absolut dikodekan sebagai 1 flag dan nilai data 8-bit. Anehnya, bitstream dikodekan sebagai endian kecil. Ini mempersulit analisis dekompresi sedikit dengan mengamati file RES di hex editor. Misalnya, jika tiga byte pertama fragmen dikodekan sebagai nilai absolut, maka data disusun sebagai berikut:

 : AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD  1: 6543210F 7  2: 543210F 76  3: 43210F 765 

Selain itu, unpacker dapat melewati byte saat membaca, jika counter cl kembali ke nol setelah menerima token berikutnya. Saya tidak tahu apakah ini optimasi, atau bug, atau retasan yang dibuat oleh pengembang game untuk memperbaiki masalah dengan alat saya.

Jika flag dihapus, unpacker melakukan penyalinan dari bagian awal data yang sudah dibongkar. Dalam hal ini, bit-bit berikut menyandikan panjang dan mengimbangi dari yang akan disalin. Offset dikodekan dalam 10 atau 13 bit, dan opsi yang diinginkan menunjukkan bendera. Ini sepertinya pilihan yang sangat aneh, karena sedikit menyulitkan kode, dan paling-paling hanya menyimpan 2 bit.

Pengkodean panjang seri terlihat sedikit aneh. Unpacker membaca bit sampai mencapai bit nol. Maka jumlah bit yang digunakan untuk mengkodekan panjangnya adalah dua ditambah jumlah bit yang tidak nol. Misalnya, saat menyandikan panjang 58 (0x3a), bitstreamnya terlihat seperti ini:

 11110 111010 

Pengkodean membutuhkan 11 bit. Panjang kecil lebih baik disandikan karena panjang bit minimum adalah 2. Panjang penyalinan hingga 3 hanya membutuhkan 3 bit untuk mengkodekan, hingga 7 membutuhkan 5 bit, dan seterusnya. Saya tidak tahu pasti apakah jenis pengkodean ini adalah teknik yang umum.

Debugger DOSBox juga sangat berguna untuk merekonstruksi algoritma dekompresi. Jika Anda tidak tahu seperti apa data dekompresi seharusnya, maka sulit untuk memahami apakah pengurai terbuka berfungsi dengan benar. Menggunakan debugger, Anda dapat melangkah melalui seluruh algoritma dekompresi dan menyimpan tumpukan memori yang belum dibongkar untuk perbandingan.

Fitur lain yang bermanfaat adalah tanda pada file NDX, yang menunjukkan bahwa sumber daya dikompresi. Karena gim asli mendukung sumber daya yang tidak dibongkar, kami dapat mengemas kembali file RES tanpa perlu algoritma kompresi. Memodifikasi dan mengemas ulang fragmen dengan peluncuran game selanjutnya adalah cara yang baik untuk menguji asumsi kami tentang format data.

Tingkat


Fantastic Dizzy adalah game dengan dunia terbuka. Level adalah area dengan pengguliran vertikal atau horizontal. Pemain bergerak di antara level, mencapai ujung level atau memasuki dan meninggalkan bangunan. Meskipun referensi ke fragmen dalam file RES dibuat melalui pengidentifikasi 16-bit (ID), file biner dari game tersebut sebenarnya berisi tabel nama level yang cocok dengan pengidentifikasi fragmen. Setiap level terdiri dari beberapa fragmen: judul, satu atau lebih layer, tileset dan palet. Ada sedikit redundansi di sini, karena beberapa level menggunakan palet dan tileset yang sama, tetapi jangan menggunakan kembali fragmen yang sama, sehingga file RES berisi banyak sumber daya rangkap.

Layers mengkodekan ubin untuk level. Untuk bagian dunia yang berbeda atau untuk lapisan latar belakang, Anda dapat menggunakan lapisan tambahan. Misalnya, pada tingkat tree1.stg, ada delapan lapisan untuk bagian berbeda dari puncak pohon dan satu lapisan latar belakang umum. Namun, level bawah laut dibagi menjadi sea1.stg dan sea2.stg, yang masing-masing memiliki satu lapisan latar depan dan satu lapisan latar belakang.

Lapisan latar belakang adalah latar belakang lebar-tetap tanpa menggulir, misalnya, hutan di bagian gim dengan puncak pohon. Ubin latar depan dan latar belakang, yang terletak di depan dan di belakang karakter, dikodekan dalam lapisan yang sama dengan ubin yang Anda bisa berjalan. Misalnya, tangkapan layar menunjukkan tingkat dari puncak pohon sejak awal permainan:


Tingkat puncak pohon

Ini adalah lapisan ketujuh tree1.stg:


Tree level level ketujuh .stg

Perlu dicatat bahwa pemain bisa lewat di depan gubuk, tetapi di belakang dua pohon. Semua informasi ubin terdapat dalam satu larik peta ubin yang terletak di satu lapisan. Ubin dalam fragmen lapisan dikodekan dalam dua byte, dan 9 bit yang lebih rendah digunakan untuk indeks ubin. Saya tidak sepenuhnya memahami bit atas, tetapi setidaknya mereka mengandung informasi tentang pergeseran palet untuk ubin dan, mungkin, informasi tentang tabrakan.

Seperti level dalam game, adegan cutscene, potret karakter, dan layar kontrol inventaris juga disimpan. Tampaknya teknik ini standar untuk game DOS, mungkin karena meminimalkan jumlah kode yang diperlukan.


"Level" manajemen persediaan

Sprite


Format sprite tidak terlalu menarik. Setiap sprite adalah bitmap dengan satu byte per piksel, tetapi dengan hanya 16 warna per sprite. Menggunakan jumlah warna yang terbatas adalah teknik umum di era 256-warna VGA, karena untuk sprite mudah untuk melakukan pergeseran palet atau menggunakannya dalam level dengan palet lain; selain itu, menghemat ruang yang dialokasikan untuk sprite.

Sprite memiliki ukuran yang berbeda, sehingga sebuah fragmen terpisah berisi informasi tentang ukuran sprite dan perpindahannya dalam x dan y. Sprite dikelompokkan ke dalam set, tetapi pengelompokannya terlihat cukup sewenang-wenang. Misalnya, satu set sprite berisi gambar screen saver, objek inventaris, serta beberapa karakter non-pemain. Ini membuat pengaturan tampilan sprite sedikit rumit karena paletnya tidak sama untuk semua sprite.


Sprite Karakter Pemain

Apa lagi yang tersisa?


Masih untuk merekayasa balik beberapa hal lagi. Sebagian besar saya tertarik pada format file data, tetapi ada beberapa aspek yang saya tidak mengerti:

  • Di mana lokasi objek (kunci, buah, dll). Tampaknya mereka tidak ditulis dalam fragmen level. Mungkin mereka disimpan dalam file biner permainan, karena pemain dapat mengambil objek di satu level dan melemparkannya di yang lain.
  • Cara kerja level collision. Seorang pemain dapat berjalan di depan atau di belakang beberapa ubin, dan lantainya mungkin datar atau miring.
  • Bagaimana level terhubung. Informasi ini dapat disimpan dalam biner game.
  • Pergeseran palet untuk ubin di tingkat tidak sepenuhnya benar. Beberapa ubin menampilkan warna yang salah.
  • Setiap set sprite memiliki tiga fragmen: header, tabel dan data. Fragmen dengan tabel dan data jelas bagi saya, tetapi beberapa informasi tentang sprite disertakan dalam header, misalnya, offset dengan x dan y. Saya tidak mengerti formatnya sepenuhnya.

Source: https://habr.com/ru/post/id436674/


All Articles