Haven: Call of the King, or simply Haven, as I’ll refer to it, is a game developed by the British video-game development studio, Traveller’s Tales. It’s quite technically impressive, and certainly ambitious, as far as PlayStation 2 games go.

To be clear, we’re only going to be diving into the technical side of the game for this one, so if that’s not your sort of thing then hopefully the next article will be more to your satisfaction.

Also, got to point out, I am (unfortunately) human, I make mistakes and every day is a learning experience. Not to mention going over all this turned my brain into mushed peas. If I’ve made an error anywhere, let me know about it, so I can correct it, rather than immediately blasting me into orbit!

DVD Contents 📀

Okay, so let’s start getting our hands dirty.

Cat digging gif.

I’d give you a complete listing of all the content as I’ve done before, but there’s a far too much for that, so we’ll just go over the things that matter.

I’m going to be looking at the final EU release of the game here, so SLES-51209 specifically. There is a pre-release build out there, from August 2002, which I’ve looked at too - but we’ll save more details about that for a future article. For now I’ll just reference it in passing where it makes sense to do so.

In the root directory of the ISO we can see the following.

Root directory of an extracted ISO

Oct 14  2002 DATA           ; where the game data is stored
Oct 14  2002 DLL.DAT        ; package with a collection of .rel files
Sep 27  1999 DUMMY.DAT      ; null padding file
Oct 14  2002 SLES_512.09    ; main game executable
Oct 14  2002 SYS250         ; system libraries
Oct  9  2002 SYSTEM.CNF     ; typical PS2 config file

As indicated in my notes above, the DLL.DAT file is actually using a package format which we’ll need to deal with. Fortunately, unlike some other formats I’ve looked at of late, the filenames are not hashed. That said, they are stored in a rather unusual way, as we’ll find out. Nothing’s ever simple… 😒

As also indicated in my notes, the DUMMY.DAT file is used for nothing more than padding the DVD and contains nothing interesting. Given the modification timestamp on it, I’d guess this exact same file may have been used for padding in some of their other titles, heh.

Next looking under the DATA directory, we can see the following.

Oct 14  2002 BIN            ; contains content used for each of the planets in the game
Oct 14  2002 CSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 ENGLISH        ; english dialogue audio as PS2 VAG format
Oct 14  2002 FRENCH         ; ditto but for french
Oct 14  2002 GERMAN         ; ditto but for german
Oct 14  2002 GSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 HSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 HSFX2          ; various sound effects using the common PS2 VAG format
Oct 10  2002 INTRO_25.PSS   ; one of the pre-rendered cutscenes featured in the game
Oct 14  2002 ITALIAN        ; italian dialogue audio
Oct 14  2002 LSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 LSFX2          ; various sound effects using the common PS2 VAG format
Oct 14  2002 LSFX3          ; various sound effects using the common PS2 VAG format
Oct 14  2002 LSFX4          ; various sound effects using the common PS2 VAG format
Oct 14  2002 MUSIC          ; VAG music
Oct 14  2002 MUSIC2         ; VAG music
Oct 14  2002 MUSIC3         ; VAG music
Oct 14  2002 PSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 RWLDS.DAT      ; another package, this holding models, scripts and other data
Oct 14  2002 SPANISH        ; spanish dialogue audio
Oct 11  2002 VID_PAL.PSS    ; a making-of video, which curiously shows an older build of the game
Oct 14  2002 VSFX1          ; various sound effects using the common PS2 VAG format
Oct 14  2002 VSFX2          ; various sound effects using the common PS2 VAG format
Oct 14  2002 WSFX1          ; various sound effects using the common PS2 VAG format

Lots of audio! The VAG format is a common audio format used for PlayStation 2 games. You can find a handy tool to convert them to WAV here.

As I also indicated in the list above, the VID_PAL.PSS video is actually one of those “making of” videos titled “Beneath the Surface”. Anyway what’s rather curious about it is that it seems to show a build even older than the August 2002 build mentioned earlier, as the HUD has a different design.

Screenshot from the VID_PAL.PSS video.

An area shown in the video, which I’ve captured in the screenshot above, also appears to be the same starting area as the August 2002 build (with some of the same quirks), though we’ll talk about that more in a future piece.

We’ve also got yet another package, this time RWLDS.DAT. It’s much bigger than that last one we looked at, and seems to hold the bulk of the game data.

If you’re curious about its name, this is seemingly an abbreviation of “realworlds” which was possibly a working name for the game, or the technology. We’ll touch on it a little more soon.

Alright, next up we’ll have a look under the BIN directory before looking at the contents of the RWLDS.DAT package. “Bin” is a pretty classic vague name for a place to throw binary files.

Oct 14  2002 AURIA          ; folder for planet-specific data (see below)
Feb  8  2001 CUBE.BIN       ; ...
Oct 14  2002 EXARCO         ; folder for planet-specific data (see below)
Oct 14  2002 FERRA          ; folder for planet-specific data (see below)
Oct 14  2002 FERVEN         ; folder for planet-specific data (see below)
Sep  2  2002 MKSPACE.DAT    ; package containing .pnt (texture) files
Oct 14  2002 NEQUAM         ; folder for planet-specific data (see below)
Oct  8  2002 SYSTEM.RW3     ; some unknown data + planet textures

./AURIA:
Oct  8  2002 HEIGHTS.RW3    ; presumably height information for the planet terrain?
Aug  9  2002 NOISE.PNT      ; noise... :)
Oct  9  2002 PATCHES.RW3    ; unsure
Oct  9  2002 PLANET.RW3     ; some unknown data + planet textures
Oct  9  2002 TEX004.RW3     ; holds textures for the planet in specific sizes (4x4)
Oct  9  2002 TEX008.RW3     ; holds textures for the planet in specific sizes (8x8)
Oct  9  2002 TEX016.RW3     ; holds textures for the planet in specific sizes (16x16)
Oct  9  2002 TEX032.RW3     ; holds textures for the planet in specific sizes (32x32)
Oct  9  2002 TEX064.RW3     ; holds textures for the planet in specific sizes (64x64)
Oct  9  2002 TEX128.RW3     ; holds textures for the planet in specific sizes (128x128)
Oct  9  2002 TEXNDX.RW3     ; unsure

