ニューロマンサーの逆。 パート2:フォントのレンダリング









こんにちは、ニューロマンサーのリバースエンジニアリングに関する記事の続きを読んでいます。これは、ウィリアムギブソンによる同名の小説に基づいて、1988年にInterplay Productionsがリリースしたビデオゲームです。 そして、あなたが最初の部分を見ていないなら、それから始めることをお勧めします、そこで私は私の動機について話し、最初の結果を共有します。







ニューロマンサーの逆。 パート1:スプライト

そして、前回停止した場所から文字通り続行します。










同じ.PIC



が.PICに保存されているという仮定に基づいて、 .IMH



リソースを解凍した.PIC



ファイルに同じ関数を適用しようとすることができます。 私は最初に.PIC



R1.PIC



)を取り、それをバッファに読み込みます:







 R1.PIC: 4934  0x0000: 0008 010a 000f 020c 0102 020f 0600 0f0f 0x0010: 0004 060e 0000 0607 0309 010f 0a0d 0d0f 0x0020: 981d 0000 2002 0611 74a2 38c5 d003 e9cb 0x0030: fb18 ac3b 8fbf 2713 459e 8c3f 3aa1 6ca7 ... 0x1330: 6f8a c3f5 d9c7 53e5 f47c d945 51d9 c753 0x1340: e5bf 1ebf 3f00
      
      





このファイルは、 .IMH



同じように.IMH











今、慎重に、デバッガの下で、それをデコードしてみてください。 解凍関数は成功し、解凍されたデータの長さを返します。







 R1.PIC_decompd: 7576  0x0000: e901 1130 1113 0111 3011 1301 1130 1113 0x0010: 0111 3011 1301 3130 0113 f801 3330 1333 ... 0x1d80: 00ff 0816 00fe 8000 0108 0100 0180 ff08 0x1d90: 2b00 ff80 0288 0200
      
      





ただし、ここに表示される内容は、 .IMH



リソースを圧縮解除した後に観察したものとは若干異なります。 いいえ、同じRun-Lengthアルゴリズムでエンコードされたビットマップのように見えますが、 ピクセルサイズを含む _ [前の部分のstruct rle_hdr_t



]ヘッダーはありません これらのサイズを使用してデコードアルゴリズム [ decode_rle()



関数の前の部分] _ をタイムリーに停止し.BMP



ヘッダーを作成したため、これは良くありません 。 画像が1つだけエンコードされていると仮定すると、入力データの長さはアルゴリズムを停止するのに十分ですが、 .BMP



画像を保存するために線形寸法なしで行う方法はありません。







しかし、問題が解決したら解決します。 各.PIC



実際には1つの画像に格納されており、ピクセルサイズは同じであるとしましょう。 decode_rle()



関数をdecode_rle()



デコードされた出力ではなく、処理された入力バイトの数をカウントします。 バッファーr1_pic_decompd



ます。 それは成功し、サイズが34048バイトのビットマップを返します(17024 * 2、2つのピクセルが1バイトで記録されるという事実を考慮に入れて):







 R1.PIC_decoded: 17024  0x0000: 0111 3011 1301 1130 1113 0111 3011 1301 0x0010: 1130 1113 0131 3013 1301 3330 1333 0333 ... 0x4270: 7777 7777 7777 7777 7708 8800 0088 8880
      
      





幅と高さは1日あたり不明ですが、場所の背景を扱っていると思います。つまり、ゲーム内でそれらを直接測定することができます。 ただし、エクスポートされた.IMH



リソースには、フルサイズのゲーム内ユーザーインターフェース( NEURO.IMH



)のイメージが含まれていたため、さらに簡単です。 ペイントで画像を開き、測定を行います。















幅と高さを乗算すると、目的の34048バイトが得られます。これとその他すべてのバックを.BMP



(合計55)で安全にラップできます。




















この波では、未知の目的のタイプのリソース.BIH



.ANH



を分割しようとすることができます。 古いパターンに従います。ランダムなファイルを読み取ります。 あなたが開くことができると思われる場合-開く; RLEのように見える場合-デコード。 そして、一般的に、計画は機能しましたが、内部にあるもの、いわば...はあいまいです。 たとえば、解凍後のリソースR1.BIH



コンテンツをR1.BIH



