
// comment out to link into the frogger testbed
// leave in to link into the ActionMan2 testbed
#define USE_NEW_LIBS

#ifdef USE_NEW_LIBS
// the "depend" batch doesn't like non-existant headers
	#include "global.h"
#else
//	#include "core.h"
	extern int	biggestVertexModel;
	#define utilStr2CRC(a) str2CRC(a);
#endif


#include "bff_load.h"
#include "map_draw.h"

#include "loadlnd.h"

#include "platform.h"
#include "overlays.h"
#include "scenics.h"
#include "main.h"
#include "collide.h"
#include "camera.h"
#include "sound.h"
#include "inventory.h"
#include "enemies.h"
#include "route.h"
#include "custom.h"
#include "fma.h"

// Keep in an included .h file, jim
//#ifdef _WINDOWS
//#include "pcHardware3D.h"
//#endif

/*
Quickie guide to BFF files:-

 A BFF is, simply, a collection of data segments, prefixed by a TYPE, a SIZE, and an ID (the CRC of a name)
Data of many different types can just be shoved together into one file.
After loading, the program scans through the list, checks the type of each segment, and calls a "link" routine
for that type, which basically fills in any required pointers.

 Then there's a little routine saying "find a bff segment of "such&such type, with this name-CRC"

*/


// A Macro and a variable that were used in Glover, but aren't in Frogger2
// (But here they are anyway)

//#define BFF_CorrectPointer(header,pointer) {if(pointer) (pointer) = ADD2POINTER((header),((int)(pointer)));}
//BFF_Header *generic_bff;



// Adjust 0...128 texture coordinates into real ones

ULONG FMA_TmapCorrect(ULONG x,ULONG w,ULONG per)
{
	ULONG calc;
	ULONG result;

	calc=(w*per)>>7; 
 	result=calc+x;

	if(result==256)	// Work around the silly playstation's lack of ability to access the top pixel of a texture
	{
//		DB("tmap end correction\n");
		result = 255;
	}

 	if (result>255)
	{
//		DB("tmap correction OV: x=%d,w=%d,per=%d\n",x,w,per);
		result=8;
	}
 	return result;
}

// ===================================================================================
// Turn the offsets & spritex numbers in a FMA MESH BFF-segment data into pointers

void BFF_Link_FMA_Mesh(FMA_MESH_HEADER *mesh)
{
//	unsigned long *tmap_crcs;
#ifdef USE_NEW_LIBS
	TextureType **tmaps;
	TextureType *tmap;
#else
	SPRITEX **tmaps;
	SPRITEX *tmap;
#endif

	int i;


	tmaps = ADD2POINTER(mesh, (int)mesh->tmap_crcs);

	mesh->gt3s = ADD2POINTER(mesh,(int)mesh->gt3s);
	mesh->gt4s = ADD2POINTER(mesh,(int)mesh->gt4s);
	mesh->verts = ADD2POINTER(mesh,(int)mesh->verts);

	mesh->g3s = ADD2POINTER(mesh,(int)mesh->g3s);
	mesh->g4s = ADD2POINTER(mesh,(int)mesh->g4s);

// Yuk, oh Yuk, oh Yuk, the PSI library does a REALLOC on it's
// transformed vertex list. So we have to do it too.
// No longer necessary, coz Andy S only does the alloc afterwards. Phew.
	if(mesh->n_verts > biggestVertexModel)
	{
#ifdef USE_NEW_LIBS

#else
		DB("Horrible realloc of the transformed vertex buffers to %d\n",mesh->n_verts);
		DB("This will go away when using Andy Sid's version of the PSI load\n");
		if (transformedVertices!=0)
		{
			 FREE(transformedVertices);
			 FREE(transformedDepths);
			 FREE(transformedNormals);
		}
		transformedVertices = (long*)MALLOC( mesh->n_verts*sizeof(SVECTOR),"verts");
		transformedDepths = (long*)MALLOC( mesh->n_verts*sizeof(SVECTOR),"depths");
		transformedNormals = (VERT*)MALLOC( mesh->n_verts*sizeof(VERT),"normals");
#endif
		biggestVertexModel = mesh->n_verts;
	}


// Having done that, let's get down to the real work of converting the loaded memory block
// into a usable one with pointers and real-world texture info.


// Number one - We've got a list of texture CRC's to convert into pointers.
//	printf("Mesh has %d textures\n",mesh->n_tmaps);
	for(i = 0; i < mesh->n_tmaps; i++)
	{
		tmap = textureFindCRCInAllBanks((unsigned long)tmaps[i]);
		if(!tmap)
		{
			DB("World Texture (crc &%8x) not found\n",(unsigned long)tmaps[i]);
		}
		tmaps[i] = tmap;
	}

// Number two. Every polygon contains a texture number, which needs converting
// into a clut & a tpage. They also need their tu and tv adjusting.
	for(i = 0; i < mesh->n_gt3s; i++)
	{
		tmap = tmaps[mesh->gt3s[i].tpage];
		if(tmap)
		{

#ifndef _WINDOWS

			mesh->gt3s[i].tpage = tmap->tpage | mesh->gt3s[i].clut;	// the clut is used to pass in the SEMITRANS_SEMI/ADD/SUBTRACT
			mesh->gt3s[i].clut = tmap->clut;


			mesh->gt3s[i].u0 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt3s[i].u0);
			mesh->gt3s[i].v0 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt3s[i].v0);
			mesh->gt3s[i].u1 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt3s[i].u1);
			mesh->gt3s[i].v1 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt3s[i].v1);
			mesh->gt3s[i].u2 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt3s[i].u2);
			mesh->gt3s[i].v2 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt3s[i].v2);
