I peer amongst my assorted collection of unfinished works, noticing one in particular that’s been sitting for quite some time. Letting off a sigh, I grab it and proceed to blow the dust off it; an immediate coughing fit follows, and shock, seeing the only word on the page is nothing more than “The”.

Oops. This has only been waiting two years. Alright, I guess it’s about time to get this out the door.

Titan A.E., if you didn’t know already, was a game that was being developed by Blitz Games. I’m not going to go too much into the history and details of the game itself, as there’s been a fantastic amount of research into this already published here by Edward Kirk.

Anyway, for this reason, I decided to focus on more so on the technical side of things, rather than the game generally. This tends to be my usual trend anyway.

Let’s Dive In!

Into the box we go Into the box of adventure we go!

So we’re taking a look at the demo for Titan A.E. that was released in 2000, around early June? I couldn’t find a date specified online, but judging from the modification timestamps amongst the content on the CD, it seems likely that’s when it was printed. The debug menu says “May 31 2000” in the top corner, which is probably when it was compiled. Regardless, not too far off before the game was cancelled near the end of July.

I’m not going to go too much into the game itself as mentioned already, but you can find a series of videos via the playlist below from our YouTube channel.

The additional levels were accessed using the debug menu which was activated using the GameShark codes available here. I’ve also provided them below.

D006F6A8 0001
3007066E 000D
D006F6A8 0001
30070324 0001

With the level select, there’s a lot to explore!

When recording footage for YouTube I was a little concerned the music might get flagged (or grate a little as it’s fairly repetitive), so I’d disabled the music by hex editing the executable. That said, if you want the music, you can grab it below.

Download

Download

Download

Download

Download

Reviewing The CD Contents

After extracting the contents of the CD, we’ve got the following directory tree.

.
├── BOOT.EXE
├── SLUS_900.82
├── SYSTEM.CNF
├── TITAN.DAT
├── TITAN.MAP
├── VIDEO
│   ├── BLITZ1N.STR
│   ├── FOX_1_N.STR
│   ├── GAMEDEMO.STR
│   ├── ROLDEM_N.STR
│   └── TITAN_N.STR
└── XA
    ├── FLYING.XA
    ├── LEVELS1.XA
    └── TESTALL.XA

I’m not entirely sure what BOOT.EXE does. From looking at the strings within, it certainly sounds like some sort of bootstrapper? Though the main executable, SLUS_900.82, doesn’t appear to make any references to it, so it might be unused? Regardless, not too interested in that for now.

The TITAN.MAP file is a linker map, which was probably unintentionally included on the CD. It provides all the addresses for the various sections and functions, etc.


    Start     Stop   Length      Obj Group            Section name
 00018000 0001B8E3 000038E4 00018000 text             .rdata
 0001B8E4 000655CF 00049CEC 0001B8E4 text             .text
 000655D0 0006D68B 000080BC 000655D0 text             .data
 0006D68C 0006D733 000000A8 0006D68C text             .sdata
 0006D738 0006F5CF 00001E98 0006D738 text             incbins
 0006F5D0 0006F5D3 00000004 1F800000 dcache           cachedata
 0006F5D4 0006F643 00000070 0006F5D4 bss              .sbss
 0006F644 0007D6C3 0000E080 0006F644 bss              .bss
 0007D6C4 0007D6C7 00000004 0007D6C4 explore          explore$SN_OVL_explore
 0007D6C8 0007FB63 0000249C 0007D6C8 explore          explore.rdata
 0007FB64 000CD177 0004D614 0007FB64 explore          explore.text
 000CD178 000CD8E3 0000076C 000CD178 explore          explore.data
 000CD8E4 000CFE9F 000025BC 000CD8E4 explore          explore.bss
 