します。







 R1.BIH_decompd: 2151  0x0000: 00 00 00 00 00 00 fe 00 2a 00 00 00 00 00 fb 00 .......*...... 0x0010: fd 00 fc 00 00 00 c7 00 46 5a 5a 5f 9b 5f 9f 73 .....FZZ_._џs 0x0020: 46 73 5a 77 00 5f 02 73 01 00 2e 00 7a 00 01 02 FsZw._.s....z... 0x0030: 13 00 03 04 05 12 00 7f 2c 00 08 14 00 2e 13 00 ........,....... 0x0040: 0e 3d 00 01 00 0e 12 00 ff ff 0e 14 00 00 00 03 .=............ 0x0060: 01 11 16 db 00 01 1f 02 20 0e 14 00 00 00 0e 12 ....... ....... 0x0070: 00 ff ff 12 06 10 00 ff cc ff 05 10 00 06 07 00 ........... 0x0080: 01 08 04 04 00 01 07 04 bc ff 01 0b 13 00 0c 03 ........ј...... 0x0090: 17 05 10 00 ff 05 00 04 f9 ff 05 10 00 0c 07 00 ............. 0x00A0: 01 0f 04 0a 00 05 10 00 0d 0b 00 01 10 13 00 0c ................ 0x00B0: 03 04 df ff 01 13 13 00 17 02 06 10 00 ff f8 ff ........... 0x00C0: 05 10 00 17 07 00 01 19 04 ed ff 13 00 1b 02 01 .............. 0x00D0: 1a 06 10 00 ff fc ff 05 10 00 1b 07 00 01 1d 04 ............. 0x00E0: 04 00 01 1e 13 00 ff 00 04 ff ff e8 13 00 81 c3 ..........Ѓ 0x00F0: 04 00 8b 1f 8b 47 14 01 87 ca 00 83 97 cc 00 00 .....G....ѓ—.. 0x0100: cb e8 01 00 90 5b 81 eb f4 00 c3 cb cb cb 59 6f ..ђ[Ѓ.Yo 0x0110: 75 27 76 65 20 6a 75 73 74 20 73 70 65 6e 74 20 u've just spent 0x0120: 74 68 65 20 6e 69 67 68 74 20 73 6c 65 65 70 69 the night sleepi 0x0130: 6e 67 20 66 61 63 65 2d 64 6f 77 6e 20 69 6e 20 ng face-down in 0x0140: 61 20 70 6c 61 74 65 20 6f 66 20 73 79 6e 74 68 a plate of synth 0x0150: 2d 73 70 61 67 68 65 74 74 69 20 69 6e 20 61 20 -spaghetti in a 0x0160: 62 61 72 20 63 61 6c 6c 65 64 20 74 68 65 20 43 bar called the C 0x0170: 68 61 74 73 75 62 6f 2e 20 41 66 74 65 72 20 72 hatsubo. After r 0x0180: 75 62 62 69 6e 67 20 74 68 65 20 73 61 75 63 65 ubbing the sauce 0x0190: 20 6f 75 74 20 6f 66 20 79 6f 75 72 20 65 79 65 out of your eye 0x01A0: 73 2c 20 79 6f 75 20 63 61 6e 20 73 65 65 20 43 s, you can see C 0x01B0: 68 69 62 61 20 73 6b 79 20 74 68 72 6f 75 67 68 hiba sky through 0x01C0: 20 74 68 65 20 77 69 6e 64 6f 77 2c 20 74 68 65 the window, the 0x01D0: 20 63 6f 6c 6f 72 20 6f 66 20 74 65 6c 65 76 69 color of televi 0x01E0: 73 69 6f 6e 20 74 75 6e 65 64 20 74 6f 20 61 20 sion tuned to a 0x01F0: 64 65 61 64 20 63 68 61 6e 6e 65 6c 2e 0d 0d 41 dead channel...A ... 0x0840: 3f 00 0d 0d 52 61 74 7a 20 72 65 66 75 73 65 73 ?...Ratz refuses 0x0850: 20 74 6f 20 74 61 6b 65 20 79 6f 75 72 20 63 72 to take your cr 0x0860: 65 64 69 74 73 2e 00 edits..
      
      





[はい、これはまさにあなたが考えたことです。] R%n.BIH



という名前の他のファイルも同様の構造を持っています:上部にバイト数があり、その後にテキストのキャンバスが続きます。 明らかに、私はゲーム内の行を、それらが属する場所ごとに分けて扱っています[ R1



最初、 R2



番目など。
たとえば、開始場所はR1.PIC



から背景をR1.PIC



、最初に表示されるテキストは次のR1.PIC



] You've just spent the night sleeping face-down in a plate of synth-spaghetti in a bar called the Chatsubo



です。 上部からのバイトは特定の制御構造を編成しますが、コードとは別に、それを解析することはできません。他の.BIH



ファイルを調べて.BIH



ます。







 CORNERS.BIH_decompd: 128  0x0000: ff ff f0 00 ff f0 0f ff ff 0f ff ff f0 ff ff ff ... 0x0010: f0 ff ff ff 0f ff ff ff 0f ff ff ff 0f ff ff ff ... 0x0020: 00 00 ff ff 0f ff 00 ff 0f ff ff 0f 0f ff ff f0 ....... 0x0030: 0f ff ff f0 0f ff ff ff 0f ff ff ff 0f ff ff ff .... 0x0040: 0f ff ff ff 0f ff ff ff 0f ff ff ff f0 ff ff ff ... 0x0050: f0 ff ff ff ff 0f ff ff ff f0 0f ff ff ff f0 00 ... 0x0060: ff ff ff f0 ff ff ff f0 ff ff ff f0 ff ff ff 0f . 0x0070: ff ff ff 0f ff ff f0 ff ff f0 0f ff 00 0f ff ff ....
      
      





 ROOMPOS.BIH_decompd: 1160  0x0000: 68 8f 75 0d 4b 69 00 02 8e 63 02 17 71 74 29 02 hЏu.Ki..Ћc..qt). 0x0010: 0e 63 02 17 68 8f 75 15 72 69 24 02 8e 63 02 17 .c..hЏu.ri$.Ћc.. 0x0020: 15 74 7a 02 16 63 02 17 68 8f 75 0d 2a 69 00 02 .tz..c..hЏu.*i.. 0x0030: 8e 63 02 17 08 74 8c 02 0e 63 02 17 70 63 75 0d Ћc...tЊ..c..pcu. ... 0x0470: 0e 6b 02 0f 6e 8f 75 0d 08 6f 8c 02 8e 69 02 11 .k..nЏu..oЊ.Ћi.. 0x0480: 08 74 8c 02 0e 69 02 11 .tЊ..i..
      
      





