/******************************************************************************************
	CRTest PS   (c) 1999-2001 ISL

	main.c:			Top level shell
******************************************************************************************/


#include "global.h"
#include "timer.h"
#include "main.h"
#include "viewer.h"
#include "menu.h"
#include "snapshot.h"
#include "collide.h"
#include "overlays.h"
#include "sound.h"
#include "sptstream.h"
#include "scenics.h"
#include "enemies.h"
#include "inventory.h"
#include "charactr.h"
#include "card.h"
#include "options.h"
#include "map_play.h"
#include "loadsnd.h"
#include "pad.h"
#include "overlays.h"
#include "maths.h"
#include "demo.h"
//#include "particle.h"
#include "language.h"
#include "dualshock.h"
#include "overlays.h"
#include "pause.h"
#include "credits.h"

#include "fireworks.h"
#include "seesaw.h"
#include "catapult.h"
#include "engine.h"
#include "construct.h"
#include "egglaying.h"
#include "wings.h"
#include "crate.h"
#include "dogchase.h"

#define MAX_PRIMS	3000

extern int FRAMESEC;
void mapplayCycle(void);
void fireworksCycle(void);
void FMAtestCycle(void);
void inventoryCycle(void);
extern int demoshell_leaving;

#ifndef _WINDOWS

extern unsigned short	*VRAMpalBlock;

#include "gametext.h"

extern char __bss_orgend[];
extern char _vid_ovl_bss_orgend[];
extern char _sub1_ovl_bss_orgend[];
extern char _sub2_ovl_bss_orgend[];
extern char _sub4_ovl_bss_orgend[];
extern char _deb_ovl_bss_orgend[];

extern char _vid_ovl_obj[];
extern char _sub1_ovl_obj[];
extern char _sub2_ovl_obj[];
extern char _sub4_ovl_obj[];
extern char _deb_ovl_obj[];

#if GOLDCD==0
// Turn on if you're hunting for crash problems :)
// (It does a memory compare of 0...1023 with its original contents,
//  at cycle-routine-changing time)
 #define NULL_POINTER_CHECK
#endif



UBYTE	*version = VERSIONSTR;
UBYTE	quitMainLoop;
UBYTE	hardReset = 1;
ULONG	RAMstart, RAMsize;
ULONG	frame;
ULONG	expected_frame;

UBYTE	memoryCardCheck;

static RECT VRAMarea = {0,0,1024,512};

// Define as two until we drop to three later on ;)
#define  FRAMERATE 2

displayPageType displayPage[2];

// note - I'm now making heavy use of the cache in the rendering routines, so it's not
// available for general variable use any more
//displayPageType *currentDisplayPage __attribute__ ((section("cachedata"))) = 0;
displayPageType *currentDisplayPage;

// main loop counter. Never zeroed
ULONG loop_ticker = 0;
// loop counter that the game can zero when it starts a level, or something
ULONG loop_ticker2 = 0;
// loop counter that's paused during pauses (used for timing acts) It's part of the save structure
char playing_ticker_enabled = 1;

psFont *font;

#if TERRIT==0
  int		gameTextLang = LANG_UK;	// TBD - US
#endif
#if TERRIT==1
 #ifdef NO_UK_IN_EUROPE
   int		gameTextLang = LANG_D;
 #else
   int		gameTextLang = LANG_UK;
 #endif
#endif
#if TERRIT==2
  int		gameTextLang = LANG_UK;
#endif

GsRVIEW2	mycamera;
int padRead=0;
int cycle_loops;
int eidos_debugs = 0;

VOID_SUBROUTINE draw_routine = NULL;
VOID_SUBROUTINE cycle_routine = NULL;

#ifdef _WINDOWS
void	*gameTextStr[TEXTSTR_MAX];
#endif


/**************************************************************************
	FUNCTION:	vsyncCallback()
	PURPOSE:	Vsync interrupt callback
	PARAMETERS:	
	RETURNS:	
**************************************************************************/

static void vsyncCallback()
{
	frame++;
#if GOLDCD==0
	asm("break 1024");
#endif

	if(overlayLoadingDisplay)
	{
		// Deal with swapping a pad over during loading.
		// (Which, previously, would fail to realise that a dualshock had been swapped to a normal one. E2014)
		// (The normal cycle's "handle input" won't be called when the overlayloadingdisplay's going on,
		// See "overlayLoadingEnd")
		padHandleInput();
		overlayLoadingVsync();
	}

	SpuFlush(SPU_EVENT_ALL);
}


/**************************************************************************
// zero-page write checks
**************************************************************************/

#ifdef NULL_POINTER_CHECK

unsigned char zp_checklist[1024];

void zpCheckInit()
{
//	*(unsigned char *)0xe4 = 0;		// Reset so we can find the change

	FMEMCPY(&zp_checklist[0],NULL,1024);
}

void zpCheck2(char *id)
{
	int i;
	for(i = 0; i < 1024; i++)
	{
		if(*(unsigned char *)i != zp_checklist[i])
		{
			utilPrintf("zp changed &%8x from &%2x to &%2x. id = <%s>\n",i,(int)zp_checklist[i],(int)*(unsigned char *)i,id);
			CRASH;
		}
	}
	memoryCheck();

}

#endif

