Go library for Source Engine .bsp maps

February 16, 2018

Having recently been learning golang, and to aid my understanding by rewriting my game_text tool, it became apparent I could write a much cleaner solution by abstracting the concept of a bsp into its own package, rather than having to extract the entdata lump within the application code itself.

About bsp

So what is the format. In simple terms a .bsp is divided up into 65 sections. A 1036byte header that defines some general information, as well as the lengths and offsets of the other 64 sections (referred to as Lumps). Lumps can be located in any order, anywhere in the file, so long as the header correctly points to them.

Each lump has its own data structures, but ultimately boil down to just bytes. This is important to remember, as we can quite happily implement different Lump structures as and when we need them, and unimplemeted lumps can just be stored as []byte‘s.

Many Lumps are not well documented, so much research may be required to understand their underlying implementatiom.

Requirements

Requirements are quite simple. We need to be able to parse some form of input .bsp into some usable structures, make our modifications to said structures, then write them back out as a new valid .bsp. The library takes a *os.File for simplicity and ease of use for import. It returns a single []byte for export.

Due to the lack of documentation on many Lumps, an ideal approach would be to include a barebones library with generic implementations that can be easily swapped out for specialised structures when implemented. Great we can reduce the scope, without creating any future pain.

It is also important to consider that the size of a Lump may change when making adjustments, and we absolutely must account for that. Although an decrease in size is a simple matter of recording the new length in the header, an increase in size will likely require the lump (or all subsequent lumps) to be reallocated and/or reordered.

Usage

Usage is really simple. Instantiate the reader with a file, and call it. Then you can extract lump data and use it however you want:

package main

import (
	"github.com/galaco/bsp"
	"log"
	"os"
)

func main() {
	f,_ := os.Open("de_dust2.bsp")

	// Create a new bsp reader
	reader := bsp.NewReader(f)

	// Read buffer
	file,err := reader.Read()
	if err != nil {
		log.Fatal(err)
	}
	f.Close()

	lump := file.GetLump(bsp.LUMP_ENTITIES)
	log.Println(lump.GetContents().GetData().(string))
}