早在 80 年代,一家名為 Binary Systems 的不知名公司就發布了遊戲 Starflight。遊戲中玩家將飾演一名被派往銀河系探索的星艦艦長。沒有設定的路徑,讓玩家可以在採礦、艦對艦戰鬥和外星人外交之間自由切換。遊戲的更廣闊的情節慢慢浮現,玩家發現一個古老的生物種族正在導致星星閃耀並摧毀所有生物。該遊戲受到當代和現代評論家的廣泛好評,是最早的沙盒遊戲之一。該遊戲發布後數十年影響了許多其他遊戲的設計。
要了解有關遊戲的更多信息,請查看以下連結:
遊戲可以在GoG購買
當我第一次聽說這個遊戲時,我就想玩它。然而,我太小了,不會說英語。 20年後我再次嘗試,這是一次非常愉快的經驗。探索很有趣,故事情節史詩般,結尾充滿驚喜,這是我經歷過的最好的體驗之一。當然,這款遊戲還沒成熟,但你可以感受到開發者對遊戲的投入。這款遊戲既有藝術的一面,也有工匠對細節的關注。
儘管玩這款真正令人驚嘆的遊戲很有趣,但對這款遊戲進行逆向工程也很有趣。您追隨開發者的腳步,體驗他們的思考過程,彷彿回到了 1985 年。對於這場比賽,期待意想不到的事情。通常,當你對這樣的老遊戲進行逆向工程時,你必須收到數萬行純組譯程式碼,你可以使用 IDA Pro 等常用工具進行分析。但這次不是。實際上,在這個遊戲中你可以丟掉常用的工具。它們毫無用處。你只能靠你自己了。原因是《星際飛行》是用 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 是第四個字「+」的等效編碼。記住上面的描述。單字“+”彈出最後兩個堆疊條目,將它們相加並將結果推回堆疊頂部。根據間接線程,這個 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 ]
前四個指令完全執行單字“+”應執行的操作。從“lodsw”開始的最後三個組譯指令增加指令指標並跳到下一個程式碼。
讓我們繼續吧。現在指令指標指向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。第四個字「@」將從堆疊中彈出位址,讀取其內容並將其推回堆疊上。
到目前為止,我可以識別 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 語言。
第四代碼:
: .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 | CDEPTH(); |
0x15fa 0x0020 | 如果 | if (Pop() != 0) { |
0x54ae | CXSP | Push(Read16(pp_CXSP) + 3); |
0xbae | @ | |
0x3b73 | 3 | |
0x0f72 | + | |
0x4ffd | END-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 | +循環 | 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 | 'T' | |
0x20 | '' | |
0x53 | 'S' | |
0x54 | 'T' | |
0x4b | 'K' | |
然後 | } | |
0x0642 | CR | Exec("CR"); |
0x1690 | 出口 | } |
遊戲有 3 個文件
STARA.com 的內容
入口 | 尺寸 | 描述 |
---|---|---|
目錄 | 4096 | 包含 STARA 和 STARB 目錄 |
ELO-CPIC | 4816 | |
瓦斯太保 | 3120 | |
MEC-太保 | 2848 | |
MYS-CPIC | 6064 | |
NOM-CPIC | 1136 | |
特殊PE-太保 | 1888年 | |
太熱太保 | 2480 | |
維爾-CPIC | 4672 | |
VPR-CPIC | 1248 | |
閩太保 | 2096 | |
濺 | 16384 | 圖片 |
醫學-PIC | 2048 | 圖片 |
相位 | 6144 | |
HUM-PIC | 第480章 | 圖片 |
威利PIC | 第432章 | 圖片 |
THR-PIC | 第272章 | 圖片 |
埃洛-PIC | 608 | 圖片 |
和-PIC | 640 | 圖片 |
節省 | 124000 | |
音樂 | 4960 | 程式碼疊加 |
地球 | 第1152章 | 地球地圖 |
星系 | 6304 | |
學分 | 16384 | 圖片 |
太保集團 | 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 | 圖片 |
BPORT-PIC | 3984 | 圖片 |
分析文字 | 3200 | 桌子 |
按鈕 | 第944章 | 桌子 |
圖標1:1 | 912 | |
圖示1:2 | 912 | |
圖標1:4 | 912 | |
圖標名稱 | 第736章 | |
DPART-OV | 第1552章 | 程式碼疊加 |
地區 | 176 | 桌子 |
生物 | 17024 | 桌子 |
CHK航班-OV | 960 | 程式碼疊加 |
分形OV | 4640 | 程式碼疊加 |
ICONP-OV | 第832章 | 程式碼疊加 |
站點-OV | 1888年 | 程式碼疊加 |
超MSG-OV | 4112 | 程式碼疊加 |
吉寶利 | 第368章 | |
面 | 288 | |
頂點 | 第416章 | |
BLT-OV | 第864章 | 程式碼疊加 |
雜項OV | 1440 | 程式碼疊加 |
OV銀行 | 1520 | 程式碼疊加 |
螺絲-OV | 2800 | 程式碼疊加 |
人員-OV | 4192 | 程式碼疊加 |
船舶圖形OV | 2112 | 程式碼疊加 |
配置-OV | 3072 | 程式碼疊加 |
TDEPOT-OV | 4800 | 程式碼疊加 |
埠選單-OV | 3120 | 程式碼疊加 |
維他-OV | 3552 | 程式碼疊加 |
高壓OV | 4832 | 程式碼疊加 |
LP-OV | 5280 | 程式碼疊加 |
發送OV | 4784 | 程式碼疊加 |
電視OV | 3472 | 程式碼疊加 |
通訊OV | 7232 | 程式碼疊加 |
通訊規範-OV | 2864 | 程式碼疊加 |
種子OV | 2400 | 程式碼疊加 |
清單圖示 | 720 | 程式碼疊加 |
移動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 | 程式碼疊加 |
系統圖示 | 第752章 | |
系統圖示 | 第448章 | |
系統圖示 | 176 | |
行為OV | 5360 | |
CMAP | 1008 | |
安裝 | 800 | |
治癒OV | 第1232章 | 程式碼疊加 |
修復OV | 1696 | 程式碼疊加 |
遊戲OV | 5920 | 程式碼疊加 |
PLSET-OV | 2400 | 程式碼疊加 |
地圖OV | 2240 | 程式碼疊加 |
VES-BLT | 4528 | |
風暴OV | 第1232章 | 程式碼疊加 |
化合物 | 176 | 桌子 |
IT-OV | 1936年 | 程式碼疊加 |
戰鬥OV | 6192 | 程式碼疊加 |
傷害OV | 2752 | 程式碼疊加 |
陸地-OV | 1088 | 程式碼疊加 |
統計統計 | 64 | 桌子 |
STP-OV | 1440 | 程式碼疊加 |
將原始 Starflight 遊戲的檔案放入資料夾starflt1-in
和starflt2-in
並執行make
。您應該會取得兩個執行檔( disasOV1
和disasOV2
),它們會在資料夾starflt1-out
和starflt2-out
中產生內容。產生的輸出是該儲存庫的一部分。