/**************************************************************************
	FUNCTION:	videoInit()
	PURPOSE:	Initialise video mode/GPU
	PARAMETERS:	Order table depth, max number of GT4 primitives needed or 0
	RETURNS:	
**************************************************************************/
void videoInit(int otDepth, int maxPrims)
{
	if (maxPrims>0)
	{
		displayPage[0].ot = MALLOC(otDepth*4);
		displayPage[1].ot = MALLOC(otDepth*4);
		displayPage[0].primPtr = displayPage[0].primBuffer = MALLOC(maxPrims*sizeof(POLY_GT4));
		displayPage[1].primPtr = displayPage[1].primBuffer = MALLOC(maxPrims*sizeof(POLY_GT4));
		FMEMZERO(displayPage[0].primBuffer,maxPrims*sizeof(POLY_GT4));
		FMEMZERO(displayPage[1].primBuffer,maxPrims*sizeof(POLY_GT4));
	}

// Following Chris Swan's comments about preventing the screen from flickering on the PSX logo...
// This is what Glover did at video init
	VSync(0);
//	ResetGraph(1);
	ResetGraph(0);
	SetGraphDebug(0);    /* Turn GPU debugging off. */
	SetDispMask(0);


// And this is just to make sure it's clear when we turn it back on!
	VSync(0);
	ClearImage2(&VRAMarea, 0,0,0);
	DrawSync(0);


// Okay, back to business.

#if PALMODE==1
	SetVideoMode(MODE_PAL);
	GsInitGraph(512,273, 4,1,0);
#else
	SetVideoMode(MODE_NTSC);
	GsInitGraph(512,240, 4,1,0);
#endif



 	GsDefDispBuff(0,256,0,0);
	GsInit3D();
	SetGeomOffset(0, 0);

	SetDefDrawEnv(&displayPage[0].drawenv, 0,   0, 512,256);
	SetDefDrawEnv(&displayPage[1].drawenv, 0, 256, 512,256);
	SetDefDispEnv(&displayPage[0].dispenv, 0, 256, 512,256);
	SetDefDispEnv(&displayPage[1].dispenv, 0,   0, 512,256);

  	displayPage[0].drawenv.ofs[0] = displayPage[1].drawenv.ofs[0] = 512/2;

// okay, this one's the offset of poly coords to screen ones
  	displayPage[0].drawenv.ofs[1] = 120+PALMODE*8;
	displayPage[1].drawenv.ofs[1] = 256+120+PALMODE*8;

	displayPage[0].drawenv.isbg = displayPage[1].drawenv.isbg = 1;

#if PALMODE==1
  		displayPage[0].dispenv.screen.y =displayPage[1].dispenv.screen.y =18;  //moves whole screen up & down! bigger=down
  		displayPage[0].dispenv.screen.h =displayPage[1].dispenv.screen.h =256;	// SetDefDispEnv always sets it to 240
  		displayPage[0].dispenv.screen.x =displayPage[1].dispenv.screen.x =2;  //moves whole left/right
#else
		displayPage[0].dispenv.screen.y = displayPage[1].dispenv.screen.y = 3;
#endif
	GsSetProjection(950);

	
	SetDispMask(1);	// screen on again
	VSync(0);
}

#ifdef DEBUG
// Find out how much of the packet space's been used
void mainFindHighWaterMark()
{
	int i;
	int maxPrims = MAX_PRIMS;
	int *a = (int *)(displayPage[0].primBuffer);
	int *b = (int *)(displayPage[1].primBuffer);
	a = ADD2POINTER(a,(maxPrims * sizeof(POLY_GT4)));
	b = ADD2POINTER(b,(maxPrims * sizeof(POLY_GT4)));
	for(i = maxPrims*sizeof(POLY_GT4) / 4; i >= 0; i--)
	{
		a--;
		b--;
//		DB("i=%d\n",i);
		if((*a) || (*b))
			break;
	}
	DB("High Water Mark = %d / %d\n",i / (sizeof(POLY_GT4) / 4), maxPrims);
}
#endif

// CPW: checks whether or not a pointer is valid
void verifyPointer(void *ptr, size_t size, char *sFi, int line)
{
	unsigned long int RAMend = RAMstart + RAMsize;
	unsigned long int p = (int)ptr;
	unsigned long int rp;

	char *sMess = "VERIFY FAILED:";

	// CPW: handle structures
	if(size > 4)
		size = 4;

	// CPW: mustn't forget void pointers
	if(size == 0)
		size = 1;

	rp = p;
	p=p&0x7fffffff;

	if(!p)
	{
		DB("%s, %d: %s NULL pointer\n", sFi, line, sMess);
		CRASH;
	}

	if(p != ((p/size)*size))
	{
		DB("%s, %d: %s %08x, %d, Alignment error\n", sFi, line, sMess, rp, size);
		CRASH;
	}

	if((p > RAMend) && (p < 0x1f800000) && (p > 0x1f8003ff))
	{
		DB("%s, %d: %s %08x, Invalid address\n", sFi, line, sMess, rp);
		CRASH;
	}

	// CPW: TBD - Check linker areas, and MALLOC area.
}


