
80년대에 Binary Systems라는 알려지지 않은 회사가 Starflight 게임을 출시했습니다. 게임에서 플레이어는 은하계를 탐험하기 위해 파견된 우주선 선장의 역할을 맡게 됩니다. 정해진 경로가 없으므로 플레이어는 채굴, 함대함 전투, 외계인 외교 사이를 자유롭게 전환할 수 있습니다. 플레이어는 고대 종족이 별을 폭발시키고 모든 생명체를 파괴하고 있다는 사실을 발견하면서 게임의 더 넓은 줄거리가 천천히 나타납니다. 이 게임은 동시대 비평가와 현대 비평가 모두로부터 널리 호평을 받았으며 샌드박스 게임의 초기 사례 중 하나입니다. 이 게임은 출시 후 수십 년 동안 수많은 다른 게임의 디자인에 영향을 미쳤습니다.
게임에 대해 자세히 알아보려면 다음 링크를 확인하세요.
GoG에서 게임을 구매할 수 있습니다.
처음으로 게임에 대한 이야기를 들었을 때 꼭 플레이해보고 싶었습니다. 하지만 저는 너무 어려서 영어를 할 수 없었습니다. 20 나중에 다시 시도했는데 매우 즐거운 경험이었습니다. 탐험은 재미있고 스토리라인은 서사적이며 놀라움으로 끝납니다. 이는 제가 경험한 것 중 최고 중 하나입니다. 물론, 게임이 잘 숙성되지는 않았지만, 게임에 대한 개발자들의 헌신이 느껴지네요. 이 게임에는 예술적인 측면뿐만 아니라 세부 사항에 대한 장인의 관심도 있습니다.
이 정말 놀라운 게임을 플레이하는 것이 재미있는 만큼, 이 게임을 리버스 엔지니어링하는 것도 재미있습니다. 개발자의 발자취를 따라가며 개발자의 사고 과정을 마치 1985년으로 돌아간 것처럼 경험하게 됩니다. 이 게임에서는 예상치 못한 일이 일어날 것으로 예상됩니다. 일반적으로 이러한 오래된 게임을 리버스 엔지니어링하려면 수만 줄의 순수 어셈블러 코드를 받아야 하며, 이를 IDA Pro와 같은 일반적인 도구로 분석할 수 있습니다. 하지만 이번에는 그렇지 않습니다. 실제로 이 게임에서는 일반적인 도구를 버릴 수 있습니다. 그들은 쓸모가 없습니다. 당신은 스스로입니다. 그 이유는 Starflight가 내가 거의 알지 못하는 언어인 Forth로 작성되었기 때문입니다.
네 번째는 구문에 관한 궁극적인 미니멀리즘을 갖춘 언어입니다. "단어" 사이의 공백보다 더 많은 구문은 없습니다. 기본적으로 몇 줄의 코드로 Forth 리더와 인터프리터를 작성할 수 있습니다.
현대 언어에서는 다음과 같이 씁니다.
print ( 2 + 3 )2+3의 결과를 인쇄합니다. 그러나 Forth에서는 다음과 같이 보입니다.
2 3 + .네번째는 역폴란드 표기법을 사용하는 스택 머신입니다. 해석은 다음과 같습니다
구문은 간단하고 인터프리터도 간단합니다. "2", "3", "+" 및 "." 그냥 "단어"라고 합니다. 데이터와 코드 사이에는 구문상의 차이가 없습니다. 확실히 초기 가정용 컴퓨터의 한계에 부응하는 언어였습니다.
실행 가능한 STARFLT.COM을 분석하면 몇 가지 환상적인 내부 기능이 드러납니다.
위에서 설명한 것처럼 네 번째는 스택 머신입니다. 코딩 메커니즘으로서 컴파일된 코드를 저장하는 매우 공간 효율적인 방법인 간접 스레딩을 사용합니다. 스레드 코드에는 본질적으로 서브루틴 호출로만 구성된 형식이 있습니다. 간접 스레딩은 기계어 코드를 가리키는 위치에 대한 포인터를 사용합니다.
명령 포인터가 주소 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 0x53a30x53a3 주소를 읽으면 다음과 같은 사실이 드러납니다.
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 ( -- ) | void DrawC() { | |
unsigned short int i, imax; | ||
| 0x0642 | CR | Exec("CR"); |
| 0x75d5 | CD심도 | 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 | '중' | |
| 0x54 | '티' | |
| 0x20 | ' ' | |
| 0x53 | '에스' | |
| 0x54 | '티' | |
| 0x4b | '케이' | |
| 그 다음에 | } | |
| 0x0642 | CR | Exec("CR"); |
| 0x1690 | 출구 | } |
게임은 3개의 파일로 제공됩니다.
STARA.com의 콘텐츠
| 기입 | 크기 | 설명 |
|---|---|---|
| 예배 규칙서 | 4096 | STARA 및 STARB 디렉토리가 포함되어 있습니다. |
| ELO-CPIC | 4816 | |
| GAZ-CPIC | 3120 | |
| MEC-CPIC | 2848 | |
| MYS-CPIC | 6064 | |
| NOM-CPIC | 1136 | |
| SPE-CPIC | 1888년 | |
| THR-CPIC | 2480 | |
| VEL-CPIC | 4672 | |
| VPR-CPIC | 1248 | |
| 최소 CPIC | 2096 | |
| 튀김 | 16384 | 그림 |
| 메드픽 | 2048년 | 그림 |
| 단계 | 6144 | |
| 험픽 | 480 | 그림 |
| 벨픽 | 432 | 그림 |
| THR-PIC | 272 | 그림 |
| ELO-PIC | 608 | 그림 |
| AND-PIC | 640 | 그림 |
| 구하다 | 124000 | |
| 음악 | 4960 | 코드 오버레이 |
| 지구 | 1152 | 행성 지구 지도 |
| 은하 | 6304 | |
| 크레딧 | 16384 | 그림 |
| COP-CPIC | 2928 | |
| 글꼴 | 768 | |
| CGA | 3600 | CGA 그래픽 카드의 기계 코드 루틴 |
| EGA | 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 | 테이블 |
| CHKFLIGHT-OV | 960 | 코드 오버레이 |
| 프랙트-OV | 4640 | 코드 오버레이 |
| ICONP-OV | 832 | 코드 오버레이 |
| 사이트-OV | 1888년 | 코드 오버레이 |
| 하이퍼MSG-OV | 4112 | 코드 오버레이 |
| 지폴리 | 368 | |
| 한 면 | 288 | |
| 꼭지점 | 416 | |
| BLT-OV | 864 | 코드 오버레이 |
| 기타-OV | 1440 | 코드 오버레이 |
| 은행-OV | 1520 | 코드 오버레이 |
| ASSCREW-OV | 2800 | 코드 오버레이 |
| 인사-OV | 4192 | 코드 오버레이 |
| SHIPGRPH-OV | 2112 | 코드 오버레이 |
| 구성-OV | 3072 | 코드 오버레이 |
| TDEPOT-OV | 4800 | 코드 오버레이 |
| 포트메뉴-OV | 3120 | 코드 오버레이 |
| VITA-OV | 3552 | 코드 오버레이 |
| HP-OV | 4832 | 코드 오버레이 |
| LP-OV | 5280 | 코드 오버레이 |
| 보냄-OV | 4784 | 코드 오버레이 |
| TV-OV | 3472 | 코드 오버레이 |
| COMM-OV | 7232 | 코드 오버레이 |
| COMMSPEC-OV | 2864 | 코드 오버레이 |
| SEED-OV | 2400 | 코드 오버레이 |
| 목록 | 720 | 코드 오버레이 |
| MOVE-OV | 3808 | 코드 오버레이 |
| 엔지니어 | 2320 | 코드 오버레이 |
| 의사 | 1280 | 코드 오버레이 |
| 궤도-OV | 6640 | 코드 오버레이 |
| 선장 | 5952 | 코드 오버레이 |
| 과학 | 3952 | 코드 오버레이 |
| 내비게이터 | 880 | 코드 오버레이 |
| 배송버튼 | 1984년 | |
| 지도-OV | 4160 | 코드 오버레이 |
| 하이퍼-OV | 7168 | 코드 오버레이 |
| 분석-OV | 2560 | 코드 오버레이 |
| 발사-OV | 1360 | 코드 오버레이 |
| 플럭스 효과 | 464 | |
| OP-OV | 4400 | 코드 오버레이 |
| 항목-OV | 6016 | 코드 오버레이 |
| LSYSICON | 752 | |
| MSYSICON | 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년 | 코드 오버레이 |
| COMBAT-OV | 6192 | 코드 오버레이 |
| 손상-OV | 2752 | 코드 오버레이 |
| 랜드-OV | 1088 | 코드 오버레이 |
| PSTATS | 64 | 테이블 |
| STP-OV | 1440 | 코드 오버레이 |
원본 Starflight 게임 파일을 starflt1-in 및 starflt2-in 폴더에 넣고 make 실행하세요. starflt1-out 및 starflt2-out 폴더에 콘텐츠를 생성하는 두 개의 실행 파일( disasOV1 및 disasOV2 )을 가져와야 합니다. 생성된 출력은 이 저장소의 일부입니다.