ここで見たものは、 R%n.BIH



観察したパターンとはR%n.BIH



ます。 彼らは似ていません! CORNERS.BIH



のコンテンツはビットマップに似ており、名前によって判断するROOMPOS.BIH



は、場所にオブジェクトを配置することに関連する場合がありますが、そのコンテンツは明確ではありません。

これらに加えて、 COPEN%n.BIH



DB%nBIH



FIJU0.BIH



HITACHI0.BIH



などがありますが、読みやすいテキストが含まれていてもR%n.BIH



似ていますが、見出し場所が異なります。 後でそれを残して、 .ANH



何があるか見てみましょう。







ここでは状況が良くなっています。 すべての.ANH



R%n.ANH



があります。これは、それらが場所に関連するR%n.ANH



であることを意味します。 それらの多くはありません:すべてのn



R%n.PIC



R%n.BIH



がある場合、対応するR%n.ANH



はいくつかのn



についてのみ見つかります。 それらはすべて同じアルゴリズムで圧縮されています。内部の内容を見てみましょう。







 R1.ANH_decomp: 1100  0x0000 04 00 4e 01 0f 00 17 00 00 00 0c 00 00 00 02 00 ..N............. 0x0010 0e 00 03 00 0e 00 01 00 0e 00 02 00 0e 00 02 00 ................ 0x0020 0e 00 03 00 0e 00 01 00 0e 00 02 00 0e 00 0e 00 ................ 0x0030 00 00 15 00 19 00 03 00 72 00 11 00 72 00 03 00 ........r...r... 0x0040 ba 00 23 25 03 17 fd 87 00 87 3e 00 ff 44 01 00 є.#%.....>.D.. ... 0x0160 73 00 04 00 73 00 03 00 73 00 14 00 73 00 04 00 s...s...s...s... 0x0170 00 00 03 00 00 00 03 00 73 00 04 00 73 00 04 00 ........s...s... 0x0180 00 00 03 00 00 00 03 00 73 00 04 00 73 00 0e 00 ........s...s... 0x0190 00 00 17 00 00 00 03 00 73 00 04 00 73 00 04 00 ........s...s... 0x01A0 00 00 03 00 00 00 03 00 73 00 04 00 73 00 1b 30 ........s...s..0 0x01B0 0b 10 01 00 ff 03 09 00 ff 30 08 00 fc 13 30 08 .......0...0. ... 0x0430 30 02 05 fe 08 88 02 08 fb 88 08 00 08 00 71 36 0...€..€....q6 0x0440 02 05 fe 00 80 02 08 ff 88 03 08 00 ...Ђ..€...
      
      