#else
			mesh->gt3s[i].u0 = ((((long)tmap->w << 16) / 128) * mesh->gt3s[i].u0) >> 16;
			mesh->gt3s[i].v0 = ((((long)tmap->h << 16) / 128) * mesh->gt3s[i].v0) >> 16;
			mesh->gt3s[i].u1 = ((((long)tmap->w << 16) / 128) * mesh->gt3s[i].u1) >> 16;
			mesh->gt3s[i].v1 = ((((long)tmap->h << 16) / 128) * mesh->gt3s[i].v1) >> 16;
			mesh->gt3s[i].u2 = ((((long)tmap->w << 16) / 128) * mesh->gt3s[i].u2) >> 16;
			mesh->gt3s[i].v2 = ((((long)tmap->h << 16) / 128) * mesh->gt3s[i].v2) >> 16;

			mesh->gt3s[i].tpage = tmap->tpage | mesh->gt3s[i].clut;	// the clut is used to pass in the SEMITRANS_SEMI/ADD/SUBTRACT
			mesh->gt3s[i].clut = tmap->clut;
#endif
// Multiply the vertex numbers by 4.
// A nice little optimisation when looking the coordinates up
// (this code now moved to the PC compiler end)
//			mesh->gt3s[i].vert0 *= 4;
//			mesh->gt3s[i].vert1 *= 4;
//			mesh->gt3s[i].vert2 *= 4;
		}
	}

	for(i = 0; i < mesh->n_gt4s; i++)
	{
		tmap = tmaps[mesh->gt4s[i].tpage];
//		DB("tmap x,y,w,h = %d %d %d %d\n",tmap->x,tmap->y,tmap->w,tmap->h);
		if(tmap)
		{
#ifndef _WINDOWS
			mesh->gt4s[i].tpage = tmap->tpage | mesh->gt4s[i].clut;
			mesh->gt4s[i].clut = tmap->clut;


			mesh->gt4s[i].u0 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt4s[i].u0);
			mesh->gt4s[i].v0 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt4s[i].v0);
			mesh->gt4s[i].u1 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt4s[i].u1);
			mesh->gt4s[i].v1 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt4s[i].v1);
			mesh->gt4s[i].u2 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt4s[i].u2);
			mesh->gt4s[i].v2 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt4s[i].v2);
			mesh->gt4s[i].u3 = FMA_TmapCorrect(tmap->x,tmap->w,mesh->gt4s[i].u3);
			mesh->gt4s[i].v3 = FMA_TmapCorrect(tmap->y,tmap->h,mesh->gt4s[i].v3);
