Hey guys,
I'm looking to manipulate (edit and delete) entities in a HL BSP. I don't have the map source and i wanted to remove some entities, as well change some values from others. Is there any program for doing this? I can edit things with an hex editor, but not remove entities without corrupting the whole thing.
HL BSP entity editor
Re: HL BSP entity editor
Open the .bsp in NotePad and locate the "worldspawn" (Edit->Find "worldspawn") and select the entire block of text starting with { and ending with } and save that block of text to a file like "c1a10.ent" and then you can probably run whatever the Half-Life equivalent of qbsp is with "-doents" or "-entsonly" or whatever the command line param is. With Quake the .bsp and the .ent name must be the same, so I imagine this would be true for Half-Life too.JasonX wrote:Hey guys,
I'm looking to manipulate (edit and delete) entities in a HL BSP. I don't have the map source and i wanted to remove some entities, as well change some values from others. Is there any program for doing this? I can edit things with an hex editor, but not remove entities without corrupting the whole thing.
The night is young. How else can I annoy the world before sunsrise? Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
Re: HL BSP entity editor
Oh, i see. It will recompile the BSP with the entities only? Clever! Thanks.
Re: HL BSP entity editor
for quake you name it *.map and the command line is "-onlyents" but it may not be the same for HL.
Also for quake, you need to run both "qbsp -onlyents" and "light -onlyents" to get switchable lights to function. Not sure about HL.
Also for quake, you need to run both "qbsp -onlyents" and "light -onlyents" to get switchable lights to function. Not sure about HL.
Re: HL BSP entity editor
I found a nice little utility by Zoner called ripent. It's included in the ZHLT:
http://zhlt.info/
Also, for research, this small C tool helped me a lot to understand the format a little better:
http://zhlt.info/
Also, for research, this small C tool helped me a lot to understand the format a little better:
Code: Select all
/* entitytool.c
*
* Copyright (C) 2008 Jakob Westhoff
*
* Half Life BSP Entity Tool 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; version 3 of the License.
*
* Half Life BSP Entity Tool 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.
*
* You should have received a copy of the GNU General Public License along with
* arbit; if not, write to the Free Software Foundation, Inc., 51 Franklin St,
* Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define TITLE \
"Half Life BSP Entity Tool\n" \
"Copyright 2008 Jakob Westhoff <jakob@westhoffswelt.de>\n"
// These structs representing the file data are based on the specs from:
// http://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_4.htm
typedef struct _bsp_lump
{
long offset; // offset (in bytes) of the data from the beginning of the file
long length; // length (in bytes) of the data
} __attribute__((__packed__)) bsp_lump;
typedef struct _bsp_header
{
long version; // version of the BSP format (30)
bsp_lump lump[15];
} __attribute__((__packed__)) bsp_header;
unsigned int swapBytes( unsigned int i )
{
unsigned char b1, b2, b3, b4;
b1 = i & 255;
b2 = ( i >> 8 ) & 255;
b3 = ( i>>16 ) & 255;
b4 = ( i>>24 ) & 255;
return ((int)b1 << 24) + ((int)b2 << 16) + ((int)b3 << 8) + b4;
}
unsigned int copyBytes( unsigned int i )
{
return i;
}
FILE* sfopen( const char* filename, const char* mode )
{
FILE *tmp;
if ( ( tmp = fopen( filename, mode ) ) == NULL )
{
fprintf( stderr, "Error: Could not open file \"%s\".\n", filename );
exit( EXIT_FAILURE );
}
return tmp;
}
size_t sfread( void *ptr, size_t size, size_t count, FILE* stream )
{
size_t read;
if ( ( read = fread( ptr, size, count, stream ) ) != count )
{
fprintf( stderr, "Error: Could not read from file.\n" );
exit( EXIT_FAILURE );
}
return read;
}
size_t sfwrite( void *ptr, size_t size, size_t count, FILE* stream )
{
size_t written;
if ( ( written = fwrite( ptr, size, count, stream ) ) != count )
{
fprintf( stderr, "Error: Could not write to file.\n" );
exit( EXIT_FAILURE );
}
return written;
}
void* smalloc( size_t size )
{
void* tmp;
if ( ( tmp = malloc( size ) ) == NULL )
{
fprintf( stderr, "Error: Could not allocate %i bytes.\n", size );
exit( EXIT_FAILURE );
}
return tmp;
}
int main( int argc, char** argv )
{
FILE *bsp_file;
FILE *entity_file;
int action;
bsp_header header;
unsigned int (*endianAwareInt)( unsigned int i );
// Check for endian support
char EndianTest[2] = { 0, 1 };
if ( *( short* )EndianTest == 1 )
{
// big endian machine
endianAwareInt = swapBytes;
}
else
{
// little endian machine
endianAwareInt = copyBytes;
}
printf( TITLE );
if ( argc < 3 )
{
printf( "Usage: %s [ACTION] [BSPFILE] [ENTITYFILE]\n", argv[0] );
printf( "Example: %s extract boot_camp.bsp entities.txt\n", argv[0] );
printf( "Actions:\n" );
printf( " extract: Extract the entity map from a bsp file\n" );
printf( " integrate: Reintegrate the entity map into a bsp file\n" );
return 1;
}
// Check action
if ( !strcasecmp( argv[1], "extract" ) )
{
action = 0;
} else if ( !strcasecmp( argv[1], "integrate" ) )
{
action = 1;
} else
{
fprintf( stderr, "Error: Invalid action \"%s\"\n", argv[1] );
exit( EXIT_FAILURE );
}
// Open the files
if ( action == 0 )
{
bsp_file = sfopen( argv[2], "r" );
entity_file = sfopen( argv[3], "w" );
}
else
{
bsp_file = sfopen( argv[2], "r+" );
entity_file = sfopen( argv[3], "r" );
}
// Read the header
sfread( &header, sizeof( bsp_header ), 1, bsp_file );
// Execute the action
if ( action == 0 )
{
char* buffer;
// Check version
if ( header.version != 30 )
{
fprintf( stderr, "Error the file \"%s\" is not a Half Life bsp file.\n", argv[2] );
exit( EXIT_FAILURE );
}
printf( "Found entity table at offset: %u\nExtracting %u bytes\n", endianAwareInt( header.lump[0].offset ), endianAwareInt( header.lump[0].length ) );
buffer = smalloc( endianAwareInt( header.lump[0].length ) );
fseek( bsp_file, endianAwareInt( header.lump[0].offset ), SEEK_SET );
sfread( buffer, endianAwareInt( header.lump[0].length ), 1, bsp_file );
sfwrite( buffer, endianAwareInt( header.lump[0].length ), 1, entity_file );
free( buffer );
printf( "%s written.\n", argv[3] );
} else
{
char* newmap;
char* buffer;
int entities_size;
int bsp_size;
int bsp_new_size;
fseek( entity_file, 0, SEEK_END );
entities_size = ftell( entity_file );
fseek( entity_file, 0, SEEK_SET );
fseek( bsp_file, 0, SEEK_END );
bsp_size = ftell( bsp_file );
fseek( bsp_file, 0, SEEK_SET );
printf( "Integrating new entity table into map file\n" );
bsp_new_size = bsp_size + ( entities_size - endianAwareInt( header.lump[0].length ) );
// Allocate memory for new map data
newmap = smalloc( bsp_new_size );
// Store new entity table
sfread( newmap + endianAwareInt( header.lump[0].offset ), entities_size, 1, entity_file );
printf( "Writing lump %u at offset %u (%u kb in size)\n", 0, endianAwareInt( header.lump[0].offset ), entities_size );
// Update header according to new size and store new data
{
int i;
for ( i=1; i<15; i++ )
{
int offset = endianAwareInt( header.lump[i].offset );
// Update offset if needed
if ( endianAwareInt( header.lump[i].offset ) > endianAwareInt( header.lump[0].offset ) )
{
header.lump[i].offset = endianAwareInt( endianAwareInt( header.lump[i].offset ) + ( entities_size - endianAwareInt( header.lump[0].length ) ) );
}
// Copy data to memory
printf( "Writing lump %u at offset %u (%u kb in size)\n", i, endianAwareInt( header.lump[i].offset ), endianAwareInt( header.lump[i].length ) );
fseek( bsp_file, offset, SEEK_SET );
sfread( newmap + endianAwareInt( header.lump[i].offset ), endianAwareInt( header.lump[i].length ), 1, bsp_file );
}
}
// Update entity length
header.lump[0].length = endianAwareInt( entities_size );
// Set header
memcpy( newmap, &header, sizeof( bsp_header ) );
// Write data to file
fseek( bsp_file, 0, SEEK_SET );
sfwrite( newmap, bsp_new_size, 1, bsp_file );
free( newmap );
printf( "%s written.\n", argv[2] );
}
fclose( bsp_file );
fclose( entity_file );
}