
80 年代に遡ると、Binary Systems という無名の会社が Starflight というゲームを発行しました。このゲームでは、プレイヤーは銀河を探索するために派遣された宇宙船の船長の役割を果たします。決まった道筋はなく、プレイヤーは採掘、艦船戦闘、異星人外交の間を自由に切り替えることができます。古代の種族が星々をフレアさせ、すべての生き物を破壊していることをプレイヤーが発見すると、ゲームのより広範なプロットがゆっくりと明らかになります。このゲームは現代および現代の批評家から広く賞賛されており、サンドボックス ゲームの最も初期の例の 1 つです。このゲームは、リリース後数十年間、他の多くのゲームのデザインに影響を与えました。
ゲームの詳細については、次のリンクを確認してください。
ゲームはGoGで購入できます
初めてこのゲームのことを聞いたとき、ぜひプレイしてみたいと思いました。しかし、私は若すぎて英語を話すことができませんでした。 20 年後にもう一度試してみましたが、とても楽しい経験でした。探索は楽しく、ストーリー展開は壮大で、サプライズで終わります。これは私が経験した中で最高のものの 1 つです。確かに、ゲームはあまり古くなっていませんが、開発者のゲームに対する献身が感じられます。このゲームには、職人の細部へのこだわりだけでなく、アート的な側面もあります。
この本当に素晴らしいゲームをプレイするのと同じくらい楽しいのは、このゲームのリバース エンジニアリングも楽しいです。あなたは開発者の足跡をたどり、あたかも 1985 年が再び起こったかのように、彼らの思考プロセスを体験します。このゲームでは予想外のことが予想されます。通常、このような古いゲームをリバース エンジニアリングする場合は、数万行の純粋なアセンブラ コードを受け取る必要がありますが、これは IDA Pro などの通常のツールで分析できます。しかし今回は違います。実はこのゲームでは、いつもの道具を捨てることができます。それらは役に立たないのです。あなたは自分自身で生きています。その理由は、Starflight が Forth という、私がほとんど知らなかった言語で書かれていたからです。
Forth は、構文に関して究極のミニマリズムを備えた言語です。 「単語」間のスペース以上の構文はありません。 Forth リーダーとインタープリターは、基本的に数行のコードで作成できます。
現代語では次のように書きます。
print ( 2 + 3 )2+3 の結果を出力します。ただし、Forth では次のようになります。
2 3 + .Forth はスタック マシンで、逆ポーランド記法が使用されます。解釈は次のとおりです
構文もインタプリタもシンプルです。 「2」、「3」、「+」、「.」単に「言葉」と呼んでいます。データとコードの間には構文上の区別はありません。確かに、初期の家庭用コンピューターの限界に耐えた言語です。
実行可能ファイル STARFLT.COM を分析すると、素晴らしい内部構造が明らかになります。
上で説明したように、Forth はスタック マシンです。コーディングメカニズムとして、コンパイルされたコードを保存するための非常にスペース効率の高い方法である間接スレッドを使用します。スレッド化されたコードは、基本的に完全にサブルーチンの呼び出しで構成される形式になっています。間接スレッドでは、マシンコードを指す位置へのポインターが使用されます。
命令ポインタがアドレス 0x1000 を指しており、16 ビット値 Read16(0x1000)=0x0f72 が含まれているとします。
0x1000 : dw 0x0f72値 0x0f72 は、Forth ワード「+」に相当するコード化値です。上の説明を思い出してください。 「+」という単語は、最後の 2 つのスタック エントリをポップし、それらを加算して結果をスタックの一番上に戻します。間接スレッドによれば、この 16 ビット値 0x0f72 は、マシン コードを指す場所へのポインタです。メモリ内容 Read16(0x0f72) を読み取ると、0x0f74 へのポインタが得られます。実際、このメモリの場所を調べて逆アセンブルすると、次のような結果が得られます。
0x0f72 : dw 0x0f74
0x0f74 : pop ax
0x0f75 : pop bx
0x0f76 : add ax , bx
0x0f78 : push ax
0x0f79 : lodsw
0x0f7a : mov bx , ax
0x0f7c : jmp word ptr [ bx ]最初の 4 つの命令は、単語「+」が実行する必要がある操作を正確に実行します。 「lodsw」で始まる最後の 3 つのアセンブラ命令は、命令ポインタを増加させ、次のコードにジャンプします。
続けましょう。命令ポインタは 0x1002 を指しています。
0x1002 : dw 0x53a3アドレス 0x53a3 を読み取ると、
0x53a3 : dw 0x1d29
0x53a5 : dw 0x0001と対応するコード
0x1d29 : inc bx
0x1d2a : inc bx
0x1d2b : push bx
0x1d2c : lodsw
0x1d2d : mov bx , ax
0x1d2f : jmp word ptr [ bx ]このとき、レジスタ bx にはワードアドレス 0x53a3 が含まれています。したがって、このコードはアドレス 0x53a5 をスタックの最上位にプッシュするだけです。私たちが行ったことは、プログラムに変数へのポインターを提供することです。変数の内容は 0x0001 です。 4 番目の単語「@」はスタックからアドレスをポップし、その内容を読み取ってスタックに戻します。
これまでのところ、コードまたはデータを含む 6256 個の単語を識別できました。
実際、コード構造について知っておく必要があるのはこれだけです。ご覧のとおり、これはスペース効率の高いエンコードですが、速度の点では大惨事です。いくつかのマシンコード命令ごとに、別のコード ブロックにジャンプする必要があります。
C の間接スレッドに相当するものは次のようになります。
uint16_t instruction_pointer = start_of_program_pointer ;
void Call ( uint16_t word_adress )
{
// the first two byte of the word's address contain
// the address of the corresponding code, which must be executed for this word
uint16_t code_address = Read16 ( word_address );
switch ( code_address )
{
.
.
.
case 0x0f74 : // word '+'
Push16 ( Pop16 () + Pop16 ());
break ;
.
.
.
}
}
void Run ()
{
while ( 1 )
{
uint16_t word_address = Read16 ( instruction_pointer );
instruction_pointer += 2 ;
Call ( word_address );
}
}特定の単語に対して実行されるコードは、5 つの主要変数 (16 ビット) にアクセスできます。
逆アセンバは、FORTH コードを C スタイルのコードにトランスパイルします。トランスパイルされたコードのほとんどはコンパイルされます。プログラムの動作を理解するには、次の表を参照してください。 「バイトコード」(主に 16 ビット ポインター) を入力として受け取り、それを C に変換します。
4 番目のコード:
: .C ( -- )
Display context stack contents.
CR CDEPTH IF CXSP @ 3 + END-CX
DO I 1.5@ .DRJ -3 +LOOP
ELSE ." MT STK"
THEN CR ;
EXIT変換:
| 16 ビット ポインタ | フォース | C |
|---|---|---|
| : .C ( -- ) | void DrawC() { | |
unsigned short int i, imax; | ||
| 0x0642 | CR | Exec("CR"); |
| 0x75d5 | 深さ | CDEPTH(); |
| 0x15fa 0x0020 | もし | if (Pop() != 0) { |
| 0x54ae | CXSP | Push(Read16(pp_CXSP) + 3); |
| 0xbae | @ | |
| 0x3b73 | 3 | |
| 0x0f72 | + | |
| 0x4ffd | エンドCX | Push(Read16(cc_END_dash_CX)); |
| 0x15b8 | する | i = Pop(); |
imax = Pop(); | ||
do { | ||
| 0x50e0 | 私 | Push(i); |
| 0x4995 | 1.5@ | _1_dot_5_at_(); |
| 0x81d5 | .DRJ | DrawDRJ(); |
| 0x175d 0xfffd | -3 | Push(-3); |
| 0x155c 0xffff | +LOOP | int step = Pop(); |
i += step; | ||
if (((step>=0) && (i>=imax)) || ((step<0) && (i<=imax))) break; | ||
} while(1); | ||
| 0x1660 0x000b | それ以外 | } else { |
| 0x1bdc | 「MT STK」 | PRINT("MT STK", 6); |
| 0x06 | ||
| 0x4d | 「M」 | |
| 0x54 | 「て」 | |
| 0x20 | 「」 | |
| 0x53 | 「す」 | |
| 0x54 | 「て」 | |
| 0x4b | 「き」 | |
| それから | } | |
| 0x0642 | CR | Exec("CR"); |
| 0x1690 | 出口 | } |
ゲームは 3 つのファイルで構成されています
STARA.comのコンテンツ
| エントリ | サイズ | 説明 |
|---|---|---|
| ディレクトリ | 4096 | STARA と STARB のディレクトリが含まれています |
| エロCPIC | 4816 | |
| GAZ-CPIC | 3120 | |
| MEC-CPIC | 2848 | |
| MYS-CPIC | 6064 | |
| ノム-CPIC | 1136 | |
| SPEC-CPIC | 1888年 | |
| THR-CPIC | 2480 | |
| ベル-CPIC | 4672 | |
| VPR-CPIC | 1248 | |
| MIN-CPIC | 2096年 | |
| スプラッシュ | 16384 | 写真 |
| MED-PIC | 2048年 | 写真 |
| フェーズ | 6144 | |
| ハムピック | 480 | 写真 |
| ベルピック | 432 | 写真 |
| THR-PIC | 272 | 写真 |
| エロ画像 | 608 | 写真 |
| アンドピク | 640 | 写真 |
| 保存 | 124000 | |
| 音楽 | 4960 | コードオーバーレイ |
| 地球 | 1152 | 地球の地図 |
| 銀河 | 6304 | |
| クレジット | 16384 | 写真 |
| COP-CPIC | 2928 | |
| フォント | 768 | |
| CGA | 3600 | CGA グラフィックス カードのマシン コード ルーチン |
| エガ | 3600 | EGA グラフィックス カードのマシン コード ルーチン |
STARB.COMのコンテンツ
| エントリ | サイズ | 説明 |
|---|---|---|
| ディレクトリ | 4096 | STARA と STARB のディレクトリが含まれています |
| 実例 | 150528 | ゲームのほとんどのコンテンツを含むツリー構造 |
| 箱 | 1024 | テーブル |
| 銀行振込 | 144 | テーブル |
| 乗組員 | 128 | テーブル |
| 容器 | 1936年 | テーブル |
| 要素 | 544 | テーブル |
| アーチファクト | 1584年 | テーブル |
| 惑星 | 1360 | テーブル |
| 検体 | 448 | テーブル |
| バイオデータ | 448 | テーブル |
| TPORT-PIC | 2416 | 写真 |
| Bポート-PIC | 3984 | 写真 |
| テキストの分析 | 3200 | テーブル |
| ボタン | 944 | テーブル |
| アイコン1:1 | 912 | |
| アイコン1:2 | 912 | |
| アイコン1:4 | 912 | |
| アイコン名 | 736 | |
| DPART-OV | 1552年 | コードオーバーレイ |
| 地域 | 176 | テーブル |
| 生き物 | 17024 | テーブル |
| CHKFLIGHT-OV | 960 | コードオーバーレイ |
| フラクトオブ | 4640 | コードオーバーレイ |
| ICONP-OV | 832 | コードオーバーレイ |
| サイト-OV | 1888年 | コードオーバーレイ |
| ハイパーMSG-OV | 4112 | コードオーバーレイ |
| グポリ | 368 | |
| ファセット | 288 | |
| 頂点 | 416 | |
| BLT-OV | 864 | コードオーバーレイ |
| MISC-OV | 1440 | コードオーバーレイ |
| バンク-OV | 1520 | コードオーバーレイ |
| アススクリュー-OV | 2800 | コードオーバーレイ |
| 人事-OV | 4192 | コードオーバーレイ |
| SHIPGRPH-OV | 2112 | コードオーバーレイ |
| 構成-OV | 3072 | コードオーバーレイ |
| TDEPOT-OV | 4800 | コードオーバーレイ |
| ポートメニュー-OV | 3120 | コードオーバーレイ |
| ビタ-OV | 3552 | コードオーバーレイ |
| HP-OV | 4832 | コードオーバーレイ |
| LP-OV | 5280 | コードオーバーレイ |
| セント・オヴ | 4784 | コードオーバーレイ |
| TV-OV | 3472 | コードオーバーレイ |
| COMM-OV | 7232 | コードオーバーレイ |
| コムスペック-OV | 2864 | コードオーバーレイ |
| シード-OV | 2400 | コードオーバーレイ |
| リストコン | 720 | コードオーバーレイ |
| MOVE-OV | 3808 | コードオーバーレイ |
| エンジニア | 2320 | コードオーバーレイ |
| 医者 | 1280 | コードオーバーレイ |
| オービット-OV | 6640 | コードオーバーレイ |
| キャプテン | 5952 | コードオーバーレイ |
| 科学 | 3952 | コードオーバーレイ |
| ナビガート | 880 | コードオーバーレイ |
| 発送ボタン | 1984年 | |
| MAP-OV | 4160 | コードオーバーレイ |
| ハイパーOV | 7168 | コードオーバーレイ |
| 分析-OV | 2560 | コードオーバーレイ |
| ローンチ-OV | 1360 | コードオーバーレイ |
| フラックス効果 | 464 | |
| OP-OV | 4400 | コードオーバーレイ |
| アイテム-OV | 6016 | コードオーバーレイ |
| LSYSICON | 752 | |
| ミシシコン | 448 | |
| シシアイコン | 176 | |
| BEHAV-OV | 5360 | |
| CMAP | 1008 | |
| インストール | 800 | |
| ヒールオヴ | 1232 | コードオーバーレイ |
| 修理-OV | 1696年 | コードオーバーレイ |
| ゲーム-OV | 5920 | コードオーバーレイ |
| PLSET-OV | 2400 | コードオーバーレイ |
| マップ-OV | 2240 | コードオーバーレイ |
| VES-BLT | 4528 | |
| ストーム-OV | 1232 | コードオーバーレイ |
| 化合物 | 176 | テーブル |
| IT-OV | 1936年 | コードオーバーレイ |
| コンバットOV | 6192 | コードオーバーレイ |
| ダメージ-O | 2752 | コードオーバーレイ |
| ランド-OV | 1088 | コードオーバーレイ |
| PSTATS | 64 | テーブル |
| STP-OV | 1440 | コードオーバーレイ |
オリジナルの Starflight ゲームのファイルをstarflt1-inおよびstarflt2-inフォルダーに置き、 make実行します。 2 つの実行可能ファイル ( disasOV1およびdisasOV2 ) を取得する必要があります。これにより、フォルダーstarflt1-outおよびstarflt2-outにコンテンツが生成されます。生成された出力はこのリポジトリの一部です。