/*
 *  Copyright (C) 2008 Nuke (wiinuke@gmail.com)
 *
 *  this file is part of GeckoOS for USB Gecko
 *  http://www.usbgecko.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
 // Please note some functions need modifications to the libogc card due to
 // errors in the lib

#include <gctypes.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <gccore.h>	
#include <malloc.h>
#include "font.h"
#include "geckomem.h"
#include "geckomenu.h"
#include "geckocard.h"
#include "tools.h"

#define gciheadersize 64
#define blocksize 8192
#define packetsize  63488
#define cardmemoryarea 0x80100000

static u8 packetbuffer[packetsize] ATTRIBUTE_ALIGN(32);
static u8 SysArea[CARD_WORKAREA] ATTRIBUTE_ALIGN (32);
static card_dir dir;
static card_file file;
static card_stat stat;

u8 ack_packet = 0xAA;
u8 fail_nocard = 0xFA;
u8 fail_init = 0xFB;
u8 fail_nomount = 0xFC;
u8 fail_noentry = 0xFD;
u8 fail_sectorsize = 0xFE;
u8 fail_open = 0xEF;
u8 fail_status = 0xED;
u8 fail_attribs = 0xEC;	
u8 fail_erase = 0xEB;
u8 fail_exists = 0xEA;
u8 fail_create = 0xE9;
u8 fail_write = 0xE8;
u8 fail_setstatus = 0xE7;
u8 end_packet = 0xEE;

typedef struct {
	char gamecode[4];
	char company[2];
	char reserved;	
	char banner_format;
	char filename[CARD_FILENAMELEN];
	s32 last_modify_time;
	s32 icon_offset;
	s16 icon_gfx_format;
	s16 icon_anim_speed;
	char file_perms;	
	char copy_counter;	
	s16 index;		
	s16 file_length;	
	s16 reserved1;	
	s32 comment_address;
} ATTRIBUTE_PACKED GCI;


void
card_removed (s32 chn, s32 result)
{
  CARD_Unmount (chn);
}

/*---------------------------------------------------------------------------*
    Name:               commands_uploadsave
    Description:        Upload a GCI save file
*---------------------------------------------------------------------------*/
u32 commands_uploadsave()
{
	u8 dolfilesize[4];
	char company[4];
	char gamecode[6];
	char filename[34];
	u8 *cardsavebuffer = (u8*) cardmemoryarea;
	u8 ack_packet = 0xAA;
	
	s32 gcsave_size = 0;
	s32 fullpackets = 0;
	s32 currentpacket = 0;
	s32 lastpacketbytes = 0;
	u32 sectorsize;
	s32 total;
	s32 sectorcount = 0;
	s32 packetcount = 0;
	s32 errorcode;
	s32 filelen;

	GCI gci;
	card_dir dir;
	card_file file;
	card_stat stat;
	
	memset (SysArea, 0, CARD_WORKAREA);

	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	usb_recvbuffer_safe(EXI_CHANNEL_1,&dolfilesize, 4);

	gcsave_size = be32(dolfilesize);			
	memset((char*)cardmemoryarea, 0, gcsave_size);			
	
	// Calculate size
	fullpackets = gcsave_size / packetsize;				
    lastpacketbytes  = gcsave_size % packetsize;	
	doprogress(0,fullpackets); // Init and draw progress box
	
	// Upload save
	for (currentpacket=0; currentpacket < fullpackets;) 
	{
		memset(packetbuffer, 0, packetsize);
		usb_recvbuffer_safe(EXI_CHANNEL_1,&packetbuffer, packetsize);
		memcpy(cardsavebuffer, packetbuffer, packetsize);
		
		cardsavebuffer += packetsize;
		doprogress(1,fullpackets);
		currentpacket++;								
		
		usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	}
	doprogress(2,fullpackets);
	// And remainder
	memset(packetbuffer, 0, packetsize);
	usb_recvbuffer_safe(EXI_CHANNEL_1,&packetbuffer, lastpacketbytes);
	
	memcpy(cardsavebuffer, packetbuffer, lastpacketbytes);
	DCFlushRange((void*)cardmemoryarea, gcsave_size);
	cardsavebuffer = (void*)cardmemoryarea;

	memset(&gci, 0, gciheadersize);
	memcpy(&gci, cardsavebuffer, gciheadersize);

	company[2] = 0;
	gamecode[4] = 0;
	filename[32] = 0;
	memcpy(company, &gci.company, 2);
	memcpy(gamecode, &gci.gamecode, 4);
	memcpy(filename, &gci.filename, CARD_FILENAMELEN);
	
	filelen = gcsave_size - gciheadersize;

	// Probe Port to see if it is a memory card //
	errorcode = CARD_ProbeEx (CARD_SLOTA, NULL, NULL);
	if(errorcode < CARD_ERROR_BUSY){						
		write_font(195, 178, "No Memory Card in Slot A.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nocard,1);
		return 0;
	}
	while(errorcode == CARD_ERROR_BUSY){	
		errorcode = CARD_ProbeEx (CARD_SLOTA, NULL, NULL);
	}
	// Card Init
	errorcode = CARD_Init(gamecode, company);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Init Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_init,1);
		return 0;
	}
	CARD_SetGamecode(gamecode);
	CARD_SetCompany(company);
	// Mount
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Mount Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		return 0;
	}
	// Get Sector Size //
	errorcode = CARD_GetSectorSize (CARD_SLOTA, &sectorsize);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Sector Size Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_sectorsize,1);
		CARD_Unmount (CARD_SLOTA);
		return 0;
	}
	// Check file exists
	CARD_FindFirst (CARD_SLOTA, &dir, true);
	while (errorcode != CARD_ERROR_NOFILE){
    if (strcmp ((char *) dir.filename, (char *)filename) == 0){
		 write_font(195, 178, "This filename already exists");
		 usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_exists,1);
	     CARD_Unmount (CARD_SLOTA);
	     return 0;
	}
		errorcode = CARD_FindNext (&dir);
	}
	// Create file
	errorcode = CARD_Create (CARD_SLOTA, filename, filelen, &file);
	if (errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Create Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_create,1);
		CARD_Unmount (CARD_SLOTA);
		return 0;
	}
	// Write file
	cardsavebuffer = (void*)0x80100040;
	packetcount = 0;
	total = filelen / sectorsize;
	for(sectorcount = 0; sectorcount < total; sectorcount++)
	{
		errorcode = CARD_Write(&file, cardsavebuffer+packetcount, sectorsize, sectorcount * sectorsize);
		if (errorcode < CARD_ERROR_READY){
			write_font(195, 178, "Card Write Error");
			usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_write,1);
			CARD_Close (&file);
			CARD_Unmount (CARD_SLOTA);
			return 0;
		}
		packetcount += sectorsize;
	}
	// Set Status Attributes //
	memset(&stat, 0, sizeof(card_stat));
	memcpy(stat.gamecode, gci.gamecode, 4);
	memcpy(stat.company, gci.company, 2);
	memcpy(stat.filename, gci.filename, 32);
	stat.banner_fmt = gci.banner_format;
	stat.time = gci.last_modify_time;
	stat.icon_addr = gci.icon_offset;
	stat.icon_fmt = gci.icon_gfx_format;
	stat.icon_speed = gci.icon_anim_speed;
	stat.len = gci.file_length;
	stat.comment_addr = gci.comment_address;

	errorcode = CARD_SetStatus(CARD_SLOTA, file.filenum, &stat);
	if (errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Set Status Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_setstatus,1);
		CARD_Close (&file);
		CARD_Unmount (CARD_SLOTA);
		return 0;
	}
	// Close
	CARD_Close (&file);
	CARD_Unmount (CARD_SLOTA);
	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	return 1;
}