悲しいかな、ここではほとんど役に立ちません。まあ、わかりました。途中で理解できます。 他のこともできますが。 [ゲームの開始場所を起動すると、この場所の背景がアニメーション化されていることに気付きました。 .PIC



静的な画像.PIC



いることを.PIC



、これは興味深いことです。
まだテストされていませんが、これらのアニメーションが含まれているのは.ANH



あると思います。]










簡単なウィンドウユーティリティであるリソースビューアーを書くのに少し時間を費やしました。 その過程で、最初のMFCが壊れたという事実のために、2017年のスタジオから2015年のスタジオに移動する必要がありました。 同じソリューションで、 LibNeuroRoutines



ライブラリLibNeuroRoutines



を作成しました。 LibNeuroRoutines



には、元のゲームから逆の手順を徐々に追加します。 最初に、解凍機能とリソースデコード機能が導入されました。 これらを分けておくと便利です。


















ScummVMプロジェクトwikiでリバースエンジニアリングに関するメモを読んだ後、ローカルテキストレンダリングをリバースすることにしました。 [最初は単純にフォントを抽出したかったのですが、最終的にはもっと多くのことができるようになりました。]この方向で最初のステップを踏むのは簡単でした-逆アセンブルされたリストでmain



関数を調べることで、メイン入力に表示される文字列のアドレスを受け取る関数を見つけましたゲームメニュー-「 New/Load



」:







  ... sub ax, ax push ax mov ax, 1 push ax mov ax, 5098h ; "New/Load" push ax call sub_13C6E ; sub_13C6E("New/Load", 1, 0) ...
      
      





デバッガーでsub_13C6E



関数をsub_13C6E



すると、送信された文字列を表示するのは彼女だと確信しています。















そして、ここでトレースを開始することは可能ですが、微妙な違いがあり、座標に似たものは受け入れません。 この場合、テキストはフレームの中央に正確に描画されます。 たぶん、この関数でもレンダリングされますか? しかし、引数1と0はそれと何の関係があるのでしょうか? 次に、 sub_13C6E



すぐ上にある別の関数の呼び出しに気付きました。







  ... sub ax, ax push ax push ax mov ax, 1 push ax mov ax, 0Ah push ax mov ax, 14h push ax mov ax, 5 push ax mov ax, 6 push ax call sub_13A9E ; sub_13A9E(6, 5, 20, 10, 1, 0, 0) add sp, 0Eh sub ax, ax push ax mov ax, 1 push ax mov ax, 5098h ; "New/Load" push ax call draw_string ; draw_string("New/Load", 1, 0) ...
      
      





このコードをトレースすると、 sub_13A9E



関数sub_13A9E



フレームをsub_13A9E



draw_string



関数がdraw_string



テキストをdraw_string



draw_string



ました。 これらの呼び出しは、何らかのグローバル変数を介して接続されている可能性があります。 いずれにせよ、特に興味深い引数のセットを必要とするため、 sub_13A9E



理解を開始することをおsub_13A9E



ます。









測定を行い、測定値がこれらの引数の値に何らかの形で関連しているかどうかを確認する必要があります。 ここでは数値間に明らかな依存関係はありません。 sub_13A9E



をトレースしsub_13A9E












