Hai, seperti yang sudah Anda pahami, ini adalah kelanjutan dari sejarah saya tentang teknik reverse dan porting Neuromant.

Kebalikan dari Neuromancer. Bagian 1: Sprite
Kebalikan dari Neuromancer. Bagian 2: Render Font
Kebalikan dari Neuromancer. Bagian 3: Render selesai, buat game
Hari ini, mari kita mulai dengan dua kabar baik:
- pertama, saya tidak sendirian lagi - pengguna viiri telah bergabung dengan proyek dan telah memberikan kontribusi yang signifikan;
- kedua, kita sekarang memiliki repositori terbuka di github .
Secara umum, semuanya berjalan sangat baik, dan mungkin sebentar lagi kita akan mendapatkan setidaknya beberapa permainan yang bisa dimainkan. Dan di bawah potongan, seperti biasa, mari kita bicara tentang apa dan bagaimana yang telah dicapai saat ini.
Dia mulai berurusan dengan suara. Sayangnya, di antara sumber daya gim tidak ada yang mirip dengan audio, dan karena saya tidak tahu bagaimana musik bekerja di MS-DOS , sangat tidak jelas harus mulai dari mana. Setelah membaca sedikit tentang semua jenis SoundBlaster , yang terbaik yang saya lakukan adalah menggulir kode yang dibongkar dengan harapan melihat beberapa tanda tangan yang sudah dikenal. Dan siapa pun yang mencari, ia biasanya menemukan, bahkan jika tidak cukup apa yang ia cari (komentar dilontarkan oleh Ida ):
sub_20416: ... mov ax, [si+8] out 42h, al ; 8253-5 (AT: 8254.2). mov al, ah out 42h, al ; Timer 8253-5 (AT: 8254.2). mov bx, [si+0Ah] and bl, 3 in al, 61h ; PC/XT PPI port B bits: ; 0: Tmr 2 gate ββ¦ββΊ OR 03H=spkr ON ; 1: Tmr 2 data ββ AND 0fcH=spkr OFF ; 3: 1=read high switches ; 4: 0=enable RAM parity checking ; 5: 0=enable I/O channel check ; 6: 0=hold keyboard clock low ; 7: 0=enable kbrd and al, 0FCh or al, bl out 61h, al ; PC/XT PPI port B bits: ; 0: Tmr 2 gate ββ¦ββΊ OR 03H=spkr ON ; 1: Tmr 2 data ββ AND 0fcH=spkr OFF ; 3: 1=read high switches ; 4: 0=enable RAM parity checking ; 5: 0=enable I/O channel check ; 6: 0=hold keyboard clock low ; 7: 0=enable kbrd
Setelah melewati Timer 8253-5 ini, saya menemukan sebuah artikel yang menjadi kunci pertama untuk memahami apa yang terjadi. Di bawah ini saya akan mencoba menjelaskan apa itu.
Jadi, di era IBM-PC , sebelum munculnya kartu suara yang terjangkau, perangkat reproduksi suara yang paling umum adalah yang disebut PC Speaker , juga dikenal sebagai "bip". Perangkat ini tidak lebih dari speaker biasa yang tersambung ke motherboard, dalam banyak kasus, melalui konektor empat pin. Pager itu, menurut ide tersebut, memungkinkan untuk mereproduksi pulsa persegi dua tingkat (sesuai dengan dua level tegangan, biasanya 0V dan + 5V) dan dikendalikan melalui port 61 dari pengontrol PPI (Programmable Peripheral Interface) . Secara khusus, dua bit pertama dari nilai yang dikirim ke pelabuhan bertanggung jawab untuk mengendalikan "speaker" (lihat komentar pada baris in al, 61h
dan out 61h, al
).
Seperti yang saya katakan (dengan kata yang sedikit berbeda), pembicara kami dapat berada di dua negara - "dalam" dan "keluar" ( "rendah" - "tinggi" , "mati" - "nyala" , "mati" - "nyala" , terserah). Untuk membuat satu impuls, perlu untuk mengubah keadaan saat ini ke yang sebaliknya dan, setelah beberapa waktu, kembali. Ini dapat dilakukan secara langsung dengan memanipulasi bit pertama (hitung dari awal) port 61, misalnya, seperti ini:
PULSE: in al, 61h ; and al, 11111100b ; ... or al, 00000010b ; ... ; , 0 out 61h, al ; 61- mov cx, 100 ; DELAY: loop DELAY ; in al, 61h ; and al, 11111100b ; out 61h, al ; 61-
Hasil dari mengeksekusi kode ini akan terlihat seperti ini:
loop DELAY +5V +----------------------+ ! ! 0V ---+ +-------------------------- or al, 00000010b and al, 11111100b out 61h, al out 61h, al
Mengulangi PULSA dengan penundaan, kami mendapatkan sinyal persegi panjang:
mov dx, 100 ; 100 PULSE: ... mov cx, 100 WAIT: loop WAIT dec dx jnz PULSE PULSE +5V +---------+ +---------+ +---------+ ! ! ! ! ! ! 0V ---+ +---------+ +---------+ +--- loop WAIT
Jika dalam kasus pertama kita hampir tidak pernah mendengar apa pun, maka dalam yang kedua kita akan mendapatkan nada frekuensi, tergantung pada kecepatan mesin di mana kode ini dieksekusi. Ini hebat, tetapi terkait dengan kesulitan tertentu. Bagaimanapun, ada cara yang lebih nyaman untuk mengendalikan speaker.
Di sinilah permainan timer tiga saluran yang dapat diprogram - Intel 8253 , saluran kedua yang (mulai dari nol) terhubung ke penyuara bip. Timer ini menerima sinyal dari jam Intel 8254 , mengirimkan 1193180 pulsa per detik (~ 1,193 MHz), dan dapat diprogram untuk reaksi tertentu setelah jumlah pulsa tertentu. Salah satu reaksi ini adalah mengirim pulsa persegi ke pembicara. Dengan kata lain, 8253 dapat bekerja dalam bentuk generator sinyal persegi panjang frekuensi yang dapat disesuaikan, ini membuatnya relatif mudah untuk mensintesis berbagai efek suara pada speaker. Dan inilah yang Anda butuhkan untuk ini:
- Atur saluran kedua dari timer ke mode generasi sinyal persegi panjang. Untuk melakukan ini, tulis nilai byte tunggal khusus ke port 43 ( 8253 Mode / Command register ). Dalam kasus saya, ini adalah
10110110B
(lebih detail di sini ):
Bits Usage 6 and 7 Select channel : 1 0 = Channel 2 4 and 5 Access mode : 1 1 = Access mode: lobyte/hibyte 1 to 3 Operating mode : 0 1 1 = Mode 3 (square wave generator) 0 BCD/Binary mode: 0 = 16-bit binary
Tetapkan frekuensi yang diinginkan pada saluran kedua. Untuk melakukan ini, byte-by-bit, dari yang termuda ke yang tertua, kami mengirim ke port ke-42 ( port data 8253 Saluran 2 ) nilai yang sama dengan 1193180 / freq
, di mana freq
adalah nilai frekuensi yang diperlukan dalam Hertz.
Biarkan speaker menerima pulsa dari timer. Untuk melakukan ini, atur dua bit pertama dari nilai di port 61 ( PPI ) menjadi satu. Faktanya adalah bahwa jika bit nol diatur ke 1, maka bit pertama ditafsirkan sebagai "switch":
Bit 0 Effect ----------------------------------------------------------------- 0 The state of the speaker will follow bit 1 of port 61h 1 The speaker will be connected to PIT channel 2, bit 1 is used as switch ie 0 = not connected, 1 = connected.
Hasilnya, kami memiliki gambar berikut:
mov al, 10110110b out 43h, al ; mov ax, 02E9Bh ; 1193180 / 100 = ~0x2E9B out 42h, al ; mov al, ah out 42h, al ; in al, 61h ; or al, 00000011b ; 1 out 61h, al ; ... ; ~100 in al, 61h and al, 11111100b out 61h, al ;
Dan ini persis seperti apa yang saya kutip di awal (kecuali untuk inisialisasi, tapi saya menemukannya di fungsi lain): pada si + 8
ada pembagi frekuensi yang dikirim ke port 42, dan pada si + 0Ah
- status speaker ( "on" - "off" ) direkam pada port 61.
Mekanisme pemutarannya sederhana dan mudah, tetapi kemudian Anda harus berurusan dengan pengaturan waktu. Setelah mempelajari kode terdekat, saya melihat bahwa dalam fungsi yang sama di mana timer diinisialisasi ( sub_2037A
, kemudian init_8253
), interrupt handler kedelapan diganti dengan fungsi sub_20416
(selanjutnya - play_sample
). Segera menjadi jelas bahwa gangguan ini dihasilkan sekitar 18,2 kali per detik dan berfungsi untuk memperbarui waktu sistem. Mengganti pawang interupsi ini adalah praktik yang umum jika Anda perlu melakukan beberapa tindakan 18 kali per detik (pada kenyataannya, pawang asli juga harus dipanggil di dalam kait, jika tidak, waktu sistem akan berhenti). Berdasarkan ini, ternyata frekuensi berikutnya dibebankan ke generator setiap (1 / 18.2) * 1000 ~ 55
.
Rencananya adalah ini:
- letakkan breakpoint dalam fungsi
play_sample
, pada baris di mana pembagi frekuensi berikutnya diekstraksi; - menghitung frekuensi sesuai dengan rumus
freq = 1193180 / divisor
; - menghasilkan 55ms sinyal frekuensi
freq
persegi di beberapa jenis editor audio (saya menggunakan Adobe Audition ); - ulangi tiga langkah pertama hingga setidaknya 3 detik diakumulasikan.
Jadi saya mendapatkan awal melodi dari menu utama, tetapi bermain 10 kali lebih lambat dari yang diperlukan. Lalu saya mengurangi durasi "sampel" dari 55 ms menjadi 5 ms - itu menjadi jauh lebih baik, tapi tetap saja tidak. Masalah waktu tetap terbuka sampai saya menemukan artikel ini . Ternyata interupsi kedelapan dihasilkan dengan mengumpankan 8253 yang sama, saluran nol yang terhubung ke pengendali interupsi ( PIC ). Ketika mesin melakukan boot, BIOS menetapkan nol saluran untuk menghasilkan pulsa dengan frekuensi ~ 18,2 Hz (yaitu, interupsi dihasilkan setiap ~ 54,9 ms). Namun, saluran nol dapat diprogram ulang sehingga menghasilkan pulsa dengan frekuensi yang lebih tinggi, untuk ini, dengan analogi dengan saluran kedua, Anda perlu menulis nilai sama dengan 1193180 / freq
ke port ke-40, di mana freq
adalah nilai frekuensi yang diperlukan dalam Hertz. Ini terjadi pada fungsi init_8253
, hanya saja pada awalnya saya tidak memperhatikannya:
init_8253: ... mov al, 0B6h ; 10110110b out 43h, al ; Timer 8253-5 (AT: 8254.2). mov ax, 13B1h out 40h, al ; Timer 8253-5 (AT: 8254.2). mov al, ah out 40h, al ; Timer 8253-5 (AT: 8254.2).
Nilai 13B1h
diterjemahkan ke dalam frekuensi: 1193180 / 13B1h ~ 236.7
, maka kita mendapatkan sekitar (1 / 236.7) * 1000 ~ 4.2
per "sampel". Teka-teki telah berkembang.
Maka itu masalah teknologi - untuk mengimplementasikan fungsi yang mengekstraksi soundtrack dari game. Tapi ada satu hal, nilai-nilai pembagi frekuensi yang direkam di port 42 tidak disimpan secara eksplisit. Mereka dihitung oleh beberapa algoritma rumit, data input dan area kerja yang terletak langsung di file executable game (di segmen ketujuh, menurut Ida ). Selain itu, dari fitur-fiturnya, tidak ada tanda-tanda akhir trek, ketika tidak ada lagi yang harus diputar, algoritme ini secara tak terbatas menghasilkan keadaan nol dari speaker. Tetapi saya tidak repot dan, seperti dalam kasus algoritma dekompresi (bagian pertama ), saya hanya memindahkan porter ke assembler 64-bit fungsi pengaturan trek untuk pemutaran dan algoritma untuk mendapatkan frekuensi berikutnya (dan saya mengambil segmen ketujuh seluruhnya).
Dan itu berhasil. Setelah itu, saya mengimplementasikan fungsi pembuatan soundtrack untuk trek yang dipilih ( PCM, 44100 Hz, 8 bit, mono ; melakukan sesuatu seperti generator yang digunakan dalam emulator speaker di DosBox ). Saya memecahkan masalah dengan tanda akhir dengan counter sederhana keheningan: menghitung detik - kami menyelesaikan algoritma. Membungkus lagu yang dihasilkan di header WAV dan menyimpan hasilnya ke file, saya mendapatkan lagu dari menu utama. Dan 13 lagu lagi yang bisa Anda dengarkan di bawah ini [atau di penampil sumber daya, yang sekarang memiliki pemutar bawaan dan kemampuan untuk menyimpan trek apa pun di .WAV] :
[Mempelajari masalah ini, saya belajar tentang teknik βpagerβ yang lebih canggih, seperti menggunakan modulasi lebar-pulsa untuk reproduksi suara PCM berkualitas rendah. Di akhir artikel ini, saya akan memberikan daftar materi yang dapat Anda pelajari lebih lanjut.]
Pada bagian kedua , ketika berbagai format sumber daya dipertimbangkan, saya menyarankan agar file .ANH berisi animasi untuk latar belakang lokasi (yaitu, untuk gambar yang disimpan dalam .PIC ). [Ini benar.] Setelah selesai dengan suaranya, saya memutuskan untuk memeriksanya. Murni dengan asumsi bahwa animasi diterapkan langsung ke gambar latar belakang yang disimpan dalam memori (bukan dalam memori video , tetapi dalam rantai sprite ), saya memutuskan untuk membuang memori ini, masing-masing, sebelum dan setelah menerapkan animasi (lihat di mana kursor menunjuk ke atas string huruf 'S'):