void mainDebugNumbers(void)
{
#if (EIDOS_DEBUG_STUFF==1) || (defined DEBUG )
	int y;
	char temp[128];
	int tot;

	y = -100;

	if(padData.debounce[4] & PAD_R2)
	{
		eidos_debugs = !eidos_debugs;
	}
	if(eidos_debugs)
	{
		// Put the "total" timer up on screen
		char temp[128];
		int tot;

		sprintf(temp,"%s",__DATE__);
		fontPrint(font,150-60,y,temp,128,100,53);
		y += 12;
#if PALMODE==1
		sprintf(temp, "PAL");
#else
		sprintf(temp,"NTSC");
#endif
		fontPrint(font,150-50,y,temp,128,100,53);
		y += 12;

		fontPrint(font, 150-50,y, sLevelName, 32,128,128);
		y += 12;


		if(characterVars.pMainChar)
		{
			sprintf(temp,"%d %d %d",
				(int)characterVars.pMainChar->pEnemy->pos.vx>>3,
				(int)characterVars.pMainChar->pEnemy->pos.vy>>3,
				(int)characterVars.pMainChar->pEnemy->pos.vz>>3
				);
			fontPrint(font, 150-80,y, temp, 32,128,128);
			y += 12;
		}

		sprintf(temp,"XA %d",music_playing);
		fontPrint(font, 150-50,y, temp, 32,128,64);
		y += 12;

		tot = (100*prevTimer[TIMER_TOTAL])/((PALMODE)?(620):(520));
		sprintf(temp, "Tim: %d", (int)loop_ticker2);
		fontPrint(font, 150-50,y, temp, 128,128,64);
		y += 12;
#ifdef DEBUG

		// Put the "total" timer up on screen
		tot = (100*prevTimer[TIMER_TOTAL])/((PALMODE)?(620):(520));
		sprintf(temp, "%d (%d%%)", prevTimer[TIMER_TOTAL], tot);
		if(tot > 100)
			fontPrint(font, 150-50,y, temp, 128,0,0);
		else
			fontPrint(font, 150-50,y, temp, 64,100,64);
		y += 12;

// Cycs is always 1 when the debug location stuff's on-screen
//		sprintf(temp,"Cycs:%d",cycle_loops);
//		fontPrintCentred(font, 150,y, temp, 32,128,128);
//		y += 12;

#endif	// debug
	}
	else
	{
#ifdef DEBUG
		sprintf(temp,"Cycs:%d",cycle_loops);
		fontPrintCentred(font, 150,y, temp, 32,128,128);
		y += 12;

#ifdef POLY_COUNTERS
		{
			int tot;
			int i;

			tot = 0;
			for(i =0; i < N_POLY_COUNTERS; i++)
			{
				tot += polycounts[i];
			}

			sprintf(temp,"Map:%4d",polycounts[MAP_POLYS]);
			fontPrint(font, 150,y, temp, 128,100,60);
			y += 12;
			sprintf(temp,"Psi:%4d",polycounts[MODEL_POLYS]);
			fontPrint(font, 150,y, temp, 128,100,60);
			y += 12;
			sprintf(temp,"Tot %4d",tot);
			fontPrint(font, 150,y, temp, 128,100,60);
			y += 12;
			for(i =0; i < N_POLY_COUNTERS; i++)
			{
				polycounts[i] = 0;
			}
		}
#endif	// polycounters

#endif
	}
#endif	// eidos-debug
	timerDisplay();

}


// ===================================================================
//           "Removing a pad" checks
//      (Broadly similar to a PAUSE routine, but with fewer options)
// ===================================================================
int pad_removed_count = 0;
int ignore_pad_removal = 0;	// Set to 20 by memory card access and initialisation
int pad_removed_looptick;

VOID_SUBROUTINE old_draw_routine_disconnect;
VOID_SUBROUTINE old_cycle_routine_disconnect;

extern TextureType *pause_wood;

void padRemovedDraw()
{
	int col;
	if(pReadPad->present[0]==PADTYPE_NONE)
	{
		fontCentredMultiLine(font,0,-50,250,TEXTSTR(TEXTSTR_OPTIONS_CONTROLLER_NO_PAD),128,128,128);
	}
	else
	{
//		fontPrintCentred(font,0,-30,TEXTSTR(TEXTSTR_PAUSED1),128,128,128);
		fontPrintCentred(font,0,-30,TEXTSTR(TEXTSTR_MENU_PAUSED),128,128,128);

	}

	fontCentredMultiLine(font,0,30,250,TEXTSTR(TEXTSTR_PAUSED_PRESS_START),128,128,128);

// Pause is 0x202020
	if(pReadPad->present[0]==PADTYPE_NONE)
	{
		col = 0x303070 + ((0x60 * (rsin((loop_ticker<<8)&4095)))/4096);
	}
	else
	{
		col = 0x303030;
	}

	overlaySprite(pause_wood,-128,-64, 0, col);
	overlaySprite(pause_wood,0,-64, 0, col);
	overlaySprite(pause_wood,-128,0, 0, col);
	overlaySprite(pause_wood,0,0, 0, col);
	overlayTwoToneRectangle(-129,-65,257,129);


// Damp down the background. Note that since we're not clearing the screen,
// we need to be careful to do this an EVEN number of times!
	if(pad_removed_looptick < 10)
	{
		overlayScreenAdd(16, 16, 16, SEMITRANS_SUB);
		pad_removed_looptick++;
	}
}

void padRemovedEnd()
{
	draw_routine = old_draw_routine_disconnect;
	cycle_routine = old_cycle_routine_disconnect;
	displayPage[0].drawenv.isbg = 1;	// tbd: preserve
	displayPage[1].drawenv.isbg = 1;
	sound_XA_Resume(-1);
	pad_removed_count = 0;
	gamepaused = 0;
}