[...]

 00020F8C MATHS_SqrtFull
 00020FC4 MATHS_ATanFast
 00021138 MATHS_AngleDifference
 0002116C MATHS_Magnitude
 0002124C MATHS_MagnitudeS
 0002132C MATHS_Magnitude2D
 000213E0 MATHS_Rotate
 00021490 MATHS_ProjectPointForward
 00021544 MATHS_ProjectPointDown
 000215F8 MATHS_ProjectPointRight
 000216AC MATHS_GetPointDistanceFromLineSegment3D
 00021AD0 MATHS_GetPointDistanceFromLineSegment2D
 00021D04 MATHS_Get2DLineSegmentsIntersectPoint
 00022164 MATHS_LineSegmentsIntersect
 00022214 MATHS_ShortAntiClockWise
 00022278 MATHS_VECTORCrossProduct
 0002233C MATHS_TSVECTORSafeCrossProduct
 00022400 MATHS_NormaliseVECTOR
 000224FC MATHS_NormaliseTVECTOR
 000225D4 MATHS_NormaliseTSVECTOR
 000226E4 MATHS_NormaliseTSVECTOR2D
 000227BC MATHS_LineSegmentIntersectPlane
 
[...]

The files under XA and VIDEO use fairly standard formats as far as PSX games are concerned, and you can view/extract them using existing tools.

The file we’re most interested in is TITAN.DAT, as this is what holds the bulk of the data used by the game.

Turns out this DAT container is a common format amongst some other games Blitz developed at the time, such as Chicken Run and Action Man: Destruction X. There are probably others, but I’d confirmed those two at least.

I’ve documented the general structure as the following here.

struct
{
	int32_t magic;// '78563412'
	int32_t numFiles;
	struct
	{
		int32_t hash;
		int32_t block;
		int32_t length;
	} files[ numFiles ];
} header;

local int i = 0;
struct Data( int &i )
{
	FSeek( 16384 + ( header.files[ i ].block * 2048 ) );
	char chunk[ header.files[ i ].length ];
	i++;
};

struct Data data[ 0 ];
ArrayResize( data, header.numFiles, i );

This format is heavily oriented around streaming from a PSX CD, which has sectors sized at 2,048 bytes each—so to get the file offset, it’s 16384 + (block * 2048).

That doesn’t seem so bad, right? But hang on, there’s a problem…

Angry cat

Hashed Filenames

If we were to extract the data right now, we wouldn’t know what any of the files are actually called as they’re identified by a hash. I’m not sure how many formats I’ve come across now that feature hashed filenames, but it’s one too many.

So the first problem to solve here is the hashing algorithm that was used. On this occasion I got a little lazy and took a quick look at the Chicken Run code for clues, and it turns out it can be found under islutil.c per utilStr2CRC. Essentially, this code generates a hash based on a pre-existing table (likely done for the sake of speed).

unsigned long utilStr2CRC(char *ptr)
{
	register int i, j;
	int size = strlen(ptr);
	unsigned long CRCaccum = 0;

	for (j=0; j<size; j++)
		{
		i = ((int)(CRCaccum>>24)^(*ptr++))&0xff;
		CRCaccum = (CRCaccum<<8)^CRCtable[i];
		}
	return CRCaccum;
}

After studying the code a bit, I’d ended up writing my own variation of it to use for matching names.

The next thing to do was to determine all the original filenames, which is uh, a little trickier. In the end, through a combination of scraping the executable, scraping through much of the extracted content and guessing based on naming patterns, I’d managed to match a reasonable amount, though sadly not everything.

51.53% matched (675/1310) for Blitz package “TITAN.DAT”

I don’t think I’ve got more in me for now. At times, it felt like a game of battleship. You can find all the matched names here. Mind that the last section in that list is for SPT files, which we’ll talk about later.

Reviewing The DAT Contents

Right so we’ve first gone over the files on the CD and found where the bulk of the game data was stored, spent a silly amount of time trying to determine the filenames, and now we’re here. Your secrets can’t hide from me!

Extracted assets overview Oh look, folders…

As you can see, we’ve got a number of files that still don’t have valid names—we’ll ignore those for now. But hey, we’ve also got a number of folders here too.

  • BIN
    • This contains different binaries of logic for different areas of the game which are loaded as needed. Likely to save on memory, they are separate from the main executable, which is not all uncommon for PSX games.
  • EXPLORE
    • This contains all the assets specific to the explore mode, i.e., when we’re not flying a spaceship. This is, unsurprisingly, given the number of levels for this mode, quite a significant amount of data.
  • FLYING
    • All the assets specific to the parts of the game involving flying spaceships along a fixed track.
  • GRAPHICS
    • Graphics that I’d figure are shared between both modes of gameplay, including assets for the frontend.
  • MODELS
    • Fairly self-explanatory as to what this contains. Many of these are actually specific to the explore mode.
  • SOUND
    • Holds sound banks used by the game, for, well, sounds.
  • STRTMENU
    • More frontend assets.