#else

			mesh->gt4s[i].u0 = ((((long)tmap->w << 16) / 128) * mesh->gt4s[i].u0) >> 16;
			mesh->gt4s[i].v0 = ((((long)tmap->h << 16) / 128) * mesh->gt4s[i].v0) >> 16;
			mesh->gt4s[i].u1 = ((((long)tmap->w << 16) / 128) * mesh->gt4s[i].u1) >> 16;
			mesh->gt4s[i].v1 = ((((long)tmap->h << 16) / 128) * mesh->gt4s[i].v1) >> 16;
			mesh->gt4s[i].u2 = ((((long)tmap->w << 16) / 128) * mesh->gt4s[i].u2) >> 16;
			mesh->gt4s[i].v2 = ((((long)tmap->h << 16) / 128) * mesh->gt4s[i].v2) >> 16;
			mesh->gt4s[i].u3 = ((((long)tmap->w << 16) / 128) * mesh->gt4s[i].u3) >> 16;
			mesh->gt4s[i].v3 = ((((long)tmap->h << 16) / 128) * mesh->gt4s[i].v3) >> 16;
		
			mesh->gt4s[i].tpage = tmap->tpage | mesh->gt4s[i].clut;	// the clut is used to pass in the SEMITRANS_SEMI/ADD/SUBTRACT
			mesh->gt4s[i].clut = tmap->clut;

#endif

//			mesh->gt4s[i].vert0 *= 4;
//			mesh->gt4s[i].vert1 *= 4;
//			mesh->gt4s[i].vert2 *= 4;
//			mesh->gt4s[i].vert3 *= 4;
		}
	}

	// ================================================================================
	// JS: Because of the huuuuge penalties incurred on the PC when changing textures
	// we're going to create a biiiig list of polys using each of the textures in the
	// mesh and store a pointer to this list in dummy1+dummy2.  
	// ================================================================================
#ifdef _WINDOWS
	PCH3D_CreateList(mesh);
#endif


// Tra-la. The BFF memory-image is now a usable, drawable, thing.

}