/*---------------------------------------------------------------------------*
    Name:               commands_dumpsave
    Description:        Erase a save on the card
*---------------------------------------------------------------------------*/
u32 commands_dumpsave()
{
	char company[4];
	char gamecode[6];
	char filename[34];
	char gcibuffer[gciheadersize];
	char *cardsavebuffer = (char*) cardmemoryarea;
	u8 fileperms;
	char savenumber;
	s32 memorystart = cardmemoryarea;
	s32 memoryend;
	s32 memrange;
	s32 fullpackets;
	s32 lastpacketbytes;
	s32 sectorcount;
	s32 total;
	s32 errorcode = 0;
	s32 savefilesize;
	s32 saveitem =0;
	s32 i;
	GCI gci;

	// Init Card
	errorcode = CARD_Init ("    ", "  ");
	if(errorcode < CARD_ERROR_READY){						
		write_font(195, 178, "Couldn't Init Card.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_init,1);
		return 0;
	}
	CARD_SetGamecode("    ");
	CARD_SetCompany("  ");

	// Mount Card
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Couldn't Mount Card.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		return 0;
	}
	// Get Directory info and store //
	errorcode = CARD_FindFirst (CARD_SLOTA, &dir, true);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Entry Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_noentry,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// ACK
	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	usb_recvbuffer_safe(EXI_CHANNEL_1,&savenumber,1);
	saveitem = savenumber;

	for (i=0; i < saveitem; i++) {
		CARD_FindNext (&dir);
	}
	
	company[2] = 0;
	gamecode[4] = 0;
	filename[32] = 0;
	memcpy(company, &dir.company, 2);
	memcpy(gamecode, &dir.gamecode, 4);
	memcpy(filename, &dir.filename, CARD_FILENAMELEN);
	
	CARD_SetGamecode(gamecode);
	CARD_SetCompany(company);

	// Card Mount
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Mount Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		return 0;
	}
	// Get Sector Size
	u32 sectorsize = 0;
	errorcode = CARD_GetSectorSize (CARD_SLOTA, &sectorsize);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Sector Size Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_sectorsize,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// Card Open	
	errorcode = CARD_Open (CARD_SLOTA, filename, &file);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Open Card");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_open,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// Get card status info for gci header //
	errorcode = CARD_GetStatus (CARD_SLOTA, file.filenum, &stat);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Status Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_status,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// Get file permission information and store
	errorcode = CARD_GetAttributes (CARD_SLOTA, file.filenum, &fileperms);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Attribute Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_attribs,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// ACK
	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);

	// Set the GCI header
	memset(&gci, 0, gciheadersize);
	memcpy(gci.gamecode, stat.gamecode, 4);
	memcpy(gci.company, stat.company, 2);
	gci.reserved = 0xff;
	gci.banner_format = (stat.banner_fmt &CARD_BANNER_MASK) + 4;
	memcpy(gci.filename, stat.filename, CARD_FILENAMELEN);
	gci.last_modify_time = stat.time;
	gci.icon_offset = stat.icon_addr;
	gci.icon_gfx_format = stat.icon_fmt;
	gci.icon_anim_speed = stat.icon_speed;
	gci.file_perms = fileperms;
	gci.copy_counter = 0x00;
	gci.index = file.iblock;
	gci.file_length = (stat.len / blocksize);
	gci.reserved1 = 0xffff;
	gci.comment_address = stat.comment_addr;
	memcpy(gcibuffer, &gci, gciheadersize);

	// Send the GCI header 
	usb_sendbuffer_safe(EXI_CHANNEL_1,&gcibuffer, gciheadersize);	

	 // Calculate buffer size 
	savefilesize = file.len;
	memoryend = memorystart + savefilesize;
	memset((char*)cardmemoryarea, 0, savefilesize);	// Clear the buffer first //
	memrange = (memoryend - memorystart);
	
	fullpackets = memrange / packetsize;		// 64k chunks
	lastpacketbytes  = memrange % packetsize;	// get remainder chunks
	doprogress(0,fullpackets); // Init and draw progress box

	total = file.len / sectorsize;
	// Send file length to PC //
	usb_sendbuffer_safe(EXI_CHANNEL_1,(u8 *) &savefilesize, 4);
	// Do Read loop to copy all the save into the buffer //
	int packetcount = 0;
	cardsavebuffer = (char*)cardmemoryarea;
	for(sectorcount = 0; sectorcount < total; sectorcount++)
	{
		errorcode = CARD_Read(&file, cardsavebuffer+packetcount, sectorsize, sectorcount * sectorsize);
		if (errorcode < 0){
			write_font(195, 178, "Card Read Error");
			CARD_Unmount(CARD_SLOTA);
			break;
		}
		packetcount = packetcount + sectorsize;
		doprogress(1,fullpackets);
	}
	doprogress(2,fullpackets);
	// Close and Unmount //
	CARD_Close (&file);
	CARD_Unmount(CARD_SLOTA);
	gecko_readrange(memorystart, memoryend);		
	return 1;
}

