FAQ  •  Register  •  Login

HL BSP entity editor

Moderator: Inside3D Admins

<<

JasonX

Posts: 300

Joined: Tue Apr 21, 2009 2:08 pm

Post Wed Dec 14, 2011 11:12 pm

HL BSP entity editor

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

User avatar

Posts: 3172

Joined: Tue Mar 14, 2006 5:15 am

Post Thu Dec 15, 2011 12:30 am

Re: HL BSP entity editor

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: 300

Joined: Tue Apr 21, 2009 2:08 pm

Post Thu Dec 15, 2011 1:01 am

Re: HL BSP entity editor

Oh, i see. It will recompile the BSP with the entities only? Clever! Thanks. :D
<<

metlslime

Posts: 310

Joined: Tue Feb 05, 2008 11:03 pm

Post Thu Dec 15, 2011 4:15 pm

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.
<<

JasonX

Posts: 300

Joined: Tue Apr 21, 2009 2:08 pm

Post Thu Dec 22, 2011 4:13 am

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:

  Code:
/* 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 );
}

Return to General Programming

Who is online

Users browsing this forum: No registered users and 2 guests

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by ST Software for PTF.
Icons provided by Aha Soft