// New version mesh... Oooohboy...
void BFF_Link_FMA_Mesh4(FMA_MESH_HEADER *mesh)
{
	TextureType **tmaps;
	TextureType *tmap;
	int i;
	FMA4_GT4 *gt4;
	FMA4_GT3 *gt3;

	tmaps = ADD2POINTER(mesh, (int)mesh->tmap_crcs);

	mesh->gt3s = ADD2POINTER(mesh,(int)mesh->gt3s);
	mesh->gt4s = ADD2POINTER(mesh,(int)mesh->gt4s);
	mesh->verts = ADD2POINTER(mesh,(int)mesh->verts);

	mesh->g3s = ADD2POINTER(mesh,(int)mesh->g3s);
	mesh->g4s = ADD2POINTER(mesh,(int)mesh->g4s);

	gt3 = (FMA4_GT3 *)mesh->gt3s;
	gt4 = (FMA4_GT4 *)mesh->gt4s;
// Having done that, let's get down to the real work of converting the loaded memory block
// into a usable one with pointers and real-world texture info.

// Number one - We've got a list of texture CRC's to convert into pointers.
//	printf("Mesh has %d textures\n",mesh->n_tmaps);
	for(i = 0; i < mesh->n_tmaps; i++)
	{
		tmap = textureFindCRCInAllBanks((unsigned long)tmaps[i]);
		if(!tmap)
		{
			DB("World Texture (crc &%8x) not found\n",(unsigned long)tmaps[i]);
		}
		tmaps[i] = tmap;
	}

// Number two. Every polygon contains a texture number, which needs converting
// into a clut & a tpage. They also need their tu and tv adjusting.
	for(i = 0; i < mesh->n_gt3s; i++)
	{
		tmap = tmaps[gt3[i].poly[0].tpage];
		if(tmap)
		{

#ifndef _WINDOWS
			gt3[i].poly[0].tpage = gt3[i].poly[1].tpage = tmap->tpage | gt3[i].poly[0].clut;	// the clut is used to pass in the SEMITRANS_SEMI/ADD/SUBTRACT
			gt3[i].poly[0].clut = gt3[i].poly[1].clut = tmap->clut;

			gt3[i].poly[0].u0 = gt3[i].poly[1].u0 = FMA_TmapCorrect(tmap->x,tmap->w,gt3[i].poly[0].u0);
			gt3[i].poly[0].v0 = gt3[i].poly[1].v0 = FMA_TmapCorrect(tmap->y,tmap->h,gt3[i].poly[0].v0);
			gt3[i].poly[0].u1 = gt3[i].poly[1].u1 = FMA_TmapCorrect(tmap->x,tmap->w,gt3[i].poly[0].u1);
			gt3[i].poly[0].v1 = gt3[i].poly[1].v1 = FMA_TmapCorrect(tmap->y,tmap->h,gt3[i].poly[0].v1);
			gt3[i].poly[0].u2 = gt3[i].poly[1].u2 = FMA_TmapCorrect(tmap->x,tmap->w,gt3[i].poly[0].u2);
			gt3[i].poly[0].v2 = gt3[i].poly[1].v2 = FMA_TmapCorrect(tmap->y,tmap->h,gt3[i].poly[0].v2);
#else
// Let Jim sort this one out later
#endif
		}
	}

	for(i = 0; i < mesh->n_gt4s; i++)
	{
		tmap = tmaps[gt4[i].poly[0].tpage];
//		DB("tmap x,y,w,h = %d %d %d %d\n",tmap->x,tmap->y,tmap->w,tmap->h);
		if(tmap)
		{
#ifndef _WINDOWS
			gt4[i].poly[0].tpage = gt4[i].poly[1].tpage = tmap->tpage | gt4[i].poly[0].clut;
			gt4[i].poly[0].clut = gt4[i].poly[1].clut = tmap->clut;

			gt4[i].poly[0].u0 = gt4[i].poly[1].u0 = FMA_TmapCorrect(tmap->x,tmap->w,gt4[i].poly[0].u0);
			gt4[i].poly[0].v0 = gt4[i].poly[1].v0 = FMA_TmapCorrect(tmap->y,tmap->h,gt4[i].poly[0].v0);
			gt4[i].poly[0].u1 = gt4[i].poly[1].u1 = FMA_TmapCorrect(tmap->x,tmap->w,gt4[i].poly[0].u1);
			gt4[i].poly[0].v1 = gt4[i].poly[1].v1 = FMA_TmapCorrect(tmap->y,tmap->h,gt4[i].poly[0].v1);
			gt4[i].poly[0].u2 = gt4[i].poly[1].u2 = FMA_TmapCorrect(tmap->x,tmap->w,gt4[i].poly[0].u2);
			gt4[i].poly[0].v2 = gt4[i].poly[1].v2 = FMA_TmapCorrect(tmap->y,tmap->h,gt4[i].poly[0].v2);
			gt4[i].poly[0].u3 = gt4[i].poly[1].u3 = FMA_TmapCorrect(tmap->x,tmap->w,gt4[i].poly[0].u3);
			gt4[i].poly[0].v3 = gt4[i].poly[1].v3 = FMA_TmapCorrect(tmap->y,tmap->h,gt4[i].poly[0].v3);
#else
#endif
		}
	}

	// ================================================================================
	// JS: Because of the huuuuge penalties incurred on the PC when changing textures
	// we're going to create a biiiig list of polys using each of the textures in the
	// mesh and store a pointer to this list in dummy1+dummy2.  
	// ================================================================================
#ifdef _WINDOWS
	PCH3D_CreateList(mesh);
#endif


// Tra-la. The BFF memory-image is now a usable, drawable, thing.

}

// Turn the FMA CRCs in a FMA MAP BFF-segment data into pointers
// This is an easy one. The world just has a list of mesh-name CRC's, which
// need converting into pointers. This is done by BFF_Finding them, even
// though they're actually part of the same file as the FMA_WORLD!
void BFF_Link_FMA_World(FMA_WORLD *world)
{
	int i;
	unsigned long *crcs;
	FMA_MESH_HEADER *mesh;

#define meshes ((FMA_MESH_HEADER **)crcs)

	meshes = ADD2POINTER(world, sizeof(FMA_WORLD));
	for(i = 0; i < world->n_meshes;i++)
	{
		mesh = (void *)BFF_FindObject(BFF_FMA_MESH_ID,crcs[i]);
//		if(!mesh)
//		{
//			mesh = (void *)BFF_FindObject(BFF_FMA_SKYMESH_ID,crcs[i]);
//		}
		if(!mesh)
		{
			DB("Mesh %d of the world1 not found (crc = &%8x)\n",i,crcs[i]);
		}
		meshes[i] = mesh;
	}
#undef meshes
}

