/************************************************************************************
// Chicken Run Memory Card Stuff...
************************************************************************************/

#include "global.h"
#include "main.h"
#include "card.h"
#include "gametext.h"
#include "menu.h"
#include "overlays.h"
#include "maths.h"
#include "options.h"
#include "startup.h"
#include "inventory.h"
#include "sound.h"
#include "islcard.h"

char *cardOURTITLE = "Chicken Run";

// ===================================================================
// Okay, this is the important one. Current state of the entire game
GameSaveDataType gameSaveData;

// For use during replays of acts. Not saved to card.
GamePlayDataType gamePlayDataMain;

// Points to either the replay one, or to the current one inside the savedata.
GamePlayDataType *gamePlayData = &gamePlayDataMain;

// ===================================================================
// Ascii & SJIS. Lovely.

// LookUp Tables for the Ascii to Sjis Function.
static unsigned short ascii_table[3][2] = {
	{0x824f, 0x30},	/* 0-9  */
	{0x8260, 0x41},	/* A-Z  */
	{0x8281, 0x61},	/* a-z  */
};

// ASCII code to Shift-JIS code transfer table (kigou)
 
static unsigned short ascii_k_table[] = {
	0x8140,		/*   */
	0x8149,		/* ! */
	0x8168,		/* " */
	0x8194,		/* # */
	0x8190,		/* $ */
	0x8193,		/* % */
	0x8195,		/* & */
	0x8166,		/* ' */
	0x8169,		/* ( */
	0x816a,		/* ) */
	0x8196,		/* * */
	0x817b,		/* + */
	0x8143,		/* , */
	0x817c,		/* - */
	0x8144,		/* . */
	0x815e,		/* / */
	0x8146,		/* : */
	0x8147,		/* ; */
	0x8171,		/* < */
	0x8181,		/* = */
	0x8172,		/* > */
	0x8148,		/* ? */
	0x8197,		/* @ */
	0x816d,		/* [ */
	0x818f,		/* \ */
	0x816e,		/* ] */
	0x814f,		/* ^ */
	0x8151,		/* _ */
	0x8165,		/* ` */
	0x816f,		/* { */
	0x8162,		/* | */
	0x8170,		/* } */
	0x8150,		/* ~ */
};


// Convert the Ascii Text String to SJIS.
void asciiStringToSJIS(unsigned char *string, unsigned char *dest) 
{
	int	i;
	
	int sjis_code;
	int ascii_code;
	
	unsigned char stmp = 0;
	unsigned char stmp2;

	unsigned char *dest2;
						   
	dest2 = dest;

	for( i=0; i<32; i++ )
	{
		*dest2++ = 0x81;				  
		*dest2++ = 0x40;
	}
	
							    
	while(*string)
	{
		stmp2 = 0;
		ascii_code = *string++;
		if ((ascii_code >= 0x20) && (ascii_code <= 0x2f))
			stmp2 = 1;
		else
		if ((ascii_code >= 0x30) && (ascii_code <= 0x39))
			stmp = 0;
		else
		if ((ascii_code >= 0x3a) && (ascii_code <= 0x40))
			stmp2 = 11;
		else
		if ((ascii_code >= 0x41) && (ascii_code <= 0x5a))
			stmp = 1;
		else
		if ((ascii_code >= 0x5b) && (ascii_code <= 0x60))
			stmp2 = 37;
		else
		if ((ascii_code >= 0x61) && (ascii_code <= 0x7a))
			stmp = 2;
		else
		if ((ascii_code >= 0x7b) && (ascii_code <= 0x7e))
			stmp2 = 63;
		else {
			printf("bad ASCII code 0x%x\n", ascii_code);
			break;
			//			exit(1);
		}

		if(stmp2)
			sjis_code = ascii_k_table[ascii_code - 0x20 - (stmp2 - 1)];
		else
			sjis_code = ascii_table[stmp][0] + ascii_code - ascii_table[stmp][1];

		/* Write sjis */
		*dest++ = (sjis_code&0xff00)>>8;
		*dest++ = (sjis_code&0xff);
	}
}

// ===================================================================
// Routines to extract / fill in the entire save block

