/***************************************************************************
 *
 * main.c
 *
 *************************************************************************
 *
 * main game loop
 *
 *************************************************************************/

/*
 * includes
 */

#include "ult_64.h"
#include <PR/sched.h>
#include <PR/os_internal.h>
#include "main.h"
#include "static_s.h"
#include "dma.h"
#include "ctrl.h"
#include "file.h"
#include "memory.h"
#include "resman.h"

#include "define.h"
#include "min.h"
#include "shell.h"
//#include "shellgfx.h"
//#include "shellres.h"
#include "camera.h"
#include "threads.h"
#include "misc.h"
#include "audio.h"
#include "overgfx.h"
#include "shadfont.h"
	#include "ml.h"


#include "gamefx.h"

extern u8
	*GameBank;


#ifdef DEBUG
	#define MAIN_STACK_SIZE (32*1024)
#else
	#define MAIN_STACK_SIZE (8*1024)
#endif


extern s32
	ALines[2][2];


extern u64
	IdleStack[],
	MainStack[],
	AudioThreadStack[];


extern int
	Num3PtPlayers,
	Cur3PtRoundNum,
	Human3PtMatchPlayers[2];

extern int time_frame;
extern int time_rate;
extern int rep_frame;
extern int rep_frame2;
extern bool	replay_flag;

extern void	FlipPlayerCoords();

extern s32	Paused;
/*
 * defines
 */

#define PROG_END_SPACE (0*1024)
#define MEM_TOP_SPACE (4*1024)

#define DEAD_ZONE 5

//#define PROFILE

#if 0
	#define PROF_LEFT (XRES/16)
	#define PROF_STEP ((XRES-(XRES/8))/2)
#else
	static s32
		PROF_LEFT,
		PROF_STEP,
		PROF_BOT;
#endif


/*
 * typedefs
 */

/*
 * structures
 */

/*
 * globals
 */

// THIS VARIABLE MAY BE MODIFIED BY FIXCART.EXE IN THE RELEASE VERSIONS!!!
//int		romFileOffset=0xB0200000;
extern char _fileSegmentRomStart[];
int		romFileOffset;

s32
	BuildTime;


// Local vars...

static SCamera
	CamO,
	CamN;


// storage for tweaked video modes
static OSViMode	OldModes[42];

static u8
	VidMode_Game,
	VidMode_Shell,
	VidMode_Intro;


static void IntroOpen( void *arg );
static void IntroClose();
static void IntroLogic( signed int *c );
static void IntroRender( dyn *d, int draw_buffer, Gfx **gl );

static void TitleOpen( void *arg );
static void TitleClose();
static void TitleLogic( signed int *c );
static void TitleRender( dyn *d, int draw_buffer, Gfx **gl );


enum process_enums {

	INTRO_PROCESS_NUM,
	TITLE_PROCESS_NUM,
	SHELL_PROCESS_NUM,
	GAME_PROCESS_NUM,

	MAX_PROCESS_NUM
};

process processes[MAX_PROCESS_NUM]=
{
	{
		IntroOpen,
		IntroLogic,
		IntroRender,
		IntroClose,
		PROCESS_DEAD,

		FALSE,			// Game proc.
		VIDMODE_INTRO,
		576, 460,
		TRUE,			// Step mode.
		FALSE,			// Swap screens.
	},
	{
		TitleOpen,
		TitleLogic,
		TitleRender,
		TitleClose,
		PROCESS_DEAD,

		FALSE,			// Game proc.
		VIDMODE_INTRO,
		576, 460,
		TRUE,			// Step mode.
		FALSE,			// Swap screens.
	},
	{
		ShellOpen,
		ShellLogic,
		ShellRender,
		ShellClose,
		PROCESS_DEAD,

		FALSE,			// Game proc.
		VIDMODE_SHELL,
		512, 240,
		TRUE,			// Step mode.
		TRUE,			// Swap screens.
	},
	{
		MinOpen,
		MinProcess,
		MinRender,
		MinClose,
		PROCESS_DEAD,

		TRUE,			// Game proc.
		VIDMODE_GAME,
		SCR_X_RES,
		SCR_Y_RES,
		FALSE,			// Step mode.
		TRUE,			// Swap screens.
	},
};



process		*currentProcess=NULL;

/* handy buffers */

u16		*cfb[2]={ NULL, NULL };
u16		*dfb=NULL;

static u64 *dram_stack_ptr;

/* some flags */
int		freshScreen=0;

s32
	FrameTicks = 0,
	FrameTime = 0;

/* handy globals for external use... */

volatile s32
	PreNMI = FALSE;

dyn
	DBdata[2],
	*dynamicp;

int
	dbuf,
	frame;


static int draw_buffer=0;

/* heap initting stuff */
static unsigned long	programEnd;
static unsigned long	heapSize;
extern int	__sbss_objend;

/* controller stuff */
long		cont[4];

/*static*/ u8
	VideoMode = -1;

static u8
	ChangeVidMode = 0,
	AllowSubHack;

static s32
	CalcTime,
	DrawTime;


#ifdef PROFILE

	#define FRAMES_HISTORY 64

	static u8
		FrameRates[FRAMES_HISTORY];

#endif


/*
 * function prototypes
 */

static void MemoryInit();
static void TweakVideoModes();
void SetVidMode( s32 mode );
void RestoreVideoModes();
static void BuildControllerValue(ctrlData *in,unsigned long *out);

/*
 * code
 */

void Stack_Setup( void *_stack, s32 size )
{
	u32
		*stack = (u32 *)_stack;

	while (size > 0)
	{
		*stack++ = 0xDeadCafe;
		size -= 4;
	}
}


