Forum

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.

Moderator: InsideQC Admins

HL BSP entity editor

Postby JasonX » Wed Dec 14, 2011 11:12 pm

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

Re: HL BSP entity editor

Postby Baker » Thu Dec 15, 2011 12:30 am

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

Re: HL BSP entity editor

Postby JasonX » Thu Dec 15, 2011 1:01 am

Oh, i see. It will recompile the BSP with the entities only? Clever! Thanks. :D
JasonX
 
Posts: 411
Joined: Tue Apr 21, 2009 2:08 pm

Re: HL BSP entity editor

Postby metlslime » Thu Dec 15, 2011 4:15 pm

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.
metlslime
 
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Re: HL BSP entity editor

Postby JasonX » Thu Dec 22, 2011 4:13 am

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 );
}
JasonX
 
Posts: 411
Joined: Tue Apr 21, 2009 2:08 pm


Return to General Programming

Who is online

Users browsing this forum: No registered users and 1 guest