// addr points to a MAX_SAVE_SIZE-byte malloc to fill in
void cardBuildBlock(CARD_SAVE_STR *data)
{

// Header first...
	memset(data,0,MAX_SAVE_SIZE);
	{
		NSPRITE *spr;

		sound_XA_PauseForLoading();
		spr = (NSPRITE *)fileLoad("SHELL\\SAVEICON.SPT",0);
		sound_XA_Resume(-1);

		if(spr)
		{
// NB - The card data headers are not quad aligned
			memcpy(&data->header.icons[0],ADD2POINTER(spr,(int)spr->image + 4), 128);
			memcpy(&data->header.icons[1],ADD2POINTER(spr,(int)spr->image + 4), 128);	// only got the one image at the mo
			memcpy(&data->header.icons[2],ADD2POINTER(spr,(int)spr->image + 4), 128);
			memcpy(&data->header.clut,ADD2POINTER(spr,(int)spr->pal), 32);
		}
		FREE(spr);
/*
		ULONG *icondata;
		icondata=(ULONG *)fileLoad("SHELL\\SAVE.BIN",0);
		if(icondata)
		{
			memcpy(&data->header,icondata,512);
			FREE(icondata);
		}
*/
	}
	data->header.magic[0] = 'S';								// Sony header format
	data->header.magic[1] = 'C';
#if PALMODE==1
	data->header.type = 0x11;
#else
	data->header.type = 0x13;
#endif
	data->header.blockEntry = 1;
	asciiStringToSJIS(cardOURTITLE, &data->header.title[0]);

	data->version = CARD_SAVE_VERSION;
	data->size = sizeof(CARD_SAVE_STR);

	memcpy(&data->options,&optionData, sizeof(OPTIONDATASTR));
	memcpy(&data->state,&gameSaveData,sizeof(GameSaveDataType));
	memcpy(&data->play,&gamePlayDataMain,sizeof(GamePlayDataType));


}

// ===================================================================
// addr points to a MAX_SAVE_SIZE-byte area that's just been read in
void cardExtractGameState(CARD_SAVE_STR *data)
{
	memcpy(&gameSaveData,&data->state,sizeof(GameSaveDataType));
	memcpy(&gamePlayDataMain,&data->play,sizeof(GamePlayDataType));

}

// ** But Not The Language **
void cardExtractPrefs(CARD_SAVE_STR *data)
{
	memcpy(&optionData, &data->options, sizeof(OPTIONDATASTR));
	optionsHitOptions(OPTIONS_HIT_ALL);
}


// initialise anything that's non-zero
void cardInitialGameState()	//GameSaveDataType *data)
{
	int i;
	GameSaveDataType *data = &gameSaveData;

#ifdef DEBUG
	if(sizeof(CARD_SAVE_STR) > MAX_SAVE_SIZE)
	{
		DB("card save str is %d. Too big - should be <= %d\n",sizeof(CARD_SAVE_STR),MAX_SAVE_SIZE);
	}
	ASSERT(sizeof(CARD_SAVE_STR) <= MAX_SAVE_SIZE);
#endif

	FMEMZERO(data,sizeof(GameSaveDataType));
	FMEMZERO(gamePlayData,sizeof(GamePlayDataType));

	for(i = 0; i < NUM_CHARACTER_INVS; i++)
	{
		currentInv = &gamePlayData->inventories[i];
		inventoryReset();
	}

	gamePlayData->map_pieces_got[8] = 1;	// Always got the first map piece
	gameSaveData.movies_available[0]=1;	// RFW - Make the game intro available

	gameSaveData.levels_available[REPLAY_AVAILABLE_ACT1] = REPLAY_NOTBEATEN;

	gameSaveData.best_times[TIME_DOGCHASE] = INIT_DOGCHASE_BEST;		// Lets make it 5 minutes...
	gameSaveData.best_times[TIME_COGCHASE] = INIT_COGCHASE_BEST;		// Lets make it 5 minutes...
	gameSaveData.best_times[TIME_ACT1] = INIT_ACT1_BEST;		// Lets make it 30 minutes...
	gameSaveData.best_times[TIME_ACT2] = INIT_ACT2_BEST;		// Lets make it 90 minutes...
	gameSaveData.best_times[TIME_ACT4] = INIT_ACT3_BEST;		// Lets make it 120 minutes...

	currentInv = &gamePlayData->inventories[INVNO_GINGER];

#ifdef GAME_SHOW_DEMO
	for(i = 0; i < N_REPLAY_LEVELS; i++)
	{
		//This makes the door go to a1_coop1_n, but everything is available thru the bomb
		gameSaveData.levels_available[i] = REPLAY_NOTBEATEN;
	}
#endif

}

