重返得军总部模型系统(未公开的技术)

      技术 2004-12-24 9:35
重返得军总部模型系统(未公开的技术)
重返得军总部的模型系统很好啊~但是别人不公开代码,花了些时间来反汇编,收

获不小,发贴出来和大家分享

1.使用工具:
WinDasm
Visual C++ 6.0

2.QuakeIII引擎基础:
要反汇编一个程序,就要对这个程序的整体框架有所了解,德军使用

QuakeIII的引擎,熟悉QuakeIII引擎当然就是反汇编它的前提,否则可能性是0
QuakeIII引擎绘制1 frame(这个字我用智能ABC打不出来) 可以简单的分为:

游戏逻辑端,前端绘制,后端绘制 3个阶段,前端和后端在引擎内,就是我们运

行游戏的EXE文件,游戏逻辑端就是游戏逻辑喽~,可以是动态连接库

cgamex86.dll,也可以是虚拟机文件cgame.qvm。
游戏在运行的时候,服务器发给引擎玩家的位置,状态等,引擎把这些数据

发给客户段cgame,在绘制1 frame 的开始,引擎把控制权交给cgame,让cgame主

宰整个frame的绘制,cgame告诉引擎以3D空间的某点做为视点来绘制场景,以及

在某个位置,某个方向,绘制3D人物,以及3D人物在当前frame内播放的动画,引

擎通过这些信息来进行实际的绘制操作,这些交互是通过2个接口进行的,vmMain

和dllEntry,前者是引擎访问cgame的,后者是cgame访问引擎的,引擎和游戏逻

辑可以通过这2个接口,访问对方的一系列的函数,vmMain提供的函数主要有

CG_Init( cgame的一些初始化操作,告诉引擎需要载入哪个场景,那些模型,那

些声音文件等 ) CG_DrawActiveFrame( cgame绘制 )。 dllEntry有100多个函数

,如trap_R_LoadWorldMap(载入地图),trap_R_RenderScene(绘制场景),

trap_R_RegisterModel(载入模型),trap_R_AddRefEntityToScene(绘制实体,可

以是模型,精灵,多边形),trap_R_DrawStretchPic(绘制2D的图片,文字

等),trap_S_StartSound(播放声音),下面是简单的程序模型


// 引擎绘制屏幕的操作
void UpdateScreen( void ) {
R_BeginFrame(); // 绘制前的准备工作
.
.
CG_DrawActiveFrame(); // 进入cgame运行
.
.
R_EndFrame(); // 引擎用cgame提供的绘制指令进行实际绘制
}

// cgame初始化
void CG_Init( void ) {
trap_R_LoadWorldMap( mapName ); // 告诉引擎载入地图
.
trap_R_RegisterModel( "player/body.mdl" ); // 告诉引擎载入模型
.
}

// cgame绘制
void CG_DrawActiveFrame( void ) {
snap_t snap;
.
trap_GetSnapshot( &snap, &curSnapNumber ); // 得到snap,玩家位置,

状态等信息
.
for( i=0; i<snap->numClient; i++ ) {
trap_R_AddRefEntityToScene( PlayerModel, snap->player[i].origin

); // 绘制场景内所有玩家
}
// 绘制自己手上拿的枪
trap_R_AddRefEntityToScene( WeaponModel, snap->me );
.
camera.x = snap->me[0];
camera.y = snap->me[1];
camera.z = snap->me[2];
trap_R_RenderScene( &camera ); // 绘制场景
}

// 绘制模型仅仅把模型加入列表,不绘制
void trap_R_AddRefEntityToScene( int model, float origin[3] ) {
refDef_t *entity;

entity = EntityList[ numEntities++ ];
entity->position = origin;
}


引擎的前端绘制,把场景内需要绘制的的surface加入surface列表(一系列使

用同样的材质的三角形),这些surface是可能看到的,肯定看不到的,已经在前

端绘制裁减掉了,模型的surface也经过裁减加入surface列表,加入的模型

surface是没有经过计算的,比如骨骼动画中,顶点和骨骼距阵相乘的操作,不是

在前端算,而是放在后端,在前端绘制完成后要进行一次surface排序操作,把使

用相同材质的surface放到一起绘制(好象是因为OpenGL选择贴图的操作比较慢),

有alpha通道的surface必须放最后绘制,这样才不会出错

void R_RenderScrene( void ) {
numSurface = 0;

R_SetupFrustum(); // 设置OpenGL透视投影
R_AddWorldSurfaces(); // PVS裁减场景,可能看见的surface加入

surface列表
R_AddEntitySurfaces(); // PVS裁减实体,可看见的实体surface加入

surface列表
}

引擎的后端绘制,是在R_EndFrame()这个函数里实现,把surface列表内的三

角形实际的绘制出来,如果是模型的surface,要进行相应的计算,并绘制计算后

