HL BSP entity editor

Discuss programming topics for any language, any source base. If it is programming related but doesn't fit in one of the below categories, it goes here.
Post Reply
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

HL BSP entity editor

Post by JasonX »

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.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: HL BSP entity editor

Post by Baker »

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.
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.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Re: HL BSP entity editor

Post by JasonX »

Oh, i see. It will recompile the BSP with the entities only? Clever! Thanks. :D
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Re: HL BSP entity editor

Post by metlslime »

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.
JasonX
Posts: 422
Joined: Tue Apr 21, 2009 2:08 pm

Re: HL BSP entity editor

Post by JasonX »

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:

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 );
}
Post Reply