void cardInitialPrefs(OPTIONDATASTR *data)
{
	FMEMZERO(data,sizeof(OPTIONDATASTR));
	optionsResetAllToDefaults();

	optionsHitOptions(OPTIONS_HIT_ALL);	// actually called by "resetalltodefaults", but better safe than sorry

}

// ================================================================
// The bootup load is done before we know what language we're in.
// Consequently, we can't put up an "accessing" screen

#define CARD_FROM_LANGUAGE	0
#define CARD_FROM_FOWLERS	1
#define CARD_FROM_SPLASH	2

typedef struct CARDSTR
{
	char from;
	char doing;
	char other_territory;
	int ticker;
	int message;
}CARDSTR;
CARDSTR cardState;

enum
{
	CARD_POSTLANG_INIT,
	CARD_POSTLANG_LOADING,
	CARD_POSTLANG_EXIT,

	CARD_HUTLOAD_INIT,
	CARD_HUTLOAD_LOADING,
	CARD_HUTLOAD_EXIT,

	CARD_HUTSAVE_INIT,
	CARD_HUTSAVE_SAVING,
	CARD_HUTSAVE_FORMAT_YN,
	CARD_HUTSAVE_FORMATTING,
	CARD_HUTSAVE_EXIT
};


//#define CARD_HEADING_Y -60
//#define CARD_MESSAGE_Y -20
//#define CARD_MESSAGE_Y -65

// ================================================================

// Allow loading from either territory's save game

// Check size of data matches the expected size, and report a corrupt save rather than a non-present one
// for testers using old savegames

int cardCheckRead(char *name, CARD_SAVE_STR *data, int size)
{
	int rval;

	utilPrintf("Card Check Read....\n");
	cardState.other_territory = 0;
	rval = cardRead(name,data,size);

	if(rval == CARDREAD_OK)
	{
		if(data->version != CARD_SAVE_VERSION || data->size != sizeof(CARD_SAVE_STR))
		{
			overlayDebugMessage("Old Version Save File. Sorry", 60);
			utilPrintf("Old-version save file. Claiming Corruption\n");
			utilPrintf("rqd vsn,len = %d %d. Actual vsn,len = %d %d\n",
				CARD_SAVE_VERSION,sizeof(CARD_SAVE_STR),data->version, data->size);
			return(CARDREAD_CORRUPT);
		}
	}
// Takes too long with a 15-item card
/*
	if(rval == CARDREAD_NOTFOUND || rval ==  CARDREAD_NOTFOUNDANDFULL)
	{
		rval = cardRead(OTHERMEMORYCARDNAME,data,MAX_SAVE_SIZE);
		if(rval == CARDREAD_OK)
		{
			utilPrintf("Other Territory\n");
			if(data->version != CARD_SAVE_VERSION || data->size != sizeof(CARD_SAVE_STR))
			{
				utilPrintf("Old-version save file. Claiming Corruption\n");
				return(CARDREAD_CORRUPT);
			}
			cardState.other_territory = 1;
		}
	}
*/
	return rval;
}

// ================================================================
// Initial preferences load
// On Action Man, this comes AFTER the "language flags" screen, and is actually part of the
// "intro cutscene" playing code...
// Basically, it loads the prefs, and whinges if there's not enough room on the memory card
// It *times out*, and doesn't deal with "unformatted" issues
#define POSTLANG_OK_DELAY		20
#define POSTLANG_IGNORE_DELAY	30
#define POSTLANG_WARNING_DELAY	150

