2013年7月23日,在第二现实演示(1993年)的源代码公布。像许多人一样,我很想看看这个演示的内部,这些年来给我们带来了很多启发。我本来希望从汇编程序中看到整体混乱,但令我惊讶的是,我发现了一个复杂的体系结构,该体系结构优雅地结合了多种语言。我以前从未见过这样的代码,可以完美地代表开发演示的两个基本方面:像往常一样,我为自己的笔记写了一篇文章:我希望这可以节省一个人几个小时的时间,也许可以激发其他人阅读更多源代码并成为更有经验的工程师。第1部分:简介
演示版
在开始编写代码之前,我将提供一个链接,以捕获高清视频中的传奇演示(Michael Hut)。如今,这是在没有图形故障的情况下完全评估该演示的唯一方法(即使DOSBox也无法正确启动它)。首次接触代码
源代码发布在GitHub上。只需输入一个命令git
:git clone git@github.com:mtuomi/SecondReality.git
最初,内容令人困惑:32个文件夹和一个U2.EXE
未在DosBox中启动的神秘文件夹。该演示的工作名称为“虚幻2”(第一个“虚幻”是上一个Future Crew演示,于1992年为第一届大会发布)。而且仅在开发过程中,名称才更改为“第二现实”。这说明了文件名“ U2.EXE”,但不是文件不起作用的原因...如果运行CLOC,我们将获得有趣的指标: -------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Assembly 99 3029 1947 33350
C++ 121 1977 915 24551
C/C++ Header 8 86 240 654
make 17 159 25 294
DOS Batch 71 3 1 253
-------------------------------------------------------------------------------
SUM: 316 5254 3128 59102
-------------------------------------------------------------------------------
- «» 50% .
- Doom.
- 它有十七个makefile。为什么不只是一个?
启动演示
很难弄清楚,但是可以在DosBox中启动发布的演示:您需要重命名U2.EXE
并在正确的位置运行它。当我了解了代码的内部工作原理后,它开始显得非常合乎逻辑: CD主
移动U2.EXE数据/ SECOND.EXE
CD数据
第二个EXE
瞧!建筑
在90年代,演示程序大多分布在软盘上。解压缩后,有必要安装两个大文件:SECOND.EXE
和REALITY.FC
: 。<目录> 08/08/2013 16:40
.. <DIR> 2013/08/01 16:40
FCINFO10 TXT 48,462 04-10-1993 11:48
FILE_ID DIZ 378 04-10-1993 11:30
README 1ST 4.222 1993年10月10日12:59
现实FC 992.188 07-10-1993 12:59
第二个EXE 1,451,093 1993-07-10 13:35
5个文件2.496,343字节。
2 Dir(s)262,111,744字节免费。
根据我在游戏开发中的经验,我始终希望整个图片看起来像这样:SECOND.EXE
:在可执行文件中具有所有效果的引擎。REALITY.FC
:la WAD
Doom游戏中专有/加密格式的资产(音乐,声音效果,图像)。
但是看完之后,MAIN/PACK.C
我发现自己弄错了:Second Reality引擎只是一个加载程序和一个中断服务器(称为DIS)。每个场景(也称为“ PART”)演示都是功能齐全的DOS可执行文件。每个部分均由Loader加载程序加载,然后一个接一个地启动。零件最后以加密形式存储SECOND.EXE
:REALITY.FC
包含两个在演示过程中播放的音乐作品(用于填充混淆,并在开始时添加了填充和标记)。SECOND.EXE
包含引导加载程序和演示中断服务器(DIS)。- 最后
SECOND.EXE
,演示的32个部分(PART)被添加为可执行的DOS文件(已加密)。
这样的体系结构具有许多优点:- : PART ,
_start
(450 ). - EXE
SECOND.EXE
-. - : Loader DIS 20 . DOS .
- : PART PART .
- / : , PART ( ), : EXE , .
- 任何语言都可以用于PART编程:在代码中,我们可以找到C,Assembly ...和Pascal。
推荐读物
理解Second Reality源代码的三个支柱是VGA,汇编器和PC体系结构(PIC和PIT编程)。这里有一些非常有用的链接:第2部分:第二现实引擎
如第1部分所述,“第二现实”的基础包括:- 引导加载程序作为DOS可执行文件。
- 内存管理器(简单的堆栈池)
- 演示中断服务器(DIS)。
在这一部分中,我将向想要阅读引擎和引导加载程序的程序员提供建议(在下一部分中将讨论DIS)。引擎代码
引擎代码是100%ASM,但是编写得非常好,文档也相当齐全:用伪代码可以这样写: exemus db 'STARTMUS.EXE',0
exe0 db 'START.EXE',0
...
exe23 db 'ENDSCRL.EXE',0
start:
cli ; Disable all interrupts
mov ah,4ah ; Deallocate all memory
call checkall ; Check for 570,000 bytes of mem, 386 CPU and VGA
call file_getexepath
call dis_setint ; Install Demo Interrupt Server on Interrupt 0fch
call file_initpacking ; Check exe signature (no tempering) !
call file_setint ; Replace DOS routines (only OPENFILE, SEEK and READ) on Interrupt 021h
call flushkbd ; Flush the keyboard buffer
call checkcmdline ; check/process commandline
;======== Here we go! ========
call vmode_init ; Init VGA (not necessarly Mode13h or ModeX), each PARTs had its own resolution
mov si,OFFSET exe0
call executehigh ; loaded to high in memory. Used for loading music loaders and stuff.
call _zinit ; Start music
call restartmus
mov si,OFFSET exe1 ;Parameter for partexecute: Offset to exec name
call partexecute
; Execute all parts until exe23
call fademusic
;======== And Done! (or fatal exit) ========
fatalexit:
mov cs:notextmode,0
call vmode_deinit
所有步骤都非常容易阅读:- 将DIS中断服务器设置为interrupt
0fch
。 - 在中断时替换DOS系统调用
021h
(有关更多详细信息,请参见“开发和生产模式”部分)。 - 通过EMS存储器将音乐下载到声卡。
- 运行音乐。
- 执行演示的每个部分。
- 做完了!
详细程序execute
: execute:
cld
call openfile ; Open the DOS executable for this PART
call loadexe ; loads the specified exe file to memory, does relocations and creates psp
call closefile
call runexe ;runs the exe file loaded previously with loadexe.
; returns after exe executed, and frees the memory
; it uses.
内存管理器
有很多传说说,Second Reality通过MMU使用复杂的内存管理器;引擎中没有任何痕迹。内存管理实际上是转移到DOS:发动机启动后通过释放所有的RAM,然后分发它的要求。唯一棘手的技巧是从堆末尾分配RAM的能力:当请求太多RAM时,使用malloc DOS返回值完成此操作。第3部分:DIS
演示中断服务器(DIS)为每个PART提供了广泛的服务:从不同PART之间的数据交换到与VGA同步。DIS服务
在运行时,PART,DIS服务器为其提供服务。可以在找到功能列表DIS/DIS.H
。最重要的服务:- 在不同的PART(
dis_msgarea
)之间交换:DIS提供了三个每个64字节的缓冲区,以便PART可以从前一个PART的加载程序中接收参数。 - Emulation Copper(
dis_setcopper
):Amiga Copper模拟器,允许您执行通过VGA状态切换的操作。 - Dev / Prod(
dis_indemo
)模式:允许PART知道它正在DEV模式下运行(这意味着它必须初始化视频)或以PROD模式从引导加载程序启动。 - VGA帧数(
_dis_getmframe
) - 等待VGA逆止器(
dis_waitb
)。
演示中断服务器代码
DIS的源代码也是100%ASM的,并且评论得相当好:怎么运行的
DIS设置为程序化int的中断处理程序0fch
。很棒的是,它可以SECOND.EXE
在演示运行时在内部运行,也可以在开发模式下作为驻留程序(TSR)运行。这种灵活性使您可以在开发过程中分别测试不同的PART演示: //让我们假装我们是FC开发人员,想直接启动STAR部分。
C:\> CD DDSTARS
C:\ DDSTARS> K
错误:未加载DIS。
//糟糕,PART在int 0fch处找不到DIS。
C:\ DDSTARS> CD .. \ DIS
C:\ DIS> DIS
演示服务器(DIS)V1.0版权所有(C)1993 The Future Crew
测试版-编译:93年7月26日03:15:53
已安装(int fc)。
注意:此DIS服务器不支持铜线或音乐同步!
// DIS已安装,让我们再试一次。
C:\ DIS> CD ../DDSTARS
C:\ DDSTARS> K
瞧!铜
“铜”是Amiga演示开发人员喜欢的协处理器。它是原始芯片组的一部分,可让您执行与视频设备同步的可编程命令流。PC上没有这样的协处理器,因此Future Crew必须编写在DIS中运行的Copper模拟器。FC团队使用8254-PIT和8259-PIC PC硬件芯片组来模拟Copper。她创建了一个与VGA频率同步的系统,能够在垂直后向光束的三个位置启动程序:- 放置0:打开显示器后(大约在扫描线25上)
- 位置1:反向光束扫描后立即发生(有可能避免)
- 位置2:位于扫描光束的反向光束中
可以阅读此操作的方法MAIN/COPPER.ASM
(请参见下图):- 8254芯片的计时器配置为以所需频率触发IRQ0。
- 8h中断处理程序(在收到IRQ0之后由8259 PIC调用)在此处被替换为过程
intti8
。
注意:DIS帧计数服务实际上是由铜缆模拟器提供的。第4部分:开发和生产模式
在阅读《第二现实》的源代码时,您最震惊的是团队对从DEV到PROD的无缝转换给予了多大的关注。开发模式
在开发模式下,演示的每个组件都是一个单独的可执行文件。- DIS被加载到驻留的TSR中并通过中断进行访问
0cfh
。 - 引导加载程序导致DOS中断
21h
打开,读取,搜索和关闭文件。
该DEV配置具有以下优点:- 每个编码人员和美术人员都可以处理可执行文件并分别对其进行测试,而不会影响团队的其他成员。
- 随时可以使用一个较小的演示版
SECOND.EXE
(无需在末尾添加所有EXE)来测试完整的演示版。每个PART的可执行文件都是使用DOS中断021h
从单独的文件加载的。
生产(演示模式)
在生产模式下,小的SECOND.EXE
(包含引导加载程序),DIS和作为独立EXE的演示部分被合并为一个厚文件SECOND.EXE
。- 通过中断仍然可以访问DIS
0fch
。 - DOS 21h中断API由其自己的Future Crew例程进行了修补,该例程从大文件的末尾打开文件
SECOND.EXE
。
这种PROD配置在加载时间和防止逆向工程方面具有优势……但是最重要的是,从编程或加载PART的角度来看,从DEV切换到PROD时,NOTHING会发生变化。第5部分:单独的部分
每个“第二现实”视觉效果都是一个功能齐全的DOS可执行文件。它们被称为PART以及全部23。分开的部分
可以在引擎源代码U2.ASM中找到所有PART / EXE的列表。这是所有23个部分的简短说明(带有源代码的位置,尽管名称可能非常混乱):似乎每个开发人员都有自己的专长,可以分一部分共享。这在滚动,飞船和爆炸的第一个场景中特别明显(Alkutekstit)。尽管这看起来像是一种连续的效果,但实际上它是由三个不同的人编写的三个可执行文件:资产
.LBM
使用90年代极为流行的位图编辑器Deluxe Paint生成
图像资源()。有趣的是,它们被转换为字节数组并在PART中进行编译。因此,该exe文件还会下载所有资产。另外,这使逆向工程复杂化。最酷的资产包括最新3D场景中著名的CITY和SHIP:内部单元PART
由于它们都被编译成DOS可执行文件,因此在PART中可以使用任何语言:至于内存的使用,我在Wikipedia和其他网站上读到了很多有关MMU的信息……但是实际上,每个部分都可以使用任何东西,因为执行后它完全从内存中卸载了。使用VGA时,每个部分都使用自己的技巧,并以其分辨率工作。所有这些都不使用模式13h和ModeX,而是使用具有自己分辨率的修改模式13h模式。SCRIPT文件经常提到320x200和320x400。不幸的是,在分析PART时,阅读源代码成为一项艰巨的任务:代码和注释的质量急剧下降。可能是因为匆忙或因为每个PART都在自己的开发人员上工作(即,没有“真正的”需求注释或代码可理解性)而发生这种情况,但是结果却完全令人困惑:复杂的算法不仅难以理解的变量连名字(a
,b
,co[]
...)。如果开发人员在发行说明中给我们提示,则代码将更具可读性。结果,我没有花太多时间研究每个部分。例外是负责U2A.EXE和U2E.EXE的3D引擎。3D引擎第二现实
无论如何,我决定详细研究3D引擎,该引擎分为两个部分使用:U2A.EXE
和和U2E.EXE
。源代码是带有汇编程序优化过程的C(尤其是Gouro填充和阴影):这些组件的体系结构非常出色:该库VISU
执行所有复杂的任务,例如,加载资产:3DS对象,材料和流(摄像机和船舶运动)。引擎对需要绘制的对象进行分类,并使用艺术家的算法对其进行渲染。这会导致大量重绘,但是由于VGA锁存器允许您同时记录4个像素,所以还不错。一个有趣的事实:引擎以“老式”方式执行变换:它不使用常见的齐次4x4矩阵,而是使用3 * 3旋转矩阵和位移矢量。这是伪代码的摘要: main(){
scenem=readfile(tmpname);
scene0=readfile(tmpname);
for(f=-1,c=1;c<d;c++){
sprintf(tmpname,"%s.%03i",scene,e);
co[c].o=vis_loadobject(tmpname);
}
vid_init(1);
vid_setpal(cp);
for(;;){
vid_switch();
_asm mov bx,1 _asm int 0fch
vid_clear();
for(;;){}
vid_cameraangle(fov);
for(a=1;ac<conum;a++) if(co[a].on)
calc_applyrmatrix(o->r,&cam);
for(a=0;ac<ordernum;a++)
for(b=a-1;b>=0 && dis>co[order[b]].dist;b--)
for(a=0;ac<ordernum;a++)
vis_drawobject(o);
}
}
return(0);
}
现代系统的端口
本文发布后,许多开发人员开始将Second Reality移植到现代系统中。Claudio Matsuoka着手创建sr-port,这是Linux和OpenGL ES 2.0的C端口,到目前为止看起来还不错。Nick Kovacs在PART PLZ方面做得很好,将其移植到C(现在它是sr-port源代码的一部分)以及javascript: