ロストバむキングのリバヌス゚ンゞニアリング

Comprehendゲヌム゚ンゞンの興味深いリバヌス開発 Recomprehendを参照の埌、DOSでゲヌムをリバヌス゚ンゞニアリングするための新しいプロゞェクトを遞択したした。 長幎にわたり、さたざたな人々が倚くの叀い人気のあるゲヌムを反転させ、それらの仕様ずツヌルを公開したした。 たずえば、 shikadi.netには、私が子䟛の頃にプレむしたゲヌムに関する膚倧な情報がありたす。



BlizzardのThe Lost Vikingsゲヌム圓時はSiliconずSynapseず呌ばれおいたしたは、リバヌス゚ンゞニアリングを真剣に詊みおいなかったようです。 このゲヌムはDOS時代の終わりに1993幎にリリヌスされたした。 Lost Vikingsは、プレむダヌがそれぞれ独自のスキルを持぀3぀のVikingを制埡するプラットフォヌムパズルゲヌムです。 ノァむキングは、宇宙船、先史時代、叀代゚ゞプトなどのさたざたなトピックでパズルを解き、レベルをクリアするために力を合わせる必芁がありたす。 以䞋の画像は、ゲヌムの最初のレベルを瀺しおいたす゜ヌス Strategy Wiki 



画像



このゲヌムはかなり簡単に䜜成できるず思われたした。 レベルはタむルマップに基づいおおり、オブゞェクトのオン/オフを切り替えるボタン、モバむルドロワヌ、オブゞェクトを持ち䞊げるクレヌンなどのシンプルなパズルが含たれおいたす。 実際、リバヌス゚ンゞニアリングプロゞェクトのほずんどは非垞に簡単でした。 ゲヌムには、ファむルの圧瞮ブロックを含む1぀のバッチデヌタファむルがありたす。 ブロックは、スプラむト、マップ、サりンドなどのさたざたなゲヌムリ゜ヌスを゚ンコヌドしたす。 ゲヌムリ゜ヌスの衚瀺に䜿甚できるナヌティリティをいく぀か䜜成したした。TheLost Vikings Toolsです。



仮想マシン



゚ンゞンの動䜜の興味深い偎面は、ゲヌム内のオブゞェクトがクラスのテンプレヌトシステムを䜿甚するこずです。 䞖界ごずに、オブゞェクトのクラスのセットを定矩するブロックがデヌタファむルにありたす。 たずえば、䞊蚘の最初のレベルでは、䞡方の非垞口ドアのタむプはクラス0x4fです。 オブゞェクトクラスのコヌドのリバヌス゚ンゞニアリングは、次の機胜をもたらしたした。



画像



アドレス0x142a2ぞの代替パスをスキップする間、このコヌドはオブゞェクトクラスの配列の構造でむンデックスずしおsiを䜿甚したす各クラステンプレヌトは0x15バむトで構成されたす。 オフセット0x3のワヌドは、ESセグメントのアドレスずしお䜿甚される構造です。 ESセグメントには、オブゞェクトクラスのテンプレヌトブロックのすべおのデヌタが含たれたす。 次に、コヌドはアドレス0x142a6でルヌプに入りたす。 このルヌプは、ESセグメントから次のバむトを取埗し、それを関数テヌブルのむンデックスずしお䜿甚したす。 サむクルの各ステップで、bxにはESセグメントのアドレスが含たれ、siには珟圚のオペレヌションコヌドopcodeが含たれたす。



ルヌプは無条件ゞャンプを䜿甚するため、完了しないこずに泚意しおください。 少なくずも通垞のプログラム実行䞭。 オブゞェクトの各クラスには、オペコヌドに基づいた䜕らかの皮類の関連プログラムがあり、この関数によっお解釈されるようです。 最初は、呌び出しテヌブルの関数のいく぀かを調べたした。 IDAグラフモヌドで最初のものは次のようになりたす。



画像



ここでのIDAスタックの分析は倱敗し、これには理由がありたす。 最初の関数コマンドずしおのポップアックスはかなり奇劙な動䜜です。 x86呌び出しコマンドはコマンドポむンタヌipをスタックにプッシュし、retコマンドはそれをプッシュしお呌び出し関数を返したす。 ここのpopコマンドは、実際には戻りアドレスを砎棄したす。 これは、次のretコマンドが1぀ではなく2぀のスタックフレヌムに戻るこずを意味したす。 このオペコヌドは、むンタヌプリタヌの無限ルヌプを終了したす。



オペコヌドの実際の実装方法を理解するには、線圢IDAモヌドに切り替える必芁がありたす。



画像



コヌドのこの郚分がより理解しやすいように、察応するオペコヌド番号ずずもに関数名に぀いおコメントしたした。 ここでは、コヌドの再利甚は非垞に賢明です。 開始する最も簡単な方法は、0x03オペコヌドを調べるこずです。 オペコヌドの凊理サむクルでは、ESセグメントにロヌドされたオブゞェクトクラスのブロックからのデヌタがあり、bxは仮想マシンコマンドぞのポむンタヌずしお䜿甚されるこずを思い出しおください。 したがっお、0x03オペコヌドは無条件のゞャンプコマンドです。 珟圚の呜什ポむンタのアドレスにあるワヌドをロヌドし、それに呜什ポむンタを蚭定しおから、オペコヌド凊理サむクルに戻りたす。



逆の順序で動䜜する0x05オペコヌドは、次のオペランドワヌドをスキップし、次のアドレスを配列に栌玍したす。 他の関数の逆は、word_28522がオブゞェクトの珟圚のむンスタンスのむンデックスであるこずを瀺しおいたす。぀たり、このオペコヌドは、レベルの各オブゞェクトに察しお1぀のアドレスを栌玍したす。 次に、bxの倀を埩元し、コヌドをオペコヌド0x03遷移に枡したす。 したがっお、このオペコヌドは、スタックレベルが1぀だけの非垞に限定された呌び出しコマンドのようです。



オペコヌド0x00は、珟圚の関数ポむンタヌをグロヌバル配列に栌玍したす。 呌び出しアドレスオペランドの埌にアドレスを保存する0x05オペコヌドずは異なるこずに泚意しおください。 次に、圌はコヌルオペコヌドハンドラヌに進みたす。 ただし、最初のpopコマンドのため、コマンドは実行されたせん。 代わりに、呌び出しルヌプが終了したす。 このコマンドで保存されたアドレスは、関数呌び出しルヌプぞの代替゚ントリパスずしお䜿甚されたすが、䞊蚘では考慮したせんでした。 ここで、0x00オペコヌドは、コルヌチンを終了するようなものずしお䜿甚されたす。 プログラム内の珟圚䜍眮を保存し、オペコヌド凊理サむクルを終了し、ゲヌム゚ンゞンが他のタスクスケゞュヌルの曎新、ナヌザヌ入力の取埗などを実行できるようにし、仮想マシンプログラムの元に戻りたす。 これにより、仮想マシンプログラムは、ゲヌム゚ンゞンの残りの郚分のダりンタむムなしで耇雑なタスクを実行できたす。



オペコヌドの分解



この段階で、仮想マシンのオペコヌドハンドラヌがどのように機胜するかに぀いおの䞀般的な考えは既にありたした。 関数呌び出しのテヌブルを調べたずころ、玄215個の実装されたオペコヌドが芋぀かったため、それらを盎接リバヌス゚ンゞニアリングする代わりに、目的のクラスのオブゞェクト甚にプログラムを逆コンパむルする簡単なスクリプトを曞き始めたした。 したがっお、第1レベルのオブゞェクトによっお呌び出されるオペコヌドにのみ焊点を圓おるこずができたした。



この時点で、私は䞻にオペコヌドを持぀オペランドの数ずその䞻なタスクを決定しようずしおいたした。 オペコヌドハンドラヌが短かった堎合、私は通垞、それが䜕をしおいたかを完党に把握しようずしたした。 コヌドのブロックが数ブロックを超える堎合は、オペランドの数ず、条件付きゞャンプたたは呌び出しを実行するかどうかを確認しようずしたした。 オペコヌドの目的が、逆アセンブルされたプログラムの呚囲のコンテキストから明らかになるこずがあるためです。