void cardPrintMessage()
{
	int card_message_y = 0;

	switch(cardState.from)
	{
	case CARD_FROM_LANGUAGE:
		card_message_y = -65;
		break;
	case CARD_FROM_FOWLERS:
		card_message_y = -65;
		break;

	case CARD_FROM_SPLASH:
		card_message_y = 50;
		break;
	}

	if(cardState.message)
	{
		fontCentredMultiLine(font, 0, card_message_y, 400, TEXTSTR(cardState.message), 128,128,128);
	}
	switch(cardState.message)
	{
		case TEXTSTR_SAVE_ACCESSING:
		case TEXTSTR_SAVE_LOADING:
		case TEXTSTR_SAVE_SAVING:
		case TEXTSTR_SAVE_FORMATTING:
			fontCentredMultiLine(font, 0, card_message_y + 12, 400, TEXTSTR(TEXTSTR_SAVE_DONTREMOVE), 128,128,128);
			break;
	}
}

void cardPostLangDraw()
{
//	startupDraw();
//	languageDrawBackground();


// Note - the backdrop needs to be black when we hit the screen position
	int alpha;
	unsigned int plank_colour;
	int i, j;

/*	if(cardState.doing == CARD_POSTLANG_INIT)
	{
		// damp the language planks down to black...
	//	alpha = -cardState.ticker * 127 / 10;
	//	plank_colour = alpha * 0x02010101;
//		utilPrintf("tick=%d, alpha = %d, plankcol &%8x\n",cardState.ticker,alpha,plank_colour);
	}
	else if(cardState.doing == CARD_POSTLANG_EXIT)
	{
		// fade the planks back in again after setting the screen position
		alpha = (cardState.ticker - (POSTLANG_WARNING_DELAY-POSTLANG_OK_DELAY)) * 127 / POSTLANG_OK_DELAY;
		plank_colour = 0;//alpha * 0x02010101;
	}
	else
	{
		plank_colour = 0;
	}*/

	for(i=-256;i<256;i+=64)
	{
		for(j=-127;j<120;j+=64)
		{
			//alpha * 
			overlayAlphaSprite(plank, i, j, 1023, 0x808080);//plank_colour);
		}
	}

	cardPrintMessage();
}

void cardPostLangExit()
{
	startupContinue();
}


void cardPostLangCycle()
{
	CARD_SAVE_STR *data;
	int rval;

	if(cardState.ticker < 0)	// Allow a little "wait for the display to flip to what we're talking about" delay
	{
		cardState.ticker++;
		return;
	}
	if(cardState.ticker < 1000)
		cardState.ticker++;

	switch(cardState.doing)
	{
	case CARD_POSTLANG_INIT:
		cardState.message = TEXTSTR_SAVE_ACCESSING;	// "Accessing memory card, do not remove..."
		cardState.doing = CARD_POSTLANG_LOADING;
		cardState.ticker = -2;
		break;

	case CARD_POSTLANG_LOADING:
		data = MALLOC(MAX_SAVE_SIZE);

		rval = cardCheckRead(MEMORYCARDNAME,data,MAX_SAVE_SIZE);
		cardState.ticker = POSTLANG_WARNING_DELAY-POSTLANG_OK_DELAY;

		switch(rval)
		{
// ** The bootup load is just THE OPTIONS **
			case CARDREAD_OK:
				cardState.ticker = POSTLANG_WARNING_DELAY-POSTLANG_OK_DELAY;
				cardState.message = 0;
				cardInitialGameState(data);
				cardExtractPrefs(data);
				break;


// This is a happy "do nowt" routine
			case CARDREAD_NOTFOUND:
				cardState.ticker = POSTLANG_WARNING_DELAY-POSTLANG_OK_DELAY;
				cardState.message = 0;
				break;


// Action man didn't do this one, but it might be worth dealing with...
// (Warning that you won't be able to save without a memory card. Like, duh!)
			case CARDREAD_NOCARD:
				cardState.ticker = POSTLANG_WARNING_DELAY-POSTLANG_IGNORE_DELAY;
				cardState.message = 0;
				break;


// This is the one that we *NEED* to deal with here...
// (Print up a warning that you won't be able to save without freeing some space)
			case CARDREAD_NOTFOUNDANDFULL:
				cardState.message = TEXTSTR_SAVE_CARD_FULL;
				cardState.ticker = 0;	// delay for the full warning delay
				break;


// AM didn't deal with these on the post-language memory-card check, so I won't either
			default:
			case CARDREAD_CORRUPT:
			case CARDREAD_BADCARD:
			case CARDREAD_NOTFORMATTED:
				cardState.message = 0;
				cardState.ticker = POSTLANG_WARNING_DELAY-POSTLANG_IGNORE_DELAY;
				break;

		}
		FREE(data);
		cardState.doing = CARD_POSTLANG_EXIT;
		break;


	case CARD_POSTLANG_EXIT:
		if(padData.debounce[0] & (PAD_CROSS + PAD_CIRCLE + PAD_START + PAD_SELECT))
		{
			if(cardState.ticker < POSTLANG_WARNING_DELAY - POSTLANG_OK_DELAY)
			{
				cardState.ticker = POSTLANG_WARNING_DELAY - POSTLANG_OK_DELAY;
			}
		}

		if(cardState.ticker >= POSTLANG_WARNING_DELAY)
		{
			cardPostLangExit();
		}
		break;
	}

}