There are additional folders under some of these for further organisation.

There are also a number of different formats used by the game, all proprietary, of course. The below doesn’t even cover everything.

PSI

These are the models used by the game and also appear to be a common format used by some other Blitz titles. It was my plan to write a tool to convert these in time for this piece, but it kept tumbling down my list.

You can find a rather limited write-up of the spec below.

struct RGB
{
	uint8_t r;
	uint8_t g;
	uint8_t b;
	uint8_t padding;
};

struct
{
	int32_t magic;  // 'PSI\0'
	int32_t version;// 1
	int32_t flags;
	char    name[ 32 ];// typically seems to be blank...

	int32_t numMeshes;
	int32_t numVertices;

	int32_t numPolygons;
	int32_t polygonOffset;

	uint16_t firstFrame;
	uint16_t lastFrame;

	int32_t numSegments;
	int32_t segmentOffset;

	int32_t numTextures;
	int32_t textureOffset;

	int32_t meshOffset;

	int32_t radius;
} header;

// textures
if ( header.numTextures > 0 )
{
	FSeek( header.textureOffset );
	struct
	{
		char name[ 32 ];
	} textures[ header.numTextures ];
}

There’s more here but not much. At some stage I’d like to look at these again.

XED

Not sure how you would pronounce this one in the real world. Zed?

Anyway, these appear to be scripts that outline the objects that are placed in the world and their properties. There is one for each level.

// MAP: Drej_3

SEGMENT
{
	INDEX {0}
	POLYGONS {0}
	VERTICES {0}
	TYPE {LANDSCAPE}
	NAME {NAVROOM}
	POS {0, 0, 0}
	ANGLE {0, 0, 0}
	SIZE {4096, 4096, 4096}
}

[...]

LIGHT
{
	POS {2880, -600, 6401}
	VAL {158, 69, 194}
	RANGE {2481}
	AMBIENCE {0}
	BOOST {2500}
	INFINITE {0}
}

[...]

SWITCH
{
	TAG {T34SWTCH}
	TAG_CRC {488158572}
	NAME {CSWITCH}
	POS {0, -125, -1251} // x,y,z
	ANGLE {0, 2048, 0} // 0->4095
	LINK {0}
	STATE {0}
}

DOOR
{
	NAME {CONDUIT}
	TAG {TP34}
	TAG_CRC {-1266880023}
	POS {683, -220, -268} // x,y,z
	ANGLE {0, 1024, 0} // 0->4095
	ID {0}
	STATE {0}
}

AUTO_DOOR
{
	NAME {CONDUIT}
	TAG {EMITTER3}
	TAG_CRC {-1416931696}
	POS {2831, -491, 6867}
	ANGLE {0, 0, 0} // 0->4095
	RADIUS {10}
	STATE {0}
}

[...]

The SEGMENT type appears to be referencing entries in the LND files, so my guess is that these are specifying what chunks of geometry are being used for the level and where. Otherwise, many of the other chunks are somewhat self-explanatory.

FUK

These are located under the EXPLORE/EVENTS directory. There is usually one per level, but some levels don’t appear to have one at all.

They’re similar in syntax to the XED files, however, these are used for adding in some basic logic for handling level-specific events. They also have a rather unfortunate file extension.

At the start of the file, the number of events and atoms (variables) are specified like so.

TOTALEVENTS	{35}
TOTALATOMS	{152}

Following this, there’s then an entry for each event, such as seen below.

[...]