簡単なオペコヌドの䟋を次に瀺したす。



画像



このオペコヌドはオペランドワヌドを受け取り、それをグロヌバル倉数に栌玍したす。 他の関数を逆にするず、このグロヌバル倉数が䞀時ストレヌゞたたは汎甚レゞスタヌであるこずを瀺したす。 仮想マシンにはそのようなレゞスタは1぀しかありたせんが、このオペコヌドもありたす。



画像



このオペコヌドは、ワヌドオペランドで瀺されるデヌタセグメントDSのアドレスに汎甚レゞスタの珟圚の倀を栌玍したす。 ゲヌムデヌタファむル内のプログラムは、このオペコヌドを䜿甚しお、バむキングむンベントリスロットなど、ゲヌムデヌタの特定の郚分に曞き蟌みたす。 耇数のDSオフセットは、耇数の時間が必芁な堎合の远加の時間倀ずしおも䜿甚されたす。 たずえば、オフセット0x0206および0x0208は、倚くの堎合、オブゞェクトの時間座暙xおよびyずしお䜿甚されたす。



このオペコヌドず、DSから汎甚レゞスタヌに読み蟌む反察のオペコヌド0x53は、元のゲヌムの䜜成者によっお割り圓おられたものだけでなく、あらゆるゲヌムデヌタをDSに読み曞きできるずいう点で興味深いです。 これにより、元のゲヌムを拡倧するための興味深い芋通しが埗られたす。



より耇雑なのは0x14オペコヌドです



画像



䞀芋するず、その反転は問題を匕き起こさないはずです。 バむトオペランドを受け取り、サブ関数のペアを呌び出したす。 次に、別のバむトオペランドを受け取り、同じサブ関数を再床呌び出したす。 しかし、最初のサブ関数sub_15473を芋るず、少し耇雑になりたす



画像



IDAスタック分析は再び倱敗し、このブロックからの4぀の出口が衚瀺されたすが、それらはすべお間違っおいるため切り捚おたした。 実際、これが起こるこずです。最初のバむトオペランドがaxでこの関数に枡され、ゞャンプテヌブルのむンデックスずしお䜿甚されたす。 AND挔算は7のxで実行されたすが、実際には、ゞャンプテヌブルには実際の倀は4぀しかありたせん。 遷移衚のタむプ0を芋るず、次のこずがわかりたす。



画像



即倀オペランドワヌドがここにロヌドされたす。 関数のこのフラグメントはretnによっお実行されたす。retnは、トップレベルのオペコヌドハンドラヌ0x14から返されたす。 IDAがこのコヌドを適切に分解するのに問題がある理由は簡単にわかりたす。



ゞャンプテヌブルのその他の゚ントリは、さたざたな方法で倀をロヌドし、それぞれが再び取埗されたす。 タむプ1は、オブゞェクトフィヌルドのむンデックスずしお䜿甚されるバむトオペランドを取りたす。 オブゞェクトには、xおよびyオフセット、フラグなどの倀のフィヌルドがありたす。 タむプ2はワヌドオペランドを受け取り、DSから倀をロヌドしたす䞊蚘の䟋のオペコヌド0x53ずしお。 タむプ3は、バむトオペランドを受け取り、タヌゲットフィヌルドをロヌドしお、衝突を怜出するなどのアクションを実行したす。



トップレベルのオペコヌドハンドラヌ0x14に戻りたしょう。 ここで、次のsub_15470が呌び出されたす。 呌び出される最初のサブ関数の3バむト先だけです。 そしお再び、スマヌトなコヌド最適化がありたす。 このサブ関数は、単にaxを3だけ右にシフトしおから、䞊蚘で反転したコヌドに移動したす。 そのため、0x14オペコヌドの最初のバむトオペランドは、次の2぀の匕数の取埗方法を゚ンコヌドするために䜿甚されたす。 2番目のバむトオペランドの凊理を逆にするず、同じように機胜するこずを瀺したす。



すべおのオペランド可倉長を受け取った埌、0x14オペコヌドハンドラヌはsub_13809を呌び出したす。 関数は非垞に倧きく、他の倚くのサブ関数を呌び出すため、ここではコヌドを挿入したせん。各サブ関数も膚倧です。 これは、埌からコンテキストから明らかになるので、関数のアクションを逆にするこずに煩わされなかったケヌスの1぀です。



CISC仮想マシン



The Lost Vikings仮想マシンのコマンドの長さは可倉です。 0x14オペコヌドおよび他の同様のオペコヌドの堎合、長さを決定するにはいく぀かのオペランドをデコヌドする必芁がありたす。 可倉長呜什の存圚は、プログラムを反埩的に逆アセンブルし、少なくずもプログラム内の新しいオペコヌドごずにオペランドの数を決定する必芁があるこずを意味したす。 すべおのオペコヌドの長さが固定されおいる堎合、未知のオペコヌドはスキップされたす。



倚くのプログラムで䜿甚される暙準操䜜を実行するコマンドもありたす。 たずえば、倚くのプログラムでは、オブゞェクトの方向に基づいおオブゞェクトの珟圚の䜍眮を加算たたは枛算したす。 これは耇数のコマンドずしお゚ンコヌドできたすが、0x91オペコヌドはDSのオフセットずしお単䞀のオペランドワヌドを受け取り、オブゞェクトの珟圚の氎平方向に基づいお䞀時レゞスタの珟圚の倀を加算たたは枛算したすフラグ0x0040。 次の擬䌌コヌドは、0x90オペコヌドの操䜜を瀺しおいたす。



if (this.flags & 0x0040) DS[operand] -= var; else DS[operand] += var;
      
      





このため、特にこの操䜜は耇数のプログラムによっお実行されるため、゚ンコヌドに必芁なバむト数ははるかに少なくなりたす。 DOSでは、ディスクスペヌスず凊理胜力が䞍足しおいたずき、それは非垞に理にかなっおいたした。 珟代のリバヌス開発者にずっお、これは単に理解する必芁があるいく぀かの新しいオペコヌドを远加するだけです。



逆アセンブラヌ



仮想マシンプログラムをCに挠然ず䌌たものに分解できる単玔なPythonスクリプトを䜜成したした。プログラムのコマンドを線圢にデコヌドし、ゞャンプアドレスず呌び出しアドレスを保存したす。 コヌドの実行を停止たたはリダむレクトする関数リタヌンや無条件ゞャンプなどに遭遇するず、プログラム内の他の未蚪問アドレスを停止しおチェックしたす。



仮想マシンプログラムは、ゲヌムのバむナリコヌドで䜿甚されるものず同様の最適化のトリックを䜿甚したした。 たずえば、仮想マシンプログラムでサブプロシヌゞャを終了するいく぀かの方法は、他のサブプロシヌゞャからサブプロシヌゞャにナビゲヌトするこずにより再利甚されたす。 逆アセンブラは、呜什を2回デコヌドするこずでこれを行いたす。そのため、2぀のサブプロシヌゞャが同じアドレスのコヌドを持぀こずができたす。



逆アセンブラは、䞀時的なレゞスタアクセス操䜜を切り捚おお、最終的なコヌドを読みやすくするこずができたす。 たずえば、仮想マシンプログラムは、倚くの堎合、次のシヌケンスを䜿甚したす。



 var = 0x1000; obj->field_10 = var;
      
      





逆アセンブラヌは、次のように再線成したす。



 obj->field_10 = 0x1000;
      
      





逆アセンブラは、コヌドブロックをifやwhileなどの構造に再構築しようずしないため、結果のプログラムはスパゲッティコヌドのように芋えたす。 いく぀かの倧きなプログラム自䜓は、リバヌス開発のための本栌的なプロゞェクトにするこずができたす。



フルプログラム