void padRemovedCycle()
{
	if(padData.present[0]!=PADTYPE_NONE && (padData.debounce[0] & PAD_START))
	{
		padRemovedEnd();
	}
}
void padRemovedStart()
{
	pause_wood = textureFindInAllBanks("WOODSTRIP");


	soundMuteAllChannels();		// Kill whatever sfx are currently playing (set all volumes to 0)
	sound_XA_PauseForLoading();

	gamepaused = 1;				// Necessary for stopping the music!
	if(cycle_routine != pauseCycle)
	{
		old_draw_routine_disconnect = draw_routine;
		old_cycle_routine_disconnect = cycle_routine;
	}
	else
	{
		// If we go to "disconnect" from "pause", we need to return to the cycle routine below that, not to the pause one.
		old_draw_routine_disconnect = old_draw_routine;
		old_cycle_routine_disconnect = old_cycle_routine;
	}
	draw_routine = padRemovedDraw;
	cycle_routine = padRemovedCycle;
	displayPage[0].drawenv.isbg = 0;
	displayPage[1].drawenv.isbg = 0;
	dsEvent_Init();
	// Copy page 0 over page 1 to stop that annoying flickeriness
	pad_removed_looptick = 0;
	{
		static RECT rect = {0,256,512,256};
		MoveImage(&rect,0,0);	
		DrawSync(0);
	}
}


void mainHandlePadRemoval()
{
// New one, so we can reduce the delay in normal gameplay
	if(ignore_pad_removal)
	{
		pad_removed_count = 0;
		ignore_pad_removal--;
	}
// At the start of the game, and also after accessing the memory card,
// the "present" will be = 0 for a while
	if(pad_removed_count < 4)
	{
		if(padData.present[0]==PADTYPE_NONE)
		{
			pad_removed_count++;
			if(pad_removed_count == 4)
				padRemovedStart();
		}
		else
		{
			pad_removed_count = 0;
		}
	}
/*
// High water mark would appear to be "10" when loading/saving
	{
		static int pad_removed_hwm = 0;
		if(pad_removed_count > pad_removed_hwm)
		{
			pad_removed_hwm = pad_removed_count;
		}
		DB("removed hwm = %d\n",pad_removed_hwm);
	}
*/
}




int	digistick = 0;
int digistick_debounced = 0;
void mainDigitiseAnalog()
{
	int prev,temp;

	if(pReadPad->analog[0])
	{
		prev = digistick;

		// Merge together the analog OR the digital, then debounce 'em
		digistick = pReadPad->digital[0] & (PAD_LEFT + PAD_RIGHT + PAD_UP + PAD_DOWN);

		if(pReadPad->analogXS[0]<-3172)
			digistick |= PAD_LEFT;
		if(pReadPad->analogXS[0]>3172)
			digistick |= PAD_RIGHT;

		if(pReadPad->analogYS[0]<-3172)
			digistick |= PAD_UP;
		if(pReadPad->analogYS[0]>3172)
			digistick |= PAD_DOWN;

		temp = (digistick ^ prev);
		digistick_debounced = (temp & digistick);
	}
	else
	{
		digistick = pReadPad->digital[0] & (PAD_LEFT + PAD_RIGHT + PAD_UP + PAD_DOWN);
		digistick_debounced = pReadPad->debounce[0] & (PAD_LEFT + PAD_RIGHT + PAD_UP + PAD_DOWN);


// The way Andy S has done the simulated analog makes the XS and the YS taks ages to actually return to zero
		if(    padData.analogXS[0] > -512 && padData.analogXS[0] < 512
			&& padData.analogYS[0] > -512 && padData.analogYS[0] < 512
			)
		{
			padData.analogXS[0] = 0;
			padData.analogYS[0] = 0;
		}
	}

}

/**************************************************************************
	FUNCTION:	main()
	PURPOSE:	Top level shell function
	PARAMETERS:	
	RETURNS:	0
**************************************************************************/

#if (GOLDCD==1) && defined(OPSM_DEMO)               /* If we have linked none2.obj */
  #define LINKED_NONE2 1
//  #undef  LINKED_NONE2
#else
  #undef  LINKED_NONE2
#endif

#define MODE_INTERACTIVE			0
#define MODE_ATTRACT			1


int timeout = 60*4;
int mode = 0;               /* Demo mode and timeout in seconds. */
int start_track,			  /* First DA track and number of DA tracks. */
   num_tracks;
int timedown = 60*4;
int idletime = 0;


TextureBankType *generic_textures;
TextureType *sprs[9];
char *names[9] = {"TRIANGLE","CIRCLE","CROSS","SQUARE", "L1", "L2", "R1", "R2", "STARTBUTTON"};
int i;
int d;
int n;
unsigned long * ptr;
unsigned long * top;
char temp[40];



#ifdef LINKED_NONE2
  int main( int argc, char** argv)  /* DDP will pass argc, argv to you. */
#else
  int main()