/*---------------------------------------------------------------------------*
    Name:               commands_erasesave
    Description:        Erase a save on the card
*---------------------------------------------------------------------------*/
u32 commands_erasesave()
{
	char company[4];
	char gamecode[6];
	char filename[34];
	int i;
	u8 savenumber;
	s32 errorcode = 0;
	s32 saveitem = 0;
	
	// Init Card
	errorcode = CARD_Init ("    ", "  ");
	if(errorcode < CARD_ERROR_READY){						
		write_font(195, 178, "Card Init Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_init,1);
		return 0;
	}
	CARD_SetGamecode("    ");
	CARD_SetCompany("  ");
	// Mount Card
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Mount Card.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		return 0;
	}
	// Get Directory info and store //
	errorcode = CARD_FindFirst (CARD_SLOTA, &dir, true);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Entry Found.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_noentry,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	usb_recvbuffer_safe(EXI_CHANNEL_1,&savenumber,1);
	
	saveitem = savenumber;
	for (i=0; i < saveitem; i++) {
		CARD_FindNext (&dir);
	}
	
	company[2] = 0;
	gamecode[4] = 0;
	filename[32] = 0;
	memcpy(company, &dir.company, 2);
	memcpy(gamecode, &dir.gamecode, 4);
	memcpy(filename, &dir.filename, CARD_FILENAMELEN);

	// Re Init
	CARD_SetGamecode(gamecode);
	CARD_SetCompany(company);
	// Card Mount
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if (errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Mount Card.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}
	// Delete Save
	errorcode = CARD_Delete (CARD_SLOTA, filename);
	if (errorcode < CARD_ERROR_READY) {
      write_font(195, 178, "Card Erase Error.");
	  usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_erase,1);
	  CARD_Unmount(CARD_SLOTA);
	  return 0;
   }
   // Done
    usb_sendbuffer_safe(EXI_CHANNEL_1,&end_packet,1);
	CARD_Unmount(CARD_SLOTA);
	return 1;
}