// ok, here we go with the new version world linker
void BFF_Link_FMA_World2(FMA_WORLD2 *world)
{
	int i;
//	unsigned long *crcs;
//	FMA_WORLDENTRY *entries;
	FMA_MESH_HEADER *mesh;
	WORLD_MESH_INSTANCE *entries;

//#define meshes ((FMA_MESH_HEADER **)crcs)

	entries = ADD2POINTER(world, sizeof(FMA_WORLD));
	for(i = 0; i < world->n_meshes;i++)
	{
//		DB("Mesh %d of the world2 =  (crc = &%8x)\n",i,(int)entries[i].mesh);
//		DB("pos = %d %d %d\n",entries[i].posx,entries[i].posy,entries[i].posz);

		mesh = (void *)BFF_FindObject(BFF_FMA_MESH_ID,(int)entries[i].mesh);
		if(!mesh)
			mesh = (void *)BFF_FindObject(BFF_FMA_MESH4_ID,(int)entries[i].mesh);
//		if(!mesh)
//		{
//			mesh = (void *)BFF_FindObject(BFF_FMA_SKYMESH_ID,crcs[i]);
//		}
		if(!mesh)
		{
			DB("Mesh %d of the world2 not found (crc = &%8x)\n",i,(int)entries[i].mesh);

// Fix to avoid potential crash problems
			memcpy(&entries[i],&entries[i+1],(world->n_meshes-1-i) * sizeof(WORLD_MESH_INSTANCE));
			i--;
			world->n_meshes--;
		}
		else
		{
			int crc;
//			FMA_MESH_HEADER *mesh2;

//			crc = (int)entries[i].mesh;
//			crc = crc + 1;

			if(entries[i].subdiv)
				entries[i].subdiv = &entries[(int)entries[i].subdiv];
/*
			// A mesh with crc (n+1) is an instance created by the mesh subdivision process.
			// We need to link it to the original to deal with, for example, meshswaps using landscape tags
			mesh2 = (void *)BFF_FindObject(BFF_FMA_MESH_ID,crc);
			if(mesh2)
			{
//				utilPrintf("Subdiv mesh found\n");
				mesh->subdiv = mesh2;

			}
			else
			{
				mesh->subdiv = NULL;
			}
*/
//			utilPrintf("World Mesh Instance %d. CRC = &%8x\n",i,(int)entries[i].mesh);

			entries[i].mesh = mesh;
	// The world builder moves lscape meshes so that (0,0,0)'s in the centre of them
	// However the LND parser doesn't know about this. So we need to do it now.

//			utilPrintf("  Original position = %d %d %d\n",entries[i].posx/8,-entries[i].posy/8,entries[i].posz/8);

			entries[i].posx += mesh->offsetx;
			entries[i].posy += mesh->offsety;
			entries[i].posz += mesh->offsetz;

/*
			utilPrintf("  Final, offset-adjusted, position = %d %d %d\n",entries[i].posx/8,-entries[i].posy/8,entries[i].posz/8);
			utilPrintf("  Mesh Bounds = %d -> %d, %d -> %d, %d -> %d\n",
				mesh->minx/8,mesh->maxx/8, mesh->miny/8,mesh->maxy/8, mesh->minz/8,mesh->maxz/8
				);
*/


//			utilPrintf("x = %d -> %d, y = %d -> %d, z = %d -> %d\n",mesh->minx,mesh->maxx,mesh->miny,mesh->maxy,mesh->minz,mesh->maxz);
//			utilPrintf("offsets = %d %d %d\n",entries[i].mesh->offsetx,entries[i].mesh->offsety,entries[i].mesh->offsetz);
		}


	}
#undef meshes
}



// ===================================================================================
//                GENERAL   BFF   HANDLING   STUFF
// ===================================================================================

#define MAX_BFF_FILES 4
BFF_Header *BFF_Pointers[MAX_BFF_FILES] = {NULL,NULL,NULL,NULL};
int BFF_Lens[MAX_BFF_FILES];

BFF_Header *BFF_FindObject(int id, unsigned long crc)
{
	int i;
	BFF_Header *work;
	for(i = 0; i < MAX_BFF_FILES; i++)
	{
		if(BFF_Pointers[i])
		{
//			printf(" scanning bff %d",i);
			work = BFF_Pointers[i];
			for(;;)
			{
				if(work->id == id && (crc == 0 || work->crc == crc))
				{
//					printf("\n");
					return work;
				}
				if(work->len & 0x80000000)
					break;
				work = ADD2POINTER(work,work->len);
			}
//			printf("\n");
		}
	}

//	if(id == BFF_PSI_ID)	//There's nothing checking for psi_id any more
//		return BFF_FindObject(BFF_PSI1_ID,crc);

	return NULL;	// yes there are occasions when we need to try to find things that aren't there
}