ここで何が起こりますか:まず、特定の構造がデータセグメントに書き込まれます(コメントで、記録された値を計算する実行と特定の操作の順序を示しました)。







 ; sub_13A9E(6, 5, 20, 10, 1, 0, 0) ; (a, b, c, d, e, f, g) .dseg ... word[0x65FA]: 0x20 ; 10: word[0x6602] - 8 = 32 word[0x65FC]: 0x98 ; 11: word[0x6604] - 8 = 152 word[0x65FE]: 0x7F ; 12: word[0x6606] + 8 = 127 word[0x6600]: 0xAF ; 13: word[0x6608] + 8 = 175 word[0x6602]: 0x28 ; 2: b << 3 = 40 word[0x6604]: 0xA0 ; 3: c << 3 = 160 word[0x6606]: 0x77 ; 4: (d << 3) + word[0x6602] - 1 = 119 word[0x6608]: 0xA7 ; 5: (e << 3) + word[0x6604] - 1 = 167 word[0x660A]: 0x28 ; 6: word[0x6602] = 40 word[0x660C]: 0xA0 ; 7: word[0x6604] = 160 word[0x660E]: 0x77 ; 8: word[0x6606] = 119 word[0x6610]: 0xA7 ; 9: word[0x6608] = 167 word[0x6612]: 0x06 ; 1: a = 6 word[0x6614]: 0x00 ; 14: 0 ... word[0x66D6]: 0x30 ; 17: (d << 2) + 8 = 48 word[0x66D8]: 0x02 ; 15: 2 word[0x66DA]: 0x22FB ; 16: seg11
      
      





アドレス0x65FA



および0x65FC



の座標が、それぞれフレームの左(32)上部(152)の座標であることを認識しないことはできません。 さらに進んで、これらの値を0x65FE



0x6600



記録された値から減算すると、95と23が得られます。1を追加すると、フレームの幅(96)と高さ(24)が正確に出力されます。 とても面白い。 ここで、 draw_frame



関数( sub_13A9E



)の引数の名前をa



からe



に変更すると( 最後の2つの引数は関数によって使用されず、おそらくコンパイラによって追加されるだけです) 、次の式を導出できます。







 left = b * 8 - 8 = (b - 1) * 8 top = c * 8 - 8 = (c - 1) * 8 width = (d * 8) + (b * 8) + 8 - ((b * 8) - 8) = (d * 8) + 16 = (d + 2) * 8 height = (e * 8) + (c * 8) + 8 - ((c * 8) - 8) = (e * 8) + 16 = (e + 2) * 8
      
      





考慮された構造にアドレス[0x66DA]:[0x66D8]



22FB:0002-セグメント:offset )で記入した後、計算されたサイズのフレームのビットマップイメージが構築され、アドレスから(VGAバッファー内で)VGAメモリに1行ずつ転送されます。計算された左上隅の座標に対応する( 152 * 320 + 32 = 0xBE20, A000:BE20



):







メモリ内のフレームのように見えます
  SEG11: 22FB:0002 0000 0000 3000 1800 22FB:000A 000000000000000000000000...000000000000000000000000 22FB:003A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:006A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:009A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:00CA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:00FA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:012A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:015A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:018A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:01BA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:01EA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:021A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:024A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:027A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:02AA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:02DA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:030A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:033A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:036A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:039A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:03CA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:03FA 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:042A 0FFFFFFFFFFFFFFFFFFFFFFF...FFFFFFFFFFFFFFFFFFFFFFF0 22FB:045A 000000000000000000000000...000000000000000000000000
      
      





  VGA: A000:BE20 000000000000000000000000...000000000000000000000000... + 0x140 (320) A000:BF60 000F0F0F0F0F0F0F0F0F0F0F...0F0F0F0F0F0F0F0F0F0F0F00... + 0x140 (320) A000:C0A0 000F0F0F0F0F0F0F0F0F0F0F...0F0F0F0F0F0F0F0F0F0F0F00... + 0x1000 (320 * 21) A000:DAE0 000000000000000000000000...000000000000000000000000
      
      





draw_string



関数に進むことができます。










draw_string



と、次のコードセグメントに巻き込まれました。







 loc_1DB47: mov bx, dx inc dx sub ax, ax mov al, ss:[bx] shl ax, 1 jz short loc_1DB97 shl ax, 1 shl ax, 1 add ax, 0FA6Eh mov si, ax mov cx, 8 mov bx, 4BA1h loc_1DB62: lodsb mov ah, al sub al, al rol ax, 1 rol ax, 1 xlat byte ptr ss:[bx] stosb sub al, al rol ax, 1 rol ax, 1 xlat byte ptr ss:[bx] stosb sub al, al rol ax, 1 rol ax, 1 xlat byte ptr ss:[bx] stosb sub al, al rol ax, 1 rol ax, 1 xlat byte ptr ss:[bx] stosb add di, ss:4BA5h loop loc_1DB62 sub di, ss:4BA7h jmp short loc_1DB47 loc_1DB97: ...
      
      