/*---------------------------------------------------------------------------*
    Name:               commands_listcard
    Description:        List the Memory Card contents
*---------------------------------------------------------------------------*/
u32 commands_listcard()
{

	char gcifilename[32];
	s32 errorcode = 0;

	memset (SysArea, 0, CARD_WORKAREA);
	
	// Check Card is inserted
	errorcode = CARD_ProbeEx (CARD_SLOTA, NULL, NULL);
	if(errorcode < CARD_ERROR_BUSY){						
		write_font(195, 178, "No Memory Card in Slot A.");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nocard,1);
		return 0;
	}
	while(errorcode == CARD_ERROR_BUSY){	
		errorcode = CARD_ProbeEx (CARD_SLOTA, NULL, NULL);
	}
	// Init Memory Card //
	errorcode = CARD_Init ("    ", "  ");
	if(errorcode < CARD_ERROR_READY){						
		write_font(195, 178, "Card Init Error");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_init,1);
		return 0;
	}
	CARD_SetGamecode("    ");
	CARD_SetCompany("  ");
	// Mount Memory Card //
	errorcode = CARD_Mount (CARD_SLOTA, SysArea, card_removed);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "Card Mount");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_nomount,1);
		return 0;
	}
	errorcode = CARD_FindFirst (CARD_SLOTA, &dir, true);
	if(errorcode < CARD_ERROR_READY){
		write_font(195, 178, "No Files On Memory Card");
		usb_sendbuffer_safe(EXI_CHANNEL_1,&fail_noentry,1);
		CARD_Unmount(CARD_SLOTA);
		return 0;
	}

	usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
	// List Loop //
	do
    { 
		usb_sendbuffer_safe(EXI_CHANNEL_1,&ack_packet,1);
		memcpy(gcifilename, &dir.filename, 32);
		DCFlushRange(gcifilename, 32);
		usb_sendbuffer_safe(EXI_CHANNEL_1,&gcifilename, 32);
	} while (CARD_FindNext (&dir) >= 0);

	usb_sendbuffer_safe(EXI_CHANNEL_1,&end_packet,1);	
	CARD_Unmount(CARD_SLOTA);
	return 1;
}