BFF_Header *BFF_FindNamedObject(int id, char *name)
{
	int i;
	unsigned long crc;

	BFF_Header *work;

	crc = utilStr2CRC(name);
	for(i = 0; i < MAX_BFF_FILES; i++)
	{
		if(BFF_Pointers[i])
		{
//			printf(" scanning bff %d",i);
			work = BFF_Pointers[i];
			for(;;)
			{

//				printf(".");
				if(work->id == id && (crc == 0 || work->crc == crc))
				{
//					printf("\n");
					return work;
				}
				if(work->len & 0x80000000)
					break;
				work = ADD2POINTER(work,work->len);
			}
//			printf("\n");
		}
	}
//	printf("null\n");
//	if(id == BFF_PSI_ID)	//There's nothing checking for psi_id any more
//		return BFF_FindObject(BFF_PSI1_ID,crc);

	return 0;	// yes there are occasions when we need to try to find things that aren't there
}

BFF_Header *BFF_LoadFile(char *filename)
{
	BFF_Header *addr;
	BFF_Header *work;
	BFF_Header *ending;
	int i;
	int lastfilelength;


	int bff_start_frame;
	int bff_mid_frame;
	int bff_end_frame;
	

// Call any routines that need their counters resetting or whatever...
	max_map_ot_depth = 760;
	fmaInfo = NULL;
	loop_ticker2 = 0;

// RFW - removed the Temp fix that was here which freed the streamed bank.
// (caused by memory leakage. oops)

	routefindPreBFF();






	bff_start_frame = frame;


//	memoryShow();
//	memoryShowStats();
// Oh lovely, Frogger's makefile didn't warn me about duff numbers of parameters being sent to functions...
#ifdef USE_NEW_LIBS
	addr = (void *)fileLoad(filename, &lastfilelength);
#else
	addr = (void *)fileLoad2(filename, &lastfilelength);
#endif
	if(!addr)
	{
		DB("WARNING: Couldn't open BFF!\n");
		return NULL;
	}

//	memoryShow();
//	memoryShowStats();

	bff_mid_frame = frame;

	ending = ADD2POINTER(addr, lastfilelength);

	work = addr;

// Using the file length, scan through the file and mark the last entry in the BFF file,
// because the "findobject" routines won't know the length.
	for(;;)
	{
/*
				printf("Scanning BFF class %d (%c%c%c%d), len &%8x\n",
					work->id,
					((char *)(&work->id))[0],
					((char *)(&work->id))[1],
					((char *)(&work->id))[2],
					(int)(((char *)(&work->id))[3]),
					work->len
					);

*/
		if((void *)ADD2POINTER(work,work->len) >= (void *)ending)
		{
			work->len |= 0x80000000;	// mark as the last one
			break;
		}
		work = ADD2POINTER(work,work->len);
	}

// Mark the BFF as being available for searching for objects.
// This needs to be done BEFORE linking, because some objects want to look up
// the addresses of other objects in the same file.
	for(i = 0; i < MAX_BFF_FILES; i++)
	{
		if(!BFF_Pointers[i])
		{
			BFF_Pointers[i] = addr;
			BFF_Lens[i] = lastfilelength;
			break;
		}
	}

// Essentially, convert any "offsets" in the loaded objects into "pointers", and
// any other work that needs doing to convert the loaded data into usable data.
// This means checking the type of the data, and calling a routine to "link"
// that type.
	work = addr;
//	i=0;
	for(;;)
	{
//		utilPrintf("Linking BFF segment %i\n", i++);
/*
				printf("Linking BFF class %d (%c%c%c%d)\n",
					work->id,
					((char *)(&work->id))[0],
					((char *)(&work->id))[1],
					((char *)(&work->id))[2],
					(int)(((char *)(&work->id))[3])
					);
*/
//	memoryShow();


		switch(work->id)
		{
			case BFF_FMA_MESH_ID:
//				DB("Mesh 1\n");
				BFF_Link_FMA_Mesh((FMA_MESH_HEADER *)work);
				break;
			case BFF_FMA_MESH4_ID:
//				DB("->Mesh 4<-\n");
				BFF_Link_FMA_Mesh4((FMA_MESH_HEADER *)work);
				break;
//			case BFF_FMA_SKYMESH_ID:
//				BFF_Link_FMA_SkyMesh((FMA_SKYMESH_HEADER *)work);
//				break;
			case BFF_FMA_WORLD_ID:
				BFF_Link_FMA_World((FMA_WORLD *)work);
				break;
			case BFF_FMA_WORLD2_ID:
				BFF_Link_FMA_World2((FMA_WORLD2 *)work);
				break;

			case BFF_PSI_ID:
//				BFF_Link_PSI((BFF_PsiStr *)work);
				DB("The Version-0 PSI code has been removed! Use BFF_PSI ... -version 1\n");
				ASSERT(0);
				break;

			case BFF_PSI1_ID:
				BFF_Link_PSI1((PSIMODEL *)work);
				break;

			case BFF_PUZ_ID:
				puzzleLinkBFF((PUZZ_LIST *)work);
				break;

			case BFF_CAMINFO_ID:
				caminfoLinkBFF((CAMINFO *)work);
				break;

			case BFF_TEXANIM_ID:
				texanimLinkBFF((TEXANIMLIST *)work);
				break;
			case BFF_LEVINFO_ID:
				levinfoLinkBFF((LEVINFO *)work);	// at the moment, this is in puzzles
				break;

			case BFF_SCENIC_ID:
				scenicLinkBFF((SCENIC_BFF *)work);
				break;


//			case BFF_VH_ID:	// This will be redundant as soon as there's no levels with old data in
//				break;


			case BFF_SBH_ID:
			   //MIKEY	soundLinkBFF((SOUND_BFF *)work);	// This assumes that a matching SBB has already been loaded
				break;


			case BFF_PLATFORM_ID:
				platformLinkBFF((PLATFORMDEF *)work);
				break;

			case BFF_STRIG_ID:
				platformLinkSoundTriggerBFF((SOUNDTRIGGERLIST *)work);
				break;


			case BFF_ENEMY_ID:
				enemyLinkBFF((ENEMYBFFLIST *)work);
				break;

			case BFF_ROUTEFIND_ID:
				routefindLinkBFF((ROUTEFIND_STR *)work);
				break;

			case BFF_COLL_ID:
				collideLinkBFF((COLL_BFF *)work);
				break;

			case BFF_FMAINFO_ID:
				fmaLinkBFF( (FMAINFO *)work);
				break;

			default:
				utilPrintf("Unknown or out-of-date BFF class %d (%c%c%c%d)\n",
					work->id,
					((char *)(&work->id))[0],
					((char *)(&work->id))[1],
					((char *)(&work->id))[2],
					(int)(((char *)(&work->id))[3])
					);
				CRASH;
		}

		if(work->len & 0x80000000)
			break;
		work = ADD2POINTER(work,work->len);
//		DB("ok\n");
	}
//	DB("linking all done\n");

	bff_end_frame = frame;
	DB("BFF Durations: load %d, process %d\n",(bff_mid_frame - bff_start_frame),(bff_end_frame - bff_mid_frame));

	return addr;
}