s32 Stack_Check( void *_stack, s32 size )
{
	u32
		*stack = (u32 *)_stack;
	s32
		left = 0;

	while (size > 0)
	{
		if (*stack++ != 0xDeadCafe)
			return( left );

		left += 4;
		size -= 4;
	}

	return( left );
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static OSMesgQueue RdpMessageQ;
static OSMesg RdpMessageBuf;
static OSMesg DummyMessage;

static s32
	Gfx_Pending = FALSE;


static void Gfx_Init()
{
	osCreateMesgQueue( &RdpMessageQ, &RdpMessageBuf, 1 );
	osSetEventMesg( OS_EVENT_DP, &RdpMessageQ, DummyMessage );
}


void Gfx_Wait()
{
	if (Gfx_Pending)
	{
		osRecvMesg( &RdpMessageQ, NULL, OS_MESG_BLOCK );
		Gfx_Pending = FALSE;
	}
}


void Gfx_Do( void *data, u32 size )
{
	OSTask
		task;

	task.t.type							= M_GFXTASK;
	task.t.flags						= OS_TASK_LOADABLE | OS_TASK_DP_WAIT;
	task.t.ucode_boot				= (u64 *)rspbootTextStart;
	task.t.ucode_boot_size	= (u32)rspbootTextEnd - (u32)rspbootTextStart;
	task.t.data_ptr					= (u64 *)data;
	task.t.data_size				= (u32)size;
	task.t.ucode						= (u64 *)gspF3DEX2_NoN_xbusTextStart;
	task.t.ucode_data				= (u64 *)gspF3DEX2_NoN_xbusDataStart;
	task.t.ucode_data_size	= SP_UCODE_DATA_SIZE;
	task.t.dram_stack				= (u64 *)dram_stack_ptr;
	task.t.dram_stack_size	= SP_DRAM_STACK_SIZE8;
	task.t.output_buff			= NULL;
	task.t.output_buff_size = NULL;
	task.t.yield_data_ptr		= NULL;
	task.t.yield_data_size	= 0;

	osWritebackDCacheAll();

	osSpTaskStart( &task );

	Gfx_Pending = TRUE;
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

#define VBL_STACK_SIZE 128


static OSMesgQueue RetraceMessageQ;
static OSMesg RetraceMessageBuf;

static OSThread
	VBLThread;

static u64
	VBLStack[VBL_STACK_SIZE/sizeof(u64)];

volatile u32
	VBLticks = 0;

static volatile u32
	VBLcounter = 0;


static void VBL_Routine( void *arg )
{
	osCreateMesgQueue( &RetraceMessageQ, &RetraceMessageBuf, 1 );
	osViSetEvent( &RetraceMessageQ, DummyMessage, 1 );

	while (TRUE)
	{
		osRecvMesg( &RetraceMessageQ, 0, OS_MESG_BLOCK );

		VBLcounter++;
		VBLticks++;
	}
}


static void VBL_Init()
{
Stack_Setup( VBLStack, VBL_STACK_SIZE );
	osCreateThread( &VBLThread,
									THREAD_VBL,
									VBL_Routine,
									NULL,
									&VBLStack[VBL_STACK_SIZE/sizeof(u64)],
									THREAD_VBL );

	osStartThread( &VBLThread );
}


u32 VBL_Wait()
{
	u32
		counter;

	counter = VBLcounter;

	while (counter == VBLcounter);

	return( VBLcounter );
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void ClearScreen( Gfx *gp, u16 *scr, u32 w, u32 h )
{
	Gfx
		*glist = gp;

	// initialise RCP
	gSPSegment( gp++, 0, 0 );
	gSPDisplayList( gp++, DLInitRDP );
	gSPDisplayList( gp++, DLInitRSP );

	// set scissor rect
	gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, w, h );

	// clear framebuffer
	gDPPipeSync( gp++ );
	gDPSetCycleType( gp++, G_CYC_FILL );
	gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, w, scr );
	gDPSetFillColor( gp++, GPACK_RGBA5551(0,0,0,255) << 16 | GPACK_RGBA5551(0,0,0,255) );
	gDPFillRectangle( gp++, 0, 0, w-1, h-1);

	gDPFullSync( gp++ );
	gSPEndDisplayList( gp++ );

	Gfx_Wait();
	Gfx_Do( glist, (u32)gp - (u32)glist );
	Gfx_Wait();
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

/*static*/ void ReadControllers()
{
	s32
		i;

	if (ctrlReading)
	{
		CtrlWaitReading();
		CtrlUpdate();
	}

	CtrlStartReading();

	for (i=0; i<4; i++)
		BuildControllerValue( &controllers[i], &cont[i] );
}


extern u8
	validcontrollers;


static void CheckControllers()
{
	void
		*font;
	Gfx
		*gp;
	u16
		*scr;

	if (!validcontrollers)
	{
		scr = MemReserve( (void *)0x80200000, 320*240*2 );

		font = ShadFont_Load( "LargShel.FNT" );
		ShadFont_Set( font );
		ShadFont_Color( 255, 255, 255 );
		ShadFont_Clip( 320, 240 );

		gp = DBdata[0].glist;

		// initialise RCP
		gSPSegment( gp++, 0, 0 );
		gSPDisplayList( gp++, DLInitRDP );
		gSPDisplayList( gp++, DLInitRSP );

		// set scissor rect
		gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 320, 240 );
		gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, scr );

		gDPSetCycleType( gp++, G_CYC_FILL );
		gDPSetFillColor( gp++, GPACK_RGBA5551(0,0,255,255) << 16 | GPACK_RGBA5551(0,0,255,255) );
		gDPFillRectangle( gp++, 0, 0, 320-1, 240-1);

		gDPSetCycleType( gp++, G_CYC_1CYCLE );
		gDPSetColorDither( gp++, G_CD_MAGICSQ );
		gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
		gDPSetTextureLUT( gp++, G_TT_RGBA16 );
		gDPSetTexturePersp( gp++, G_TP_NONE );
		gDPSetTextureFilter( gp++, G_TF_POINT );

		gp = ShadFont_DrawCentered( gp, "NO CONTROLLER", 160, 120 );

		gDPFullSync( gp++ );
		gSPEndDisplayList( gp++ );

		Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
		Gfx_Wait();

		osViSwapBuffer( scr );
		osViSetMode( &osViModeTable[VidMode_Shell] );
		osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_OFF | OS_VI_DITHER_FILTER_OFF );

		while (!PreNMI);

		osViBlack( TRUE );

		while (TRUE);
	}
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void CheckTerritory()
{
	void
		*font;
	Gfx
		*gp;
	u16
		*scr;

	if (osTvType == 0)
	{
		scr = MemReserve( (void *)0x80200000, 320*240*2 );

		font = ShadFont_Load( "SmalShel.FNT" );
		ShadFont_Set( font );
		ShadFont_Color( 255, 255, 255 );
		ShadFont_Clip( 320, 240 );

		gp = DBdata[0].glist;

		// initialise RCP
		gSPSegment( gp++, 0, 0 );
		gSPDisplayList( gp++, DLInitRDP );
		gSPDisplayList( gp++, DLInitRSP );

		// set scissor rect
		gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 320, 240 );
		gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, scr );

		gDPSetCycleType( gp++, G_CYC_FILL );
		gDPSetFillColor( gp++, GPACK_RGBA5551(0,0,255,255) << 16 | GPACK_RGBA5551(0,0,255,255) );
		gDPFillRectangle( gp++, 0, 0, 320-1, 240-1);

		gDPSetCycleType( gp++, G_CYC_1CYCLE );
		gDPSetColorDither( gp++, G_CD_MAGICSQ );
		gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
		gDPSetTextureLUT( gp++, G_TT_RGBA16 );
		gDPSetTexturePersp( gp++, G_TP_NONE );
		gDPSetTextureFilter( gp++, G_TF_POINT );

		gp = ShadFont_DrawCentered( gp, "THIS GAME IS NOT DESIGNED", 160, 100 );
		gp = ShadFont_DrawCentered( gp, "FOR USE IN THIS MARKET", 160, 140 );

		gDPFullSync( gp++ );
		gSPEndDisplayList( gp++ );

		Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
		Gfx_Wait();

		osViSwapBuffer( scr );
		osViSetMode( &osViModeTable[VidMode_Shell] );
		osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_OFF | OS_VI_DITHER_FILTER_OFF );

		while (!PreNMI);

		osViBlack( TRUE );

		while (TRUE);
	}
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void ShowDebugScreen()
{
	void
		*font;
	Gfx
		*gp;
	u16
		*scr;
	s32
		i;

	scr = cfb[0];

	font = ShadFont_Load( "LargShel.FNT" );
	ShadFont_Set( font );
	ShadFont_Color( 255, 255, 255 );
	ShadFont_Clip( 320, 240 );

	gp = DBdata[0].glist;

	// initialise RCP
	gSPSegment( gp++, 0, 0 );
	gSPDisplayList( gp++, DLInitRDP );
	gSPDisplayList( gp++, DLInitRSP );

	// set scissor rect
	gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 320, 240 );
	gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 320, scr );

	gDPSetCycleType( gp++, G_CYC_FILL );
	gDPSetFillColor( gp++, GPACK_RGBA5551(0,0,255,255) << 16 | GPACK_RGBA5551(0,0,255,255) );
	gDPFillRectangle( gp++, 0, 0, 320-1, 240-1);

	gDPSetCycleType( gp++, G_CYC_1CYCLE );
	gDPSetColorDither( gp++, G_CD_MAGICSQ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );
	gDPSetTexturePersp( gp++, G_TP_NONE );
	gDPSetTextureFilter( gp++, G_TF_POINT );

	gp = ShadFont_DrawCentered( gp, "DEBUG INFO ON", 160, 120 );

	gDPFullSync( gp++ );
	gSPEndDisplayList( gp++ );

	Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
	Gfx_Wait();

	osViSwapBuffer( scr );
	osViSetMode( &osViModeTable[VidMode_Shell] );
	osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_OFF | OS_VI_DITHER_FILTER_OFF );

	ShadFont_Free( font );

	for (i=0; i<(3*60); i++)
		VBL_Wait();

	osViBlack( TRUE );
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