3DE6:0E26 03 B4 44 B3 ... ; 3DE6:0E26 03 BC CC B3 ... ;
Persis seperti yang saya harapkan - warna merah gelap (0x4) berubah menjadi merah terang (0xC). Sekarang Anda dapat mencoba mengatur breakpoint untuk mengubah nilai di alamat, misalnya, 3DE6:0E28
dan, jika Anda beruntung, lakukan pelacakan mundur. [Saya beruntung.] Breakpoint membawa saya ke garis yang secara langsung mengubah nilai pada alamat yang diberikan: xor es:[bx], al
. Setelah memeriksa lingkungan sekitar, saya dengan mudah membangun rantai panggilan dari loop tingkat utama ke titik ini: sub_1231E (xor es:[bx], al) <- sub_12222 <- sub_105F6 <- sub_1038F ( )
.
Saya tidak akan menjelaskan secara rinci bagaimana saya membalik proses animasi. Ini adalah pekerjaan yang cukup rutin dan metodis, tetapi tidak terlalu sulit jika batas-batasnya dengan jelas digambarkan (jalur mundur yang diterima adalah batas-batas ini). Tetapi saya tidak bisa tidak berbicara tentang apa yang terjadi pada akhirnya.
Pertama tentang format .ANH . Bahkan, itu adalah satu set kontainer, dan kata pertama dalam file .ANH adalah jumlah kontainer di dalamnya:
typedef struct anh_hdr_t { uint16_t anh_entries; } anh_hdr_t;
Wadah itu sendiri adalah animasi yang diambil secara terpisah dari elemen latar belakang. Anda dapat memilih header untuk wadah yang berisi ukuran byte dan jumlah bingkai dalam animasi yang diwakilinya. Di sebelah header adalah nilai durasi (penundaan) dari frame berikutnya dan offset dari byte dari frame itu sendiri, relatif terhadap byte dari frame pertama. Jumlah pasangan tersebut jelas sama dengan jumlah bingkai:
typedef struct anh_entry_hdr_t { uint16_t entry_size; uint16_t total_frames; } anh_entry_hdr_t; typedef struct anh_frame_data_t { uint16_t frame_sleep; uint16_t frame_offset; } anh_frame_data_t; ... extern uint8_t *anh; anh_hdr_t *hdr = (anh_hdr_t*)anh; anh_entry_hdr_t *entry = (anh_entry_hdr_t*)(anh + sizeof(anh_hdr_t)); for (uint16_t u = 0; u < anh->anh_entries; u++) { uint8_t *p = (uint8_t*)entry; anh_frame_data_t *first_frame_data = (anh_frame_data_t*)(p + sizeof(anh_entry_hdr_t)); uint8_t *first_frame_bytes = p + (entry->total_frames * sizeof(anh_frame_data_t)); for (uint16_t k = 0; k < entry->total_frames; k++) { anh_frame_data_t *frame_data = first_frame_data + k; uint8_t *frame_bytes = first_frame_bytes + frame_data->frame_offset; ... } p += (entry->entry_size + 2); entry = (anh_entry_hdr_t*)p; }
Bingkai terpisah terdiri dari header empat byte yang berisi dimensi linier dan perpindahan relatif terhadap gambar latar belakang, dan piksel bingkai yang dikodekan oleh algoritma yang sudah akrab bagi saya oleh Run Length :
typedef struct anh_frame_hdr { uint8_t bg_x_offt; uint8_t bg_y_offt; uint8_t frame_width; uint8_t frame_height; };
"Hamparan" bingkai di latar belakang dapat terlihat seperti ini:
extern uint8_t *level_bg; uint8_t frame_pix[8192]; anh_frame_hdr *hdr = (anh_frame_hdr*)frame_bytes; uint16_t frame_len = hdr->frame_width * hdr->frame_height; decode_rle(frame + sizeof(anh_frame_hdr), frame_len, frame_pix); uint16_t bg_offt = (hdr->bg_y_offt * 152) + hdr->bg_x_offt + 0xFB4E; uint16_t bg_skip = 152 - hdr->frame_width; uint8_t *p1 = frame_pix, *p2 = level_bg; for (uint16_t i = hdr->frame_height; i != 0; i--) { for (uint16_t j = hdr->frame_width; j != 0; j--) { *p2++ ^= *p1++; } p2 += bg_skip; }
Ini adalah format .ANH , tetapi ada struktur lain yang membuatnya berfungsi:
typedef struct bg_animation_control_table_t { uint16_t total_frames; uint8_t *first_frame_data; uint8_t *first_frame_bytes; uint16_t sleep; uint16_t curr_frame; } bg_animation_control_table_t;
Dalam gim itu sendiri, sederetan setidaknya empat struktur semacam ini dinyatakan secara global. Setelah memuat file .ANH berikutnya, jumlah animasi di dalamnya juga disimpan dalam variabel global, dan elemen-elemen array diinisialisasi sebagai berikut:
extern uint8_t *anh; uint16_t g_anim_amount = 0; bg_animation_control_table_t g_anim_ctl[4]; ... anh_hdr_t *hdr = (anh_hdr_t*)anh; anh_entry_hdr_t *entry = (anh_entry_hdr_t*)(anh + sizeof(anh_hdr_t)); g_anim_amount = hdr->anh_entries; for (uint16_t u = 0; u < g_anim_amount; u++) { uint8_t *p = (uint8_t*)entry; g_anim_ctl[u].total_frames = entry->total_frames; g_anim_ctl[u].first_frame_data = p + sizeof(anh_entry_hdr_t); g_anim_ctl[u].first_frame_bytes = g_anim_ctl[u].first_frame_data + (entry->total_frames * sizeof(anh_frame_data_t)); g_anim_ctl[u].sleep = *(uint16_t*)(g_animation_control[u].first_frame_data); g_anim_ctl[u].curr_frame = 0; p += (entry->entry_size + 2); entry = (anh_entry_hdr_t*)p; }
Akhirnya, terapkan animasi:
for (uint16_t u = 0; u < g_anim_amount; u++) { bg_animation_control_table_t *anim = &g_anim_ctl[u]; if (anim->sleep-- == 0) { anh_frame_data_t *data = (anh_frame_data_t*)anim->first_frame_data + anim->curr_frame; ... if (++anim->curr_frame == anim->total_frames) { anim->curr_frame = 0; data = (anh_frame_data_t*)anim->first_frame_data; } else { data++; } anim->sleep = data->frame_sleep; } }
Dan kami mendapatkan yang berikut ini [Anda bisa melihat lebih banyak di penampil sumber daya] :
Ada beberapa masalah dengan animasi saat ini. Yang pertama adalah bahwa dalam kode saya, saya memainkan semua animasi yang tersedia, tetapi aslinya memeriksa beberapa flag global yang menunjukkan apakah akan menggulir melalui yang berikutnya. Dan yang kedua, karena fakta bahwa beberapa animasi menambahkan objek ke layar yang awalnya tidak ada. Dan karena frame "bertengkar" di latar belakang, kemudian dengan pengguliran siklus, pada setiap lingkaran kedua objek menghilang begitu saja. Di sini, misalnya, bagaimana tampilannya:
Tapi untuk saat ini, mari kita biarkan apa adanya.
Ingat algoritma dekompresi yang tidak diketahui dari bagian pertama ? Setelah hampir tidak terhubung ke pengembangan, viiri tidak hanya menentukan jenis algoritma apa itu, tetapi juga menulis versi dekoder sendiri, menggantikan potongan Assembler tiga baris yang mengerikan dalam basis kode. Dalam hal ini, saya meminta viiri untuk menulis esai singkat tentang pekerjaan yang dilakukan. Yang sudah dilakukan, tetapi sebelum saya berikan, beberapa kata perlu dikatakan tentang teori.
Untuk mengompresi sumber daya, para pengembang Neuromancer menggunakan kode Huffman . Ini adalah salah satu metode pengkodean informasi efektif pertama yang menggunakan kode awalan . Dalam teori pengkodean, kode-kode dengan kata panjang variabel dan kode-kode di mana tidak ada kode kata adalah awalan dari yang lain disebut kode awalan . Artinya, jika kata "a" termasuk dalam kode awalan, maka kata "ab" tidak ada dalam kode. Properti ini memungkinkan Anda untuk secara unik membobol kata-kata pesan yang dikodekan oleh kode tersebut.
Gagasan algoritma Huffman adalah bahwa, mengetahui probabilitas kemunculan karakter alfabet tertentu dalam pesan, kita dapat menggambarkan prosedur untuk membuat kode dengan panjang variabel, yang terdiri dari jumlah bit integer. Simbol dengan probabilitas kejadian yang lebih tinggi diberikan kode yang lebih pendek, dan simbol dengan probabilitas yang lebih rendah, sebaliknya, lebih lama. Secara umum, prosedur pengkodean dikurangi untuk membangun pohon kode yang optimal dan, pada dasarnya, memetakan simbol pesan ke kode yang sesuai. Properti awalan dari kode yang diterima memungkinkan Anda untuk secara unik mendekode pesan terkompresi.
Algoritma memiliki satu kelemahan signifikan (pada kenyataannya, bukan satu, tetapi sekarang hanya yang ini penting). Faktanya adalah bahwa untuk memulihkan isi pesan terkompresi, decoder harus mengetahui tabel frekuensi kemunculan karakter yang digunakan oleh encoder. Dalam hal ini, bersama dengan pesan yang disandikan, baik tabel probabilitas atau pohon kode itu sendiri (opsi yang digunakan dalam game) harus dikirim. Ukuran data tambahan bisa relatif besar, dan ini secara signifikan mempengaruhi efisiensi kompresi.
Sesuatu tentang bagaimana Anda dapat menangani hal ini, serta tentang decoder Anda dan yang diimplementasikan dalam game, memberi tahu viiri :
Patut disebutkan bahwa keseluruhan permainan ditulis sepenuhnya dalam Assembler, dengan tangan, sehingga kode tersebut berisi solusi, trik, dan optimisasi yang menarik.
Menurut prosedur. Fungsi sub_1ff94
( build_code_table
) diperlukan untuk memuat pohon Huffman terkompresi dari file. Untuk mendekode Huffman statis (terkadang dinamis , dan persyaratan ini tidak berlaku untuknya), bersama dengan pesan, pohon kode harus ditransmisikan, yang merupakan pemetaan kode Huffman ke kode karakter asli. Pohon ini cukup besar dan karenanya akan menyenangkan untuk menyimpannya dengan efisien. Cara yang paling benar adalah dengan menggunakan kode kanonik ( MOAR ). Karena sifatnya, ada cara yang sangat menarik dan efektif untuk menyimpan pohon (digunakan dalam penerapan metode kompresi Deflate dari pengarsipan PKZip ). Tetapi kode kanonik tidak digunakan dalam permainan, sebaliknya dilakukan direct tree traversal dan untuk setiap titik bit 0 ditulis ke aliran output jika simpul bukan daun, atau bit 1 jika simpul adalah daun, dan kemudian 8 bit berikutnya adalah kode karakter simpul Saat decoding, dilakukan traversal pohon serupa, yang kita lihat di game. Ada contoh dan beberapa penjelasan.
build_code_table build_code_table proc near call getbit ; jb short loc_1FFA9 ; ... shl dx, 1 inc bx call build_code_table ; build_code_table or dl, 1 call build_code_table ; build_code_table shr dx, 1 dec bx ret loc_1FFA9: call sub_1FFC2 ; (8 ) ... ; ret sub_1FF94 endp sub_1FFC2 proc near sub di, di mov ch, 8 loc_1FFC6: call getbit rcl di, 1 dec ch jnz short loc_1FFC6 retn sub_1FFC2 endp
getbit
( sub_1ffd0
) membaca sedikit dari aliran input. Analisisnya memungkinkan kita untuk menyimpulkan bahwa bit individu diekstraksi dari register lodsw
16-bit, yang nilainya dimuat dari memori oleh instruksi lodsw
, yang memuat dua byte dari stream, tetapi karena prosesor Intel memiliki urutan bit -endian byte yang kecil, xchg
menata ulang setengah dari register. Selanjutnya, urutan bit dalam aliran agak tidak logis - yang pertama bukan yang paling sedikit, tetapi bit yang paling signifikan. , shl
, jb
.
getbit getbit proc near or cl, cl jz short loc_1FFD9 dec cl shl ax, 1 retn loc_1FFD9: cmp si, 27B6h jz short loc_1FFE7 ; lodsw xchg al, ah mov cl, 0Fh shl ax, 1 retn loc_1FFE7: call sub_202FC ; lodsw xchg al, ah mov cl, 0Fh shl ax, 1 retn getbit endp
viiri , :
huffman_decompress typedef struct node_t { uint8_t value; struct node_t *left, *right; } node_t; static uint8_t *g_src = NULL; static int getbits(int numbits) { ... } static uint32_t getl_le() { } static node_t* build_tree(void) { node_t *node = (node_t*)calloc(1, sizeof(node_t)); if (getbits(1)) { node->right = NULL; node->left = NULL; node->value = getbits(8); } else { node->right = build_tree(); node->left = build_tree(); node->value = 0; } return node; } int huffman_decompress(uint8_t *src, uint8_t *dst) { int length, i = 0; node_t *root, *node; g_src = src; length = getl_le(); node = root = build_tree(); while (i < length) { node = getbits(1) ? node->left : node->right; if (!node->left) { dst[i++] = node->value; node = root; } } ... }
:
build_code_table
. , , . , . , , , β ( huffman_decompress
).
? ! ! , . ( ): . , 3- , N , (3 β N) .
ABCD
: A - 0b, B - 10b, C - 110b, D - 111b
. ( ), , :
| | |
---|
0 00b | 1 | A |
10 0b | 2 | B |
110 b | 3 | C |
111 b | 3 | D |
, . , , , 010b
β . . , 'A' 0b
, , . :
| | | |
---|
0 | 0 00b | 1 | A |
1 | 0 01b | 1 | A |
2 | 0 10b | 1 | A |
3 | 0 11b | 1 | A |
4 | 10 0b | 2 | B |
5 | 10 1b | 2 | B |
6 | 110 b | 3 | C |
7 | 111 b | 3 | D |
, 011010111b
:
- :
011b
; - ,
011b (3)
, A
, ; 011b
1, , : 110b
;- ,
110b (6)
,
, ; - , .
«» 8- . 256 . 8 . , , :
: β , . , . 4 β 32- .
, . .
, github . , , , - [ , README.md] .
, , 2015- :
- LibNeuroRoutines (, MASM) β , , . (
neuro_routines.h
) , . , :
- (
huffman_decompression.c
, decompression.c
); - (
cp437.c
); - (
dialog.c
); - (
audio.c
).
- NeuromancerWin64 () β . . , «» , , . CSFML ( SFML C ).
- ResourceBrowser (C++) β . MFC -, .DAT -. :
- BMP (8bpp) ( IMH , PIC );
- ( ANH );
- WAV (PCM, 44100Hz, 8bps, mono) ( SOUND ).
LibNeuroRoutines . LibNeuroRoutines CSFML ( ResourceBrowser SFML ).
64- Windows . , LibNeuroRoutines 64- MASM (Microsoft Macro Assembler) . β , 64- . , NASM FASM , , . VS 2015 β MASM .
, . C . , ( , MFC ).
, . - , .
. ? , . , . - , . , , , . ().
- Make sound from the speaker using assembly
- Programming the PC Speaker
- PC Speaker
- Programmable Interval Timer
- Making C Sing
- Beyond Beep-Boop: Mastering the PC Speaker