void cardPostLangStart()
{
	draw_routine = cardPostLangDraw;
	cycle_routine = cardPostLangCycle;

	cardState.from = CARD_FROM_LANGUAGE;
	cardState.doing = CARD_POSTLANG_INIT;
	cardState.ticker = -10;
	cardState.message = 0;
	cardState.other_territory = 0;
}


// ================================================================
// Okay... Now we need to do the fowler's hut stuff
// Where you can LOAD a game
// (which can be from a different memory card from that which you booted up with)
// and SAVE a game, too. Goody!
// tbd - "Load & Save in the Pie Machine Act" problems are still to deal with
// ================================================================

#define HUTLOAD_OK_DELAY		20
#define HUTLOAD_IGNORE_DELAY	30
#define HUTLOAD_WARNING_DELAY	150

void cardGameLoadDraw()
{
	optionsDrawOnly();
//	fontPrintCentred(font,0,CARD_HEADING_Y, TEXTSTR(TEXTSTR_SAVE_LOAD),128,128,128);
	cardPrintMessage();
}

void cardGameLoadExit()
{
	if(cardState.from == CARD_FROM_FOWLERS)
	{
		DB("Options Return\n");
		optionsReturn();
	}
	else
	{
		DB("Splash Return\n");
		levflowSplashSaveReturn();
	}

}
void cardGameLoadCycle()
{
	CARD_SAVE_STR *data;
	int rval;

	if(cardState.ticker < 0)	// Allow a little "wait for the display to flip to what we're talking about" delay
	{
		cardState.ticker++;
		return;
	}
	if(cardState.ticker < 1000)
		cardState.ticker++;

	switch(cardState.doing)
	{
	case CARD_HUTLOAD_INIT:
		cardState.message = TEXTSTR_SAVE_LOADING;	// "Accessing memory card, do not remove..."
		cardState.doing = CARD_HUTLOAD_LOADING;
		cardState.ticker = -2;
		break;

	case CARD_HUTLOAD_LOADING:
		data = MALLOC(MAX_SAVE_SIZE);
		utilPrintf("Card Read....\n");

		rval = cardCheckRead(MEMORYCARDNAME,data,MAX_SAVE_SIZE);
		cardState.ticker = HUTLOAD_WARNING_DELAY-HUTLOAD_OK_DELAY;
		cardState.doing = CARD_HUTLOAD_EXIT;

		switch(rval)
		{
			case CARDREAD_OK:
				cardState.message = TEXTSTR_SAVE_LOADEDOK;
				cardExtractPrefs(data);
				cardExtractGameState(data);
				break;

			case CARDREAD_NOTFOUND:
				cardState.message = TEXTSTR_SAVE_NONEFOUND;
				break;
			case CARDREAD_NOTFOUNDANDFULL:
				cardState.message = TEXTSTR_SAVE_NONEFOUND;
				break;

			case CARDREAD_NOCARD:	// loops internally
				cardState.message = TEXTSTR_SAVE_CARD_FOR_LOAD;
				break;


			case CARDREAD_BADCARD:
				cardState.ticker = 0;
				cardState.message = TEXTSTR_SAVE_FAILEDLOAD;
				break;

			case CARDREAD_NOTFORMATTED:
				cardState.ticker = -2;
// TRC text: During the load process,
// do not format the MEMORY CARD if it is unformatted
// or perform any other maintenance on it.				

// (There's not much point in formatting a card before loading from it, because, umm, a newly formatted
// card won't have a savegame on it, now will it?)

				cardState.message = TEXTSTR_SAVE_UNFORMATTED_OLD;
				break;

			case CARDREAD_CORRUPT:
			default:
				cardState.ticker = 0;
				cardState.message = TEXTSTR_SAVE_FAILEDLOAD;
				break;
		
		}
		FREE(data);
		break;

	case CARD_HUTLOAD_EXIT:
		if(cardState.message == TEXTSTR_SAVE_CARD_FOR_LOAD)
		{
			if(padData.debounce[0] & (PAD_CROSS))
			{
				cardState.message = TEXTSTR_SAVE_LOADING;	// "Accessing memory card, do not remove..."
				cardState.doing = CARD_HUTLOAD_LOADING;
				cardState.ticker = -2;
			}
			else if(padData.debounce[0] & (PAD_TRIANGLE))
			{
				cardGameLoadExit();
			}
		}
		else
		{
// After that excitement, wait for a debounced key...
// (If there wasn't a memory card there, we really should do the whole "retry/cancel" thang...)
			if(
				cardState.ticker >= HUTLOAD_WARNING_DELAY
				|| (padData.debounce[0] & (PAD_CROSS + PAD_CIRCLE + PAD_START + PAD_SELECT))
				)
			{
				cardGameLoadExit();
			}
		}
		break;
	}
}