#endif
{ 
// NO LOCAL ALLOWED

#ifdef OPSM_DEMO


   #ifdef LINKED_NONE2
		mode = ((int *)argv)[0];
		timeout = ((int *)argv)[1];
		start_track = ((int *)argv)[2];
		num_tracks = ((int *)argv)[3];
   #else
		mode = MODE_INTERACTIVE;
		timeout = 60 * 4;	// in seconds
		start_track = 0;
		num_tracks = 0;
   #endif



		utilPrintf("mode = %d, timeout = %d, start_track = %d, num_tracks = %d\n",
			mode,
			timeout,
			start_track,
			num_tracks);

		if(mode == MODE_INTERACTIVE && timeout < 60 * 4)
		{
			timeout = 60 * 4;	// Sorry, but we need the 4 minutes
		}

		timeout = timeout * FRAMESEC;
		timedown = timeout;
#endif





	//install_exc();
//#ifndef OPSM_DEMO	// On the OPSM demo, when we quit, we quit. Let's just make sure :)
//	while(1)
//#endif
	{

#ifndef OPSM_DEMO	// Frogger reckon that this screws stuff up...
		memset((void *)0x1f8000,0,0x8000);
#endif
		
#ifdef NULL_POINTER_CHECK
		zpCheckInit();
#endif
#ifdef DEBUG
					mainFindHighWaterMark();
#endif

		utilPrintf("\n\nCRTest %s\n\n",version);
		utilInitCRC();
		utilSeedRandomInt(398623);
		ResetCallback();



		RAMstart = (ULONG)__bss_orgend;
  #ifndef OPSM_DEMO
		if(RAMstart < (ULONG)_vid_ovl_bss_orgend)
			RAMstart = (ULONG)_vid_ovl_bss_orgend;
		if(RAMstart < (ULONG)_sub1_ovl_bss_orgend)
			RAMstart = (ULONG)_sub1_ovl_bss_orgend;
		if(RAMstart < (ULONG)_sub2_ovl_bss_orgend)
			RAMstart = (ULONG)_sub2_ovl_bss_orgend;
		if(RAMstart < (ULONG)_sub4_ovl_bss_orgend)
			RAMstart = (ULONG)_sub4_ovl_bss_orgend;
		if(RAMstart < (ULONG)_deb_ovl_bss_orgend)
			RAMstart = (ULONG)_deb_ovl_bss_orgend;
  #endif


// TBD - figure out why ISLMEM can't cope with the normal (80xxxxx...) memory map, and insists on the low one

//		RAMsize = (0x1fff00 - RAMstart)-8192;

		RAMsize = (0x801fff00 - RAMstart)-8192;
		RAMstart &= 0x7fffffff;


		utilPrintf("\nRAM start 0x%x  0x%x (%d)\n", RAMstart, RAMsize, RAMsize);
		memoryInitialise(RAMstart, RAMsize, 256);
#if GOLDCD==0
  #ifdef OPSM_DEMO
		fileInitialise("C:\\PSX\\CRTEST\\DEMOCD\\");
  #else
		fileInitialise("C:\\PSX\\CRTEST\\CD\\");
  #endif
		XAsetStatus(CdInit());
#else
  #ifdef OPSM_DEMO
		fileInitialise("\\CRDEMO\\CRDEMO.DAT;1");
  #else
		fileInitialise("\\CRTEST.DAT;1");
  #endif
		XAsetStatus(1);
#endif
		sound_DemoXAInit();

		MemCardInit(1);
		MemCardStart();
		padInitialise(0);

		videoInit(1024, MAX_PRIMS);
//		textureInitialise();
		textureInitialise(384,16);	// new version lib
		overlayGrabAlphaCluts();

		memoryCardCheck = 1;


		
// Some slight reordering of loads, so we can change language without fragging the memory. RFW 27-Sep-00		
		font = fontLoad("SHELL\\FONT12.FON");
		font = fontTidy(font);
		{
			generic_textures = textureLoadInOneGo("SHELL\\GENERIC.SPT");

			for(i = 0; i < 9; i++)
			{
				sprs[i] = textureFindInAllBanks(names[i]);
				ASSERT(sprs[i]);
			}

			fontRegisterButtonSprites(sprs[0],sprs[1],sprs[2],sprs[3]);

			fontRegisterOtherSprites('L', sprs[4]);
			fontRegisterOtherSprites('l', sprs[5]);
			fontRegisterOtherSprites('R', sprs[6]);
			fontRegisterOtherSprites('r', sprs[7]);
			fontRegisterOtherSprites('A', sprs[8]);
		}
// the 11 is the number of langs, 0 is the preferred
// (tbd - default to uk in uk, us in us)
		gameTextInit("SHELL\\GAMETEXT.TXT", TEXTSTR_MAX, LANG_FILE_NUMLANGS, gameTextLang);
		sound_XA_FindDemoLangFiles(gameTextLang);

		frame = 0;
		expected_frame = 0;
		loop_ticker = 0;
		loop_ticker2 = 0;

		ignore_pad_removal = 20;	// So we initialise okay

		VSyncCallback(&vsyncCallback);

// (Old location of the font load/tidy) Moved so we can change language without fragging the memory. RFW 27-Sep-00

		soundSetup();
		sound_LoadLanguageBank(gameTextLang);

//		particleInitialiseList();

// Although this is done in "startup", during development, we're not always *calling* startup
// (oops, keep after the setups!)
		cardInitialPrefs(&optionData);
		cardInitialGameState();

		loadsndLoadBank(NULL);	// CPW: ensure generic bank is loaded




// Two tests of the streaming stuff...
/*
		{
			ULONG *tim;
			tim=(ULONG *)streamLoad("SHELL\\BACKDROP.TIM",0);
			printf("tim = %d\n",tim);
			uploadTimToVRAM(tim);


		}
*/
/*
		{
			TextureBankType *bank;
			bank = streamLoadSpt("VIEWER\\GINGER.SPT",20);
			textureUnloadBank(bank);
		}
*/

		sfxSetGlobalVolume(optionData.sound.sfxvol);

 #ifndef OPSM_DEMO
		main_EnsureOverlay(VIDEO_PLAYING_OVERLAY);
		video_PlayVid(MOVIENO_BLITZ,1);
		video_PlayVid(MOVIENO_EIDOS,1);
		video_PlayVid(MOVIENO_DREAMWORKS,1);
		video_PlayVid(MOVIENO_AARDMAN,1);
 #endif

//	CRASH;

//		viewerStart();
//		mapviewStart();
//		menuStart();
		languageStart();

		quitMainLoop = 0;
		
		// CPW: overlay test
/*
		OVERLAYLOAD(overlay1);
		overlay1Test();
		OVERLAYLOAD(overlay2);
		overlay2Test();
*/
		expected_frame = frame;
		while(!quitMainLoop)
		{
//			static int loopcnt = 0;
//			DB("loop %d\n",loopcnt++);

			TIMER_START(TIMER_TOTAL);
			TIMER_START(TIMER_ALLOTHER);
				soundCycle();
				currentDisplayPage = (currentDisplayPage==displayPage)?(&displayPage[1]):(&displayPage[0]);
				ClearOTagR(currentDisplayPage->ot, 1024);
				currentDisplayPage->primPtr = currentDisplayPage->primBuffer;
			TIMER_STOP(TIMER_ALLOTHER);

		
			{
				void *old_cycle;
				int time_taken;


// ======= Cycle routine, now with added frame-rate compensation - Woohoo! ========
				old_cycle = cycle_routine;
				TIMER_START(TIMER_CYCLE);

				{
					d = frame - expected_frame;
					if(d > 6)
					{
						DB("Frame Limiter!\n");
						// Frame limiter
						expected_frame = frame;
						d = 6;
					}

					cycle_loops = 0;

#ifdef FRAME_RATE_COMPENTSATION
					do
					{
#endif
						cycle_loops++;


						if(cycle_routine)
							cycle_routine();


						expected_frame += 2;
						d -= 2;

						loop_ticker++;
						loop_ticker2++;
						if(playing_ticker_enabled)
							gameSaveData.time_spent_playing++;

#ifdef FRAME_RATE_COMPENTSATION

#ifdef DEBUG
// If we're timing %ges of cycle/draw, we don't want the compensator running
						if(eidos_debugs || timerActive)
							break;
#endif

						if(d >= 2 && old_cycle == cycle_routine)
						{
							// If we're looping round, clear out any drawing the cycle routine did, and get some new stick values
							ClearOTagR(currentDisplayPage->ot, 1024);
							currentDisplayPage->primPtr = currentDisplayPage->primBuffer;
							padHandleInput();
							padHandleShock();
							mainDigitiseAnalog();
						}
					}
					while (d >= 2 && old_cycle == cycle_routine);
#endif

				}

				mainHandlePadRemoval();

// Make damn sure that if the cycle routine's switched, then the new cycle is called before the new draw
				while(old_cycle != cycle_routine)
				{
#ifdef NULL_POINTER_CHECK
					zpCheck2("Cycle Change");
#endif

		// Kill off Andy S's delayed return-to-centre
					if(!pReadPad->analog[0])
					{
						padData.analogXS[0] = 0;
						padData.analogYS[0] = 0;
					}

// This deals with naughty cycle routines that stick stuff in the OT
// (actually *most* of 'em stick at least debug polys in!, but the FMA's fairly cycle-draw confused)

					ClearOTagR(currentDisplayPage->ot, 1024);
					currentDisplayPage->primPtr = currentDisplayPage->primBuffer;

					old_cycle = cycle_routine;

					padHandleInput();
					padHandleShock();
					mainDigitiseAnalog();
					cycle_routine();
					expected_frame = frame;
				}
	//			sfxFrame();
			}




			// JS : Call our pad handler in pad.c to do nothing more than check the ports
			//      for input.  Do the dual shock stuff AFTER the vsync when then bus is
			//      is no longer busy.

			{

				if(demoStatus == DEMO_PLAYING)
				{
					CHARACTER *pC = characterGetMainCharacter();
					if(pC)
					{
						demoPlaybackFrame(-1, &pC->pEnemy->pos, &pC->pEnemy->dir);
					}
				}
				else
				{
					padHandleInput();
					mainDigitiseAnalog();

					if(demoStatus == DEMO_RECORDING)
					{
						CHARACTER *pC = characterGetMainCharacter();
						if(pC)
						{
							demoRecordFrame(frame, &pC->pEnemy->pos, &pC->pEnemy->dir, padData.debounce[0], padData.digital[0]);
						}
					}
				}
			}



			TIMER_STOP(TIMER_CYCLE);

// ============ Draw Routine =================

			TIMER_START(TIMER_ALLDRAW);
			if(draw_routine)
				draw_routine();
			overlayDraw();
			TIMER_STOP(TIMER_ALLDRAW);


			TIMER_START(TIMER_ALLOTHER);
#if GOLDCD==0

			if(padData.debounce[4] & PAD_START)
			{
				SnapShot("C:\\PSX\\CRTEST\\SNAPSHOT\\");
			}

			if(padData.debounce[4] & PAD_SELECT)
			{
				memoryShow();
				memoryShowStats();
				memoryCheck();
			}
			if (padData.debounce[4] & PAD_R1)
				timerActive = !timerActive;
			if (padData.debounce[4] & PAD_L1)
			{
#ifndef VRAM_PALETTES
	#define VRAM_PALETTES CLUT_16S_REQUIRED
#endif

				n = 0;
				for(i=0; i<VRAM_PALETTES; i++)
   				{
   					if (VRAMpalBlock[i])
						n++;
				}
				printf("Used Palettes = %d\n",n);

				textureShowVRAM(PALMODE);
				continue;
			}
#endif
// This breaks things (eg - try it after the first FMA. Game plays intro vids. Game dies.
//			if (padData.digital[0] == (PAD_SELECT|PAD_START|PAD_L1|PAD_R1|PAD_L2|PAD_R2))
//				quitMainLoop = 1;

			TIMER_START(TIMER_DRAWSYNC);
			DrawSync(0);
			TIMER_STOP(TIMER_DRAWSYNC);


			TIMER_STOP(TIMER_ALLOTHER);
			TIMER_STOP(TIMER_TOTAL);
			
			if(cycle_routine!=creditsCycle)
				VSync(FRAMERATE);
			else
				VSync(0);


#if GOLDCD==0
			{
				static int slomo = 0;
				if(padData.debounce[4] & PAD_L2)
					slomo = !slomo;
				if(slomo)
				{
					VSync(10);
				}
			}
#endif

			// JS : Putting the stuff to handle the dual shock stuff should
			//      fix our dual shock hiccups (I think/hope/pray)
			//if (padRead)
			TIMER_START(TIMER_ALLOTHER);
			padHandleShock();



			mainDebugNumbers();





			TIMER_START(TIMER_TEMPTEST);
			{
// Trim off any unused OT pointers, to reduce GPU stalls
				i = 0;

				ptr = currentDisplayPage->ot+1020;

				top = ptr;
				if((*top) == (((unsigned long)top) - 4))
				{
					while((*ptr) == (((unsigned long)ptr) - 4))
					{
						i++;
						ptr -= 1;
					}
					// *ptr isn't = ptr - 4;
					*top = (unsigned long)ptr;
				}
//				DB("Trimmed %d\n",i);
			}
//			{
//				int i;
//				for(i = 1023; i > 1000; i--)
//					DB(" ot %d (&%8x) = &%8x\n",i,(unsigned long)(currentDisplayPage->ot+i),*(unsigned long *)(currentDisplayPage->ot+i));
//			}




// Timeouts! Sony insist. Four minutes of gameplay tops. Erkle!
			if(!demoshell_leaving)
			{
				if(!padData.digital[0] && !padData.analogXS[0] && !padData.analogYS[0])
				{
					idletime++;
				}
				else
				{
					idletime = 0;
				}


				if(cycle_routine == FMAtestCycle && !fmaStatus.holdFrameFlag)
				{
					idletime = 0;	// On FMA's only idletime out on "press X" screens
				}


				if(cycle_routine == mapplayCycle
					|| cycle_routine == fireworksCycle
//					|| cycle_routine == FMAtestCycle	// Nope, I'm not going to include FMA seqs as "playing time"
					|| cycle_routine == inventoryCycle
					)
				{
					timedown--;
					sprintf(temp,"%s %d",TEXTSTR(TEXTSTR_VIDEO_DEMO), timedown / FRAMESEC);

					fontPrintCentred(font, 40, -120, temp, 128,90,30);

	// tbd - put somewhere sensible... (Coz this could cause conflict... I think...)
					if(    (timedown < 0)
						|| (idletime > 30 * FRAMESEC)
						|| padData.digital[0] == PAD_SELECT
					  )
					{
						if(timedown < 0)
							utilPrintf("Out of Time from %d\n",timeout);
						if(idletime > 30 * FRAMESEC)
							utilPrintf("Idle > %d\n",30 * FRAMESEC);
						if(padData.digital[0] == PAD_SELECT)
							utilPrintf("pad = Select\n");
						utilPrintf("Quit 1\n");
						pauseEndQuitGame();
					}
				}
				else
				{
					if(idletime > 30 * FRAMESEC
						|| padData.digital[0] == PAD_SELECT
						)
					{
						if(idletime > 30 * FRAMESEC)
							utilPrintf("Idle > %d\n",30 * FRAMESEC);
						if(padData.digital[0] == PAD_SELECT)
							utilPrintf("pad = Select\n");
						utilPrintf("Quit 2\n");
						pauseEndQuitGame();
					}
				}
			}


			PutDispEnv(&currentDisplayPage->dispenv);
			PutDrawEnv(&currentDisplayPage->drawenv);



#ifdef DEBUG
// "check how much effect the GPU's having" test
			if(padData.digital[4] & PAD_R3)
			{
				DrawOTag(currentDisplayPage->ot+(50));
			}
			else
			{
				DrawOTag(currentDisplayPage->ot+(1024-1));
			}
#else
			DrawOTag(currentDisplayPage->ot+(1024-1));
#endif
			TIMER_STOP(TIMER_ALLOTHER);

			TIMER_ENDFRAME;
		}
		utilPrintf("\nCRTest QUIT/RESET\n");

		DrawSync(0);
//		ClearImage2(&VRAMarea, 0,0,0);
		{
			static RECT VRAMarea1 = {0,0,512,256};
			static RECT VRAMarea2 = {0,256,512,256};

			ClearImage2(&VRAMarea1, 0,0,0);
			ClearImage2(&VRAMarea2, 0,0,0);
		}



		DrawSync(0);

		sound_FreeLanguageBank();

		sound_XA_Stop();
		VSync(5);
		VSync(5);
		sfxStopSound();
		sfxDestroy();
		SpuClearReverbWorkArea(SPU_OFF);

//		memoryShow();


//		VSync(5);
//		VSync(5);
		fontUnload(font);

		VSync(5);
		VSync(5);

		DB("Bybye gametext\n");
		gameTextDestroy();
		FREE(displayPage[0].ot);
		FREE(displayPage[1].ot);
		FREE(displayPage[0].primBuffer);
		FREE(displayPage[1].primBuffer);

		textureUnloadBank(generic_textures);

		textureDestroy();

#ifdef DEBUG
		memoryShow();
#endif
		memoryReset();
		memoryDestroy();
		utilPrintf("All done. Back to the shell\n");

		DrawSync(0);
		StopCallback();
		PadStopCom();
		ResetGraph(3);

	}
	utilPrintf("Come Back Soon!\n");
	return 0;
}