命令lodsb



stosb



xlat



が大量に蓄積されたため、「フック」された[私にとって、これらの命令は有用な作業のほとんどを行うと結論付けました。最終的に、このプログラミングはすべて1つの領域からのデータの些細な動きに xlat



ます別の記憶に、右?]とそれをトレースし始めた。 loc_1DB47



ラベルから来る:









さらなる命令loop loc_1DB62



考慮して、8( cx



)反復のサイクルがここで準備されました。









最後の3つのステップが3回繰り返された後、 ss:4BA5



0x2C



)に格納されているワードがdi



に追加され、 loop



命令はラベルloc_1DB62



からそれ自体にコードをさらに7回loc_1DB62



します。 ループが完了すると、 ss:4BA7



0x17C



)に格納されているワードがdi



に追加され、プログラムはloc_1DB47



ラベルにジャンプして戻ります。







このように複雑な方法で、ここではソース文字列のすべての文字が順番に処理されます。 その結果、指定されたテキストがフレーム内に形成されます[ゼロを強調表示、またはページを縮小]







 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFF00FFF00FFFFFFFFFFFFFFFFFFFFFF00F0000FFFFFFFFFFFFFFFFFFFFFFF000FFF FFF000FF00FFFFFFFFFFFFFFFFFFFFF00FFF00FFFFFFFFFFFFFFFFFFFFFFFFF00FFF FFF0000F00FF0000FFF00FFF00FFFF00FFFF00FFFFFF0000FFFF0000FFFFFFF00FFF FFF00F0000F00FF00FF00F0F00FFF00FFFFF00FFFFF00FF00FFFFFF00FFF00000FFF FFF00FF000F000000FF0000000FF00FFFFFF00FFF0F00FF00FFF00000FF00FF00FFF FFF00FFF00F00FFFFFF0000000F00FFFFFFF00FF00F00FF00FF00FF00FF00FF00FFF FFF00FFF00FF0000FFFF00F00FF0FFFFFFF0000000FF0000FFFF000F00FF000F00FF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
      
      





ここでは、原則として、マジックナンバー0xFA6E



を除いてすべてが明確で、 0xFA6E



に加えてシンボルコードに8を掛けて、 F000



セグメントのアドレスを取得しました。 これは明らかにプログラム自体のメモリ外にあるため、 MS-DOSメモリスキームをグーグルで検索する必要があります。 そして、 FFA6:E ROM graphics character table



、具体的には、彼はこれに興味があります: FFA6:E ROM graphics character table



。 セグメント化されたアドレッシングを考えると、 FFA6:E



レコードはF000:FA6E



と同等であり、 CP437をコーディングするネイティブdosで文字を表示するためのフォントはこのアドレスで「縫製」されます。 各文字には8バイトがあります。 以下に、例として文字「N」を使用した場合の動作を示します。







 1.   : 0x4E 2.   : 0x4E * 8 = 0x270 3.       : 0xFA6E + 0x270 = 0xFCDE 4.      : C6 E6 F6 DE CE C6 C6 00 5.     ,   : C6: 11000110 E6: 11100110 F6: 11110110 DE: 11011110 (    'N') CE: 11001110 C6: 11000110 C6: 11000110 00: 00000000
      
      





そして、このコードは次のとおりです。







  sub al, al rol ax, 1 rol ax, 1 xlat byte ptr ss:[bx]
      
      





次のように説明できます。









このロジックを独自に実装し、画面にフォントを表示するには、すべてが非常に簡単です。 [テキストの配置を後のために残して、私がやったこと。]










この場合、 ResourceBrowserLibNeuroRoutinesを使用した1つのソリューションで、彼はNeuromancerWin32プロジェクトを作成しました。 SFMLをマルチメディアバックエンドとして使用することにしました。 その前に、私はすでにSDL2で非常に良い経験をしていましたが、ここで何か新しいことを試してみたかったのです。 SDL2が気に入ったのは、 Cで実装されているため、すぐに使用できるC互換のインターフェイスがあるからです。 SFMLは、 C ++で記述されています 。 物事を複雑にしないために、私はプロジェクトをCでリードします。幸いなことに、 SFMLには公式のC- building- CSFMLがあります。