void cardFowlerLoadStart()
{
	draw_routine = cardGameLoadDraw;
	cycle_routine = cardGameLoadCycle;

	utilPrintf("LOAD START\n");

	cardState.from = CARD_FROM_FOWLERS;
	cardState.doing = CARD_HUTLOAD_INIT;
	cardState.ticker = 0;
	cardState.message = 0;

}

// ================================================================

#define HUTSAVE_OK_DELAY		20
#define HUTSAVE_IGNORE_DELAY	30
#define HUTSAVE_WARNING_DELAY	150

// Cycle'n'Draw for fowlers-hut gamesave
void cardGameSaveDraw()
{
	optionsDrawOnly();

//	fontPrintCentred(font,0,CARD_HEADING_Y, TEXTSTR(TEXTSTR_SAVE_SAVE),128,128,128);

	cardPrintMessage();
}

void cardSplashSaveDraw()
{
	levflowSplashDrawOnly();
//	fontPrintCentred(font,0,CARD_HEADING_Y, TEXTSTR(TEXTSTR_SAVE_SAVE),128,128,128);
	cardPrintMessage();
}


void cardGameSaveExit()
{
	if(cardState.from == CARD_FROM_FOWLERS)
	{
		DB("Options Return\n");
		optionsReturn();
	}
	else
	{
		DB("Splash Return\n");
		levflowSplashSaveReturn();
	}
}