// Releasing a file's dead trivial. Just free the memory, and mark it as
// not to be scanned in future "findobject"s

void BFF_ReleaseFile(BFF_Header *header)
{
	int i;
	BFF_Header *work;

	if(!header)
		return;
	for(i = 0; i < MAX_BFF_FILES; i++)
	{
		if(BFF_Pointers[i] == header)
		{
			BFF_Pointers[i] = NULL;
			break;
		}
	}

// Run through the file doing any unhooking any links between object in the BFF, and the outside world.
// (Actually, there aren't that many)

	work = header;
	for(;;)
	{
		switch(work->id)
		{
			case BFF_SBH_ID:
			  //MIKEY	soundUnlinkBFF((SOUND_BFF *)work);
				break;

		}

		if(work->len & 0x80000000)
			break;
		work = ADD2POINTER(work,work->len);
	}



	FREE(header);
}

// A little diagnostic check that you can use to ensure that you've not
// kept a pointer to a BFF object in a file that has since been discarded.
// In practice, this should never be used, natch.

int BFF_IsInBFF(void *addr)
{
	void *base;
	int i;

	for(i = 0; i < MAX_BFF_FILES; i++)
	{
		base = BFF_Pointers[i];
		if(base)
		{
			if(base <= addr && ADD2POINTER(base,BFF_Lens[i]) > addr)
				return 1;
			break;
		}
	}
	return 0;
}


