forked from FWGS/Paranoia2
1212 lines
29 KiB
C++
1212 lines
29 KiB
C++
/*
|
|
bspfile.cpp - load, convert & write bsp
|
|
Copyright (C) 2016 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "bsp31migrate.h"
|
|
#include "filesystem.h"
|
|
|
|
#define VALVE_FORMAT 220
|
|
|
|
map_type g_maptype = MAP_UNKNOWN;
|
|
sub_type g_subtype = MAP_NORMAL;
|
|
|
|
//=============================================================================
|
|
bool g_found_extradata = false;
|
|
|
|
int g_nummodels;
|
|
dmodel_t g_dmodels[MAX_MAP_MODELS];
|
|
|
|
int g_visdatasize;
|
|
byte g_dvisdata[MAX_MAP_VISIBILITY];
|
|
|
|
int g_lightdatasize;
|
|
byte *g_dlightdata;
|
|
|
|
int g_deluxdatasize;
|
|
byte *g_ddeluxdata;
|
|
|
|
int g_shadowdatasize;
|
|
byte *g_dshadowdata;
|
|
|
|
int g_texdatasize;
|
|
byte* g_dtexdata; // (dmiptexlump_t)
|
|
|
|
int g_entdatasize;
|
|
char g_dentdata[MAX_MAP_ENTSTRING];
|
|
|
|
int g_numleafs;
|
|
dleaf_t g_dleafs[MAX_MAP_LEAFS];
|
|
|
|
int g_numplanes;
|
|
dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES];
|
|
|
|
int g_numvertexes;
|
|
dvertex_t g_dvertexes[MAX_MAP_VERTS];
|
|
|
|
int g_numnodes;
|
|
dnode_t g_dnodes[MAX_MAP_NODES];
|
|
|
|
int g_numtexinfo;
|
|
dtexinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO];
|
|
|
|
int g_numfaces;
|
|
dface_t g_dfaces[MAX_MAP_FACES];
|
|
|
|
int g_numclipnodes;
|
|
dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES];
|
|
dclipnode32_t g_dclipnodes32[MAX_MAP_CLIPNODES32];
|
|
|
|
int g_numedges;
|
|
dedge_t g_dedges[MAX_MAP_EDGES];
|
|
|
|
int g_nummarksurfaces;
|
|
dmarkface_t g_dmarksurfaces[MAX_MAP_MARKSURFACES];
|
|
|
|
int g_numsurfedges;
|
|
int g_dsurfedges[MAX_MAP_SURFEDGES];
|
|
|
|
int g_numfaceinfo;
|
|
dfaceinfo_t g_dfaceinfo[MAX_MAP_FACEINFO];
|
|
|
|
int g_numnormals;
|
|
dnormal_t g_dnormals[MAX_MAP_NORMS];
|
|
|
|
int g_numcubemaps;
|
|
dcubemap_t g_dcubemaps[MAX_MAP_CUBEMAPS];
|
|
|
|
int g_numleaflights;
|
|
dleafsample_t g_dleaflights[MAX_MAP_LEAFLIGHTS];
|
|
|
|
int g_numworldlights;
|
|
dworldlight_t g_dworldlights[MAX_MAP_WORLDLIGHTS];
|
|
|
|
int g_vlightdatasize;
|
|
byte *g_dvlightdata; // (dvlightlump_t)
|
|
|
|
int g_numfacedata;
|
|
dfacedata_t g_dfacedata[MAX_MAP_FACES];
|
|
|
|
int g_numTNbasis;
|
|
dTNbasis_t g_dTNbasis[MAX_MAP_TNBASIS];
|
|
|
|
int g_bumpdatasize;
|
|
byte *g_dbumpdata;
|
|
|
|
int g_numentities;
|
|
entity_t g_entities[MAX_MAP_ENTITIES];
|
|
|
|
char g_mapinfo[8192];
|
|
char g_mapname[1024];
|
|
int g_mapversion;
|
|
byte *mod_base;
|
|
|
|
void PrintMapInfo( void )
|
|
{
|
|
size_t infolen = Q_strlen( g_mapinfo );
|
|
char *ptr = &g_mapinfo[infolen-2];
|
|
|
|
if( *ptr == ',' ) *ptr = '.';
|
|
|
|
Msg( "Map name: %s", g_mapname );
|
|
Msg( "\nMap type: " );
|
|
|
|
switch( g_maptype )
|
|
{
|
|
case MAP_XASH31:
|
|
Msg( "^2XashXT BSP31^7" );
|
|
break;
|
|
default:
|
|
COM_FatalError( "%s unknown map format\n", g_mapname );
|
|
break;
|
|
}
|
|
|
|
if( g_subtype != MAP_NORMAL )
|
|
Msg( "\nSub type: " );
|
|
else Msg( "\n" );
|
|
|
|
switch( g_subtype )
|
|
{
|
|
case MAP_HLFX06:
|
|
Msg( "^4HLFX 0.6^7\n" );
|
|
break;
|
|
case MAP_XASHXT_OLD:
|
|
Msg( "^4XashXT 0.5^7\n" );
|
|
break;
|
|
case MAP_P2SAVIOR:
|
|
Msg( "^4Paranoia2: Savior^7\n" );
|
|
break;
|
|
case MAP_DEPRECATED:
|
|
Msg( "^1intermediate deprecated version^7\n" );
|
|
break;
|
|
case MAP_XASH3D_EXT:
|
|
Msg( "^4Xash3D extended^7\n" );
|
|
break;
|
|
}
|
|
|
|
if( g_mapinfo[0] ) Msg( "Map info: %s", g_mapinfo );
|
|
|
|
Msg( "\n\n" );
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
int CopyLump( int lump, void *dest, int size, dheader31_t *header )
|
|
{
|
|
int length = header->lumps[lump].filelen;
|
|
int ofs = header->lumps[lump].fileofs;
|
|
|
|
if( length % size )
|
|
COM_FatalError( "LoadBSPFile: odd lump size\n" );
|
|
|
|
// alloc matched size
|
|
if( lump == LUMP_TEXTURES )
|
|
dest = g_dtexdata = (byte *)Mem_Alloc( length );
|
|
if( lump == LUMP_LIGHTING )
|
|
dest = g_dlightdata = (byte *)Mem_Alloc( length );
|
|
memcpy( dest, (byte *)header + ofs, length );
|
|
|
|
return length / size;
|
|
}
|
|
|
|
int CopyLump( int lump, void *dest, int size, dheader31_hlfx_t *header )
|
|
{
|
|
int length = header->lumps[lump].filelen;
|
|
int ofs = header->lumps[lump].fileofs;
|
|
|
|
if( length % size )
|
|
COM_FatalError( "LoadBSPFile: odd lump size\n" );
|
|
|
|
// alloc matched size
|
|
if( lump == LUMP_TEXTURES )
|
|
dest = g_dtexdata = (byte *)Mem_Alloc( length );
|
|
if( lump == LUMP_LIGHTING )
|
|
dest = g_dlightdata = (byte *)Mem_Alloc( length );
|
|
if( lump == HLFX31_LUMP_BUMP )
|
|
dest = g_dbumpdata = (byte *)Mem_Alloc( length );
|
|
memcpy( dest, (byte *)header + ofs, length );
|
|
|
|
return length / size;
|
|
}
|
|
|
|
static int CopyExtraLump( int lump, void *dest, int size, const dheader31_t *header )
|
|
{
|
|
dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)header + sizeof( dheader31_t ));
|
|
|
|
int length = extrahdr->lumps[lump].filelen;
|
|
int ofs = extrahdr->lumps[lump].fileofs;
|
|
|
|
if( length % size )
|
|
COM_FatalError( "LoadBSPFile: odd lump size\n" );
|
|
|
|
if( lump == LUMP_LIGHTVECS )
|
|
dest = g_ddeluxdata = (byte *)Mem_Alloc( length );
|
|
if( lump == LUMP_SHADOWMAP )
|
|
dest = g_dshadowdata = (byte *)Mem_Alloc( length );
|
|
if( lump == LUMP_VERTEX_LIGHT )
|
|
dest = g_dvlightdata = (byte *)Mem_Alloc( length );
|
|
memcpy( dest, (byte *)header + ofs, length );
|
|
|
|
return length / size;
|
|
}
|
|
|
|
//=============================================================================
|
|
static void AddLump( int lumpnum, void *data, size_t len, dheader30_hlfx_t *header, long handle )
|
|
{
|
|
dlump_t *lump = &header->lumps[lumpnum];
|
|
|
|
lump->fileofs = tell( handle );
|
|
lump->filelen = len;
|
|
|
|
SafeWrite( handle, data, (len + 3) & ~3 );
|
|
}
|
|
|
|
static void AddLump( int lumpnum, void *data, size_t len, dheader_t *header, long handle )
|
|
{
|
|
dlump_t *lump = &header->lumps[lumpnum];
|
|
|
|
lump->fileofs = tell( handle );
|
|
lump->filelen = len;
|
|
|
|
SafeWrite( handle, data, (len + 3) & ~3 );
|
|
}
|
|
|
|
static void AddExtraLump( int lumpnum, void *data, int len, dextrahdr_t *header, long handle )
|
|
{
|
|
dlump_t* lump = &header->lumps[lumpnum];
|
|
|
|
lump->fileofs = tell( handle );
|
|
lump->filelen = len;
|
|
|
|
SafeWrite( handle, data, (len + 3) & ~3 );
|
|
}
|
|
|
|
void AddLumpClipnodes( int lumpnum, dheader_t *header, long handle )
|
|
{
|
|
dlump_t *lump = &header->lumps[lumpnum];
|
|
lump->fileofs = tell( handle );
|
|
|
|
if( g_numclipnodes < MAX_MAP_CLIPNODES )
|
|
{
|
|
// copy clipnodes into 16-bit array
|
|
for( int i = 0; i < g_numclipnodes; i++ )
|
|
{
|
|
g_dclipnodes[i].children[0] = (short)g_dclipnodes32[i].children[0];
|
|
g_dclipnodes[i].children[1] = (short)g_dclipnodes32[i].children[1];
|
|
g_dclipnodes[i].planenum = g_dclipnodes32[i].planenum;
|
|
}
|
|
|
|
lump->filelen = g_numclipnodes * sizeof( dclipnode_t );
|
|
SafeWrite( handle, g_dclipnodes, (lump->filelen + 3) & ~3 );
|
|
}
|
|
else
|
|
{
|
|
// copy clipnodes into 32-bit array
|
|
lump->filelen = g_numclipnodes * sizeof( dclipnode32_t );
|
|
SafeWrite( handle, g_dclipnodes32, (lump->filelen + 3) & ~3 );
|
|
}
|
|
}
|
|
|
|
//============================================
|
|
void StripTrailing( char *e )
|
|
{
|
|
char *s;
|
|
|
|
s = e + Q_strlen( e ) - 1;
|
|
|
|
while( s >= e && *s <= 32 )
|
|
{
|
|
*s = 0;
|
|
s--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
InsertLinkBefore
|
|
================
|
|
*/
|
|
void InsertLinkBefore( epair_t *e, entity_t *mapent )
|
|
{
|
|
e->next = NULL;
|
|
|
|
if( mapent->epairs != NULL )
|
|
{
|
|
e->prev = mapent->tail;
|
|
mapent->tail->next = e;
|
|
mapent->tail = e;
|
|
}
|
|
else
|
|
{
|
|
mapent->epairs = mapent->tail = e;
|
|
e->prev = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
UnlinkEpair
|
|
================
|
|
*/
|
|
void UnlinkEpair( epair_t *e, entity_t *mapent )
|
|
{
|
|
if( e->prev ) e->prev->next = e->next;
|
|
else mapent->epairs = e->next;
|
|
|
|
if( e->next ) e->next->prev = e->prev;
|
|
else mapent->tail = e->prev;
|
|
|
|
e->prev = e->next = e;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
ParseEpair
|
|
=================
|
|
*/
|
|
epair_t *ParseEpair( void )
|
|
{
|
|
const char *ext;
|
|
epair_t *e;
|
|
|
|
e = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR );
|
|
|
|
if( Q_strlen( token ) >= ( MAX_KEY - 1 ))
|
|
COM_FatalError( "ParseEpair: 'key' token too long\n" );
|
|
|
|
e->key = copystring( token );
|
|
GetToken( false );
|
|
|
|
if( Q_strlen( token ) >= ( MAX_VALUE - 1 ))
|
|
COM_FatalError( "ParseEpair: 'value' token too long\n" );
|
|
ext = COM_FileExtension( token );
|
|
|
|
if( !Q_stricmp( ext, "wav" ) || !Q_stricmp( ext, "mdl" ) || !Q_stricmp( ext, "bsp" ))
|
|
COM_FixSlashes( token );
|
|
e->value = copystring( token );
|
|
|
|
// strip trailing spaces
|
|
StripTrailing( e->key );
|
|
StripTrailing( e->value );
|
|
|
|
return e;
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseEntity
|
|
================
|
|
*/
|
|
bool ParseEntity( void )
|
|
{
|
|
entity_t *mapent;
|
|
epair_t *e;
|
|
|
|
if( !GetToken( true ))
|
|
return false;
|
|
|
|
if( Q_strcmp( token, "{" ))
|
|
{
|
|
if( g_numentities == 0 )
|
|
COM_FatalError( "ParseEntity: '{' not found\n" );
|
|
else return false; // probably entity string is broken at the end
|
|
}
|
|
|
|
if( g_numentities == MAX_MAP_ENTITIES )
|
|
COM_FatalError( "MAX_MAP_ENTITIES limit exceeded\n" );
|
|
|
|
mapent = &g_entities[g_numentities];
|
|
g_numentities++;
|
|
|
|
do
|
|
{
|
|
if( !GetToken( true ))
|
|
COM_FatalError( "ParseEntity: EOF without closing brace\n" );
|
|
|
|
if( !Q_strcmp( token, "}" ))
|
|
break;
|
|
|
|
e = ParseEpair();
|
|
InsertLinkBefore( e, mapent );
|
|
|
|
} while( 1 );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
ParseEntities
|
|
|
|
Parses the dentdata string into entities
|
|
================
|
|
*/
|
|
void ParseEntities( void )
|
|
{
|
|
ParseFromMemory( g_dentdata, g_entdatasize );
|
|
memset( &g_entities, 0, sizeof( g_entities ));
|
|
g_numentities = 0;
|
|
|
|
while( ParseEntity( ));
|
|
}
|
|
|
|
/*
|
|
================
|
|
FreeEntities
|
|
|
|
release all the entity data
|
|
================
|
|
*/
|
|
void FreeEntity( entity_t *mapent )
|
|
{
|
|
epair_t *ep, *next;
|
|
|
|
for( ep = mapent->epairs; ep != NULL; ep = next )
|
|
{
|
|
next = ep->next;
|
|
freestring( ep->key );
|
|
freestring( ep->value );
|
|
Mem_Free( ep, C_EPAIR );
|
|
}
|
|
|
|
if( mapent->cache ) Mem_Free( mapent->cache );
|
|
mapent->epairs = mapent->tail = NULL;
|
|
mapent->cache = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
FreeEntities
|
|
|
|
release all the dynamically allocated data
|
|
================
|
|
*/
|
|
void FreeEntities( void )
|
|
{
|
|
for( int i = 0; i < g_numentities; i++ )
|
|
{
|
|
FreeEntity( &g_entities[i] );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
UnparseEntities
|
|
|
|
Generates the dentdata string from all the entities
|
|
================
|
|
*/
|
|
void UnparseEntities( void )
|
|
{
|
|
char *buf, *end;
|
|
char line[2048];
|
|
char key[1024], value[1024];
|
|
epair_t *ep;
|
|
|
|
buf = g_dentdata;
|
|
end = buf;
|
|
*end = 0;
|
|
|
|
for( int i = 0; i < g_numentities; i++ )
|
|
{
|
|
entity_t *ent = &g_entities[i];
|
|
if( !ent->epairs ) continue; // ent got removed
|
|
|
|
Q_strcat( end, "{\n" );
|
|
end += 2;
|
|
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
Q_strncpy( key, ep->key, sizeof( key ));
|
|
StripTrailing( key );
|
|
Q_strncpy( value, ep->value, sizeof( value ));
|
|
StripTrailing( value );
|
|
|
|
Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value );
|
|
Q_strcat( end, line );
|
|
end += Q_strlen( line );
|
|
}
|
|
|
|
Q_strcat( end, "}\n" );
|
|
end += 2;
|
|
|
|
if( end > ( buf + MAX_MAP_ENTSTRING ))
|
|
COM_FatalError( "Entity text too long\n" );
|
|
}
|
|
|
|
g_entdatasize = end - buf + 1; // write term
|
|
}
|
|
|
|
void RemoveKey( entity_t *ent, const char *key )
|
|
{
|
|
epair_t *ep;
|
|
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( !Q_strcmp( ep->key, key ))
|
|
{
|
|
freestring( ep->key );
|
|
freestring( ep->value );
|
|
UnlinkEpair( ep, ent );
|
|
Mem_Free( ep, C_EPAIR );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetKeyValue( entity_t *ent, const char *key, const char *value )
|
|
{
|
|
epair_t *ep;
|
|
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( !Q_strcmp( ep->key, key ))
|
|
{
|
|
freestring( ep->value );
|
|
ep->value = copystring( value );
|
|
return;
|
|
}
|
|
}
|
|
|
|
ep = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR );
|
|
ep->key = copystring( key );
|
|
ep->value = copystring( value );
|
|
InsertLinkBefore( ep, ent );
|
|
}
|
|
|
|
char *ValueForKey( entity_t *ent, const char *key, bool check )
|
|
{
|
|
epair_t *ep;
|
|
|
|
for( ep = ent->epairs; ep; ep = ep->next )
|
|
{
|
|
if( !Q_strcmp( ep->key, key ))
|
|
return ep->value;
|
|
}
|
|
|
|
if( check )
|
|
return NULL;
|
|
return "";
|
|
}
|
|
|
|
vec_t FloatForKey( entity_t *ent, const char *key )
|
|
{
|
|
return atof( ValueForKey( ent, key ));
|
|
}
|
|
|
|
int IntForKey( entity_t *ent, const char *key )
|
|
{
|
|
return atoi( ValueForKey( ent, key ));
|
|
}
|
|
|
|
bool BoolForKey( entity_t *ent, const char *key )
|
|
{
|
|
if( atoi( ValueForKey( ent, key )))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int GetVectorForKey( entity_t *ent, const char *key, vec3_t vec )
|
|
{
|
|
double v1, v2, v3;
|
|
int count;
|
|
char *k;
|
|
|
|
k = ValueForKey( ent, key );
|
|
|
|
// scanf into doubles, then assign, so it is vec_t size independent
|
|
v1 = v2 = v3 = 0;
|
|
|
|
count = sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 );
|
|
|
|
vec[0] = v1;
|
|
vec[1] = v2;
|
|
vec[2] = v3;
|
|
|
|
return count;
|
|
}
|
|
|
|
void ReleaseBSPFile( void )
|
|
{
|
|
FreeEntities();
|
|
|
|
g_maptype = MAP_UNKNOWN;
|
|
g_subtype = MAP_NORMAL;
|
|
|
|
g_nummodels = 0;
|
|
memset( g_dmodels, 0, sizeof( g_dmodels ));
|
|
|
|
g_visdatasize = 0;
|
|
memset( g_dvisdata, 0, sizeof( g_dvisdata ));
|
|
|
|
g_lightdatasize = 0;
|
|
Mem_Free( g_dlightdata );
|
|
g_dlightdata = NULL;
|
|
|
|
g_deluxdatasize = 0;
|
|
Mem_Free( g_ddeluxdata );
|
|
g_ddeluxdata = NULL;
|
|
|
|
g_texdatasize = 0;
|
|
Mem_Free( g_dtexdata );
|
|
g_dtexdata = NULL;
|
|
|
|
g_shadowdatasize = 0;
|
|
Mem_Free( g_dshadowdata );
|
|
g_dshadowdata = NULL;
|
|
|
|
g_vlightdatasize = 0;
|
|
Mem_Free( g_dvlightdata );
|
|
g_dvlightdata = NULL;
|
|
|
|
g_bumpdatasize = 0;
|
|
Mem_Free( g_dbumpdata );
|
|
g_dbumpdata = NULL;
|
|
|
|
g_entdatasize = 0;
|
|
memset( g_dentdata, 0, sizeof( g_dentdata ));
|
|
|
|
g_numleafs = 0;
|
|
memset( g_dleafs, 0, sizeof( g_dleafs ));
|
|
|
|
g_numplanes = 0;
|
|
memset( g_dplanes, 0, sizeof( g_dplanes ));
|
|
|
|
g_numvertexes = 0;
|
|
memset( g_dvertexes, 0, sizeof( g_dvertexes ));
|
|
|
|
g_numnormals = 0;
|
|
memset( g_dnormals, 0, sizeof( g_dnormals ));
|
|
|
|
g_numnodes = 0;
|
|
memset( g_dnodes, 0, sizeof( g_dnodes ));
|
|
|
|
g_numtexinfo = 0;
|
|
memset( g_texinfo, 0, sizeof( g_texinfo ));
|
|
|
|
g_numfaces = 0;
|
|
memset( g_dfaces, 0, sizeof( g_dfaces ));
|
|
|
|
g_numclipnodes = 0;
|
|
memset( g_dclipnodes, 0, sizeof( g_dclipnodes ));
|
|
memset( g_dclipnodes32, 0, sizeof( g_dclipnodes32 ));
|
|
|
|
g_numedges = 0;
|
|
memset( g_dedges, 0, sizeof( g_dedges ));
|
|
|
|
g_nummarksurfaces = 0;
|
|
memset( g_dmarksurfaces, 0, sizeof( g_dmarksurfaces ));
|
|
|
|
g_numsurfedges = 0;
|
|
memset( g_dsurfedges, 0, sizeof( g_dsurfedges ));
|
|
|
|
g_numentities = 0;
|
|
memset( g_entities, 0, sizeof( g_entities ));
|
|
|
|
g_numfaceinfo = 0;
|
|
memset( g_dfaceinfo, 0, sizeof( g_dfaceinfo ));
|
|
|
|
g_numcubemaps = 0;
|
|
memset( g_dcubemaps, 0, sizeof( g_dcubemaps ));
|
|
|
|
g_numleaflights = 0;
|
|
memset( g_dleaflights, 0, sizeof( g_dleaflights ));
|
|
|
|
g_numworldlights = 0;
|
|
memset( g_dworldlights, 0, sizeof( g_dworldlights ));
|
|
|
|
g_mapinfo[0] = '\0';
|
|
g_mapname[0] = '\0';
|
|
g_mapversion = 0;
|
|
|
|
Mem_Free( mod_base );
|
|
mod_base = NULL;
|
|
}
|
|
|
|
// =====================================================================================
|
|
// WriteBSPFile
|
|
// Swaps the bsp file in place, so it should not be referenced again
|
|
// =====================================================================================
|
|
void WriteBSPFile( const char *filename )
|
|
{
|
|
dheader30_hlfx_t outheader; // long header from HLFX
|
|
dheader_t *header;
|
|
dheader30_hlfx_t *hlfxhdr;
|
|
dextrahdr_t outextrahdr;
|
|
dextrahdr_t *extrahdr;
|
|
long bspfile;
|
|
|
|
header = (dheader_t *)&outheader;
|
|
hlfxhdr = &outheader;
|
|
memset( &outheader, 0, sizeof( dheader30_hlfx_t ));
|
|
extrahdr = &outextrahdr;
|
|
memset( extrahdr, 0, sizeof( dextrahdr_t ));
|
|
|
|
header->version = BSPVERSION;
|
|
extrahdr->id = IDEXTRAHEADER;
|
|
extrahdr->version = EXTRA_VERSION;
|
|
hlfxhdr->magicID = HLFX_BSP_MAGIC_ID;
|
|
|
|
COM_CreatePath( (char *)filename );
|
|
bspfile = SafeOpenWrite( filename );
|
|
|
|
if( g_subtype == MAP_HLFX06 )
|
|
{
|
|
SafeWrite( bspfile, hlfxhdr, sizeof( dheader30_hlfx_t )); // overwritten later
|
|
}
|
|
else
|
|
{
|
|
SafeWrite( bspfile, header, sizeof( dheader_t )); // overwritten later
|
|
SafeWrite( bspfile, extrahdr, sizeof( dextrahdr_t )); // overwritten later
|
|
}
|
|
|
|
UnparseEntities();
|
|
|
|
// LUMP TYPE DATA LENGTH HEADER BSPFILE
|
|
AddLump( LUMP_PLANES, g_dplanes, g_numplanes * sizeof( dplane_t ), header, bspfile );
|
|
AddLump( LUMP_LEAFS, g_dleafs, g_numleafs * sizeof( dleaf_t ), header, bspfile );
|
|
AddLump( LUMP_VERTEXES, g_dvertexes, g_numvertexes * sizeof( dvertex_t ), header, bspfile );
|
|
AddLump( LUMP_NODES, g_dnodes, g_numnodes * sizeof( dnode_t ), header, bspfile );
|
|
AddLump( LUMP_TEXINFO, g_texinfo, g_numtexinfo * sizeof( dtexinfo_t ), header, bspfile );
|
|
AddLump( LUMP_FACES, g_dfaces, g_numfaces * sizeof( dface_t ), header, bspfile );
|
|
AddLump( LUMP_MARKSURFACES, g_dmarksurfaces, g_nummarksurfaces * sizeof( dmarkface_t ), header, bspfile );
|
|
AddLump( LUMP_SURFEDGES, g_dsurfedges, g_numsurfedges * sizeof( dsurfedge_t ), header, bspfile );
|
|
AddLump( LUMP_EDGES, g_dedges, g_numedges * sizeof( dedge_t ), header, bspfile );
|
|
AddLump( LUMP_MODELS, g_dmodels, g_nummodels * sizeof( dmodel_t ), header, bspfile );
|
|
|
|
AddLumpClipnodes( LUMP_CLIPNODES, header, bspfile ); // clipnodes can using 16-bit or 32-bit indexes
|
|
|
|
AddLump( LUMP_LIGHTING, g_dlightdata, g_lightdatasize, header, bspfile );
|
|
AddLump( LUMP_VISIBILITY, g_dvisdata, g_visdatasize, header, bspfile );
|
|
AddLump( LUMP_ENTITIES, g_dentdata, g_entdatasize, header, bspfile );
|
|
AddLump( LUMP_TEXTURES, g_dtexdata, g_texdatasize, header, bspfile );
|
|
|
|
if( g_subtype == MAP_HLFX06 )
|
|
{
|
|
AddLump( HLFX30_LUMP_FACEINFO, g_dfacedata, g_numfacedata * sizeof( dfacedata_t ), hlfxhdr, bspfile );
|
|
AddLump( HLFX30_LUMP_BUMP, g_dbumpdata, g_bumpdatasize, hlfxhdr, bspfile );
|
|
AddLump( HLFX30_LUMP_TNBASIS, g_dTNbasis, g_numTNbasis * sizeof( dTNbasis_t ), hlfxhdr, bspfile );
|
|
}
|
|
else
|
|
{
|
|
AddExtraLump( LUMP_VERTNORMALS, g_dnormals, g_numnormals * sizeof( dnormal_t ), extrahdr, bspfile );
|
|
AddExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, g_deluxdatasize, extrahdr, bspfile );
|
|
AddExtraLump( LUMP_CUBEMAPS, g_dcubemaps, g_numcubemaps * sizeof( dcubemap_t ), extrahdr, bspfile );
|
|
AddExtraLump( LUMP_FACEINFO, g_dfaceinfo, g_numfaceinfo * sizeof( dfaceinfo_t ), extrahdr, bspfile );
|
|
AddExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, g_numworldlights * sizeof( dworldlight_t ), extrahdr, bspfile );
|
|
AddExtraLump( LUMP_SHADOWMAP, g_dshadowdata, g_shadowdatasize, extrahdr, bspfile );
|
|
AddExtraLump( LUMP_LEAF_LIGHTING,g_dleaflights, g_numleaflights * sizeof( dleafsample_t ), extrahdr, bspfile );
|
|
AddExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, g_vlightdatasize, extrahdr, bspfile );
|
|
}
|
|
|
|
lseek( bspfile, 0, SEEK_SET );
|
|
|
|
if( g_subtype == MAP_HLFX06 )
|
|
{
|
|
SafeWrite( bspfile, hlfxhdr, sizeof( dheader30_hlfx_t ));
|
|
}
|
|
else
|
|
{
|
|
SafeWrite( bspfile, header, sizeof( dheader_t ));
|
|
SafeWrite( bspfile, extrahdr, sizeof( dextrahdr_t ));
|
|
}
|
|
close( bspfile );
|
|
|
|
ReleaseBSPFile();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_FindModelOrigin
|
|
|
|
routine to detect bmodels with origin-brush
|
|
=================
|
|
*/
|
|
static void Mod_FindModelOrigin( const char *modelname, vec3_t origin )
|
|
{
|
|
VectorClear( origin );
|
|
|
|
if( !g_numentities ) return;
|
|
|
|
// skip the world
|
|
for( int i = 1; i < g_numentities; i++ )
|
|
{
|
|
entity_t *mapent = &g_entities[i];
|
|
|
|
if( !Q_stricmp( modelname, ValueForKey( mapent, "model" )))
|
|
{
|
|
GetVectorForKey( mapent, "origin", origin );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
MAP LOADING
|
|
|
|
===============================================================================
|
|
*/
|
|
/*
|
|
=================
|
|
Mod_LoadSubmodels
|
|
=================
|
|
*/
|
|
static void Mod_LoadSubmodels( const dlump_t *l )
|
|
{
|
|
dmodel_t *out = g_dmodels;
|
|
dmodel_t *in;
|
|
int i, j;
|
|
|
|
in = (dmodel_t *)(mod_base + l->fileofs);
|
|
|
|
if( l->filelen % sizeof( *in ))
|
|
COM_FatalError( "Mod_LoadSubmodels: funny lump size\n" );
|
|
g_nummodels = l->filelen / sizeof( *in );
|
|
|
|
if( g_nummodels < 1 ) COM_FatalError( "Map %s without models\n", g_mapname );
|
|
if( g_nummodels > MAX_MAP_MODELS ) COM_FatalError( "Map %s has too many models\n", g_mapname );
|
|
|
|
for( i = 0; i < g_nummodels; i++, in++, out++ )
|
|
{
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
// spread the mins / maxs by a pixel
|
|
out->mins[j] = in->mins[j];
|
|
out->maxs[j] = in->maxs[j];
|
|
out->origin[j] = in->origin[j];
|
|
}
|
|
|
|
if( i != 0 && VectorCompare( vec3_origin, out->origin ))
|
|
Mod_FindModelOrigin( va( "*%i", i ), out->origin );
|
|
|
|
for( j = 0; j < MAX_MAP_HULLS; j++ )
|
|
out->headnode[j] = in->headnode[j];
|
|
|
|
if( in->visleafs >= 65535 )
|
|
COM_FatalError( "Mod_LoadSubmodels: visleafs %i exceeds an unsigned short limit\n", in->visleafs );
|
|
|
|
out->visleafs = in->visleafs;
|
|
out->firstface = in->firstface;
|
|
out->numfaces = in->numfaces;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadEntities
|
|
=================
|
|
*/
|
|
static void Mod_LoadEntities( const dlump_t *l )
|
|
{
|
|
if( l->filelen > MAX_MAP_ENTSTRING )
|
|
COM_FatalError( "Map %s exceeds MAX_MAP_ENTSTRING\n", g_mapname );
|
|
|
|
memcpy( g_dentdata, mod_base + l->fileofs, l->filelen );
|
|
g_entdatasize = l->filelen;
|
|
|
|
ParseEntities();
|
|
|
|
// grab mapversion
|
|
g_mapversion = IntForKey( &g_entities[0], "mapversion" );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadTexInfo
|
|
=================
|
|
*/
|
|
static void Mod_LoadTexInfo( const dlump_t *l )
|
|
{
|
|
dtexinfo_t *out = g_texinfo;
|
|
dtexinfo_t *in;
|
|
int i, j;
|
|
|
|
in = (dtexinfo_t *)(mod_base + l->fileofs);
|
|
if( l->filelen % sizeof( *in ))
|
|
COM_FatalError( "Mod_LoadTexInfo: funny lump size in %s\n", g_mapname );
|
|
|
|
g_numtexinfo = l->filelen / sizeof( *in );
|
|
|
|
for( i = 0; i < g_numtexinfo; i++, in++, out++ )
|
|
{
|
|
for( j = 0; j < 8; j++ )
|
|
out->vecs[0][j] = in->vecs[0][j];
|
|
|
|
out->miptex = in->miptex;
|
|
out->flags = in->flags;
|
|
|
|
// tell the engine about hi-res lightmaps
|
|
SetBits( out->flags, TEX_EXTRA_LIGHTMAP );
|
|
|
|
if( g_subtype == MAP_XASH3D_EXT )
|
|
out->faceinfo = in->faceinfo;
|
|
else out->faceinfo = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadDeluxemap
|
|
=================
|
|
*/
|
|
static void Mod_LoadDeluxemap( void )
|
|
{
|
|
char path[64];
|
|
size_t filesize;
|
|
byte *in;
|
|
|
|
if( g_subtype == MAP_XASH3D_EXT || g_subtype == MAP_HLFX06 )
|
|
return; // Xash3D ext format have built-in deluxemap
|
|
|
|
Q_strncpy( path, g_mapname, sizeof( path ));
|
|
COM_StripExtension( path );
|
|
COM_DefaultExtension( path, ".dlit" );
|
|
if( !COM_FileExists( path )) return; // missed
|
|
|
|
in = (byte *)COM_LoadFile( path, &filesize );
|
|
|
|
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
|
|
{
|
|
MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s is not a deluxemap file\n", path );
|
|
g_ddeluxdata = NULL;
|
|
g_deluxdatasize = 0;
|
|
Mem_Free( in );
|
|
return;
|
|
}
|
|
|
|
g_deluxdatasize = filesize;
|
|
|
|
// skip header bytes
|
|
g_deluxdatasize -= 8;
|
|
|
|
if( g_deluxdatasize != g_lightdatasize )
|
|
{
|
|
MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s has mismatched size (%i should be %i)\n", path, g_deluxdatasize, g_lightdatasize );
|
|
g_ddeluxdata = NULL;
|
|
g_deluxdatasize = 0;
|
|
Mem_Free( in );
|
|
return;
|
|
}
|
|
|
|
Q_strncat( g_mapinfo, "deluxemap included, ", sizeof( g_mapinfo ));
|
|
MsgDev( D_REPORT, "Mod_LoadDeluxemap: %s loaded\n", path );
|
|
g_ddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize );
|
|
memcpy( g_ddeluxdata, in + 8, g_deluxdatasize );
|
|
Mem_Free( in );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadLightVecs
|
|
=================
|
|
*/
|
|
static void Mod_LoadLightVecs( const dlump_t *l )
|
|
{
|
|
byte *in;
|
|
|
|
in = (byte *)(mod_base + l->fileofs);
|
|
g_deluxdatasize = l->filelen;
|
|
if( !l->filelen ) return;
|
|
|
|
if( g_deluxdatasize != g_lightdatasize )
|
|
{
|
|
MsgDev( D_ERROR, "Mod_LoadLightVecs: has mismatched size (%i should be %i)\n", g_deluxdatasize, g_lightdatasize );
|
|
g_ddeluxdata = NULL;
|
|
g_deluxdatasize = 0;
|
|
return;
|
|
}
|
|
|
|
Q_strncat( g_mapinfo, "deluxemap included, ", sizeof( g_mapinfo ));
|
|
g_ddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize );
|
|
memcpy( g_ddeluxdata, in, g_deluxdatasize );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Mod_LoadClipnodes
|
|
=================
|
|
*/
|
|
static void Mod_LoadClipnodes( const dlump_t *l, const dlump_t *l2, const dlump_t *l3 )
|
|
{
|
|
dclipnode_t *in, *in2, *in3;
|
|
dclipnode32_t *out = g_dclipnodes32;
|
|
int i, count, count2, count3;
|
|
|
|
in = (dclipnode_t *)(mod_base + l->fileofs);
|
|
if( l->filelen % sizeof( *in )) COM_FatalError( "Mod_LoadClipnodes1: funny lump size\n" );
|
|
count = l->filelen / sizeof( *in );
|
|
|
|
in2 = (dclipnode_t *)(mod_base + l2->fileofs);
|
|
if( l2->filelen % sizeof( *in2 )) COM_FatalError( "Mod_LoadClipnodes2: funny lump size\n" );
|
|
count2 = l2->filelen / sizeof( *in2 );
|
|
|
|
in3 = (dclipnode_t *)(mod_base + l3->fileofs);
|
|
if( l3->filelen % sizeof( *in3 )) COM_FatalError( "Mod_LoadClipnodes3: funny lump size\n" );
|
|
count3 = l3->filelen / sizeof( *in3 );
|
|
|
|
g_numclipnodes = 0;
|
|
|
|
for( i = 0; i < count; i++, out++, in++ )
|
|
{
|
|
out->planenum = in->planenum;
|
|
out->children[0] = in->children[0];
|
|
out->children[1] = in->children[1];
|
|
g_numclipnodes++;
|
|
}
|
|
|
|
// merge offsets so we have shared array of clipnodes again
|
|
for( i = 0; i < count2; i++, out++, in2++ )
|
|
{
|
|
out->planenum = in2->planenum;
|
|
out->children[0] = in2->children[0];
|
|
out->children[1] = in2->children[1];
|
|
|
|
if( out->children[0] >= 0 )
|
|
out->children[0] += count;
|
|
if( out->children[1] >= 0 )
|
|
out->children[1] += count;
|
|
g_numclipnodes++;
|
|
}
|
|
|
|
// merge offsets so we have shared array of clipnodes again
|
|
for( i = 0; i < count3; i++, out++, in3++ )
|
|
{
|
|
out->planenum = in3->planenum;
|
|
out->children[0] = in3->children[0];
|
|
out->children[1] = in3->children[1];
|
|
|
|
if( out->children[0] >= 0 )
|
|
out->children[0] += (count + count2);
|
|
if( out->children[1] >= 0 )
|
|
out->children[1] += (count + count2);
|
|
g_numclipnodes++;
|
|
}
|
|
|
|
// update headnode offsets
|
|
for( i = 0; i < g_nummodels; i++ )
|
|
{
|
|
g_dmodels[i].headnode[2] += count;
|
|
g_dmodels[i].headnode[3] += (count + count2);
|
|
}
|
|
|
|
if( g_numclipnodes != ( count + count2 + count3 ))
|
|
COM_FatalError( "Mod_LoadClipnodes: mismatch node count (%i should be %i)\n", g_numclipnodes, ( count + count2 + count3 ));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadBsp31
|
|
|
|
load XashXT level file format
|
|
=============
|
|
*/
|
|
void LoadBsp31( void )
|
|
{
|
|
dheader31_t *header = (dheader31_t *)mod_base;
|
|
dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)mod_base + sizeof( dheader31_t ));
|
|
dheader31_hlfx_t *hlfxhdr = (dheader31_hlfx_t *)mod_base;
|
|
|
|
g_maptype = MAP_XASH31;
|
|
|
|
if( extrahdr->id == IDEXTRAHEADER )
|
|
{
|
|
switch( extrahdr->version )
|
|
{
|
|
case 1:
|
|
g_subtype = MAP_XASHXT_OLD;
|
|
break;
|
|
case EXTRA_VERSION_OLD:
|
|
g_subtype = MAP_P2SAVIOR;
|
|
break;
|
|
case 3:
|
|
g_subtype = MAP_DEPRECATED;
|
|
break;
|
|
case EXTRA_VERSION:
|
|
g_subtype = MAP_XASH3D_EXT;
|
|
break;
|
|
}
|
|
}
|
|
else if( hlfxhdr->magicID == HLFX_BSP_MAGIC_ID )
|
|
{
|
|
g_subtype = MAP_HLFX06;
|
|
}
|
|
|
|
// load into heap
|
|
Mod_LoadEntities( &header->lumps[LUMP_ENTITIES] );
|
|
|
|
if( g_mapversion != VALVE_FORMAT )
|
|
MsgDev( D_WARN, "%s not a Valve 220 format\n", g_mapname );
|
|
|
|
Mod_LoadSubmodels( &header->lumps[LUMP_MODELS] );
|
|
g_numplanes = CopyLump( LUMP_PLANES, g_dplanes, sizeof( dplane_t ), header );
|
|
g_numvertexes = CopyLump( LUMP_VERTEXES, g_dvertexes, sizeof( dvertex_t ), header );
|
|
g_numedges = CopyLump( LUMP_EDGES, g_dedges, sizeof( dedge_t ), header );
|
|
g_numsurfedges = CopyLump( LUMP_SURFEDGES, g_dsurfedges, sizeof( dsurfedge_t ), header );
|
|
g_visdatasize = CopyLump( LUMP_VISIBILITY, g_dvisdata, 1, header );
|
|
Mod_LoadTexInfo( &header->lumps[LUMP_TEXINFO] );
|
|
g_texdatasize = CopyLump( LUMP_TEXTURES, g_dtexdata, 1, header );
|
|
g_lightdatasize = CopyLump( LUMP_LIGHTING, g_dlightdata, 1, header );
|
|
g_numfaces = CopyLump( LUMP_FACES, g_dfaces, sizeof( dface_t ), header );
|
|
g_nummarksurfaces = CopyLump( LUMP_MARKSURFACES, g_dmarksurfaces, sizeof( dmarkface_t ), header );
|
|
g_numleafs = CopyLump( LUMP_LEAFS, g_dleafs, sizeof( dleaf_t ), header );
|
|
g_numnodes = CopyLump( LUMP_NODES, g_dnodes, sizeof( dnode_t ), header );
|
|
Mod_LoadClipnodes( &header->lumps[LUMP_CLIPNODES], &header->lumps[LUMP_CLIPNODES2], &header->lumps[LUMP_CLIPNODES3] );
|
|
Mod_LoadDeluxemap ();
|
|
|
|
if( g_subtype == MAP_XASH3D_EXT )
|
|
{
|
|
g_found_extradata = true;
|
|
|
|
// g-cont. copy the extra lumps
|
|
g_numnormals = CopyExtraLump( LUMP_VERTNORMALS, g_dnormals, sizeof( dnormal_t ), header );
|
|
g_deluxdatasize = CopyExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, 1, header );
|
|
g_numcubemaps = CopyExtraLump( LUMP_CUBEMAPS, g_dcubemaps, sizeof( dcubemap_t ), header );
|
|
g_numfaceinfo = CopyExtraLump( LUMP_FACEINFO, g_dfaceinfo, sizeof( dfaceinfo_t ), header );
|
|
g_numworldlights = CopyExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, sizeof( dworldlight_t ), header );
|
|
g_numleaflights = CopyExtraLump( LUMP_LEAF_LIGHTING, g_dleaflights, sizeof( dleafsample_t ), header );
|
|
g_shadowdatasize = CopyExtraLump( LUMP_SHADOWMAP, g_dshadowdata, 1, header );
|
|
g_vlightdatasize = CopyExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, 1, header );
|
|
}
|
|
else if( g_subtype == MAP_P2SAVIOR )
|
|
{
|
|
// P2: Savior regular format
|
|
g_numcubemaps = CopyExtraLump( LUMP_CUBEMAPS, g_dcubemaps, sizeof( dcubemap_t ), header );
|
|
g_numworldlights = CopyExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, sizeof( dworldlight_t ), header );
|
|
}
|
|
else if( g_subtype == MAP_HLFX06 )
|
|
{
|
|
g_numfacedata = CopyLump( HLFX31_LUMP_FACEINFO, g_dfacedata, sizeof( dfacedata_t ), hlfxhdr );
|
|
g_bumpdatasize = CopyLump( HLFX31_LUMP_BUMP, g_dbumpdata, 1, hlfxhdr );
|
|
g_numTNbasis = CopyLump( HLFX31_LUMP_TNBASIS, g_dTNbasis, sizeof( dTNbasis_t ), hlfxhdr );
|
|
}
|
|
// preform some operations here...
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LoadBSPFile
|
|
=============
|
|
*/
|
|
void LoadBSPFile( const char *infilename, const char *outfilename )
|
|
{
|
|
MsgDev( D_REPORT, "Loading: %s\n", infilename );
|
|
Q_strncpy( g_mapname, infilename, sizeof( g_mapname ));
|
|
mod_base = (byte *)COM_LoadFile( g_mapname, NULL );
|
|
|
|
if( *(uint *)mod_base == XT_BSPVERSION )
|
|
{
|
|
LoadBsp31();
|
|
PrintMapInfo();
|
|
WriteBSPFile( outfilename );
|
|
}
|
|
else
|
|
{
|
|
// not an BSP31
|
|
Mem_Free( mod_base );
|
|
}
|
|
}
|
|
|
|
int BspConvert( int argc, char **argv )
|
|
{
|
|
char source[1024], name[1024];
|
|
char output[1024];
|
|
|
|
|
|
if( !COM_GetParmExt( "-file", source, sizeof( source )))
|
|
Q_strncpy( source, "*.bsp", sizeof( source ));
|
|
|
|
search_t *search = COM_Search( source, true );
|
|
|
|
if( !search ) return 0;
|
|
|
|
for( int i = 0; i < search->numfilenames; i++ )
|
|
{
|
|
COM_FileBase( search->filenames[i], name );
|
|
Q_snprintf( output, sizeof( output ), "%s.bsp", name );
|
|
#if 0
|
|
if( COM_FileExists( output ))
|
|
continue; // map already converted
|
|
#endif
|
|
LoadBSPFile( search->filenames[i], output );
|
|
}
|
|
|
|
Mem_Free( search );
|
|
Mem_Check();
|
|
|
|
Msg( "press any key to exit\n" );
|
|
system( "pause>nul" );
|
|
|
|
return 0;
|
|
} |