EVENT
{
	NAME	{STARTUP}	//sets poly colours for emergency light

	ONESHOT

	TRIGGER
	{
		NUMATOMS {1}

		OR { TRUE, 0, 0, 0, 0 } // TRUE. ALWAYS FIRES.
	}

	SCRIPT
	{
		NUMATOMS {2}

		// - set flatcolours
		DO {FLATCOL0, 10, 255, 255, 3 }
		DO {FLATCOL1, 20, 255, 255, 3}
		
		// - cheats
		//DO { WRITETOK, 944488308, 1, 0, 0} //frank
		//DO { WRITETOK, 1519763642, 1, 0, 0} //dave
		//DO { WRITETOK, 725827994, 1, 0, 0} //geoff
		//DO { WRITETOK, 1245070714, 1, 0, 0} //wayne
		//DO { WRITETOK, 2059165261, 0, 0, 0} //titan code off
	}
}

[...]

Given these looks like they’re created by hand, I’d wager it must’ve been a pain keeping the TOTALEVENTS and TOTALATOMS updated. I’d figure that these are provided so that the script interpreter can pre-allocate storage for the required events and variables up-front for speed.

SPT

These are containers for either a single image, or multiple.

enum SptFlags
{
	SPT_FLAGS_END = 1,      // indicates that it's the last image
	SPT_FLAGS_8BPP = 2,     // if this flag isn't set, it seems to be 4bpp
	SPT_FLAGS_ALPHA = 16,
};

struct SptHeader
{
	uint32_t dataOffset;
	uint32_t paletteOffset;
	uint8_t  width;
	uint8_t  height;
	int32_t  unk0;
	uint16_t flags;
	uint32_t hash;
};

You’ll notice the last variable is our friend, Mr. Hash. Fortunately, this uses the same hashing algorithm we looked at earlier, though we still need to put in some work to figure out what the names were, so we can match them.

In the end, I’d managed to match a number of them by scraping the texture names mentioned in the PSI files. You can find the end result at the bottom of here.

As mentioned, the SPT format doesn’t contain just one image but multiple—the last is denoted by an end flag. An example of how you could iterate over each of these is provided below.

	struct SptHeader header;
	do {
		if ( !parse_header( file, &header ) ) {
			break;
		}

		// save this so we can restore our position after
		PLFileOffset offset = gInterface->GetFileOffset( file );

		// code here for parsing and converting the image goes here...

		gInterface->FileSeek( file, offset, PL_SEEK_SET );
	} while ( !( header.flags & SPT_FLAGS_END ) );

There appear to be two possible formats an SPT can be - 4bpp or 8bpp, which is denoted by a flag (2) - if the flag isn’t present, the image is 4bpp but otherwise 8bpp.

You can then convert it like so. Just keep in mind I’m not currently handling the alpha channel here as I’d encountered some oddities there.

#define AAA5( x ) ( ( ( x ) << 3 ) | ( ( x ) >> 1 ) )
#define AAA6( x ) ( ( ( x ) << 2 ) | ( ( x ) >> 4 ) )
	PLColour *pColour = ( PLColour * ) &image->data[ 0 ][ 0 ];
	if ( format == SPT_FORMAT_8BPP ) {
		for ( unsigned int i = 0; i < size; ++i ) {
			uint16_t c = pal[ img[ i ] & 0x0F ];
			pColour->r = AAA5( c & 0x1F );
			pColour->g = AAA6( ( c >> 4 ) & 0x3E );
			pColour->b = AAA5( c >> 10 );
			pColour->a = 255;
			++pColour;
		}
	} else {
		for ( unsigned int i = 0; i < size; ++i ) {
			uint16_t c = pal[ img[ i ] & 0x0F ];
			pColour->r = AAA5( c & 0x1F );
			pColour->g = AAA6( ( c >> 4 ) & 0x3E );
			pColour->b = AAA5( c >> 10 );
			pColour->a = 255;
			++pColour;

			c = pal[ img[ i ] >> 4 ];
			pColour->r = AAA5( c & 0x1F );
			pColour->g = AAA6( ( c >> 4 ) & 0x3E );
			pColour->b = AAA5( c >> 10 );
			pColour->a = 255;
			++pColour;
		}
	}

Once that is all out of the way, we end up with this.

Dumped SPT

There were a couple interesting things I’d noticed once everything was converted for easy viewing.

For instance, under D72B9099 (hashed value as I didn’t find a match), there’s what appears to be an early wrench icon?

