Page 1 of 1

HL BSP entity editor

Posted: Wed Dec 14, 2011 11:12 pm
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.

Re: HL BSP entity editor

Posted: Thu Dec 15, 2011 12:30 am
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.

Re: HL BSP entity editor

Posted: Thu Dec 15, 2011 1:01 am
by JasonX
Oh, i see. It will recompile the BSP with the entities only? Clever! Thanks. :D

Re: HL BSP entity editor

Posted: Thu Dec 15, 2011 4:15 pm
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.

Re: HL BSP entity editor

Posted: Thu Dec 22, 2011 4:13 am
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 );
}