VOID_SUBROUTINE main_bankswitch_startrou;
int main_bankswitch_overlay;
VOID_SUBROUTINE menuStart2pointer = menuStart2;

// rqd for the menu to get to the subgames
VOID_SUBROUTINE fireworksStartPointer = fireworksStart;
VOID_SUBROUTINE catapultStartPointer = catapultStart;
VOID_SUBROUTINE seesawStartPointer = seesawStart;
VOID_SUBROUTINE engineStartPointer = engineStart;
VOID_SUBROUTINE constructStartPointer = constructStart;
VOID_SUBROUTINE wingsStartPointer = wingStart;
VOID_SUBROUTINE crateStartPointer = crateStart;
VOID_SUBROUTINE dogStartPointer = dogchaseStart;
VOID_SUBROUTINE elStartPointer = elStart;



// Immediately ensure the overlay and change the cycle routines again...
void main_BankswitchCycle()
{
	DB("Bankswitch Cycle\n");
	main_EnsureOverlay(main_bankswitch_overlay);
	main_bankswitch_startrou();
}
// This will never get called, because bankswitchcycle always changes the cycle routine again, straight away.
// (I've included it anyway as a safety measure)
void main_BankswitchDraw()
{
}

// Allow an overlay-to-overlay cycle routine swap
// (Note - this needs to return to the SOURCE overlay)
void main_OverlayCycleChange(int overlay, VOID_SUBROUTINE start_rou)
{
	DB("Cycle Changing\n");
	cycle_routine = main_BankswitchCycle;
	draw_routine = main_BankswitchDraw;
	main_bankswitch_startrou = start_rou;
	main_bankswitch_overlay = overlay;
}