[As the types of files under each sub-dir are the same, I've ommitted the rest]

So as far as these immediate folders go, I think we’re done for now with that. Before we jump into anything else, let’s first take a quick look at the executable (SLES_512.09) which we saw in the initial root directory.

Disassembling 🔍

One of the first things I’ll typically check for are debug symbols, or at the least, symbol names, which can sometimes help the poking and prodding process.

It turned out that there’s an interesting quirk with the executable in this game; the main executable is actually just an unpacker, and the real executable is contained within, compressed.

They used RNC ProPack for the compression, for which TT’s Nu2 framework (often described as an engine, but I’d personally call it a framework) provides the methods for decompressing the contained executable into memory when the game launches. I’d ended up using this to decompress it.

You seemingly can make the game run just fine directly from the resulting executable once done, so besides providing an unpacking solution, it doesn’t seem that the main executable was doing anything else particularly special.

Why did they do this? I’ve got no idea. Originally I’d assumed it was some sort of crude anti-tamper method, but that doesn’t make any sense.

This allows us to open the resulting unpacked executable in Ghidra, a popular reverse engineering tool (well, with the necessary extensions.) Much to our joy, there are at least symbol names but nothing too extensive as far as debug data, but hey, better than nothing.

It’s rather interesting to see the structure of the project, which was broken up between the Nu2 libraries (such as nucore, nusound2, numath, nu3d, nups2, gamelib. mp2play, edtools and coblib), a library called realwlds and finally of course the game itself. The primary libraries, nu2 and realwlds, seem to be post-fixed with the target platform, so in this case .ps2 (for instance, ..\nu2.ps2\nu3d\nuscene.c and ..\realwlds.ps2\frcube.c).

The realwlds library seems to manage all the systems behind the planets and their terrain. In the credits, it seems to be referred to as the “Fractal Render Engine” and is credited to Richard Taylor and Alistair Crowe (the latter of whom appears to still work at Traveller’s Tales).

The fact that it’s developed into a separate library makes me wonder if there was perhaps an intention to use this technology for other titles. No doubt something that Haven’s sequel, which never materialised, would’ve retained.

And unsurprisingly all of this was written in C (queue song).

With Ghidra at our aid, there are a great many additional things we can discover. Such as a selection of command-line arguments that can be passed to the game!

But unfortunately these arguments are actually hardcoded in CD/DVD builds of the game (like the ones we have), however if you open the ELF in your favourite hex editor and go to address 0x507100, you can change at least a few of them to anything of your preference.

So for instance, in the PAL version of the game, there’s a pal argument which switches the game into a 50hz mode, but if you null it out, you’ll be able to run the PAL version at 60hz instead.

Or, if you null out startpoint, you’ll get a level select screen!

Level select screen.

This is very similar to what you’ll see in the August build, only with a lot more options available.

Alternatively, if you leave the startpoint argument as is, the argument that proceeds it is actually the argument for startpoint, which by default is jp_startgame. You can change this to some hardcoded points such as sp_bonus8, to spawn at a point later in the game.

All the start points, however, are available via the level select screen, so I’d recommend just going with that instead.

With the ELF unpacked, we can also use it alongside PCSX2 to see all the globals available that we can tweak to our amusement.

PCSX2 debugger.

So, as you can tell, this is just scratching the surface. I’ll leave the rest of that to someone with a lot more time on their hands though.

DAT Package Format 📦

So before we can get at anything else, we’ll need to figure out how everything is packaged.

I’m not going to lie, this one gave me a little bit of a headache.

First, within the package, the file table is stored at the end of the file rather than the start. This is followed by a table of null-terminated strings, which are the folder and filenames. The first four bytes of the package indicate the offset of the file table. There’s no filename hashing or compression, so that makes things very easy here.

Seems simple enough so far.

But there’s a catch (of course there is). Strings for each file are not handled quite in the way you might expect, and instead it’s organized as some sort of tree? For each entry in the string table, there’s an offset into the end of the file, where the strings are located, but then two 16-bit integers that seem to indicate the relationship per file and directory.

That basically gives us a structure like this. This is in the binary template format supported by REHex.

struct TTHavenDatHeader
{
	uint32_t tocOffset;
	uint32_t tocAllocSize;
};

struct TTHavenDatIndex
{
	uint32_t offset;
	uint32_t size;
	uint32_t compressedSize;
	uint32_t flags;
};

struct TTHavenDatTocHeader
{
	uint32_t unknown;
	uint32_t numFiles;
};

struct TTHavenDatHeader header;

FSeek( header.tocOffset );
struct TTHavenDatTocHeader 	tocHeader;
struct TTHavenDatIndex 		files[ tocHeader.numFiles ];

struct TTHavenDatTreeIndex
{
	uint32_t 	offset;
// these are awful...
	int16_t 	l;// if positive, followed by directory, next index before pop
	int16_t 	r;// if zero, indicates directory
};

uint32_t numTreeEntries;
uint32_t numB;

struct TTHavenDatTreeIndex tree[ numTreeEntries ];

I couldn’t quite figure it out but came up with something that seems to be good enough, but certainly isn’t anywhere close to how you’re supposed to be handling these.

Someone with a brain, unlike myself, could probably do a better job at figuring out the correct way to deal with this. Do let me know if you do!

	unsigned int dirDepth = 0;
	DirIndex dirStack[ STACK_SIZE ] = {};
	// i = 1 because first is an empty dir, indicating root, 
	// so we'll just skip for simplicity...
	for ( unsigned int i = 1; i < numStrings; ++i ) {
		// current string
		const char *c = &buf[ tree[ i ].offset ];

		// last string is a special weird case... meh
		if ( i < numStrings - 1 ) {
			// if r is zero, seems to be a directory
			if ( tree[ i ].r == 0 ) {
				dirStack[ dirDepth ].string = c;
				dirStack[ dirDepth ].index = i;
				dirDepth++;
				continue;
			}
		}

		// build up the string here based on stack ...

		// if r isn't equal to our index, and it's not zero, 
		// it's probably the index we want to pop back to
		if ( tree[ i ].r != i ) {
			for ( unsigned int j = 0; j < dirDepth; ++j ) {
				if ( dirStack[ j ].index == tree[ i ].r ) {
					dirDepth = j;
					break;
				}
			}
		}
	}

With that out of the way, we can pretty much get everything extracted from the package with a good majority of the filenames intact, as far as I can tell.

You can find my implementation of a loader for the format here, to give you a more complete idea if the above isn’t clear enough. Just mind it’s a bit of a mess, as it just needed to extract the packages to satisfy my own curiosity and nothing more.

So, the time has come! Tally-ho!

A cat jumping into a box

What’s that left us with? Hm, well with the RWLDS.DAT package extracted, let’s take a look.

Root directory of an extracted ISO

There’s a lot here! Just what I like to see. 🤤

We’re not going to go over everything today, but let’s make a start taking a look around.

Game Flow 🌊

First, in the root directory, we’ve got a collection of .flo files. These are used for a number of things, from specifying areas of the game to load, transitions betweens areas of the game, cutscenes and more. Generally, they control various parts of the games flow, through commands. Probably hence the extension, flo.

Internally, perhaps unsurprisingly based on what I just said, this is referred to as the Game Flow.

Per haven.flo, which is the main script that’s loaded on startup, we’ve actually got some documentation on the various supported commands and what they do (and if it’s not obvious, a semicolon is interpreted as a comment).

;-----------------------------------------------------------------------------------------------
; Reserved System Commands;
;-----------------------------------------------------------------------------------------------
;
; Traditional Semaphore support
;
; AddSem    <sem> <val> <rng>   - 
; ResetSem  <sem>       	- state[sem] = 0
; SetSem    <sem> <val> 	- state[sem] = val
; SignalSem <sem>       	- state[sem] = state[sem] + 1
; WaitSem   <sem>		- while(state[sem] = 0); state[sem] = state[sem] - 1
; WaitSemMulti <sem0...sem31>   - for n=1 to numsem; if state[sem] != 0 state[sem] - 1, break; next
;
; Using Semaphores as crude variables
;
; WaitSemEQ <sem> <val>		- while(state[sem] != val);
; WaitSemLT <sem> <val>		- while(state[sem] >= val);      
; WaitSemGT <sem> <val>		- while(state[sem] <= val);      
; WaitSemNE <sem> <val>		- while(state[sem] == val);      
; IfSemEQ   <sem> <val> <lab>	- if (state[sem] = val)  pc = lab
; IfSemLT   <sem> <val> <lab>	- if (state[sem] < val)  pc = lab
; IfSemGT   <sem> <val> <lab>	- if (state[sem] > val)  pc = lab
; IfSemNE   <sem> <val> <lab>	- if (state[sem] != val) pc = lab
;
; Flow Control / Debugging
;
; GoTo	    <lab>       	- pc = lab
; GoSub	    <lab>		- stack[sp++] = pc; pc = lab
; Return			- pc = stack[--sp]
; Pop				- --sp
; PopAll			- sp = 0
; Break				- temporarily releases program flow 
; Halt				- suspends program flow indefinitely
; DebugMsg <message>		- type message to tty
; BeginGuard <name> <lab>       - if the named guard is set, jumps to label, otherwise sets the named guard
; EndGuard <name>               - clears the named guard
;
;-----------------------------------------------------------------------------------------------
; Haven Specific Commands;
;-----------------------------------------------------------------------------------------------
; --FRACTAL STUFF--
; RWInitLevel	<level>
; RWBuffer	<bufftype - HIGH/LOW> [<buffsize>] - omitting buffsize will cause either the current planets buffer size or the current size of the rw buffer to be used - set initlevel to 2 to release it
; RWPlanetMesh	<ON/OFF>
; RWPlanetRender<ON/OFF>
; RWProcessEnable <arg> - arg 0 - processing disabled, arg 1 - processing enabled, -ve - processing disabled in -ve frames
; RWTextures	<ON/OFF>
; RWDontRenderPlate <plateid or -1>
; SkyVisible <ON/OFF> - allows level with sky off to be overridden
; SeaVisible <ON/OFF> - allows level with sea off to be overridden
; RWSetPlanetTime <planet name> <time of day 0-24>
;
; --LEVCON STUFF--
; StopLogic	  <flags>   - stops internal logic from running - flags are a combination of WORLD ISLAND or LEVEL - no flags equates to all flags being present
; SyncLogic	  <flags>   - brings internal logic into sync with current state, allows logic to run if its been knocked out - flags are a combination of WORLD ISLAND or LEVEL - no flags equates to all flags being present
; ShowLevel	  <levname> - allows a loaded level to draw
; HideLevel	  <levname> - stops a level being drawn
; PreLoadLevel	  <levname> - loads a level without activating it
; LoadLevel	  <levname> - loads and activates a level (just activates if already loaded)
; PreLoadLevelNB  <levname> - loads a level without activating it - non blocking variant - script continues
; LoadIsland	  <islandname>
; LoadWorld	  <worldname>
; IfLevelProgress <levname> <bit ix> <label>
; IfNotLevelProgress <levname> <bit ix> <label>
; SetLevelProgress <levname> <bit ix>
; SetSpecialVis    <levname> <specialname> <ON/OFF> - sets the visibility state of the specified special object - setting is lost in the event of a reload
;
; --CONFLICTS--
; ResetConflict <conname> - calls the Reset() function of the conflict
; LoadConflict  <conname> - loads the named conflict
; DumpConflict  <conname> - requests the dumping of the named conflict
; DumpBuffer	<LOW/SUB/HIGH> - requests a dump from the specified buffer
; ConflictSpawnDelay <delay> - prevents auto spawning conflicts (space, sky etc) from occurring for <delay> frames, -1 stops them happening ad-infinitum
;
; --CUTSCENES--
; PlayLevelCutScene <levname> <cutname> - starts the cutscene - cutname, in level levname
; WaitLevelCutScene <levname> <cutname> [time offset] - waits for the cutscene - cutname, in level levname to finish - the optional time offset allows you to wait for a point n frames into the cutscene (+ve) or n frames from the end of the cutscene (-ve)
; Fade <out/in> [<black/white>] - fades the game to black or white - black by default
; WaitFade                      - waits for fade command to complete fade in or out
; PlayFMV <fmvname> <HIGH/SUB/SUBLOW/SUBHIGH/LOW> - plays an fmv file then waits for it to finish
; TexAnimSetSignal   <sigid>            - sets the texture animation signal identified by sigid to ON
; TexAnimResetSignal <sigid>            - sets the texture animation signal identified by sigid to OFF
; OnCutEvent         <val> <label>      - adds an event handler for the "FlowEvent" locator in cutscenes. When the animatable parameter is set to <val> and the locator is made visible, <label> will be called
;
; --GAMEPLAY--
; PlacePlayer <x> <y> <z> <ay> - puts the player at a specific location and resets the game camera
; PlacePlayerAtLevelStart <level> - puts the player at the setup start location for the named level
; SetCheckPoint [<x> <y> <z> <ay>] - sets a checkpoint at the current player position / orientation - if the optional parameters are specified, these are used to place the checkpoint instead
; IfPickupEQ <pikname> <val> <label> - if the pickup - pikname - equals val, then jump to label - valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; IfPickupNE <pikname> <val> <label> - if the pickup - pikname - doesn't equal val, then jump to label - valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; IfPickupGE <pikname> <val> <label> - if the pickup - pikname - is greater than or equal to val, then jump to label - valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; IfPickupGT <pikname> <val> <label> - if the pickup - pikname - is greater than val, then jump to label- valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; IfPickupLE <pikname> <val> <label> - if the pickup - pikname - is less than or equal to val, then jump to label - valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; IfPickupLT <pikname> <val> <label> - if the pickup - pikname - is less than val, then jump to label - valid piknames are "BLUEFEATHER","REDFEATHER","ANTIDOTE","GOLDCOG","HEROSTONE"
; JonSave <startpoint>               - saves a startpoint
;
; --VEHICLES--
; EnterVehicle <vehicle name> [USECOBPOS] - puts player into vehicle - only works with VEHCRAFT type at the moment - usecobpos makes the vehicle use its cobs current matrix rather than what's in its savegame structure
; LeaveVehicle <vehicle name> - [NOJUMP/CUTCON] takes player out of vehicle - only works with VEHCRAFT type at the moment - options not really supported at the mo.
; VehicleCameraEnable - Activates the vehicle camera - needs Chris to explain this
; SetNavigator <wldname> <islename> <x y z> - sets the location of the navigation beacon - wldname or islename can be substituted with none
;
; --MISC--
; BizarreFlags <flag> <val> - Bizarre flags are app specific variables that can be set and easily picked up by the application and used for any purpose.
; Currently assigned are;
; 0 - if 1 - sea status is overriden and the sea is drawn
;
;-----------------------------------------------------------------------------------------------
; Haven Specific Semaphores;
;-----------------------------------------------------------------------------------------------
; edit_mode        - set to 1 if running in edit mode otherwise 0 - reference this if a levels script needs to 
;                    behave differently in edit mode 
;		   
; rw_init_level    - set to reflect the current state of rw_init_level - can be used to change behaviour conditionally
;                    on the current state of the fractal
;
; in_craft_biplane - if 1, player is in the biplane
;
; in_craft_wasp    - if 1, player is in the wasp
;
; in_craft_shuttle - if 1, player is in the shuttle
;
; in_craft         - if 1, player is in one of the craft

;-----------------------------------------------------------------------------------------------
; Haven Reserved Labels;
;-----------------------------------------------------------------------------------------------
; Start - Initial entry point when starting a game
; Restart - Position in script that we jump back to when restarting the game

This pleases me. The glorious bastards even kept this documentation updated between the August and final build.

Grinning Tom Baker.

That said, there are actually three additional commands that aren’t documented. The #include at the bottom of haven.flo, for instance; these look like pre-processor macros you might see in C/C++ but they’re actually handled through the same command system as everything else.

And then there are two others that are undocumented, #strict and #sloppy. These actually work together; the #sloppy command just disables the #strict state.

But what is the strict state? If it’s enabled, and the parser encounters an invalid number of arguments for a command, it’ll throw an error rather than silently spitting out a little message that might be missed. As far as I can tell, that’s it.

#strict is unused in Haven, and #sloppy is explicitly set right at the start of execution per haven.flo. This is despite the fact that sloppy seems to be the default state? Though perhaps that was different for debug builds, or used to be the case but changed before shipping.

You probably noticed there’s an edit_mode semaphore that’s mentioned. This toggles a global variable that’s in memory called game_editmode, which actually does a lot of different things that’s a little hard for me to summarise, but it will for instance give you a different (more primitive) start menu with a screenshot option.

Simple start menu in edit mode.

And otherwise it’ll skip a lot of the rendering, and particularly skip much of the world rendering and simulation. So you’ll still be able to load into levels and run around, but otherwise anything involving the fractal rendering will be invisible to you.

I’d not had the time to verify it to the same extent but as far as it appears, this works much the same in the August build.

When looking at things in Ghidra, we can see that all the custom Haven commands are registered per an InitGameFlow method which is called on startup. This seems to register each command through a handy little method called gfcAddCommandType, while each semaphore (variable) is registered via a gfcAddSem method.

/* gfsupp.c */

undefined4
InitGameFlow(undefined4 param_1,undefined4 param_2,undefined4 param_3,undefined4 param_4,
            undefined4 param_5,undefined4 param_6,undefined4 param_7,long param_8,int *param_9,
            undefined8 param_10)
{
  [...]
  
  gfcInit();
  sem_edit_mode = (undefined *)gfcAddSem(0x603f28,game_editmode,1);

  [...]
  
  gfcAddCommandType(0x603f38,executeResetConflict,parseResetConflict);
  gfcAddCommandType(0x603f48,executeLoadConflict,parseLoadConflict);
  gfcAddCommandType(0x603048,executeDumpConflict,parseDumpConflict);
  gfcAddCommandType(0x603f58,executeSkyVisible,parseSkyVisible);
  gfcAddCommandType(0x603f68,executeSeaVisible,parseSeaVisible);
  gfcAddCommandType(0x603f78,executeDumpBuffer,parseDumpBuffer);
  gfcAddCommandType(0x603f88,executeRWInitLevel,parseRWInitLevel);
  gfcAddCommandType(0x603f98,executeRWBuffer,parseRWBuffer);
  gfcAddCommandType(0x603fa8,executeRWGetBuffer,parseRWGetBuffer);
  [...]

And all the system commands are registered in the gfcInit method that we can see called via InitGameFlow just before the Haven commands are registered.

/* gameflow.c */

void gfcInit(void)
{
  gfc_num_cmd_types = 0;
  gfc_num_sems = 0;
  gfc_num_guards = 0;
  gfc_global_data_ptr = 0;
  gfcAddCommandType(0x602c30,0,gfparseStrict);
  gfcAddCommandType(0x602c38,0,gfparseSloppy);
  gfcAddCommandType(0x602c40,0,gfparseInclude);
  gfcAddCommandType(0x602c50,0,gfparseAddSem);
  gfcAddCommandType(0x602c58,gfexecuteResetSem,gfparseResetSem);
  gfcAddCommandType(0x602c68,gfexecuteSetSem,gfparseSetSem);
  gfcAddCommandType(0x602c70,gfexecuteSignalSem,gfparseSignalSem);
  gfcAddCommandType(0x602c80,gfexecutePollSem,gfparsePollSem);
  gfcAddCommandType(0x602c88,gfexecuteWaitSem,gfparseWaitSem);
  gfcAddCommandType(0x602c90,gfexecuteWaitSemMulti,gfparseWaitSemMulti);
  gfcAddCommandType(0x602ca0,gfexecuteWaitSemEQ,gfparseWaitSem);
  gfcAddCommandType(0x602cb0,gfexecuteWaitSemLT,gfparseWaitSem);
  gfcAddCommandType(0x602cc0,gfexecuteWaitSemGT,gfparseWaitSem);
  gfcAddCommandType(0x602cd0,gfexecuteWaitSemNE,gfparseWaitSem);
  gfcAddCommandType(0x602ce0,gfexecuteIfSemEQ,gfparseIfSem);
  [...]

There are two callbacks passed into the gfcAddCommandType method, one for handling the actual execution of the command and another for parsing the arguments for that command. The first argument is of course the name of the command (Ghidra is just showing the addresses to those strings here, rather than the strings themselves).

You can see that nothing is provided for the execution callback for the first four commands above, under gfcInit, and this is because they’ll set up what they need through the parse callback instead.

So, without going over the entire implementation of the damn thing and spending another 1,000 words explaining everything as I’d be so tempted to do; it’s all quite powerful but all exposed through a fairly tidy API. Unfortunately, there are several other types of scripts, and each has its own way of doing things. Sigh…

Their script syntax here is actually case-insensitive, so for example the documentation above specifies IfSemEQ but you’ll more typically see it written as ifsemeq instead. As a matter of fact, it looks like even labels are case-insensitive too… Wouldn’t have been my choice, but hey.

As mentioned earlier, when the game starts up, it will execute from the start of haven.flo as that’s the primary script that’s explicitly loaded by the game. From there, all the commands up until the Halt are executed, but the rest of the script will continue to be parsed.

At the end of that same file, you’ll see includes for the other .flo files we also saw in the root directory. Each of these are additional scripts that consolidate everything for each given planet, so the ferra.flo script contains everything for the first planet in the game, Ferra.

A little extra tidbit is that there is a Start label just above the Restart label near the beginning of the file, but as far as I can tell it’s not actually referenced (instead, only the latter is used), which is just as well as both of them will result in the same thing.

Start:


Restart:
	SetNavigator none none 0 0 0

	ResetSem  LevShaftCutSceneEnded 
	ResetSem  guard_stationactivate 
	ResetSem  guard_testsiteend     
	ResetSem  guard_volctop	        
	ResetSem  ferry_loading
	Resetsem  newhilltotopguard     
	ResetSem  in_craft_biplane
	ResetSem  in_craft_wasp
	ResetSem  in_craft_shuttle
	ResetSem  in_craft
	ResetSem  hometownLoadScript_sem

	ResetSem  DenyInputVehicles
	ResetSem  guard_puffdragon
	
	ResetSem  MineEntSem
	ResetSem  inhibit_ml_mos
	ResetSem  inhibit_ml_ref

	ResetAllGuards

	TexAnimResetSignal 0
	TexAnimResetSignal 1
	TexAnimResetSignal 2
	TexAnimResetSignal 3
	TexAnimResetSignal 4
	TexAnimResetSignal 5
	TexAnimResetSignal 6
	TexAnimResetSignal 7
	TexAnimResetSignal 8
	TexAnimResetSignal 9
	TexAnimResetSignal 10
	TexAnimResetSignal 11
	TexAnimResetSignal 12
	TexAnimResetSignal 13
	TexAnimResetSignal 14
	TexAnimResetSignal 15

	;; Hack

	BizarreFlags 0 0	; clear frig-o-sea enable
	BizarreFlags 1 0	; tailgun hold off flag
	BizarreFlags 2 0	; level/atmosphere sorting control

	ConflictSpawnDelay 0	; reset spawn system
;;	SetLevelProgress village 2
;	SignalSem  no_sky_battle	; uncomment this line to disable the sky battle - for free flight around the planet with no attackers

; Main idle point. Script sits here most of the time waiting for subroutines to happen.
	Halt

I’d guess at some stage Start was used and executed some additional commands that were since scrapped, and they stopped bothering to use the label at all.

We could probably keep going, but hopefully you’ve got the idea on how these work now.

Dialog Script 🗣

So for this one, we’ve only got the one script which is dialog.txt. There’s no fancy file extension, so no fun for us there. That said, there’s some very limited documentation provided which makes things a little easier for us to understand.

;;
;; Commands
;;
;;	ACTOR id name colourcode eyelevel
;;	BEGINSCRIPT id
;;	  ORIGIN x y z
;; 	  RANGE n
;;	  ONCE  
;;
;;	  SETPOS actor pos x y z face actor 
;;	  CAMPOS actor shot[back|side|front] transition[cut|pan] nframes
;;        CAMTRG actor
;;	  TEXT actorname "text"
;;	ENDSCRIPT
;;
;; The actor and script id's are used directly by the code 
;;  to setup the dialog so changed to these will require
;;  changes in the code. Everything else can be changed freely
;;  

This isn’t everything though, but it’s a start. There are actually a couple extra commands available.

  • move <actor> <x y z>
  • face <actor> <actor>

The following is then provided at the start of the dialog file, just after the documentation, essentially outlining the details of each actor to be used in a conversation.

ACTOR  1  Teque  "Haven"  "#0#"  0.7
ACTOR  2  Bloke2 "C2"	  "#13#" 0.9
ACTOR  3  Bloke3 "C3"     "#13#" 0.9
ACTOR  4  Bloke4 "C4"     "#13#" 0.9
ACTOR  5  Bloke5 "C5"     "#13#" 0.9
ACTOR  6  Bloke6 "C6"     "#13#" 0.9
ACTOR  7  Bloke7 "C7"     "#13#" 0.9
ACTOR  8  Bloke8 "C8"     "#13#" 0.9

If you refer to the documentation from earlier, you’ll notice it doesn’t quite align with it, as there’s actually two names provided here, but the first appears to work essentially like an alias and then the second name is what will actually be displayed.

Evidently given this and the two other missing commands, the documentation at the start of the file is a little out of date.

You may have also noticed that Haven (which if you hadn’t guessed it, is the name of the main character) is referenced as Teque here.

This is quite likely an earlier name for the main character, as this name is used in a some other spots too (for instance, Haven’s house is referenced to as teqhouse, and a second copy of Haven’s model is stored under lowteq).

There are a number of test and placeholder conversations left in here.

So for instance, here’s an entry with an ID of zero, clearly to catch any instances where the ID provided for a conversation somewhere else has been left unset. This was quite likely done so either the developers or QA would notice and remedy it.

;;;;;;;;;;;;
;; no script
BEGINSCRIPT 0
;;  FACE    teque nobody
  
  CAMPOS  scene side pan 30
  CAMTRG  scene
  TEXT	  scene "My dialog script id has not been set"
  TEXT    teque "Oh dear"
  TEXT	  scene "Please find someone to set my dialog script id"
  TEXT	  teque "OK"
ENDSCRIPT

And then we’ve got another test or placeholder script following that.

;;;;;;;;;;;;;;
;; start bloke
BEGINSCRIPT 2
  ;; Script global settings
  ORIGIN  -546.5 19.2.5 117.2
  RANGE   1

  ;; Script dialog & camera sequence  

  FACE    teque bloke2

  CAMPOS  teque back pan 30
  CAMTRG  bloke2
  TEXT	  bloke2 "Welcome to Hero Island"
  
  CAMPOS  scene side pan 30
  CAMTRG  scene
  TEXT    teque  "Hello, my name is Haven"
  TEXT    bloke2 "You have no nose"
  TEXT    teque  "Dont say it"
  
  CAMPOS  bloke2 front cut
  CAMTRG  bloke2
  TEXT    bloke2 "How do you smell"
  
  CAMTRG  teque
  TEXT    teque  "AARRRGGG"
  
ENDSCRIPT

This one’s rather curious as the “bloke” in the conversation suggests that Haven doesn’t have a nose, which may be a reference to an older design featured in the video by Jon Burton here.

Old alien Haven design.

Or it’s nothing more than a silly joke, and I’m reading too much into it.

The entirety of this file is filled with unused, placeholder dialog, some of which was still in use in the August prototype but is otherwise left unused in the final game. And with that said, as far as I can tell, this entire dialog system actually goes completely unused in the final game, and instead it uses cutscenes (an entirely separate system, internally) to convey dialog and story to the player.

It’s curious why this is. Perhaps it was decided having cutscenes for these situations simply looked better. Perhaps originally the game was to feature more dialog, and that ended up not being the case? Who knows, but it’s surprising that it seems it was cut fairly late in production given that cutscenes likely would’ve taken more work.

The dialog system is entirely bespoke to Haven, but the system used for cutscenes is actually a system Nu2 appears to provide (though Haven has its own intermediate scripting language for cutscenes which we’ll briefly cover later).

If you’re curious what the dialog system looked like in action however, you can see it here per a video on our YouTube channel.

Placements

Amongst the files, there’s a placements.pmt file. This can be found in the root of the extracted data. It’s likely .pmt is just an abbreviation for placement.

There isn’t much to it, so I can just give you the contents below.

"SamSite"
"Fuel"
"Waypoint"
"mosshield"
"fervenshield"
"restart"

In the earlier August build, you can access the placement editor that lists the contents of this file, though it’s not really immediately obvious to me what they are and what they should do.

You can see some footage of the placement editor here.

Curiously, the last three, mosshield, fervenshield and restart were added sometime after the aforementioned August build, but as far as I can tell they’re not referenced anywhere?

Again, like the dialog system, the placements system seems bespoke to Haven. That said, as far as I can tell these are only really relevant to the placement editor? And even then I’m not entirely sure if I’m understanding what these actually do from there.

From how the editor displays them, depending on what you do, they look like waypoints. Anyway, let’s move on.

Randomisation 🔢

Under the extracted content, there’s a bin/rand.dat file. This appears to be unused by the game, and instead the game uses a table in memory, which matches the rand.dat data.

I’m not really sure if I entirely understand the purpose of having a fixed table for randomisation in this instance. Why they didn’t just do this algorithmically is a bit strange?

Just to explain a bit further, essentially there is a pointer initially set to the start of the table in memory. At the start of each frame, it’ll check to make sure that the position has not moved out of scope of the table. This is then used to produce “random” numbers in the game, as the position in the table is gradually incremented at points during the frame.

It’s very possible there’s something I’m missing, but it almost wouldn’t surprise me if the position could or does go out of scope of the table, given the check appears to only be at the start of each frame and the position is incremented at multiple points during the frame.

A rather concerned cat gif.

Probably a calculated risk… Anyway, let’s not worry about that. Nothing to see here.

Universe 🌌

We’ll take a quick look at the new.uni script which is included in the root directory of the extracted data. The extension for this format, if you couldn’t figure it out, seems to be an abbreviation of universe.

These are a lot more complicated to explain than the game flow scripts we looked at. They outline each available “world”, each of the “islands” on that world, then the “levels” on each island.

There’s at least some basic documentation provided within the script itself via the ; comments.

RWCONFIG HIGH 	9000000							; Assign a buffer to the fractal - global setting, may also be done per world
TEXANIMFILE 	"texanim.cfg"	40960					; name of texanim cfg file and buffer size for texanims

[...]

;;BEGINWORLD testworld
;;	RWCONFIG HIGH 	9000000						; Assign a buffer to the fractal - may ultimately be done on a per level or world basis
;;	PATH		world1						; where to look for bits specific to the world
;;	WBSCENE		clouds						; scene containins atmos,clouds,sea etc....
;;;	RWNAME		forest forest5					; name of realworld data set to load - 2nd parameter is wacky directory override
;;
;;	BEGINISLAND testisle
;;		PATH		island3					; where to look for bits specific to the island
;;		LOSCENE	lo_isl2						; name of low res scene
;;;;		PLATEID 	863					; fractal plate we're sat on
;;		PLATEID 	852					; fractal plate we're sat on
;;		PLATEPOS 	0,1410,0                		; origin offset on the plate
;;		ACTIVERANGE	1000					; range at which to activate island
;;		LOADTRIGGERS	island3					; loadtriggers file for this island
;;
;;;;		BEGINLEVEL boattest BUFFER high 5000000	
;;;;			PATH		boattest			; where to look for bits specific to the level
;;;;			HISCENE	boattest				; name of scene/terrain/etc file to load
;;;;;;			POS		0,0,0				; location of level - used by visibility guff
;;;;			STARTPOS	-66.4 5.26 -15			; starting position for player
;;;;			PARTICLES   particle
	
[...]

This sets everything up in conjunction with their fractal rendering engine, and this involves multiple different formats, which we would need to look at individually.

One of these formats is at least .gsc. These seem to contain textures, geometry and other data. They’re usually used to provide the main geometry for a level.

And another is .rw3. They’re found under DATA/BIN/, with each subdirectory then providing the data for each respective world. They contain textures and other data. The TEX*.RW3 files provide different sized textures for the terrain, which presumably are streamed in and out.

There are several others, but we’ll skip those for the sake of time.

It’s possible to edit the universe script, as I’ll demonstrate later on, to load in other locations but can, probably unsurprisingly, crash the game if you’re not careful.

Curiously, the way this is implemented suggests there was perhaps going to be support for loading other universe scripts? Or they just wanted to keep things flexible.

There’s only one of these scripts in the final version of the game, which is the rather curiously named new.uni. The August build has a few alternate revisions of that script. Some warning messages seem to indicate it may have been called game.uni at some point.

There is a start-up argument, uni, which will allow you to override which universe script is loaded on startup, but as we covered before, these arguments are hardcoded in the builds we’ve got.

You can see the end result of a lot of this working together, here. The player is able to approach a world from space without any loading screens, different main areas of the game can be seen which are the islands, represented by their LOSCENE geometry, and then once a player lands they move into the actual level instance itself.

Apologies this is a little vague. I’d failed to document my findings when editing these, so for the sake of time, I’ve decided to skip on a lot of this for now, and there’s so much to cover that it would probably be better served as its own article.

Models

Model used by the game have a .ghg extension. They are all stored under their own subdirectories alongside their animations, under the chars directory, within the RWLDS.DAT package.

From what I’ve understood from looking around a little bit, there’s a variations of this format used for other titles using the Nu2 framework, but it doesn’t look like there was any work done towards the version used in Haven specifically.

I’d unfortunately not finished entirely reversing them, and don’t think I’ll do so for the time otherwise we’ll be here forever, but I did learn some things to share.

Below is a rough outline of the initial structure of the files. This is in the binary template format supported by REHex.

// Trike, 18 joints?

// Texture block 672
// Pal 349 / 289~
// Header is 144 for texture? NOPE! Size isn't fixed...
// W & H are 80 bytes into the header? NOPE! Not fixed either...

struct Nu2TextureHeader
{
    uint8_t     unk0[ 80 ];
    uint32_t    width;
    uint32_t    height;
};

struct Nu2GHGHeader
{
    uint32_t fileSize;   //?
    uint32_t version;    // 0 per Haven
    uint32_t numTextures;
    uint32_t textureTableOffset;
    uint32_t numMaterials;
    uint32_t materialsOffset;
    uint32_t unkNum0;       // ? 12 per Trike
};

struct Nu2GHGHeader header;

FSeek( header.textureTableOffset );
uint32_t textureOffsets[ header.numTextures ];

for ( local uint32_t i = 0; i < header.numTextures; ++i )
{
    SetComment( textureOffsets[ i ], 0, "Texture" );
}

SetComment( header.unkOffset0, 0, "Unknown data 0" );

As you can tell, there’s still a lot to figure out here, as this is only for the header.

But these files not only contain the mesh and skeleton, but the textures too. For instance, if we take a look at chars/lowteq/rest.ghg, we can find it contains textures for Haven’s main character, Haven, including a texture for his beautiful face.

Haven's face texture, pulled from a ghg.

In this instance, these textures use a palette that’s included in the header of each texture contained within the file, but it appears they can be different formats besides that too.

Alas, that’s all I’d had time for. Though it would be nice to revisit these in the future.

Strings 🧵

Going to pass on this one quickly. There’s a set of strings.* files in the root of the extracted data, with the * being an abbreviated name of a country.

Each one of these contain a list of strings with an index so the correct text can be used depending on whichever language you want to play in.

Under strings.uk, you can find the following rather amusing set of strings from a seemingly distressed developer.

400 "task 0  Find level programmers"
401 "task 1  Ask them how the levels work"
402 "task 2  Add new level progress strings to the strings.uk file"
403 "task 3  Add level progress info to level.hlp file"
404 "task 4  Find out it doesn't quite work"
405 "task 5  Look for level programmers who have now gone home"
406 "task 6  Panic about what to do cos it was all left to the last minute"
407 "task 7  Bodge bodge bodge bodge bodge bodge bodge bodge bodge bodge"
408 "task 8  Shout across the office at who ever come in next"
409 "task 9  The solution to all problems is more hours"

More Scripts, Because Sure, Why Not? 😩

Didn’t we already cover some other types of scripts already? Yes. Yes we did. And there are still more.

Jake, from Adventure Time, yelling in agony gif.

Why you would have this many different unique script types, each with their own interpreter, loader and everything in between is beyond me, but perhaps this is the result of each developer having their own area they were in charge of and going in their own direction with that. Either way, this is pain.

Let’s go over this quickly.

There are two others I’ll just quickly touch on.

SCP Scripts 🗒

For a change, these are scripts that aren’t bespoke to Haven and are actually implemented under their Nu2 framework. Which implies other games they’ve developed probably use these. Why they implemented the game flow scripts rather than utilising this existing scripting language for the same purpose, I don’t know.

If it wasn’t capable of things they needed it for, I don’t understand why they didn’t just extend it. Am I grumpy about this? Yes, give me a time machine, so I can correct it.

The .scp extension is probably an abbreviation of script? You know what, I don’t even care at this point.

Here’s an example of what one of these looks like.

SCRIPT cgate1
	IFPARAM 2 0
		STARTCUTSCENEINST cgate2
		APPCOMMAND SoundFX "sylex door"
		SETPARAM 2 1
	ELSE
		STARTCUTSCENEINST cgate1
		APPCOMMAND SoundFX "sylex door"
	ENDIF
ENDSCRIPT

Once again, the commands used here are case-insensitive.

The APPCOMMAND appears to allow you to call a custom command that the game has registered. So for instance, Haven implements a GameFlowCall app command that allows you to execute from a label in a .flo script. So a method was essentially exposed here from Nu2 to allow games to extend the abilities of these scripts without actually requiring changes to Nu2 itself, which is nice.

Otherwise, internally these scripts are parsed by a NuScriptLoadCommands which has a fairly nasty nested structure… Though altogether we can see it supports all the following commands.

  • SCRIPT
  • STARTCUTSCENEINST
  • PLAYCUTSCENEINST
  • CHAINCUTSCENEINST
  • REPEATCUTSCENEINST
  • RESETCUTSCENEINST
  • PAUSECUTSCENEINST
  • UNPAUSECUTSCENEINST
  • SETTRIGGERFLAG
  • RESETTRIGGER
  • ENABLETRIGGER
  • IFTRIGGERPULLED
  • IFTRIGGERNOTPULLED
  • IFPARAM
  • SETPARAM
  • ELSE
  • APPCOMMAND
  • SETVISIBILITY
  • ENDSCRIPT

Just mind a lot of these are contextual, so obviously you wouldn’t use an ELSE command on its own, you’d need to match it with an IF*, and then end it with an ENDIF; although I don’t really want to start getting into how structured programming works.

Let’s move on.

CSC Scripts 🎥

Unlike the above, these are again bespoke to Haven. These essentially pick the actual .cut file to load and setup some states for that.

What’s a .cut file? They’re binary blobs for the cutscenes silly. I’ve not had the motivation or time to look at those in depth.

Curiously, the PLAYSTREAM command, which streams audio from a VAG file, goes unused and PLAYSTEREO, which is the same but allows you to specify and left and right VAG file to use for audio, is used instead.

As far as I’ve understood, the VAG format doesn’t typically support stereo audio, so I guess the developers worked around this limitation by just producing these L and R files instead, and then streaming them into the respective audio channels. You can see all the usages of PLAYSTREAM commented out, to be replaced by the latter.

The requested VAG will get mapped to a directory matching the current language, so for instance CUTSND3L\eLcityD.vag will get mapped to english\CUTSND3L\eLcityD.vag if your chosen language is English.

Exploring the Unseen 👀

Okay, this isn’t actually all the unseen elements in the game, but a selection of items you might find interesting. This is probably what most of you are going to be interested in.

I’d mentioned earlier that there’s a pre-release build from August 2003. Surprisingly there’s actually very little in that build, as far as unused content goes, which isn’t actually also left in the final game. Though the build from August does certainly make it a lot easier to view in-engine so that’s what I’d used to take these screenshots.

The majority of these were accessed by simply modifying the universe script we discussed earlier. I believe there was one which required me to amend one of the game flow scripts, but in my great wisdom I didn’t document that (mostly as a result of me flailing around figuring out how everything worked).

Mind all of these are found within the RWLDS.DAT package.

Hometown

I’ll start off looking at the hometown at the start of the game as this one will be a little confusing without more context. This area was completely overhauled in the final game, and is much more linear there, but originally was intended to be a much more open area.

So here's a fun little thing. In the August '02 Haven proto, you start in an version of the hometown which was overhauled before released, but it turns out there's an older version of the area in the files with different plants (and without the messed up geom). (left is old, right is new)

[image or embed]

— hogsy (@hogsy.me) July 16, 2025 at 3:16 PM

Essentially, there’s a dayht.gsc file under ferra/isl1home/hometown, which is in both the August build and final, which seems to feature an older(?) version of that area.

You can find some footage I recorded showing this area in more detail here while also comparing it against the one used in the August build. We’ll probably talk a little more about this in future.

dhone

This is found in the files under ferra/isl1home/tempscn/dhone.

There’s another version of this. Not entirely sure how they differ compared to the final version?

oxtest1

This is found in the files under ferra/isl1home/tempscn/oxtest1.

An appearance from our friend Crash! 😮

This is actually a huge area, unfortunately untextured. Crash is about 2 pixels in size in the second image, which hopefully gives you an idea of its scale!

tempscn

This is found in the files under ferra/isl1home/tempscn/tempscn.

Appears to be a huge unfinished area. Fairly sure this did make it into the final game, though I believe it looks a little bit different to this? Well, besides being finished.

The last image shows the camera pulled really far back, showing how massive the area is, for some reason.

test2

This is found in the files under ferra/isl1home/tempscn/test2.

The other test level, if you’re wondering, just features the same geometry as this one.

This is found in the files under ferra/isl1home/tempscn/lightbox.

Lightbox test map screenshot

tek

This is found in the files under ferra/isl1home/teqhouse/tek.

As mentioned earlier, Teque seems to be an older name for Haven (the character, not the game). And that’s often abbreviated as just Teq. Though weirdly the actual files for the scene itself (.gsc and .ter) are named “tek” instead… Probably a mistake.

I’m also probably mistaken myself, but I don’t believe any of this is used in the final game? Haven’s house looks entirely different to the one featured in the final game, for instance. And an appearance by the tower in the last screenshot is also replaced by a pre-rendered cutscene in the final game, as far as I’m aware.

Scenes from here do at least appear in a trailer for the game, which you can find here.

One more Crash

And finally, this is under fonts/testfont.bmp.

Haven test font bmp.

I’m fairly sure this same font sheet was documented as left-over in some other TT game over on The Cutting Room Floor wiki.


This is far from everything, and I’ll probably leave that for the future piece on this game, or I’ll just encourage you to try checking it out for yourself! There’s only so much I’m going to have time to look at.

One other thing that was noteworthy, but I didn’t have time to properly document, is that under ferra/island3/doomroom, there are actually multiple revisions of this area available, each one featuring slightly different designs. These exist in both the August build and final release.

Revision History 📜

One thing that stood out to me upon extracting all the data was a cvs directory; CVS is… Er, was, a fairly popular version control solution back in the day, and it stands for Concurrent Versions System. It basically lets you keep track of your changes.

This has all since been superseded by things like Git and Perforce.

This directory here basically exists because the content was checked out from their remote CVS repository, and someone forgot to delete this particular folder. Looking inside we can see three files leftover here.

In the cvs/root file, we can see a ddootson mentioned, which is David Dootson, the head programmer on the game. Perhaps it’s him to blame for this folder being left over?

Anyway, ignoring all that, we’re rather fortunate that some of the files here actually include the revision history in their header. Specifically, the .flo, .uni and strings files.

I’m not going to paste the entire revision history for each one directly here for obvious reasons, but I’ve linked each file below for you to check out if you’re interested in that.

Curious as to who’s who in the revision history? Well, in no particular order…

Username Real name
rtaylor Richard Taylor
cstanforth Chris Stanforth
smonks Steven Monks
leonwarren Leon Warren
gscragg Glynn Scragg
ddootson David Dootson
aparsons Arthur Parsons
jburton Jon Burton
ralph Ralph Ferneyhough
acrowe Alistair Crowe

If any names above are written incorrectly, blame autocorrect! If I’ve missed anyone out, well blame me (and preferably let me know.)

An article published here a couple of years after Haven shipped seems to feature a few of the developers mentioned above.

Finishing Up

So that’ll probably do for now, on the technical side. Probably. There’s actually still a lot I didn’t cover here but if I did this would’ve gone on for a long time, and there’s always the possibility of revisiting this in the future.

Working on this has made me realise that in future it may be a little more wise to just pick one aspect to focus on, rather than trying to work through everything all at once. As that certainly burnt me out a bit.

If you’ve made it to the end, with your sanity intact, and you enjoyed it, I’ve previously done pieces on Titan A.E., Wild Water Adrenaline and Creation, which you may also enjoy.

And as usual, if you want to keep up with what we’re doing, want to get involved, or have things to share, then feel free to join our Discord server.