最初のレベルの巊䞊の郚屋に銃を持぀塔のプログラムから始めたしたオブゞェクトクラス0x04。 これはかなり単玔なプログラムであるように思えたした。 倧砲のある塔は䞀定の間隔で砲匟を発射し、バむキングはそれを砎壊するこずはできたせん。 以䞋は、逆アセンブラヌによる凊理埌に受け取ったプログラムです。 各オペコヌドの䞊に、角括匧で囲たれたアドレス、匕甚笊で囲たれたオペコヌド、オペランドが続くコメントがありたす。



タワヌプログラムは次のようになりたす。



 main_374d: { // [374d] (19) 37bc this.field_21 = 0x37bc; this.field_22 = 0x0001; // [3750] (97) 00 01 if (0x0001 & (1 << 0)) var = 0x0001; // [3753] (00) this.save_state(); // [3754] (9c) 18 08 if (!(var)) this.db_flags &= ~(1 << 12); label_3757: // [3757] (2f) vm_func_2f(); // [3758] (00) this.save_state(); // [3759] (01) nop(); // [375a] (51) 0000 // [375d] (8c) 30 3798 if (this.field_30 != 0x0000) call sub_3798; // [3761] (52) 1c // [3763] (77) 0000 3757 if (this.update_time != 0x0000) jump label_3757; // [3768] (19) 37c8 this.field_21 = 0x37c8; this.field_22 = 0x0001; // [376b] (52) 1e // [376d] (57) 0206 g_tmp_a = this.xoff; // [3770] (51) 0004 // [3773] (91) 0206 if (this.flags & 0x0040) g_tmp_a -= 0x0004; else g_tmp_a += 0x0004; // [3776] (52) 20 // [3778] (57) 0208 g_tmp_b = this.yoff; // [377b] (51) fff8 // [377e] (5a) 0208 g_tmp_b += 0xfff8; // [3781] (51) 001e // [3784] (56) 1c this.update_time = 0x001e; // [3786] (02) 3030 vm_func_02(0x3030); // [3789] (14) 12 0206 0208 00 0000 0000 05 new.x = g_tmp_a; new.y = g_tmp_b; new.unknown_a = 0x0000; new.unknown_b = 0x0000; new.unknown_c &= 0x801; new.type = 0x05; spawn_obj(new); // [3795] (03) 3757 jump label_3757; } sub_3798: { // [3798] (51) 000a // [379b] (73) 30 37a5 if (this.field_30 == 0x000a) jump label_37a5; // [379f] (51) 0000 // [37a2] (56) 30 this.field_30 = 0x0000; // [37a4] (06) return; label_37a5: // [37a5] (52) 32 // [37a7] (69) 0a 37ba if (this.argument < this.field_32) jump label_37ba; // [37ab] (52) 32 // [37ad] (5c) 0a this.argument -= this.field_32; // [37af] (51) 0000 // [37b2] (56) 30 this.field_30 = 0x0000; // [37b4] (51) 0000 // [37b7] (56) 32 this.field_32 = 0x0000; // [37b9] (06) return; label_37ba: // [37ba] (0e) vm_func_0e(); // [37bb] (10) this.update(); }
      
      





ここで次のこずがわかりたす。





プログラムのリファクタリングは、プログラムで䜕が起こっおいるのかを理解するのに圹立ちたす。



 main_374d: { /*   */ this.set_graphics_prog(0x37bc); if (0x0001 & (1 << 0)) var = 0x0001; this.save_state(); if (!(var)) this.db_flags &= ~(1 << 12); label_3757: /* *   .     *     . */ while (1) { vm_func_2f(); this.save_state(); /* *    .  this.argument *       * . ,     *    . */ if (this.field_30 != 0x0000) { if (this.field_30 != 0x000a) { this.field_30 = 0x0000; } else { if (this.argument < this.field_32) { vm_func_0e(); this.update(); } else { this.argument -= this.field_32; this.field_30 = 0x0000; this.field_32 = 0x0000; } } } /*    update_time  ,    */ if (this.update_time == 0x0000) { this.set_graphics_prog(0x37c8); /*    */ this.update_time = 0x001e; vm_func_02(0x3030); /* *          *        . */ if (this.flags & OBJ_FLAG_FLIP_HORIZONTAL) new.x -= 4; else new.x += 4; new->y = this.yoff - 8; new->unknown_a = 0x0000; new->unknown_b = 0x0000; new->unknown_c &= 0x801; new->type_d = 0x05; spawn_obj(new); } } }
      
      





プログラムにはただパヌツずオペコヌドがあり、その目的は私が決定しおいたせんが、タワヌの動䜜は非垞に明確であるこずに泚意しおください。 オブゞェクトのupdate_timeフィヌルドは、砲塔の発射速床を゚ンコヌドするために䜿甚されたす。 プログラム自䜓はこの倀を少なくずも明癜な方法で枛らしたせん。 おそらく、このタスクは未知のオペコヌドの1぀たたはメむンゲヌム゚ンゞンの䞀郚によっお実行されたす。



ハッキングゲヌム



もちろん、ゲヌムで䜿甚される゜ヌスプログラムを研究する機䌚はそれ自䜓興味深いものです。 しかし、仮想マシンがオブゞェクトの動䜜をどのように実装するかに぀いお最も興味深いのは、オブゞェクトを倉曎したり、新しいプログラムを䜜成できるこずです。 コンパむラを曞くずずっず簡単になりたす以䞋で説明したすが、珟時点では16進゚ディタで倀を手動でしか倉曎できたせん。



私は繰り返したす-それは非垞に簡単なので、修正のために銃を持぀タレットのプログラムを遞びたした。 最初に、私は塔が撃っおいるオブゞェクトのタむプを倉曎するこずにしたした。 成功はさたざたでした。 緑色の゚むリアンなどの1぀のオブゞェクトのシュヌティングを実珟しようずするず、ゲヌムの「フォヌルアりト」たたはフリヌズが発生したした。 他のオブゞェクトの遞択は、タワヌが撃たなかったずいう事実に぀ながりたした。 私は圌女にファむアアロヌを発射させるこずができたした通垞のゲヌムでは、これはバむキングの1人が䜿甚できるボヌナスです。 たた、update_timeフィヌルドの倀を小さくするこずにより、銃の発射率を䞊げるこずも非垞に簡単です。



2番目の修正は、もう少し野心的なものになりたした。発射する代わりにタワヌを移動させたした。 匟䞞を䜜成するためのオペコヌド0x14は非垞に長いです。



 14 12 0206 0208 00 0000 0000 05
      
      





仮想マシンは、このコマンドを眮き換えるために䜿甚できるシングルバむトコマンドnop0x01これは非垞に高貎ですを䜿甚する機胜を提䟛したす。 䞊蚘のチヌムは、DSの方向に応じお、タワヌのx軞のプラスたたはマむナス4の珟圚䜍眮にDS [0x0206]をすでに蚭定しおいたす。 したがっお、この倀を珟圚のx䜍眮に割り圓おるコマンドを远加するだけです。 これには2぀のチヌムのみが必芁です。



 53 0206 // var = DS[0x0206]; 56 1e // this.x = var;
      
      





この倉曎により、タワヌは移動できたすが、停止するものはありたせん。 衝突認識を実装しお匕き返しようずしたしたが、適切な操䜜ができたせんでした。 より深いリバヌス゚ンゞニアリングが必芁です。



倉曎の短いビデオをたずめたした。





たた、逆アセンブラヌをオヌプンアクセスで公開したした。



https://github.com/RyanMallon/TheLostVikingsTools/blob/master/dissassembler/lv_vm_disasm.py



ゲヌムの再コンパむル



前のセクションでは、Lost Vikingsでオブゞェクトを実装するために䜿甚される仮想マシンのリバヌス゚ンゞニアリングに぀いお説明したした。



以前は、16進゚ディタヌで゜ヌスデヌタブロックに手動でパッチを適甚するこずにより、いく぀かのオペコヌドを経隓的にテストしおいたした。 このアプロヌチは機胜したしたが、非垞に単調であり、倧芏暡なプログラムやルヌプや遷移を含むプログラムでの実隓にはあたりうたくスケヌルしたせんでした。 䞊蚘で、単玔な蚀語ずコンパむラを䜜成するこずが、仮想マシンのさらなるリバヌス゚ンゞニアリングに圹立぀こずを提案したした。 だから私はそれを曞いた。



コンパむラの構築



仮想マシンの逆開発ず新しいプログラムの䜜成の利䟿性を支揎するために、コンパむラヌを䜜成するこずにしたした。 耇雑にならないように、再垰降䞋のシングルパス方匏を遞択したした。



コンパむラの蚭蚈は、䞻にPL / 0などのコンパむラから借甚されおいたす。PL/ 0は、コンパむラに関する倧孊のコヌスでよく教えられたす PL / 0を参照。 むンタヌネットにはこれに関する十分な情報があるため、基本的なコンパむラの䜜成の詳现には觊れたせん。 本質的に、コンパむラは゜ヌスプログラムを取埗し、字句解析を実行しおトヌクンのストリヌムを䜜成し、プログラムを解析しおコヌドを生成したす。



倱われたバむキングC



オブゞェクトプログラムを実装するための非垞に単玔なCのような蚀語を䜜成したした。 C蚀語に䌌た蚀語を䜿甚する利点の1぀は、既存のCプリプロセッサを䜿甚しお定数ずマクロの定矩をサポヌトできるこずです。 この蚀語では倉数を定矩できたせん。プログラマは、仮想マシンによっお定矩されたオブゞェクトおよびグロヌバル倉数のフィヌルドに制限されたす。 組み蟌み関数は、結果を取埗しおオブゞェクトを䜜成する操䜜のオペコヌドを生成するためのサポヌトを提䟛したす。 この蚀語は、字句解析ず構文解析を単玔にするために、Cずは少し異なりたす。



繰り返したすが、たずえば、䞊で修正した第1レベルの銃を持぀塔のオブゞェクトを取り䞊げたす。 次のように、ロストバむキングCに矢の射撃塔を実装できたす。



 #include "vikings.h" #define timer field_30 #define DELAY_TIME 40 function main { call set_gfx_prog(0x37bc); this.timer = 0; while (this.timer != 0xffff) { call update_obj(); call yield(); if (this.timer != 0) { this.timer = this.timer - 1; } else { //      //  . if (this.flags & OBJ_FLAG_FLIP_HORIZ) { g_tmp_a = this.x - 12; } else { g_tmp_a = this.x + 12; } g_tmp_b = this.y - 12; //    call set_gfx_prog(0x37c8); //   call spawn_obj(g_tmp_a, g_tmp_b, 0, 0, 7); //     this.timer = DELAY_TIME; } } }
      
      





各オブゞェクトには、終了しないメむンルヌプが必芁です。 メむンルヌプはyield関数を呌び出しお、仮想マシンの凊理ルヌプを終了する必芁がありたす。 そうしないず、ゲヌム゚ンゞンがフリヌズしたす。 update_objを呌び出すず、その名前が瀺すずおりに動䜜したす。



倚くのオブゞェクトフィヌルドの䜿甚は、特定のオブゞェクトを指したす。 私たちの堎合、フィヌルドfield_30をタレットの発射速床を制埡するタむマヌずしお䜿甚したした。 タむマヌがれロに達するず、タワヌが起動し、タむマヌがリセットされたす。



残っおいるのは、このプログラムのコヌドを生成するこずだけです。



コヌド生成



Lost Vikings仮想マシンは、単玔なコンパむラヌにずっお理想的な環境ではありたせん。 タむプPL / 0のコンパむラは、通垞、抜象スタックマシン甚のコヌドを生成するため、次のような単玔な匏です。



 this.x = this.y + 1;
      
      





このコヌドは以䞋を生成したす



 push this.y ;  this.y   push 1 ;  1   add ;    ,     pop this.x ;    this.x
      
      





ただし、Lost Vikings仮想マシンにはスタックがありたせん。 単玔な䞀時レゞスタ、オブゞェクトフィヌルド、グロヌバル倉数がありたす。 䞊蚘のプログラムは、次のようにLost Vikings仮想マシンに倉換されたす。



 52 20 ; var = this.y 56 1e ; this.x = var 51 0001 ; var = 0x0001 59 1e ; this.y += var
      
      





汎甚蚀語からコンパむルする堎合、圌女がコヌドを生成するこずははるかに困難です。 最初のパスで䞭間ビュヌを生成し、2番目のパスでコマンドの順序などを倉曎する必芁がある堎合がありたす。 最終コヌドを生成したす。



抜象マシン



私は次の解決策を芋぀けたした。LostVikings仮想マシン䞊で䟿利にコンパむルできる抜象スタックマシンを䜜成したす。 これは、グロヌバル倉数をロヌドおよび保存するためのオペコヌドのおかげで可胜です。 これらのオペコヌドは、ゲヌムのデヌタセグメントDSのオペランドずしおオフセットを受け取りたす。 これにより、オペコヌドを䜿甚しお、DSの任意のアドレスをロヌドおよび保存できたす。



コンパむラは、デヌタセグメントの最䞊郚0xf004に停のスタックを配眮するこずでコヌドを生成したす。 停のスタックのベヌスにある2぀のアドレスは、特殊レゞスタヌ甚に予玄されおいたす0xf000はれロレゞスタヌであり、0xf002は比范に䜿甚されるフラグレゞスタヌです。 䞊蚘の匏は、次のようにコンパむルできたす。



 52 20 ; var = this.x 57 f004 ; ds[f004] = var 51 0001 ; var = 0x0001 57 f006 ; ds[f006] = var 53 f006 ; var = ds[f006] 5a f004 ; ds[f004] += var 53 f004 ; var = ds[f004] 56 1e ; this.y = var
      
      





パフォヌマンスはそれほど高くありたせんが、このコヌドは非垞に簡単に生成できたす。 私の目暙は、小さなプログラムをテストしお、オペコヌドたたはその組み合わせが䜕をするかを刀断するためのコンパむラを䜜成するこずです。 これたでのずころ、時間ずコヌドサむズの䞡方の点で、効率に぀いおはあたり心配しおいたせん。



元の仮想マシン甚の蚀語を䜜成する堎合の2番目の問題は、倚くのオペコヌドが条件付き関数呌び出しを実行するこずです。 たずえば、オペコヌド0x1aは、珟圚のオブゞェクトがバむキングに遭遇したかどうかをチェックし、遭遇した堎合、呌び出しコマンドを実行したす。 プログラマヌは、呌び出しを䜿甚するか遷移を䜿甚するか䟋えば、ifを䜿甚した構成を自分で決定できるこずが望たしいです。



これを実装するには、フラグを蚭定しおリタヌンを実行する暙準ヘルパヌ関数のコヌドを生成したす。 条件付き呌び出しを実行するオペコヌドのコヌドを生成するず、最初にフラグレゞスタがリセットされたす。 オペコヌドが呌び出しを行っおいる堎合、フラグレゞスタが蚭定されたす。 その埌、フラグをチェックしおゞャンプ呌び出しを行うこずができたす。 たずえば、次のコヌド



 if (call collided_with_viking(0x01)) { this.x = 1; }
      
      





次のコヌドを生成したす。



 [c009] 51 0000 ; var = 0x0000 [c00c] 57 f002 ; ds[f002] = var,    [c00f] 1a 01 c0a5 ; if (collided_with_viking(0x01)) call c0a5 [c013] 53 f002 ; var = ds[f002] [c016] 74 f000 c026 ; if (var == ds[f000]) goto c026 [c01b] 51 0001 ; var = 0x0001 [c01e] 57 f004 ; ds[f004] = var [c021] 53 f004 ; var = ds[f004] [c024] 56 1e ; this.field_x = var [c026] ... ;     if ;     [c0a5] 51 0001 ; var = 0x0001 [c0a8] 57 f002 ; ds[f002] = var,    [c0ab] 06 ; return
      
      





ここでは、れロ倀を䜿甚しおフラグ倀ず比范したす。 各プログラムの開始時に、コンパむラヌは、ケヌスをれロにリセットする呜什を生成したす。



パッチプログラム



各ゲヌムワヌルドの仮想マシンプログラムは、デヌタファむルの個別のブロックにたずめられおいたす。 コンパむラは単玔なアプロヌチを䜿甚したす。生成されたコヌドをブロックの最埌に远加し、オブゞェクトのプログラムヘッダヌを倉曎しおプログラムのパッチを指すようにしたす。



これはすべお理論的には機胜したすが、実際には1぀の小さな問題がありたす。 ゲヌムは、DOSメモリ割り圓おAPIで指定された堎所の远加セグメントESに仮想マシンプログラムを栌玍したす。 メモリ割り圓お関数は次のようになりたす。



画像



仮想マシンのメモリ割り圓お呌び出しは次のようになりたす。



画像



぀たり、0xc000バむト48 KBがプログラムに割り圓おられたす。 問題は、宇宙船の䞖界のプログラムのブロックがすでに48,972バむトを占有しおいるこずです。぀たり、新しいプログラムのパッチには、最終的に180バむトしかありたせん。 非垞に冗長なコヌドを生成するコンパむラを䜿甚するずきはそれほど倚くはありたせん。



倧きなプログラムをコンパむルしようずしたずきに、この問題を発芋したした。 ゲヌムは䞍芏則に動䜜し始め、「ハング」するか、銃のある塔がランダムに消えたした。 このような゚ラヌは、コンパむラたたは生成されたコヌドのバグ、オペコヌドの動䜜原理を誀解する問題、たたは元のゲヌムのバグ/制限である可胜性があるため、远跡が非垞に困難です。



簡単な解決策は、ゲヌムのバむナリコヌドにパッチを適甚しお、プログラムに割り圓おられたメモリのサむズを拡匵するこずです。 割り圓おられたサむズを0xd000バむト52 KBに増やしおも問題は発生せず、単玔なプログラムでの実隓に十分なスペヌスが䞎えられたす。 このためのコマンドをコンパむラに远加したした。



経隓的反転



コンパむラの目的は、仮想マシンのリバヌス開発を簡玠化するこずでした。 新しいオペコヌドをテストするには、コヌドゞェネレヌタヌクラスの関数蟞曞に゚ントリを远加し、オペコヌドのコマンド生成関数を䜜成する必芁がありたす。 たずえば、最初の実隓の1぀は、疑問笊の付いたボタン/アむコンオブゞェクトなど、いく぀かの逆アセンブルされたプログラムで䜿甚される0x41で動䜜しおいたした。



コンパむラの圌の蟞曞゚ントリは、最初は次のように芋えたした。



 "vm_func_41" : (False, 4, emit_builtin_vm_func_41),
      
      





タプルは、関数が倀を返すかどうか、枡される匕数の数、およびコヌドゞェネレヌタヌの関数を瀺したす。 オペコヌドが条件付き呌び出したたはゞャンプを行う堎合、関数は戻りずしおマヌクされたす。



IDAでオペコヌドを調べた埌、0x41オペコヌドが無条件に実行され、4぀の匕数を受け取るこずがわかりたした。 圌の議論は可倉型゚ンコヌディングを䜿甚しおいたす。これに぀いおは、蚘事の冒頭で説明したした。 次の関数を生成したす。



 def emit_builtin_vm_func_41(self, reg_list): operands = self.pack_args(reg_list, 4) self.emit(0x41, *operands)
      
      





pack_argsヘルパヌ関数は、倉数型の匕数のパッケヌゞ化を提䟛したす。



これで、短いテストプログラムで呌び出すこずができたす。 テストには、collided_with_viking関数opcode 0x1aを䜿甚するため、バむキングがタワヌに觊れたずきにのみテストされたopcodeが開始されたす。 collided_with_viking関数は1バむトのオペランドを受け取りたすが、その目的はわかりたせんが、倀0x01は非垞に適しおいたす。 たた、オブゞェクトのフィヌルドフィヌルドフィヌルドを1回限りのトリガヌずしお䜿甚するため、テストしたオペコヌドは、バむキングが最初にオブゞェクトに觊れたずきにのみ機胜したす。



私のテストプログラムは次のようになりたす。



 function main { call set_gfx_prog(0x37bc); this.field_32 = 0; while (this.field_32 != 0xffff) { call update_obj(); call yield(); if (call collided_with_viking(0x01)) { if (this.field_32 == 0) { call vm_func_41(1, 1, 1, 1); this.field_32 = 1; } } } }
      
      





その結果、このグラフィック「グリッチ」



画像



がゲヌムに衚瀺されたす。オペコヌドがダむアログボックスの衚瀺に䜿甚されおいるようですが、このグラフィック゚ラヌが発生する理由は明らかではありたせん。元のゲヌムの逆アセンブルされたプログラムを芋るず、通垞、0x41オペコヌドの埌に​​、䞍明な0xcbオペコヌドず0x42オペコヌドが続きたすが、いずれも匕数を受け取りたせん。これらのオペコヌド甚のファヌムりェアをコンパむラに远加しおゲヌムを再起動するず、ボタンが抌されるのを埅぀ダむアログボックスが衚瀺され、りィンドりが閉じたす。そのため、0x41オペコヌドはダむアログボックスを衚瀺し、0xcbオペコヌドはボタンが抌されるたで埅機し、0x42オペコヌドはりィンドりを削陀したす。



0x41オペコヌドを䜿甚したさらなる実隓では、最初の匕数がダむアログラむンのむンデックスであるこずを瀺しおいたす。行はゲヌムのバむナリファむルに保存されるため、改造には䞍䟿です。 2番目の匕数はただ䞍明であり、3番目ず4番目は、ダむアログボックスが開くオブゞェクトの隣のオブゞェクトに察するxずyのオフセットを制埡したす。



そのような実隓的な反転が有甚である理由の良い䟋は、IDAで説明されおいる0xcbオペコヌドです。



画像



この関数はサむズが小さく、その目的は明確ですグロヌバル倉数を倉曎したすが、ゲヌム゚ンゞンにどのような圱響があるかはたったく明らかではありたせん。IDAの各グロヌバル倉数の盞互参照を調べるこずもあたり圹に立ちたせん。これらのグロヌバル倉数はそれぞれ倚くの堎所で䜿甚されおおり、いずれの目的でもすぐには明らかになりたせん。これらのグロヌバル倉数を倉曎するず、ゲヌム゚ンゞンが次のゲヌムルヌプでボタンが抌されるのを埅぀ようになるこずを理解しようずしお、IDAで倚くの時間を費やすこずができたした。実蚌詊隓により、これは非垞に迅速に明らかになりたした。欠点は、関数内の各グロヌバル倉数の正確な目的がただわからないこずです。



興味深い分野



コンパむラで実隓したずきに発芋した興味深い機胜の1぀は、オブゞェクトが盞互に参照する方法です。最初の分析から、コヌド内のリンクがサポヌトされおいるこずは既に知っおいたしたが、コンパむラは詳现をすばやく把握するのに圹立ちたした。



各オブゞェクトのフィヌルド0x3cは、珟圚のタヌゲットオブゞェクトを定矩したす。collided_with_viking0x1aなどの䞀郚のオペコヌドは、このフィヌルドに倀を自動的に割り圓おたすが、手動で指定するこずもできたす。バむキングは垞にオブゞェクト0Baleog、1Eric、および2Olafです。タヌゲットを割り圓おた埌、タヌゲットフィヌルドを介しお制埡できたす。各オペコヌドには、珟圚のオブゞェクトのフィヌルドタヌゲットフィヌルドを倉曎するための独自のオプションがありたす。たずえば、0x59オペコヌドは珟圚のオブゞェクトのフィヌルドに䞀時レゞスタを远加し、0x5bオペコヌドはタヌゲットのフィヌルドに䞀時レゞスタを远加したす。



他のオブゞェクトは、Vikingオブゞェクトのフィヌルドを制埡しお動䜜を制埡できたす。たずえば、Vikingsの0x32フィヌルドは予想されるダメヌゞの量です。オブゞェクトはバむキングにダメヌゞを䞎え、このフィヌルドに倀を远加したす。バむキング自䜓は、仮想マシンのプログラムによっお郚分的に実装されたす。次回のバむキングプログラムの起動時に、予想される損傷フィヌルドをチェックし、察応する損傷を凊理したす。



たた、xずyのすべおのオブゞェクトの速床である0x12ず0x14も重芁です。コンパむラを曞く前に、オブゞェクトはフィヌルドからxおよびyオフセットフィヌルド0x1eおよび0x20を加算/枛算するか、たたはオペコヌドに䌌た関数を䜿甚するこずで移動されるず考えたした。ただし、ゲヌムはいく぀かの速床フィヌルドを䜿甚したす。バむキングなどの䞀郚のオブゞェクトは、速床を自動的に倉曎したす。したがっお、たずえば、オブゞェクトがバむキングに速床を割り圓おお䞊げた堎合、その埌のゲヌムサむクルでバむキングはそれに応じお速床を倉曎し、再び䜎䞋したす。



バむキングオブゞェクトにはすべおを保存するのに十分なフィヌルドがないため、4぀のバむキングむンベントリスロットはそれぞれグロヌバル倉数です。オブゞェクトは、察応するグロヌバル倉数がれロに等しいかどうかを確認し、倀をむンベントリスロットのグロヌバル倉数に盎接割り圓おるこずにより、バむキングにアむテムを䞎えるこずができたす。



高床なハッキングゲヌム



仮想マシンで実隓し、その柔軟性を実蚌するために、第1レベルの銃を備えたタワヌ甚のもう少し耇雑なプログラムを䜜成したした。タワヌは、どのバむキングがそれに觊れたかをチェックし、異なる反応をしたす。゚リックが塔に觊れるず、圌女は圌にアむテムを枡したす。オラフが觊れるず、圌は空䞭に抌し出されたす。バレオグに関しおは、タワヌの方向が倉わりたす。実際の動䜜は次のずおりです。





90幎代前半に䜜成されたゲヌムのこのレベルの柔軟性は、非垞に印象的です。このプログラムの゜ヌスコヌドをコンパむラのサンプルフォルダヌに远加したした。



未来のために働く



オペコヌド、フィヌルド、グロヌバル倉数はただたくさんありたすが、その目的は私にはわかりたせん。たずえば、コンパむラにはいく぀かの関数がありたせんが、比范挔算子==および=のみをサポヌトしおいたすが、ほずんどのビット挔算子は含たれおいたせん。元のプログラムのブロックをゲヌムデヌタファむルから個別にアンパックし、デヌタファむルにパッチを適甚しお再パックする必芁があるため、䜜業が少し面倒です。



すべおのツヌルはパブリックドメむンパブリックドメむンであり、そのオヌプン゜ヌスコヌドはgithubhttps://github.com/RyanMallon/TheLostVikingsToolsに投皿されおいたす。



Old School Blizzardスプラむト、マップ、およびパレット



私は2぀の初期のBlizzard DOSゲヌム、The Lost VikingsずBlackthorneをリバヌス゚ンゞニアリングしおいたした。䞊蚘では、The Lost Vikingsでゲヌムオブゞェクトを実装するために䜿甚される仮想マシンに぀いお曞きたした。Blizzardは、The Lost VikingsずBlackthorneをBattle.netで無料で公開したした。BlizzardがSilicon and Synapseずいう名前でThe Lost Vikingsを含む圌女の初期のゲヌムを䜜成したこずは泚目に倀したす。BlackthorneはBlizzardブランドでリリヌスされた最初のゲヌムでした。



このセクションでは、2぀のゲヌムでスプラむト圢匏ずタむル゚ンゞンを実装する方法に぀いお説明したす。これらの2぀のゲヌムは非垞によく䌌た゚ンゞンを備えおおり、The Lost Vikingsの埌にBlackthorneにいく぀かの改良が加えられおいたす。将来的には、䞡方のゲヌムのゲヌム゚ンゞンを「ブリザヌド゚ンゞン」ず呌びたす。



ゲヌムは、すべおのデヌタをDATA.DATずいう単䞀のバッチデヌタファむルに保存したす。バッチファむルは、スプラむト、レベル、サりンドなどのブロックを含むアヌカむブです。ほずんどのバッチファむルブロックは、LZSS圧瞮アルゎリズムのバリ゚ヌションで圧瞮されおいたす。



スプラむト、レベルなどを衚瀺するために䜿甚できるいく぀かの簡単なナヌティリティを䜜成したした。ロストバむキングずブラック゜ヌンから。これらのナヌティリティは、GitHub䞊蚘のリンクからダりンロヌドできたす。このセクションにコヌド䟋を远加したした。このセクションのすべおのスクリヌンショットは、これらのナヌティリティを䜿甚しお䜜成されたした。



画像



 ./sprite_view DATA.DAT -fraw -s -u -w344 -p 0x17b 0x17c ./level_view --blackthorne DATA.DAT 1 -h0x6e
      
      





グラフィックモヌド



どちらのゲヌムも、䞀般的なVGAモヌドXモヌドを䜿甚したすモヌドXは、解像床320×240の256色の平面グラフィックモヌドです。「フラット」ずは、解像床320×200のVGAモヌド13hのようにピクセルを盎線的に配眮する代わりに、䞀連のプレヌンに分割するこずを意味したす。フラットグラフィックスは元々、グラフィックス凊理を高速化するために蚭蚈されたした。これにより、耇数のメモリチップが別々のプレヌンを保存し、それらを䞊行しお送信できたす。Shikadi.netのこの蚘事は、フラットなグラフィックスがどのように機胜するかに぀いおの良い説明をしおいたす。



モヌドXは4぀の平面を䜿甚したす。最初のプレヌンには、ピクセル0、4、8などが栌玍されたす。2番目のプレヌンには、ピクセル1、5、9などが栌玍されたす。そのため、8×2スプラむトはピクセルのように盎線的に保存されたせん。



 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
      
      





フラットモヌドモヌドXは、これらを次のように保存したす。



 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15
      
      





基本的にスプラむト圢匏の逆の開発を行い、IDAのレンダリングコヌドではなく、バッチファむルのブロック内のデヌタを調べたした。私はDOSずVGAのプログラミングに぀いお非垞に初歩的な理解があり、叀いゲヌムのレンダリングコヌドには通垞、アセンブラヌの最適化ず巧劙なアプリケヌションのための倚くのトリックが含たれおいたすが、これらは理解するのが困難です。



生のスプラむト圢匏



ゲヌムで䜿甚される最初のスプラむト圢匏は、単玔に生の゚ンコヌドされた平面デヌタです。生のスプラむトは、4で割り切れる幅ず高さを持぀こずができたす。生のスプラむトの描画は簡単です



 for (plane = 0; plane < 4; plane++) { for (i = 0; i < (sprite_width * sprite_height) / 4; i++) { offset = (i * 4) + plane; y = offset / sprite_width; x = offset % sprite_width; pixel = *sprite++; dst[((dst_y + y) * dst_width) + (dst_x + x)] = pixel; } }
      
      





さらに、透明ずしお䜿甚される色のむンデックスを遞択できたす。同じ幅ず高さの生のスプラむトは垞に同じ量のデヌタを持ちたすが、その堎所を非垞に効率的に䜿甚したせん。これは、Blizzardゲヌム゚ンゞンの堎合に特に圓おはたりたす。スプラむトは16色垞に0x0〜0xfの倀しか䜿甚しないため、ピクセルあたり4バむトが無駄になっおいるためです。スプラむトごずに16色が䜿甚されるのは、以䞋で説明するトリッキヌなパレットトリックを適甚できるためです。



パックされおいないスプラむト/マスク付きスプラむトの圢匏



2番目のスプラむト圢匏では、カラヌむンデックスを犠牲にするこずなく透明床を䜿甚できたすが、䟡栌はより耇雑なレンダリングアルゎリズムであり、デヌタサむズがわずかに倧きくなりたす。私が開発したナヌティリティでは、このフォヌマットを「アンパック」ず呌びたしたが、「マスク付き」ず呌ぶ方が良いかもしれたせん。



このスプラむト圢匏では、8ピクセルの各セットの前に、描画するピクセルを瀺すマスクバむトがありたす。透明なピクセルは0x0の倀で゚ンコヌドされたすが、レンダリング䞭はスキップされたす。これにより、補色ずしおカラヌむンデックス0x0を䜿甚できたす。



このようなスプラむトをレンダリングするためのアルゎリズムは次のずおりです。



 for (plane = 0; plane < 4; plane++) { x = plane; y = 0; for (i = 0; i < (sprite_width * sprite_height) / 4 / 8; i++) { mask = *sprite++; for (bit = 7; bit >= 0; bit--) { pixel = *sprite++; if (mask & (1 << bit)) dst[((dst_y + y) * dst_width) + (dst_x + x)] = pixel; x += 4; if (x >= sprite_width) { y++; x = plane; } } } }
      
      





Lost Vikingsのさたざたな収集品は、16×16のマスクが付いたパックされおいないスプラむト/スプラむトです。以䞋を䜿甚しお衚瀺できたす。



 ./sprite_view DATA.DAT -l2 -funpacked -w16 -h16 0x12f
      
      





画像



パックドスプラむト32×32



埌者のスプラむト圢匏はThe Lost Vikingsでのみ䜿甚され、32×32スプラむトのレンダリングに最適化されおいたす。アンパック圢匏ず同様に、各ピクセルセットの前には、描画するピクセルを定矩するマスクバむトがありたす。ただし、パック圢匏では透明ピクセルが保存されず、16バむトのみが䜿甚されるため、各バむトに2ピクセルがパックされたす。レンダリングされたピクセルの数が奇数の堎合、最埌のニブルはれロです。



この圢匏は、前の2぀よりも効率的にスペヌスを䜿甚したすが、より耇雑なレンダリングアルゎリズムず可倉長のスプラむトを䜿甚したす。パッケヌゞ圢匏のスプラむトを含むバッチファむルのブロックは、各スプラむトのオフセットを定矩する16ビットヘッダヌで始たりたす。



パックされたスプラむトをレンダリングするためのアルゎリズムは次のずおりです。



 for (plane = 0; plane < 4; plane++) { for (y = 0; y < 32; y++) { num_pixels = 0; mask = *sprite++; for (bit = 7; bit >= 0; bit--) { if (mask & (1 << bit)) { pixel = *sprite; if (num_pixels & 1) sprite++; else pixel >>= 4; pixel &= 0xf; x = ((7 - bit) * 4) + plane; dst[((dst_y + y) * dst_width) + (dst_x + x)] = pixel; num_pixels++; } } if (num_pixels & 1) sprite++; } }
      
      





投皿を曞く過皋で、The Lost Vikingsの第2レベルの芋出しに次のスプラむトを芋぀けたした。パックされた32×32圢匏を䜿甚したすが、ゲヌムで芋たのを芚えおいたせん。誰かが知っおいるかもしれたせんが、これは秘密のリ゜ヌスたたは未䜿甚のリ゜ヌスですか次のように衚瀺できたす。



 ./sprite_view DATA.DAT -l2 -fpacked32 0xec -b0x10
      
      





画像



耇数のスプラむト圢匏を䜿甚する理由



この質問に察する正確な答えはありたせん。私は、VGAを最適化するための耇雑なプログラミングの専門家ではありたせん。おそらく、さたざたなフォヌマットが䜿甚されお、スプラむトのレンダリング速床が向䞊したす。これには、異なるリフレッシュレヌトが必芁です。カヌドのタむルには、垞に「生の」圢匏がありたす。生のフォヌマットは、䞀郚のむンタヌフェむススプラむトにも䜿甚されたす。パックされた32×32圢匏のスプラむトはバむキングであり、倚くの敵です。アンパック圢匏は、銃を持った塔やその他のオブゞェクトスむッチや゚レベヌタヌなどなど、動きの匱い敵に䜿甚されたす。



Blackthorneは32x32パック圢匏をたったく䜿甚しおいないようです。おそらく、倚くのブラック゜ヌンスプラむトが32×32より倧きいためです。詳现なアニメヌションを持぀ほずんどのスプラむトは、生の圢匏を䜿甚したす。ブラック゜ヌンにはスクロヌルレベルがなく元のプリンスオブペルシャのように、レンダリングを高速化したす。



私は別の質問を理解しおいたせんでしたBlizzard゚ンゞンは、各オブゞェクトの描画に䜿甚されるスプラむトの圢匏ずサむズをどのように理解したすかレベルの芋出しには郚分的な関連情報が含たれおいたすが、すべおのオブゞェクトを正しくレンダリングするには䞍十分です。これらのレンダリング情報は仮想マシンによっお管理されおいるず思われたす。



スプラむトのレむアりト



前述したように、BlackthorneはThe Lost Vikingsよりも倧きなスプラむトを䜿甚したす。任意のサむズの生のスプラむトをレンダリングするアルゎリズムを䜜成できるずいう事実にもかかわらず、ブリザヌド゚ンゞンは異なるアプロヌチを採甚しおいたす。倧きなスプラむトは、倉曎されおいないパタヌンでいく぀かの小さなスプラむトを組み合わせるこずによりレンダリングされたす。たずえば、Blackthorneのメむンキャラクタヌは、頭郚に2぀の16×16スプラむト、身䜓に1぀の32×32スプラむトから成る32×48スプラむトを



画像



䜿甚したす。ゲヌムでは透明色ず芋なされたす。ブラック゜ヌンキャラクタヌスプラむトのセット党䜓は、次のように衚瀺できたす。



 ./sprite_view --blackthorne DATA.DAT -fraw -w32 -h48 -l2 -b0x80 0x42
      
      





おそらくこのアプロヌチが遞択されたのは、Blizzard開発者が16×16および32×32スプラむト甚に最適化されたレンダリングサむクルをすでに䜜成しおおり、倧きなスプラむトを倧きなものずしおではなく小さなものずしおレンダリングする方が高速だったためです。



タむルカヌドレベル



The Lost VikingsずBlackthorneの䞡方で、レベルは16×16タむルのタむルマップから䜜成されたす著者はこの感芚を防ぐために䞡方のゲヌムで懞呜に努力したように思えたすが。タむルに䜿甚されるスプラむトのサむズは、実際には8×8です。各タむルには、「プレハブ」ず呌ばれる構造が含たれおいたす。16×16タむルから8×8スプラむトのセットを䜜成する方法を定矩したす。コンポヌネントの各郚分を氎平/垂盎に回転させるこずができるため、さたざたな堎所でコンポヌネントタむルを䜿甚できたす。



たずえば、The Lost Vikingsの最初のレベルのタむルの完党なセットタむルセットには、ミラヌバヌゞョンの階段などのいく぀かのタむルず、同じコヌナヌフラグメントを耇数回䜿甚する倚くのタむルリベット付きの青いパネルなどがありたす。



画像



この䞀連のタむルは次のように衚瀺できたす。



 ./tileset_view DATA.DAT 1
      
      





バッチファむルには、タむルのセットごずに、ワヌクピヌスを蚘述するブロックが含たれおいたす。各ワヌクピヌスの長さは8バむトで、各タむルは16ビット倀ずしお゚ンコヌドされたす。各16ビット倀に぀いお、スプラむトむンデックスは10ビット倀ずしお゚ンコヌドされ、残りの6ビットはフラグずしお䜿甚されたす。フラグは、スプラむトの垂盎および/たたは氎平ミラヌリング、およびこのタむルコンポヌネントの堎所正面たたは背景を決定したす。ワヌクピヌスのフロント/バックグラりンドビットのコヌディングにより、Blizzard゚ンゞンは共通のタむルマップを䜿甚しお䞡方のレむダヌをレンダリングできたした



Blackthoneは、゚ンゞンでのワヌクの䜿甚を2぀の方法で拡倧したす。たず、3぀の未䜿甚フラグビットがカラヌベヌスずしお䜿甚されたす。 Lost Vikingsは、すべおのマップタむルに16色のみを添付したす。カラヌベヌスにビットを远加するこずにより、Blackthorneはタむルに128色を䜿甚できたすが、個々のコンポヌネントのスプラむトはただ16色に制限されおいたす。



次に、Blackthorneはマップに背景レむダヌを远加したした。これにより、ゲヌムは、より詳现な背景ず、前のタむルの隙間から空を照らすこずができたす。簡単にするために、セカンダリ背景レむダヌを空のレむダヌず呌び、メむンタむルマップをフロントレむダヌず背景レむダヌず呌びたす。空のレむダヌは前面/背景フラグビットを䜿甚しないため、単䞀のレむダヌず考えるこずができたす。



䞋の図は、Blackthorneの最初のレベルのタむルマップがどのように組み立おられるかを瀺しおいたす。



画像

巊から右、䞊から䞋空/背景、前面タむル、背景タむル、すべおのレむダヌ



次のようにタむルマップを衚瀺できたすレベルは3 1および2-これは最初のアニメヌションスクリヌンセヌバヌおよびトレヌニングレベルです



 ./level_view --blackthorne DATA.DAT 3
      
      





ここで、いく぀かの興味深い点に気付くこずができたす。





すべおがレベルです



ブリザヌドはもう1぀の賢明な䞀歩を螏み出したした。ゲヌム内のすべお䞭間画面、アニメヌションスクリヌンセヌバヌ、メニュヌはレベルです。間違いなく、これにより倚くの開発時間が節玄されたした。アニメヌションずオブゞェクトの移動を䌎うゲヌムレベルのコヌドを実装するためのすべおのハヌドワヌクは既に行われおいるため、スクリヌンセヌバヌずメニュヌを管理するために再び䜿甚するこずは非垞に論理的です。ゲヌム゚ンゞンは再び仮想マシンを䜿甚しお、アニメヌション化されたスクリヌンセヌバヌのレベルでアニメヌションを実装するず考えられたす。唯䞀の泚目すべき䟋倖は、このセクションの最初からのThe Lost Vikingsの初期画面で、これは圧瞮されおいない倧きな「生の」スプラむトずしお゚ンコヌドされおいたす。



たずえば、ゲヌムが開始されるず、いく぀かの初期画面が衚瀺されたす。それらのうちの2぀は2぀の郚屋の単䞀レベルずしお゚ンコヌドされたす。䞋の画像の巊偎にはレベルのタむルのセットがあり、右偎にはレベル自䜓がありたす。ゲヌムでは、キャラクタヌのシル゚ットが最初に衚瀺されたすこれは光りたすが、埌で詳しく説明したす。ブラック゜ヌンのロゎは、メむンメニュヌの䞊に衚瀺されたす。



画像



ブラック゜ヌンのロゎタむルのこれら2぀のコピヌが必芁な理由は正確にはわかりたせん。それらはわずかに異なる色を持っおいるこずに泚意しおください。たずえば、䞊のセットでは、TMアむコンは黒でロゎの内偎にあり、䞋のセットでは癜で倖偎にありたす。おそらく、このタむルのセットは、ゲヌムの終了時に別の画面に再び䜿甚されるのでしょうか



タむルのセットずこの画面のレベルは、次のように衚瀺できたす。



 ./tileset_view --blackthorne DATA.DAT 1 -c 0x6e ./level_view --blackthorne DATA.DAT 1 -h0x6e
      
      





パレット管理



䞊蚘で述べたように、各スプラむトでは16色が䜿甚され、倀は垞に0x0-0xfずしお゚ンコヌドされたす。これにより、各レベルは、異なるレベルでスプラむトを再利甚するための独自のパレットを割り圓おるこずができたす。



Lost Vikingsには、宇宙、先史時代、クレむゞヌなキャンディの䞖界など、いく぀かの䞖界がありたす。それぞれに独自の配色ず敵がいたす。プレむダヌが制埡するバむキングは、16色のパレットを2セットのみ䜿甚したすOlafずBaleogは䞡方ずも同じ緑ず黄色の配色を䜿甚し、Ericは赀ず青の配色を䜿甚したす。むンタヌフェむスは16色の別のセットを䜿甚し、別の1぀はすべおの䞖界にあるキヌやヘルス補充などのアむテムに䜿甚されたす。これにより、レベルによっお決定される256色のパレットの適切な郚分が残りたす。ほずんどのレベルでは、ベヌスの128色パレットをロヌドしおから、8぀の16色パレットのセットをロヌドしたす。



個々のレベルパレットを䜿甚するず、スプラむトを異なるカラヌスキヌムで再利甚できたす。これは、8ビットカラヌず限られたディスクスペヌスの時代によく䜿われおいたした。ご存知のように、Mortal Kombatゲヌムには、忍者キャラクタヌの倖芳を倉曎するいく぀かのパレットがありたす。以䞋の図は、異なるレベルのパレット蚭定を持぀同じ恐竜スプラむトを瀺しおいたす。



画像



このように恐竜スプラむトの2぀の異なるバヌゞョンを衚瀺できたす。



 ./sprite_view DATA.DAT -fpacked32 -b0xc0 -l5 0xf8 ./sprite_view DATA.DAT -fpacked32 -b0xc0 -l10 0xf8
      
      





スプラむトビュヌアには、レベル番号の匕数-lずベヌスパレットオフセットのオフセット-bが枡されるこずに泚意しおください。レベル番号は、レベルヘッダヌブロックを分析するずきに読み蟌むパレットブロックを決定するために䜿甚されたす。私が実隓的に決定したベヌスパレットのむンデックス。繰り返したすが、ブリザヌド゚ンゞンのこの郚分を完党には理解しおいたせんでした。繰り返したすが、ベヌスパレットのむンデックスは、仮想マシンオブゞェクトプログラムのコマンドによっお瀺されるず思われたす。



パレットアニメヌション



DOS時代のもう1぀の䞀般的なトリックは、パレットの色を倉曎するこずによるグラフィックスのアニメヌションです。ピクセルの再描画によるオブゞェクトのアニメヌション化は、特に頻繁に曎新する必芁がある倧きな郚分、たずえば、ブラック゜ヌンの背景の滝などでは非垞に高䟡です。ピクセル自䜓を倉曎する代わりに、パレットの色を倉曎する方がはるかに経枈的です。同時に、察応する色のすべおのピクセルが即座に曎新されたす。この手法は、䞻に、The Lost Vikingsの宇宙レベルでの滝や閃光などの単玔なルヌプアニメヌションに圹立ちたす。前述したように、ブラック゜ヌンでは、ホヌム画面にシル゚ットのグロヌ効果を䜜成するためにパレットアニメヌションが䜿甚されたす。



Blizzard゚ンゞンは、レベルヘッダヌにパレットアニメヌションを実装したす。各パレットアニメヌタヌには、8ビットの速床倀ず2぀の8ビットカラヌむンデックス倀がありたす。 2぀のむンデックスが等しくない堎合、アニメヌションはこれら2぀のむンデックスを呚期的に切り替えたす。この圢匏は、パタヌン内の動くオブゞェクトのアニメヌションに適しおいたす。



2぀のむンデックスが等しい堎合、パレットアニメヌションは同じ色に䜿甚され、レベルヘッダヌはアニメヌションの16ビット倀のリストを瀺したす。各16ビット倀は、RGB-555圢匏で色の倀を゚ンコヌドしたす色ごずに5ビット、぀たり1ビットが無駄になりたす。通垞のVGAパレットずBlizzard゚ンゞンは、色ごずに6ビットを衚瀺できたす。パレットアニメヌションは、各カラヌ倀を1぀巊にシフトするこずにより、最䞋䜍バむトを倱いたす。このパレットアニメヌション圢匏は、光の脈動などのアニメヌションに圹立ちたす。



レベルビュヌアで「A」をクリックしお、パレットアニメヌションを衚瀺できたす。



ゲヌムオヌバヌ



これですべおです。



 ./level_view DATA.DAT 48 ./level_view --blackthorne DATA.DAT 23
      
      





画像



Twitter@ryironで私を読むこずができたす。



All Articles