Old wrench icon

And then under FC3B0030, we find the same wrench icon alongside what appears to be an old icon for a gun, health and ammo.

All the converted results can be downloaded here for you to do your own sleuthing.

RAW!

It's f****** raw!

The RAW files are images that seem to all be displayed directly on the screen. There’s no header or anything fancy to them. 512x256 and RGB555 format.

I ended up implementing the following to convert them.

	static const unsigned int RAW_WIDTH = 512;
	static const unsigned int RAW_HEIGHT = 256;
	static const unsigned int RAW_SIZE = RAW_WIDTH * RAW_HEIGHT * 2;

	[...]

	const unsigned int shiftR = 0;
	const unsigned int shiftG = 5;
	const unsigned int shiftB = 10;

	PLColour *colour = ( PLColour * ) &image->data[ 0 ][ 0 ];
	for ( unsigned int i = 0; i < size / 2; ++i ) {
		uint16_t c = htole16( buf[ i ] );
		colour->r = ( ( c & ( 31 << shiftR ) ) >> shiftR ) << 3;
		colour->g = ( ( c & ( 31 << shiftG ) ) >> shiftG ) << 3;
		colour->b = ( ( c & ( 31 << shiftB ) ) >> shiftB ) << 3;
		colour->r |= colour->r >> 5;
		colour->g |= colour->g >> 5;
		colour->b |= colour->b >> 5;
		colour->a = 255;
		colour++;
	}

You can find the converted results below, though these are just the identified files I’ve converted here—there may be more.

STRTMENU

The first four of these are displayed when the player dies or the level ends. The one showing Cale on the floor, dubbed GAMEOVER.RAW, is possibly unused (the game references it, but I do not recall seeing it).

The screens with the controls are shown when starting to play either of the exploration or flying levels from the main menu.

The very last is used as the background for the main menu.

GRAPHICS/FRONTEND

This gets used as the background for the debug menu.

MENU.RAW

FLYING/BACKDRPS

The last image, VOLCANO.RAW, might just be a placeholder as it matches an image used for many of the other exploration levels.

EXPLORE

Most of these are just duplicates, so I’ve only included the first unique ones.

Bonus Section

These are just some minor things I wasn’t really sure where else to throw.

Source Code

We’re rather fortunate that another game by Blitz, Chicken Run, had its source code uploaded to the web a while back which can be found here (side-note: if you know who/where it was uploaded it originally, let me know, so I can provide a citation on our archive).

By using the linker map as a reference, we can see some common ancestry between Chicken Run and Titan A.E.

Overall, judging from the Chicken Run code, at least, I wouldn’t really say Blitz had an engine at this time but rather some common libraries/code their games shared. Not all uncommon for the time. Eventually, they would go on to develop Blitz Tech but that seems to have come much later (curiously, the PC Gaming Wiki suggests their technology was called Babel Engine prior to 2009, but there’s no citation, and I’d wager that still likely postdates Titan A.E. and Chicken Run.)

Shadows

I’d posted about these on Mastodon back in April.

They just use sprites essentially projected to the ground plane from each part of the model, so it’s a really simple system but not something I’ve seen very commonly on the PSX. At least as of the code recycled in Chicken Run, there can be up to five shadows projected that are emitted from lights marked to cast shadows.

Loading Screens

The screen displayed when loading a level is denoted by its corresponding RAW file; for instance, you load up the Shooting Gallery level, and it’ll use EXPLORE/SHOOTGAL.RAW for the loading screen. Despite this, most of the levels unfortunately just use the same duplicate image in this build of the game. It’s likely this probably would’ve changed had the game continued development.

The End

That concludes this poking and prodding session for now. I’m hopeful that at some stage we might see more show up in relation to this game.

If you want to support what we do, we’re not accepting donations at this time, but any feedback is appreciated. Feel free to join our Discord or leave a comment letting us know what you think! Alternatively, you can follow me on Mastodon for any of my general rambling.

In the meantime, I shall vanish back into my hiding place.

bye

Resources

Some of these are more specific to the film than the game itself, but I thought they were still interesting, and so I’ve included them here.