char * ovl_names[] =
{
	"BIN\\VID_OVL.BIN",
	"BIN\\SUB1_OVL.BIN",
	"BIN\\SUB2_OVL.BIN",
	"BIN\\SUB4_OVL.BIN",
	"BIN\\DEB_OVL.BIN",
};

char *ovl_addrs[] = 
{
 #ifdef OPSM_DEMO
	NULL,NULL,NULL,NULL,NULL
 #else
	_vid_ovl_obj,
	_sub1_ovl_obj,
	_sub2_ovl_obj,
	_sub4_ovl_obj,
	_deb_ovl_obj,
 #endif
};
int ovl_current = -1;
void main_EnsureOverlay(int which)
{
	if(which == ovl_current)
		return;

 #ifdef OPSM_DEMO
		return;
 #endif

	ovl_current = which;

	if(music_playing >= 0)
	{
		DB(" (Music was playing at overlay-load time. Stopped)\n");
		sound_XA_Stop();	// No need for pause-for-loading. Everything that loads as an overlay has a fresh track.
	}

#if GOLDCD==NO
// When running a "MCD (area)" or "MACD (area)" build on the PC,
// we still want to load the overlays from the PC side of things, not off the CD
// (For now, we'll have to do that by attrib-r, and changing the above line)
	{
		char name[256];
		long fHandle, size;

		strcpy(name, "c:\\psx\\crtest\\cd\\");
		strcat(name, ovl_names[which]);
		if ((fHandle = PCopen(name, 0,0))==-1)
		{
			printf("Error opening PC overlay file '%s'\n", name);
			return;
		}
		size = PClseek(fHandle, 0,2);
		PClseek(fHandle, 0,0);
		PCread(fHandle, ovl_addrs[which], size);
		PCclose(fHandle);
	}
#else
	fileLoadBinary(ovl_names[which], ovl_addrs[which]);
#endif
}


void mainDebugMenuVideo(int which)
{
	main_EnsureOverlay(VIDEO_PLAYING_OVERLAY);
	video_PlayVid(which,0);
	main_EnsureOverlay(DEBUG_OVERLAY);
}



#endif // _WINDOWS