typedef struct S3TC_Chunk
{
	RGBA16
		Color1,
		Color2;
	u32
		LookUp;
} S3TC_Chunk;


s32 S3TC_Load( char *filename, void *addr )
{
	FH
		file;
	s32
		size;

	file = FileOpen( filename, "rb" );

	FileSeek( file, 0, SEEK_END );
	size = FileTell( file );
	FileSeek( file, 0, SEEK_SET );

	FileRead( addr, size, 1, file );

	FileClose( file );

	return( size / sizeof( S3TC_Chunk ) );
}


static void S3TC_DecompChunk( S3TC_Chunk *chunk, RGBA16 *rgb, s32 width )
{
	RGBA16
		colors[4];
	u32
		bits;
	s32
		n,
		i,
		x,
		y;

	colors[0] = chunk->Color1;
	colors[1] = chunk->Color2;

	for (i=1; i<3; i++)
	{
		n = (s32)colors[1].r - (s32)colors[0].r;
		n = (n * i) / 3;
		n += colors[0].r;
		colors[i+1].r = n;

		n = (s32)colors[1].g - (s32)colors[0].g;
		n = (n * i) / 3;
		n += colors[0].g;
		colors[i+1].g = n;

		n = (s32)colors[1].b - (s32)colors[0].b;
		n = (n * i) / 3;
		n += colors[0].b;
		colors[i+1].b = n;
	}


	bits = chunk->LookUp;

	for (y=0; y<4; y++)
	{
		for (x=0; x<4; x++)
		{
			*rgb++ = colors[bits & 3];
			bits >>= 2;
		}

		rgb += (width - 4);
	}
}