的三角型

void R_EndFrame( void ) {

for( i=0; i<numSurface; i++ ) {
switch( surface[i].type ) {
case SURF_TYPE_WORLD: // map surface
RB_DrawWorldSurface( &surface[i] );
break;
case SURF_TYPE_MD3: // 模型surface
RB_CalcAndDrawMd3Surface( &surface[i] );
break;
.
.
.
}
}
}

3.破解方法:
得军虽然不公开引擎代码,但是有游戏逻辑的代码,cgame(客户端)/game(服

务器端),分别对应cgamex86.dll, qagamex86.dll, 他们通过接口和引擎交互,这

样就可以在接口处设短点用汇编跟踪进去了,虽然麻烦些,嘿嘿~,还是值得

德军有3套模型系统,mds(骨骼动化,人物模型的身体部分), mdc(主要是武

器模型),md3(QuakeIII模型系统,mesh动化,用做人物头部,表情就是用他做出

来的),很复杂是不是?德军绘制一个人物,头,身体,武器,使用的模型系统都

不一样,但是,给人的感觉他是一个,QuakeIII模型系统引入定位器(tag)这个概

念,在用开发工具制作模型的时候,用一个特殊的三角形来确定与他相连接模型的

位置,比如头,在绘制完身体后,通过身体的定位器,来定位头的位置,绘制头

,武器同样道理,这样,头和武器虽然用不同的模型系统,但也可以和身体始终

连接在一起。
载入模型的接口是trap_R_RegisterModel(),设短点,跟踪。
进入引擎后,首先判断模型是否被注册过,如果注册就返回模型的handle
根据cgame提供的模型名,读文件,进行比较的初始化操作,主要是数据转换

和载入模型贴图
判断不同的模型系统,进行不同的初始化操作

int R_RegisterModel( char *modelName ) {
int handle;
char *buf;

handle = FindModel( char *modelName );
if( handle ) {
return handle; // 已注册返回
}

buf = FS_LoadFile( modelName );

switch( *((int*)buf) ) {
case IDENT_MDS:
handle = R_LoadModelMds( buf, modelName );
break;
case IDENT_MDC:
handle = R_LoadModelMdc( buf, modelName );
break;
case IDENT_MD3:
handle = _LoadModelMd3( buf, modelName );
break;
default:
Error( "bed model type" );
}
free( buf );

return handle;
}

德军虽然对QuakeIII引擎有些改动,但是整体框架并没有改变,按照这套思

路,很快就能找到装载MDS模型的代码,写出MDS文件结构每个成员的类型,不能

确定名称,先用Unknow代替

int R_LoadModelMds( char *data, char *modelName ) {
mdsHeader_t *mds;
mdsSurface_t *surface;
mdsBoneDef_t *bone;

// 处理文件头
mds = (mdsHeader_t *)data;
mds->version = LittleLong( mds->version );
mds->numSurfaces = LittleLong( mds->numSurfaces );
mds->ofsSurfaces = LittleLong( mds->ofsSurfaces );
mds->numBones = LittleLong( mds->numBones );
.
.
.

// 处理surface
surface = (mdsSurface_t *)((byte *)mds + mds->ofsSurfaces);
for( i=0; i<mds->numSurfaces; i++ ) {
surface->numVertices = LittleLong( surface->numVertices );
surface->ofsVertices = LittleLong( surface->ofsVertices );
surface->numTriangles = LittleLong( surface->numTriangles );
surface->ofsTriangles = LittleLong( surface->ofsTriangles );
surface->numBoneReferences = LittleLong( surface-

>numBoneReferences );

surface->TextureHandle = R_RegisterTexture( surface-

>TextureName ); // 载入贴图文件

// next surface
surface = (mdsSurface_t *)((byte *)surface + surface->ofsEnd);
}

// 处理bone
bone = (mdsBoneDef_t *)((byte *)mds + mds->ofsBones);
for( i=0; i<mds->numBones; i++ ) {
...
...
...
}
}

在反汇编代码时有一个技巧,很多函数有错误处理,比如printf(

"R_LoadModelMds: %i Vertices > 1200\n", surface->numVertices ); 在用

winDasm时,涉及到的字符串会显示出来,根据错误字符传,至少可以得到2个信

息,可以确定这个函数的函数名""R_LoadModelMds: ", surface结构定义内

numVertices的位置



刚写了开头该睡觉了,欲知后事,以后有时间再说,将mds文件结构提供给大

家,希望能有所帮助,如果对此感兴趣,请关注我的个人主页


作者: 小陆
QQ: 5405847
Email: g3d@163.net
个人主页:http://www.190hz.com/
标签集:TAGS:
回复Comments() 点击Count()

回复Comments

{commentauthor}
{commentauthor}
{commenttime}
{commentnum}
{commentcontent}
作者:
{commentrecontent}