CSFMLは最初から使いやすさ喜んでいます。 たとえば、ウィンドウを作成するために必要なものは次のとおりです。







 sfEvent event; sfVideoMode mode = { 320, 200, 32 }; sfRenderWindow *window = sfRenderWindow_create(mode, "NeuromancerWin32", sfClose, NULL); while (sfRenderWindow_isOpen(window)) { while (sfRenderWindow_pollEvent(window, &event)) { if (event.type == sfEvtClosed) { sfRenderWindow_close(window); } } sfRenderWindow_clear(window, sfBlack); sfRenderWindow_display(window) } sfRenderWindow_destroy(window);
      
      





元のゲームは、256色VGA モードモード0x13 )を使用して、画面にグラフィックを表示します。 このモードでは、描画はVGAメモリ内のピクセルのカラー値( A000:0000



320 * 200



バイト)の書き込みにA000:0000



ます。 同時に、1バイトは1ピクセルに対応します。 同様の方法で行動することにしましたが、32ビットビデオモードが使用されるという事実に合わせて調整しました。 したがって、私はバッファuint8_t *g_vga[320*200*4]



(32ビットモードの各ピクセルは4つのコンポーネントuint8_t *g_vga[320*200*4]



で表されます)を取得しました。これは元のVGAメモリのアナログとして機能します。 , , SFML , :







 sfRenderWindow *g_ window = NULL; sfTexture *g_texture = NULL; uint8_t *g_vga[320*200*4]; ... g_texture = sfTexture_create(320, 200); ... void render() { sfTexture_updateFromPixels(g_texture, g_vga, 320, 200, 0, 0); sfSprite *sprite = sfSprite_create(); sfSprite_setTexture(sprite, g_texture, 1); sfRenderWindow_clear(g_window, sfBlack); sfRenderWindow_drawSprite(g_window, sprite, NULL); sfRenderWindow_display(g_window); sfSprite_destroy(sprite); } ... sfTexture_destroy(g_texture);
      
      





:









 for (int i = 0; i < 320 * 240 * 4; i++) { g_vga[i] = rand() % 256; } while (sfRenderWindow_isOpen(g_window)) { ... render(); }
      
      












, . , VGA -: void draw_to_vga(int32_t l, int32_t t, uint32_t w, uint32_t h, uint8_t *pixels)



[ VGA, , ] :









 uint8_t red_rectangle[96*48]; memset(red_rectangle, 0x44, 96*48); draw_to_vga(10, 10, 96, 48, red_rectangle); ... render();
      
      












, (8x8) :







 static uint8_t cp437_font[1024] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 NULL ... 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, // 0x41 A 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, // 0x42 B 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, // 0x43 C ... }; static uint8_t cp437_font_pixels[4] = { 0xFF, 0xF0, 0x0F, 0x00 }; static uint8_t cp437_font_mask[4] = { 0xC0, 0x30, 0x0C, 0x03 }; void build_character(char c, uint8_t *dst) { uint32_t index = c * 8; /* unprintable */ if (c < 0x20 || c > 0x7E) { return; } memset(dst, 0, 32); for (int i = 0; i < 8; i++) { uint8_t al = cp437_font[index++]; for (int j = 0; j < 4; j++) { dst[i * 4 + j] = cp437_font_pixels[(al & cp437_font_mask[j]) >> (6 - j * 2)]; } } }
      
      





:









 memset(g_vga, 0xFF, 320 * 200 * 4); uint8_t character_bm[32]; int left = 2, top = 2; for (char c = 0x20; c <= 0x7E; c++) { if (left + 8 >= 320) { left = 2; top += 10; } build_character(c, character_bm); draw_to_vga(left, top, 8, 8, character_bm); left += 8; } render();
      
      












build_character



build_string(char *s, uint32_t w, uint32_t h, uint8_t *dst)



, :









 memset(g_vga, 0xFF, 320 * 200 * 4); uint8_t string[320 * 20]; build_string("The future is here.\n" "It's just not widely distributed yet.", 320, 20, dst); draw_to_vga(20, 88, 320, 20, string); render();
      
      















. , - . , . .







«». 3: ,



All Articles