void S3TC_Decompress( void *src, s32 count, void *dst, s32 width )
{
	S3TC_Chunk
		*chunk;
	RGBA16
		*rgb,
		colors[4*4];
	s32
		line;

	chunk = (S3TC_Chunk *)src;
	rgb = (RGBA16 *)dst;
	line = width >> 2;

	while (count)
	{
		S3TC_DecompChunk( chunk, rgb, width );

		rgb += 4;

		line--;
		if (!line)
		{
			line = width >> 2;
			rgb += (width << 2) - width;			// width*3
		}

		chunk++;
		count--;
	}
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

#define INTRO_N64		0
#define INTRO_LFP		1
#define INTRO_NBA		2
#define INTRO_TEAMS	3

#define INTRO_LOAD	0
#define INTRO_WAIT	1


static u16
	*IntroPal;

static u8
	IntroScr,
	IntroState;


static void IntroOpen( void *arg )
{
	DidIntro = TRUE;

	IntroScr = INTRO_N64;
	IntroState = INTRO_LOAD;

	IntroPal = MemMalloc( 256*2, MEM_ANY );
}


static void IntroClose()
{
	MemFree( IntroPal );
}


static void IntroLogic( signed int *c )
{
	static char
		*names[] =
		{
			"Nintendo.SCR",
			"LF_Logo.SCR",
			"Legal.SCR",
			"Teams.SCR"
		};
	static u16
		wait,
		held;
	FH
		file;
	u8
		*tex;
	u32
		i,
		bits;

	if (IntroState == INTRO_LOAD)
	{
		held = TRUE;

		VBL_Wait();
		osViBlack( TRUE );

		file = FileOpen( names[IntroScr], "rb" );
		FileRead( IntroPal, (256*2), 1, file );
		FileRead( dfb, (576*460), 1, file );
		FileClose( file );


		tex = (u8 *)dfb;

		for (i=0; i<(576*460); i++)
			cfb[0][i] = IntroPal[*tex++];

		osWritebackDCacheAll();


		SetVidMode( VIDMODE_SHELL );
		SetVidMode( VIDMODE_INTRO );


		IntroState = INTRO_WAIT;
		wait = (3*ONE_SEC);
	}
	else
	{
		wait--;

		bits = c[0] | c[1] | c[2] | c[3];

		if (bits & (CTRL_START | CTRL_FIRE_A | CTRL_FIRE_B))
		{
			if (!held)
			{
#ifndef DEBUG			// Don't allow button out in first second.
				if (wait < (2*ONE_SEC))
#endif
					wait = 0;
			}
		}
		else
			held = FALSE;

		if (!wait)
		{
			IntroScr++;
			IntroState = INTRO_LOAD;

			if (IntroScr > INTRO_TEAMS)
				currentProcess->status = PROCESS_FREEING;
		}
	}
}


static void IntroRender( dyn *d, int draw_buffer, Gfx **gl )
{
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

extern u32
	ChangeSong( const s32 change );


static u8
	FromTitle,
	TitleDraw,
	TitleDelay,
	*TitleFont;

static u16
	TitleDir,
	TitleFade,
	*TitlePal;

static void
	*MusicData = NULL;
	

static void TitleOpen( void *arg )
{
	FH
		file;
	u32
		song;

	osViBlack( TRUE );
	SetVidMode( VIDMODE_SHELL );


	TitleDir = 4;
	TitleFade = 3;
	TitleDraw = TRUE;
#ifdef FINAL
	TitleDelay = (3*ONE_SEC);
#else
	TitleDelay = (ONE_SEC/2);
#endif

	TitleFont = ShadFont_Load( "LargShel.FNT" );


	TitlePal = MemMalloc( 256*2, MEM_ANY );

	file = FileOpen( "Title.SCR", "rb" );
	FileRead( TitlePal, (256*2), 1, file );
	FileRead( dfb, (576*460), 1, file );
	FileClose( file );


	song = ChangeSong( FALSE );

	MusicData = Audio_LoadMusic( "Shell.MUS", "Shell.BNK", song );
	Audio_MusicPlay( MusicData, song );
}


static void TitleClose()
{
	Audio_MusicStop();
	Audio_Process();
	Audio_Process();
	Audio_FreeMusic( MusicData );

	MemFree( TitlePal );

	ShadFont_Free( TitleFont );

	ShadFont_Clip( 512, 240 );

	ChangeSong( TRUE );
}


static void TitleLogic( signed int *c )
{
	u32
		i,
		bits,
		done = FALSE;

	if (TitleDelay)
		TitleDelay--;
	else
	{
		if (!Audio_MusicActive())
		{
			FromTitle = TRUE;
			done = TRUE;
		}


		bits = c[0] | c[1] | c[2] | c[3];

		if (bits & CTRL_START)
		{
			FromTitle = FALSE;
			done = TRUE;
		}


		if (done)
		{
			currentProcess->status = PROCESS_FREEING;
			osViBlack( TRUE );
		}
	}
}


static void TitleRender( dyn *d, int draw_buffer, Gfx **gl )
{
	Gfx
		*gp;
	u8
		*tex;
	u32
		y,
		h;;

	gp = *gl;

	// initialise RCP
	gSPSegment( gp++, 0, 0 );
	gSPDisplayList( gp++, DLInitRDP );
	gSPDisplayList( gp++, DLInitRSP );

	// set scissor rect
	gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 576, 460 );
	gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 576, cfb[0] );

//gDPSetCycleType( gp++, G_CYC_FILL );
//gDPSetFillColor( gp++, GPACK_RGBA5551(0,0,255,255) << 16 | GPACK_RGBA5551(0,0,255,255) );
//gDPFillRectangle( gp++, 0, 0, 640-1, 480-1);

	gDPSetCycleType( gp++, G_CYC_1CYCLE );
	gDPSetColorDither( gp++, G_CD_MAGICSQ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetTexturePersp( gp++, G_TP_NONE );
	gDPSetTextureFilter( gp++, G_TF_POINT );
	gDPSetRenderMode( gp++, G_RM_OPA_SURF, G_RM_OPA_SURF2 );
	gDPSetCombineMode( gp++, G_CC_DECALRGB, G_CC_DECALRGB );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );

	gDPLoadTLUT_pal256( gp++, TitlePal );

	tex = (u8 *)dfb;

	if (TitleDraw)
	{
		TitleDraw = FALSE;

		y = 0;
		h = 460;
	}
	else
	{
		y = 340-10;
		h = 16;
	}

	tex += (y * 576);

	while (h)
	{
		gDPLoadTextureBlock( gp++,
												 tex,
												 G_IM_FMT_CI,
												 G_IM_SIZ_8b,
												 576,
												 2,
												 0,
												 G_TX_CLAMP,
												 G_TX_CLAMP,
												 10, 1,
												 G_TX_NOLOD,
												 G_TX_NOLOD );

		gSPTextureRectangle( gp++, 0<<2, y<<2, 576<<2, (y+2)<<2, 0, 0<<5, 0<<5, (1<<10), (1<<10) );

		y += 2;
		h -= 2;
		tex += (576*2);
	}


	ShadFont_Set( TitleFont );
	ShadFont_Color( TitleFade, TitleFade, TitleFade );
	ShadFont_Clip( 576, 460 );
	gp = ShadFont_Draw( gp, "PRESS START", 340-32, 340-10 );


	if (!TitleDelay)
	{
		TitleFade += TitleDir;
		if (TitleFade > 255)
		{
			TitleFade -= TitleDir;
			TitleDir = -TitleDir;
		}
	}


	*gl = gp;
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

#ifndef DEBUG


#define ERROR_STACK_SIZE (2*1024)

static OSThread
	ErrorThread;

static u64
	ErrorStack[ERROR_STACK_SIZE/sizeof(u64)];

static OSMesgQueue ErrorMessageQ;
static OSMesg ErrorMessageBuf;

static void ErrorProc( void *arg )
{
	OSThread
		*thread;
	char
		addr[16],
		text[32];

	osCreateMesgQueue( &ErrorMessageQ, &ErrorMessageBuf, 1 );
	osSetEventMesg( OS_EVENT_FAULT, &ErrorMessageQ, DummyMessage );

	while (TRUE)
	{
		osRecvMesg( &ErrorMessageQ, NULL, OS_MESG_BLOCK );

		thread = __osGetCurrFaultedThread();

		IntToHexStr( thread->context.pc, addr );
		sprintf( text, "Crash:%s", addr );
		FatalError( text, "?", 0 );

		while (TRUE);
	}
}


#endif


#define PRENMI_STACK_SIZE (1*1024)

static OSThread
	PreNmiThread;

static u64
	PreNmiStack[PRENMI_STACK_SIZE/sizeof(u64)];

static OSMesgQueue PreNmiMessageQ;
static OSMesg PreNmiMessageBuf;

static void PreNmiProc(void *arg)
{
	osCreateMesgQueue( &PreNmiMessageQ, &PreNmiMessageBuf, 1 );
	osSetEventMesg( OS_EVENT_PRENMI, &PreNmiMessageQ, DummyMessage );

	osRecvMesg( &PreNmiMessageQ, NULL, OS_MESG_BLOCK );

	PreNMI = TRUE;

#if 0
	FatalError( "PreNMI", "?", 0 );
	while (TRUE);
#endif
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static Gfx *ShowBack( Gfx *gp, SprTexture_T *sprite, const s32 xpos, const s32 half, const u8 fade )
{
	u8
		*tex;
	s32
		v,
		x,
		y,
		w,
		h,
		tw,
		th;

	gDPSetCombineMode( gp++, G_CC_MODULATEI_PRIM, G_CC_MODULATEI_PRIM );
	gDPSetPrimColor( gp++, 0, 0, fade, fade, fade, 255 );

	gDPLoadTLUT_pal256( gp++, *sprite->sprPal );


	// Render the sprite in columns.

	x = xpos;
	y = 0;

	tex = (u8 *)&sprite[1];

	w = sprite->sprW;

	while (w > 0)
	{
		tw = w;
		if (tw > 64) tw = 64;
		if (half) tw >>= 1;

		v = y;

		h = sprite->sprH;

		while (h > 0)
		{
			th = h;
			if (th > 32) th = 32;

			gDPLoadTextureBlock( gp++,
													 tex,                // ptr (8-byte aligned)
													 G_IM_FMT_CI,        // texel fmt
													 G_IM_SIZ_8b,        // texel siz
													 64,                 // w
													 32,                 // h
													 0,                  // palette
													 G_TX_CLAMP,         // clamp/wrap/mirror s
													 G_TX_CLAMP,         // clamp/wrap/mirror t
													 6,                  // mask s
													 5,                  // mask t
													 G_TX_NOLOD,         // shift s
													 G_TX_NOLOD);        // shift t

			gSPTextureRectangle( gp++,
													 (x) << 2,           // ulx  (10.2)
													 (v) << 2,           // uly  (10.2)
													 (x + tw) << 2,      // lrx  (10.2)
													 (v + th) << 2,      // lry  (10.2)
													 G_TX_RENDERTILE,    // tile
													 0,                  // s    (s10.5)
													 0,                  // t    (s10.5)
													 1 << (10+half),     // dsdx (s5.10)
													 1 << 10);           // dtdy (s5.10)

			tex += 64*32;

			v += 32;
			h -= 32;
		}

		if (half)
			x += 64/2;
		else
			x += 64;

		w -= 64;
	}

	return( gp );
}


static void ShowLogo( u8 *logo, s32 db, s32 xpos, s32 ypos )
{
	u16
		*pal;
	u8
		*tex;
	s32
		x,
		y;

	pal = (u16 *)logo;
	tex = &logo[256*2];

	for (y=0; y<85; y++)
	{
		for (x=0; x<268; x++)
		{
			if (*tex)
				cfb[db][((y+ypos)*512)+(x+xpos)] = pal[*tex];

			tex++;
		}
	}

	osWritebackDCacheAll();
}


void BuildTeamName(char * string, TeamRamData_T *teamram)
{

	strcpy(string,teamram->pcl__tTeamRom->pcz__tCity);
	strcat(string," ");	
	strcat(string,teamram->pcl__tTeamRom->pcz__tName);
	StrToUprCase(string);
}


void ShowWaitScreen()
{
	PlyrRomData_T
		*rom;
	Gfx
		*gp;
	void
		*font;
	u8
		*logo;
	s32
		i,
		home,
		away,
		attract;

	char teamname[64];

	osViBlack( TRUE );

	ClearScreen( DBdata[0].glist, cfb[0], SCR_X_RES, SCR_Y_RES*2 );

	if (FromTitle)
	{
		osViSetMode( &osViModeTable[VidMode_Shell] );
		osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_ON | OS_VI_DITHER_FILTER_ON );

		VBL_Wait();
	}

#ifndef DEBUG
	if (fl___gDemoFlag && (DemoMatchCounter & 1))
		attract = TRUE;
	else
#endif
		attract = FALSE;


	BG_Init();

	home = pcl__gMatch->asb__mTeam[0] - 1;
	away = pcl__gMatch->asb__mTeam[1] - 1;

	if (C3PT || attract)
	{
		home = BG_KOBE1 + random( 5 );
		away = home;
	}

	font = ShadFont_Load( "LargShel.FNT" );
	ShadFont_Set( font );
	ShadFont_Color( 255, 255, 255 );

	BG_Load( home, 0 );
	BG_Load( away, 1 );

	if (attract)
	{
		logo = AllocAndLoadFile( "logo.scr", MEM_ANY );
		*(u16 *)logo = 0;
	}

	gp = DBdata[0].glist;

	// initialise RCP
	gSPSegment( gp++, 0, 0 );
	gSPDisplayList( gp++, DLInitRDP );
	gSPDisplayList( gp++, DLInitRSP );

	// set scissor rect
	gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 512, 240 );

	gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 512, cfb[0] );
	gDPSetCycleType( gp++, G_CYC_1CYCLE );
	gDPSetColorDither( gp++, G_CD_MAGICSQ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );
	gDPSetTexturePersp( gp++, G_TP_NONE );
	gDPSetTextureFilter( gp++, G_TF_BILERP );
	gDPSetAlphaCompare( gp++, G_AC_NONE );
	gDPSetRenderMode( gp++, G_RM_OPA_SURF, G_RM_OPA_SURF2 );
	gDPSetCombineMode( gp++, G_CC_MODULATEI_PRIM, G_CC_MODULATEI_PRIM );
	gDPSetPrimColor( gp++, 0, 0, 192, 192, 192, 255 );


	gDPSetCombineMode( gp++, G_CC_DECALRGB, G_CC_DECALRGB );

	if (PracticeMode)
	{
		gp = BG_Draw( gp, 0, 0, FALSE, 255 );

		BuildTeamName(teamname, pcl__gMatch->apcl_mTeamRam[HOME_TEAM]);
		gp = ShadFont_DrawCentered( gp, teamname, 256, 100 );
		gp = ShadFont_DrawCentered( gp, "PRACTICE", 256, 140 );
	}
	else if (C3PT)
	{
		char
			text[64];

		gp = BG_Draw( gp, 0, 0, FALSE, 255 );

		strcpy( text, "ROUND 0" );
		text[6] = '0' + Cur3PtRoundNum;
		gp = ShadFont_DrawCentered( gp, text, 256, 60 );

		rom = pcl__gMatch->apcl_mTeamRam[HOME_TEAM]->ppcl_tPlyrLst[Human3PtMatchPlayers[0]]->pcl__pPlyrRom;

		BuildPlayerName(text,rom->pcz__pCName,rom->pcz__pSName,512-(2*40) );

		gp = ShadFont_DrawCentered( gp, text, 256, 120 );

		if (Human3PtMatchPlayers[1] != -1)
		{
			rom = pcl__gMatch->apcl_mTeamRam[HOME_TEAM]->ppcl_tPlyrLst[Human3PtMatchPlayers[1]]->pcl__pPlyrRom;

			BuildPlayerName(text,rom->pcz__pCName,rom->pcz__pSName,512-(2*40) );

			gp = ShadFont_DrawCentered( gp, text, 256, 160 );
		}
	}
	else if (attract)
	{
		gp = BG_Draw( gp, 0, 0, FALSE, 255 );

		gp = ShadFont_DrawCentered( gp, "WELCOME TO", 256, 50 );
	}
	else
	{
		gp = BG_Draw( gp, 1, 0, TRUE, 255 );
		gp = BG_Draw( gp, 0, 256, TRUE, 255 );

		BuildTeamName(teamname, pcl__gMatch->apcl_mTeamRam[AWAY_TEAM]);
		gp = ShadFont_DrawCentered( gp, teamname, 256, 80 );

		gp = ShadFont_DrawCentered( gp, "AT", 256, 120 );

		BuildTeamName(teamname, pcl__gMatch->apcl_mTeamRam[HOME_TEAM]);
		gp = ShadFont_DrawCentered( gp, teamname, 256, 160 );
	}

	gDPFullSync( gp++ );
	gSPEndDisplayList( gp++ );

	Gfx_Wait();
	Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
	Gfx_Wait();


	if (attract)
	{
		ShowLogo( logo, 0, 122, 80 );

		MemFree( logo );
	}

	BG_Deinit();

	ShadFont_Free( font );

	osViSwapBuffer( cfb[0] );
	osViSetMode( &osViModeTable[VidMode_Shell] );
	osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_ON | OS_VI_DITHER_FILTER_ON );
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void SetNextProcess()
{
	u32
		next;

	switch (currentProcess - &processes[0])
	{
		case INTRO_PROCESS_NUM:
			next = TITLE_PROCESS_NUM;
			break;

		case TITLE_PROCESS_NUM:
			if (FromTitle)
			{
				// Setup demo/attract.
				InitDemoMatch (0);

				next = GAME_PROCESS_NUM;
			}
			else
			{
				next = SHELL_PROCESS_NUM;
			}
			break;

		case SHELL_PROCESS_NUM:
			if (DidIntro && DidSaveOptions)
			{
				next = GAME_PROCESS_NUM;
			}
			else
			{
				next = INTRO_PROCESS_NUM;
			}
			break;

		default:
		case GAME_PROCESS_NUM:
			if (FromTitle)
			{
				DoneDemoMatch();
				next = TITLE_PROCESS_NUM;
			}
			else
			{
				next = SHELL_PROCESS_NUM;
			}
			break;
	}

	currentProcess = &processes[next];

	currentProcess->status = PROCESS_DEAD;
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static u8
	StepMode;

void SetStepMode( const s32 on )
{
	StepMode = on;
}


static void PartialAI()
{
	time_frame+=time_rate;
	if (time_frame>=0x100)
	{
		time_frame&=0xff;
		CamO=CamN;
		Cam_Load( &CamN );

		currentProcess->AI((SI *)&cont[0]);

		Cam_Save( &CamN );
	}
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

void MainProc(void *arg)
{
	Gfx
		*gp;
	int		i,j,k;
	s32
		size,
		diff,
		steps = 0,
		ticks = 0,
		counter = 0;

	/*
	 * reset NMI flag, init counters etc...
	 */

	Set32s( DBdata, 0, 2 * sizeof( dyn ) / 4 );

	frame=0;


	/*
	 * initialise memory manager
	 */

	MemoryInit();

	/*
	 * init dma
	 */

	DmaOpen();


	/*
	 * initialise video & gfx managers
	 */

	osCreateViManager( OS_PRIORITY_VIMGR );
	VBL_Init();
	Gfx_Init();

#ifdef PAL
	VidMode_Game	= OS_VI_PAL_HAF1;
	VidMode_Shell	= OS_VI_PAL_LAN1;
	VidMode_Intro	= OS_VI_PAL_HPF1;
#else
	if (osTvType == 1)
	{
		VidMode_Game	= OS_VI_NTSC_HAF1;
		VidMode_Shell	= OS_VI_NTSC_LAN1;
		VidMode_Intro	= OS_VI_NTSC_HPF1;
	}
	else
	{
		VidMode_Game	= OS_VI_MPAL_HAF1;
		VidMode_Shell	= OS_VI_MPAL_LAN1;
		VidMode_Intro	= OS_VI_MPAL_HPF1;
	}
#endif


#ifndef DEBUG
Stack_Setup( ErrorStack, ERROR_STACK_SIZE );
	osCreateThread( &ErrorThread,
									THREAD_ERROR,
									ErrorProc,
									NULL,
									&ErrorStack[ERROR_STACK_SIZE/sizeof(u64)],
									THREAD_ERROR );

	osStartThread( &ErrorThread );
#endif


	/*
	 * set up dram matrix stack - align 16
	 */

	dram_stack_ptr=(u64 *)MemMalloc(SP_DRAM_STACK_SIZE8+1,MEM_ANY);


	/*
	 * initialise the cartridge rom filesystem
	 */

        romFileOffset=(int)_fileSegmentRomStart;
	FileInit( (void *)romFileOffset );


	/*
	 * initialise the controllers
	 */

	memset(cont,0,sizeof(cont));
	CtrlInit();


	/*
	 * start the audio thread ticking
	 */

	Audio_Init();


Stack_Setup( PreNmiStack, PRENMI_STACK_SIZE );
	osCreateThread( &PreNmiThread,
									THREAD_PRENMI,
									PreNmiProc,
									NULL,
									&PreNmiStack[PRENMI_STACK_SIZE/sizeof(u64)],
									THREAD_PRENMI );

	osStartThread( &PreNmiThread );


	// Load the shell from a binary if debug mode...

#if defined(DEBUG)
	{
		u8
			*ptr;
		int
			file,
			size;

		file = PCopen( "C:\\HOCKEY\\BBALL2\\N64\\SHELL.BIN", 0, 0 );
		size = PClseek( file, 0, SEEK_END );
		PClseek( file, 0, SEEK_SET );
		ptr = MemReserve( (void *)0x80700000, size );
		PCread( file, ptr, size );
		PCclose( file );
	}
#endif


	CheckTerritory();


	CheckControllers();
	ReadControllers();


	/*
	 * tell resource manager not to use tmp buffer
	 */

	ResSetTmpBuffer(NULL);


	InitFlashRom();


	/*
	 * load permanent shell data (audio indexes, etc...)
	 */

	ShellLoadPermData();


	/*
	 * reserve video buffers & zbuffer
	 */

	cfb[0]=(u16 *)MemReserve((void *)0x80200000,SCR_X_RES*SCR_Y_RES*2*2);
	cfb[1]=cfb[0]+(SCR_X_RES*SCR_Y_RES);
	dfb=(u16 *)MemReserve((void *)0x80300000,SCR_X_RES*SCR_Y_RES*2);
#ifdef DEBUG
	if (!cfb[0] || !dfb)
		FatalError( "Cannot allocate buffers", __FILE__, __LINE__ );
#endif
	ClearScreen( (Gfx *)dfb, cfb[0], SCR_X_RES, SCR_Y_RES*2 );


	ReadControllers();
	DidIntro = FALSE;

//	if (cont[0] & CTRL_START)
//		currentProcess = &processes[SHELL_PROCESS_NUM];
//	else
	{
#ifdef FINAL
		currentProcess = &processes[INTRO_PROCESS_NUM];
#else
		currentProcess = &processes[TITLE_PROCESS_NUM];
#endif
		DidIntro = TRUE;
	}


	i = (CTRL_SHOULDER_L | CTRL_FIRE_B | CTRL_C_UP | CTRL_C_LEFT);
	j = (CTRL_SHOULDER_R | CTRL_FIRE_A | CTRL_C_DOWN | CTRL_C_RIGHT);
	if (cont[1] == i && cont[3] == j)
	{
		ShowDebug = TRUE;
		ShowDebugScreen();
	}


	/*
	 * main game loop
	 */

	osViSwapBuffer( cfb[0] );
	TweakVideoModes();
	SetVidMode( VIDMODE_SHELL );

	while (TRUE)
	{
		if (PreNMI)
		{
			stopAllMotors = TRUE;
			Audio_Deinit();
		}

		/*
		 * update controller values
		 */

		ReadControllers();


		if (PreNMI)
		{
			Gfx_Wait();
			osViBlack( TRUE );
			RestoreVideoModes();
			while (TRUE);
		}


		if (currentProcess->status == PROCESS_DEAD)
		{
			AllowSubHack = FALSE;
			currentProcess->Open( NULL );

			currentProcess->status = PROCESS_RUNNING;

			dynamicp = &DBdata[0];
			gp = dynamicp->glist;
			ClearScreen( gp, cfb[0], SCR_X_RES, SCR_Y_RES*2 );

			SetVidMode( currentProcess->VideoMode );
			SetStepMode( currentProcess->StepMode );

			counter = VBLcounter;
			ticks = 2;
		}


		if (!currentProcess->GameProc || Paused)
			currentProcess->AI((SI *)&cont[0]);
		else 
		{
			if (StepMode)
				PartialAI();
			else
			{
				steps += ticks;
				while (steps >= 2)
				{
					PartialAI();

					steps -= 2;
				}
			}
		}

		Audio_Process();
#ifdef FINAL
		rand();
#endif


		dynamicp = &DBdata[draw_buffer];
		gp = dynamicp->glist;


		if (currentProcess->status == PROCESS_FREEING)
		{
			ClearScreen( gp, cfb[0], SCR_X_RES, SCR_Y_RES*2 );

			currentProcess->Close( NULL );
			currentProcess->status = PROCESS_DEAD;

			SetNextProcess();

//			SetVidMode( currentProcess->VideoMode );

			counter = VBLcounter;
			ticks = 2;
		}
		else
		{
			/*
			 * render the scene
			 */

			if (!currentProcess->GameProc || time_rate>=0x100 || Paused)	// True frame or Arcade mode
				currentProcess->Render(dynamicp,draw_buffer,&gp);
			else	// Slo-mo partial frame
			{
				copy_vars();
				load_replay((rep_frame2<<8)+time_frame);

				FlipPlayerCoords();
				Cam_Interpolate( &CamO, &CamN, time_frame );

				currentProcess->Render(dynamicp,draw_buffer,&gp);

				copy_back_vars();
			}
		}


#ifdef PROFILE

		memcpy( &FrameRates[0], &FrameRates[1], FRAMES_HISTORY-1 );
		FrameRates[FRAMES_HISTORY-1] = ticks;

				gDPSetCycleType(gp++,G_CYC_1CYCLE);
				gSPTexture(gp++,0,0,0,0,G_OFF);
				gDPSetCombineMode(gp++,G_CC_PRIMITIVE, G_CC_PRIMITIVE);
				gDPSetRenderMode( gp++, G_RM_OPA_SURF, G_RM_OPA_SURF2 );

PROF_LEFT = (currentProcess->ScreenX >> 4);
PROF_STEP = ((currentProcess->ScreenX - (currentProcess->ScreenY >> 3)) / 3);
PROF_BOT = currentProcess->ScreenY - (currentProcess->ScreenY / 5);
CalcTime = (CalcTime * PROF_STEP) / 525;
DrawTime = (DrawTime * PROF_STEP) / 525;

		gDPSetPrimColor(gp++,0,0,255,255,255,0xFF);

		for (i=0; i<FRAMES_HISTORY; i++)
			gDPFillRectangle( gp++, PROF_LEFT+(i*4), PROF_BOT-2-(FrameRates[i]*4), PROF_LEFT+(i*4)+3, PROF_BOT-2 );

		gDPSetPrimColor(gp++,0,0,0xFF,0x00,0x00,0xFF);
		gDPFillRectangle( gp++,PROF_LEFT,PROF_BOT,PROF_LEFT+CalcTime,PROF_BOT+7);

		gDPSetPrimColor(gp++,0,0,0x00,0x00,0xFF,0xFF);
		gDPFillRectangle( gp++,PROF_LEFT,PROF_BOT+8,PROF_LEFT+DrawTime,PROF_BOT+8+7);

		gDPSetPrimColor(gp++,0,0,0,255,0,0xFF);
		for (i=0; i<2; i++)
		{
			diff = (ALines[i][0] * PROF_STEP) / 525;
			size = (ALines[i][1] * PROF_STEP) / 525;
			diff += (i * PROF_STEP);
			size += (i * PROF_STEP);
			gDPFillRectangle(gp++,(PROF_LEFT+diff),PROF_BOT+16,(PROF_LEFT+size),PROF_BOT+16+7);
		}

		gDPSetPrimColor(gp++,0,0,0xFF,0xFF,0xFF,0xFF);
		for(i=0;i<4;i++)
			gDPFillRectangle(gp++,(PROF_LEFT-1)+(i*PROF_STEP),PROF_BOT,(PROF_LEFT+1)+(i*PROF_STEP),PROF_BOT+15);

#endif

		gDPFullSync(gp++);
		gSPEndDisplayList(gp++);


		i = ((u32)gp - (u32)dynamicp->glist) / sizeof( Gfx );
		if (i > GLIST_LEN)
			FatalError( "GLIST_LEN TOO SMALL", __FILE__, __LINE__ );


		CalcTime = osViGetCurrentLine() + (VBLticks * 525);

		Gfx_Wait();

		DrawTime = osViGetCurrentLine() + (VBLticks * 525);

#if 0
{
	char
		numstr[8],
		text[32];

	memset( numstr, 0, 8 );
	IntToDecStr( sl___gCurMemFree*16/1024, numstr );

	sprintf( text, "Mem %s", numstr );
	BlastMessage( text );
}
#endif

		draw_buffer=1-draw_buffer;

		if (currentProcess->SwapScrs)
			osViSwapBuffer( cfb[draw_buffer] );
		else
			osViSwapBuffer( cfb[0] );


#ifndef FINAL
	if (!Stack_Check( IdleStack, 128 ))										FatalError( "bad idle stack", __FILE__, __LINE__ );
	if (!Stack_Check( MainStack, MAIN_STACK_SIZE ))				FatalError( "bad main stack", __FILE__, __LINE__ );
	if (!Stack_Check( VBLStack, VBL_STACK_SIZE ))					FatalError( "bad vbl stack", __FILE__, __LINE__ );
	if (!Stack_Check( PreNmiStack, PRENMI_STACK_SIZE ))		FatalError( "bad prenmi stack", __FILE__, __LINE__ );
	if (!Stack_Check( AudioThreadStack, (2*1024) ))				FatalError( "bad audio stack", __FILE__, __LINE__ );
	#ifndef DEBUG
	if (!Stack_Check( ErrorStack, ERROR_STACK_SIZE ))			FatalError( "bad error stack", __FILE__, __LINE__ );
	#endif
#endif


		if (ChangeVidMode)
		{
			if (ChangeVidMode == 2)
				osViBlack( TRUE );
			else
			{
				osViSetMode( &osViModeTable[VideoMode] );
				osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_ON | OS_VI_DITHER_FILTER_ON );

				AllowSubHack = TRUE;
			}

			ChangeVidMode--;
		}

		FrameTime = (osViGetCurrentLine() * 128) / 525;
		ticks = VBL_Wait() - counter;
		if (ticks < 2)
		{
			ticks++;
			VBL_Wait();
		}
		FrameTicks = ticks;
		counter += ticks;
		if (ticks > 8)
			ticks = 8;
		VBLticks = 0;

		frame++;

		Gfx_Do( dynamicp->glist, (u32)gp - (u32)dynamicp->glist );
	}
}



/*************************************************************************
 *
 * void BuildControllerValue(ctrlData *in,unsigned long *out)
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void BuildControllerValue(ctrlData *in,unsigned long *out)
{
	int 		xx,yy;

	/*
	 * initialise output
	 */

	*out=0;

	/*
	 * get analog X/Y appropriate scaled
	 */

	xx=in->x;		yy=in->y;
	xx=(xx*127)/80;		yy=(yy*127)/80;
	if(xx<-127) xx=-127;	if(xx>127) xx=127;
	if(yy<-127) yy=-127;	if(yy>127) yy=127;

	/*
	 * convert to suitable bits
	 */

	if(xx < -DEAD_ZONE)
	{
		*out |= CTRL_LEFT;
		*out |= ((-xx)>>3) << 24;
	}
	else if(xx > DEAD_ZONE)
	{
		*out |= CTRL_RIGHT;
		*out |= ((xx)>>3) << 24;
	}
	if(yy < -DEAD_ZONE)
	{
		*out |= CTRL_DOWN;
		*out |= ((-yy)>>3) << 28;
	}
	else if(yy > DEAD_ZONE)
	{
		*out |= CTRL_UP;
		*out |= ((yy)>>3) << 28;
	}

	/*
	 * now process all the buttons
	 */

	if(in->buttons & CONT_A)	*out |= CTRL_FIRE_A;
	if(in->buttons & CONT_B)	*out |= CTRL_FIRE_B;
	if(in->buttons & CONT_START)	*out |= CTRL_START;
	if(in->buttons & CONT_E) 	*out |= CTRL_C_UP;
	if(in->buttons & CONT_D) 	*out |= CTRL_C_DOWN;
	if(in->buttons & CONT_C) 	*out |= CTRL_C_LEFT;
	if(in->buttons & CONT_F)	*out |= CTRL_C_RIGHT;
	if(in->buttons & CONT_UP) 	*out |= CTRL_PAD_UP;
	if(in->buttons & CONT_DOWN) 	*out |= CTRL_PAD_DOWN;
	if(in->buttons & CONT_LEFT) 	*out |= CTRL_PAD_LEFT;
	if(in->buttons & CONT_RIGHT)	*out |= CTRL_PAD_RIGHT;
	if(in->buttons & CONT_L)	*out |= CTRL_SHOULDER_L;
	if(in->buttons & CONT_R)	*out |= CTRL_SHOULDER_R;
	if(in->buttons & CONT_G)	*out |= CTRL_TRIGGER;
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void TweakVideoModes()
{
	OSViMode
		*mode;
	u32
		i,
		m,
		n,
		vid_w,
		vid_h;

	memcpy( OldModes, osViModeTable, 42 * sizeof( OSViMode ) );


// VIDMODE_SHELL

	mode = &osViModeTable[VidMode_Shell];

	mode->comRegs.width = 512;

	mode->comRegs.xScale = 0x0200 * 512 / 320;

	for (i=0; i<2; i++)
		mode->fldRegs[i].origin = 512 * 2;


// VIDMODE_GAME

	m = VidMode_Game;
	mode = &osViModeTable[m];

	mode->comRegs.width = SCR_X_RES;

	n = (OldModes[m].comRegs.hStart >> 16) + X_BORDER;
	mode->comRegs.hStart = (n + VID_X_RES) | (n << 16);

	mode->comRegs.xScale = 0x0400 * SCR_X_RES / VID_X_RES;

	for (i=0; i<2; i++)
	{
		mode->fldRegs[i].origin = SCR_X_RES * ((i*2)+2);

		n = OldModes[m].fldRegs[i].yScale & 0xffff0000;
		mode->fldRegs[i].yScale = (0x0800 * SCR_Y_RES / VID_Y_RES) | n;

		n = (OldModes[m].fldRegs[i].vStart >> 16) + Y_BORDER;
		mode->fldRegs[i].vStart = (n + VID_Y_RES - 6) | (n << 16);
	}


// VIDMODE_INTRO

	m = VidMode_Intro;
	mode = &osViModeTable[m];

	mode->comRegs.width = 576;

	n = (OldModes[m].comRegs.hStart >> 16) + 32;
	mode->comRegs.hStart = (n + 576) | (n << 16);

//	mode->comRegs.xScale = 0x0400 * SCR_X_RES / VID_X_RES;

	for (i=0; i<2; i++)
	{
		mode->fldRegs[i].origin = 576 * ((i*2)+2);

//		n = OldModes[m].fldRegs[i].yScale & 0xffff0000;
//		mode->fldRegs[i].yScale = (0x0800 * SCR_Y_RES / VID_Y_RES) | n;

		n = (OldModes[m].fldRegs[i].vStart >> 16) + 10;
		mode->fldRegs[i].vStart = (n + 460 - 6) | (n << 16);
	}
}


void SetVidMode( s32 mode )
{
	switch (mode)
	{
		case VIDMODE_INTRO:		mode = VidMode_Intro;		break;
		case VIDMODE_SHELL:		mode = VidMode_Shell;		break;
		case VIDMODE_GAME:		mode = VidMode_Game;		break;
	}

	if (mode != VideoMode)
	{
		ChangeVidMode = 2;
		VideoMode = mode;
	}
}


void RestoreVideoModes()
{
	memcpy( osViModeTable, OldModes, 42 * sizeof( OSViMode ) );
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/


static void MemoryInit()
{
	/*
	 * create memory heap
	 */

        extern char _bootSegmentBssEnd[];
        programEnd=(unsigned long)_bootSegmentBssEnd;
	heapSize=((osMemSize^0x80000000L)-MEM_TOP_SPACE)-(programEnd+PROG_END_SPACE);
	MemInit(NULL,0L,(unsigned long *)(programEnd+PROG_END_SPACE),heapSize);
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static void RenderHack()
{
	Gfx
		*gp;

	dynamicp = &DBdata[draw_buffer];
	gp = dynamicp->glist;

	currentProcess->Render(dynamicp,draw_buffer,&gp);

	gDPFullSync(gp++);
	gSPEndDisplayList(gp++);

	Gfx_Wait();

	draw_buffer=1-draw_buffer;
	osViSwapBuffer( cfb[draw_buffer] );
	VBL_Wait();

	Gfx_Do( dynamicp->glist, (u32)gp - (u32)dynamicp->glist );
}


void GfxSubHack()
{
	Gfx
		*gp;

	move_camera();

	if (AllowSubHack)
	{
		RenderHack();
		RenderHack();
	}
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

static s8
	AttractLine,
	AttractScreen;


void SetupAttractText()
{
	AttractScreen = 0;
	AttractLine = 0;
}


static s32 CheckButtonOut()
{
	u32
		bits;

	ReadControllers();

	bits = cont[0] | cont[1] | cont[2] | cont[3];
	if (bits)
	{
		currentProcess->status = PROCESS_FREEING;
		return( TRUE );
	}

	return( FALSE );
}


void ShowAttractText()
{
	static char
		*text[2][5] =
		{
			"NEW SPECTACULAR DUNKS",
			"HI-RES FACES AND ANIMATIONS",
			"ARCADE MODE",
			"KOBE MOVES",
			"REAL PLAYER SIGNATURE MOVES",

			"ALL NEW PLAY-BY-PLAY",
			"3-POINT CONTEST",
			"ALL NEW DRAFT MODE",
			"CAREER MODE",
			"ROOKIE CONTROL MODE",
		};
	Gfx
		*gp;
	void
		*large_font,
		*small_font;
	u8
		*logo;
	s32
		i,
		off,
		dir,
		db = 0,
		done = FALSE,
		quit = FALSE;

	osViBlack( TRUE );
	Gfx_Wait();

	ClearScreen( DBdata[0].glist, cfb[0], SCR_X_RES, SCR_Y_RES*2 );

	osViSetMode( &osViModeTable[VidMode_Shell] );
	osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_ON | OS_VI_DITHER_FILTER_ON );
	osViSwapBuffer( cfb[db] );

	VBL_Wait();


	BG_Init();

	BG_Load( BG_KOBE1 + random( 5 ), 0 );

	large_font = ShadFont_Load( "LargShel.FNT" );
	small_font = ShadFont_Load( "SmalShel.FNT" );

	logo = AllocAndLoadFile( "logo.scr", MEM_ANY );
	*(u16 *)logo = 0;


	while (!done && !quit)
	{
		off = 24;
		dir = (AttractLine & 1) ? +1 : -1;

		if (AttractLine < 5)
		{
			Audio_SfxPlay( GameBank, SFX_FASTTEXT, AUDIO_MAX_VOLUME );
			Audio_Process();
		}

		while (off && !quit)
		{
			db = (1 - db);

			gp = DBdata[0].glist;

			// initialise RCP
			gSPSegment( gp++, 0, 0 );
			gSPDisplayList( gp++, DLInitRDP );
			gSPDisplayList( gp++, DLInitRSP );

			// set scissor rect
			gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, 512, 240 );

			gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 512, cfb[db] );
			gDPSetCycleType( gp++, G_CYC_1CYCLE );
			gDPSetColorDither( gp++, G_CD_MAGICSQ );
			gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
			gDPSetTextureLUT( gp++, G_TT_RGBA16 );
			gDPSetTexturePersp( gp++, G_TP_NONE );
			gDPSetTextureFilter( gp++, G_TF_BILERP );
			gDPSetAlphaCompare( gp++, G_AC_NONE );
			gDPSetRenderMode( gp++, G_RM_OPA_SURF, G_RM_OPA_SURF2 );

			gp = BG_Draw( gp, 0, 0, FALSE, 204 );

			ShadFont_Set( small_font );
			ShadFont_Color( 255, 255, 255 );

			for (i=0; i<AttractLine; i++)
			{
				gp = ShadFont_DrawCentered( gp,
																		text[AttractScreen][i],
																		256,
																		120+(i*20) );
			}

			if (AttractLine < 5)
			{
				ShadFont_Color( 237, 183, 0 );

				i = dir * ((off * off) - 4);

				gp = ShadFont_DrawCentered( gp,
																		text[AttractScreen][AttractLine],
																		256+i,
																		120+(AttractLine*20) );
			}

			if (AttractScreen)
			{
				ShadFont_Color( 237, 183, 0 );

				gp = ShadFont_Draw( gp, "PLUS", 100, 94 );
			}

			gDPFullSync( gp++ );
			gSPEndDisplayList( gp++ );

			Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
			Gfx_Wait();


			if (1)
			{
				u16
					*pal;
				u8
					*tex;
				s32
					x,
					y;

				pal = (u16 *)logo;
				tex = &logo[256*2];

				for (y=0; y<85; y++)
				{
					for (x=0; x<268; x++)
					{
						if (*tex)
							cfb[db][((y+20)*512)+(x+122)] = pal[*tex];

						tex++;
					}
				}

				osWritebackDCacheAll();
			}


			osViSwapBuffer( cfb[db] );
			VBL_Wait();

			if (CheckButtonOut())
				quit = TRUE;

			off -= 2;
		}


		AttractLine++;

		if (AttractScreen)
		{
			for (i=0; i<(2*60); i++)
			{
				VBL_Wait();

				if (CheckButtonOut())
				{
					quit = TRUE;
					break;
				}
			}

			if (AttractLine > 5)
			{
				done = TRUE;
				AttractScreen = 0;
				AttractLine = 0;
			}
		}
		else
		{
			if (AttractLine < 6)
			{
				done = TRUE;

				for (i=0; i<(2*60); i++)
				{
					VBL_Wait();

					if (CheckButtonOut())
					{
						quit = TRUE;
						break;
					}
				}
			}
			else
			{
				for (i=0; i<(2*60); i++)
				{
					VBL_Wait();

					if (CheckButtonOut())
					{
						quit = TRUE;
						break;
					}
				}

				AttractScreen++;
				AttractLine = 0;
			}
		}
	}


	MemFree( logo );

	ShadFont_Free( small_font );
	ShadFont_Free( large_font );

	BG_Deinit();


	SetVidMode( VIDMODE_SHELL );

//while (1);
}


/*************************************************************************
 *
 *
 *
 *************************************************************************
 *
 *
 *
 *************************************************************************/

void ShowGameHint()
{
	Gfx
		*gp;
	s32
		db = 0;

	osViBlack( TRUE );
	Gfx_Wait();

	ClearScreen( DBdata[0].glist, cfb[0], SCR_X_RES, SCR_Y_RES*2 );

	osViSetMode( &osViModeTable[VidMode_Game] );
	osViSetSpecialFeatures( OS_VI_GAMMA_OFF | OS_VI_DIVOT_ON | OS_VI_DITHER_FILTER_ON );
	osViSwapBuffer( cfb[0] );

	VBL_Wait();


	gp = DBdata[0].glist;

	// initialise RCP
	gSPSegment( gp++, 0, 0 );
	gSPDisplayList( gp++, DLInitRDP );
	gSPDisplayList( gp++, DLInitRSP );

	// set scissor rect
	gDPSetScissor( gp++, G_SC_NON_INTERLACE, 0, 0, SCR_X_RES, SCR_Y_RES );

	gDPSetColorImage( gp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCR_X_RES, cfb[0] );
	gDPSetCycleType( gp++, G_CYC_1CYCLE );
	gDPSetColorDither( gp++, G_CD_MAGICSQ );
	gSPTexture( gp++, 0x8000, 0x8000, 0, G_TX_RENDERTILE, G_ON );
	gDPSetTextureLUT( gp++, G_TT_RGBA16 );
	gDPSetTexturePersp( gp++, G_TP_NONE );
	gDPSetTextureFilter( gp++, G_TF_BILERP );
	gDPSetAlphaCompare( gp++, G_AC_NONE );
	gDPSetRenderMode( gp++, G_RM_OPA_SURF, G_RM_OPA_SURF2 );

	OverGfx_BuildNextHint( 1, FALSE );
	gp = OverGfx_DrawGameHint( gp, 1 );

	gDPFullSync( gp++ );
	gSPEndDisplayList( gp++ );

	Gfx_Do( DBdata[0].glist, (u32)gp - (u32)DBdata[0].glist );
	Gfx_Wait();


	osViSwapBuffer( cfb[0] );
	VBL_Wait();
}