void cardGameSaveCycle()
{
	CARD_SAVE_STR *data;
	int rval;

	if(cardState.ticker < 0)	// Allow a little "wait for the display to flip to what we're talking about" delay
	{
		cardState.ticker++;
		return;
	}
	if(cardState.ticker < 1000)
		cardState.ticker++;

	switch(cardState.doing)
	{
	case CARD_HUTSAVE_INIT:
		cardState.message = TEXTSTR_SAVE_SAVING;	// "Saving data. Do not remove MEMORY CARD!" - Actually, this one is "Accessing memory card, do not remove..."
		cardState.doing = CARD_HUTSAVE_SAVING;
		cardState.ticker = -2;
		break;

	case CARD_HUTSAVE_SAVING:
		data = MALLOC(MAX_SAVE_SIZE);
		cardBuildBlock(data);
		utilPrintf("Card Write....\n");
		if(!cardState.other_territory)
			rval = cardWrite(MEMORYCARDNAME,data,MAX_SAVE_SIZE);
		else
			rval = cardWrite(OTHERMEMORYCARDNAME,data,MAX_SAVE_SIZE);

		cardState.ticker = HUTSAVE_WARNING_DELAY-HUTSAVE_OK_DELAY;
		cardState.doing = CARD_HUTSAVE_EXIT;

		switch(rval)
		{
			case CARDWRITE_OK:
				cardState.message = TEXTSTR_SAVE_SAVEDOK;
				break;

			case CARDWRITE_NOCARD:
				cardState.message = TEXTSTR_SAVE_CARD_FOR_SAVE;
				cardState.ticker = 0;	// full length
				break;


			case CARDWRITE_NOTFORMATTED:
				cardState.message = TEXTSTR_SAVE_UNFORMATTED_NEW;
				cardState.doing = CARD_HUTSAVE_FORMAT_YN;
				break;

			case CARDWRITE_FULL:
				cardState.message = TEXTSTR_SAVE_NOSPACE;
				cardState.ticker = 0;	// full length
				break;

			case CARDWRITE_BADCARD:
			default:
				cardState.message = TEXTSTR_SAVE_FAILEDSAVE;
				cardState.ticker = 0;	// full length
				break;
		}
		FREE(data);
		break;


	case CARD_HUTSAVE_FORMAT_YN:
		if(padData.debounce[0] & (PAD_CROSS))
		{
			cardState.message = TEXTSTR_SAVE_FORMATTING;
			cardState.doing = CARD_HUTSAVE_FORMATTING;
			cardState.ticker = -2;
		}
		else if(padData.debounce[0] & (PAD_TRIANGLE))
		{
			cardGameSaveExit();
		}
		break;

	case CARD_HUTSAVE_FORMATTING:
		rval = cardFormat();
		if(rval != 0)
		{
			cardState.message = TEXTSTR_SAVE_FAILEDFORMAT;
		}
// Loopback to the save game.
// The loopback is a sony requirement.
// They *recommend* that saving happens automatically.
		cardState.doing = CARD_HUTSAVE_INIT;
		break;


	case CARD_HUTSAVE_EXIT:
		if(cardState.message == TEXTSTR_SAVE_CARD_FOR_SAVE)
		{
			if(padData.debounce[0] & (PAD_CROSS))
			{
				cardState.message = TEXTSTR_SAVE_SAVING;	// "Accessing memory card, do not remove..."
				cardState.doing = CARD_HUTSAVE_SAVING;
				cardState.ticker = -2;
			}
			else if(padData.debounce[0] & (PAD_TRIANGLE))
			{
				cardGameSaveExit();
			}
		}
		else if(
			cardState.ticker >= HUTSAVE_WARNING_DELAY
			||
			(padData.debounce[0] & (PAD_CROSS + PAD_CIRCLE + PAD_START + PAD_SELECT))
			)
		{
			cardGameSaveExit();
		}
		break;
	}
}

void cardFowlerSaveStart()
{
	int rval;

	utilPrintf("SAVE START\n");
	draw_routine = cardGameSaveDraw;
	cycle_routine = cardGameSaveCycle;

	cardState.from = CARD_FROM_FOWLERS;
	cardState.doing = CARD_HUTSAVE_INIT;
	cardState.ticker = 0;
	cardState.message = 0;
}

void cardSplashSaveStart()
{
	int rval;

	utilPrintf("SAVE START\n");
	draw_routine = cardSplashSaveDraw;
	cycle_routine = cardGameSaveCycle;

	cardState.from = CARD_FROM_SPLASH;
	cardState.doing = CARD_HUTSAVE_INIT;
	cardState.ticker = 0;
	cardState.message = 0;
}

// ================================================================
// tbd
// the "save" is also needed at the end of some levels, called from the stats screen (save / continue options)
// Consequently, we need a save routine with definable "backdrop draw" and "finished" routines


