/*---------------------------------------------------------------------------*
  Project:  NitroSDK - wireless_shared - demos - wfs
  File:     wfs_archive.c

  Copyright 2003,2004 Nintendo.  All rights reserved.

  These coded instructions, statements, and computer programs contain
  proprietary information of Nintendo of America Inc. and/or Nintendo
  Company Ltd., and are protected by Federal copyright law.  They may
  not be disclosed to third parties or copied or duplicated in any form,
  in whole or in part, without the prior written consent of Nintendo.

  $Log: wfs_archive.c,v $
  Revision 1.1  2004/11/16 01:12:55  yosizaki
  moved from /demos/wbt/wbt-fs.


 -[old log at /demos/wbt/wbt-fs/wfs]-

  Revision 1.12  2004/10/28 14:50:21  yosizaki
  fix around FSi_LoadTables.

  Revision 1.11  2004/10/25 15:20:35  yosizaki
  fix WFSi_LoadTables.

  Revision 1.10  2004/10/25 04:19:32  yosizaki
  change WFSi_LoadTables.

  Revision 1.9  2004/09/29 07:11:38  yosizaki
  merge from branch.

  Revision 1.8  2004/09/15 09:10:25  yosizaki
  add Seek&Read.

  Revision 1.7  2004/09/15 01:11:06  yosizaki
  add WFS_SetPacketSize().

  Revision 1.6  2004/09/07 03:05:51  yosizaki
  source code Maintenance.

  Revision 1.5  2004/09/06 08:47:48  yosizaki
  fix archive. (around result of FS_ReadFile())

  Revision 1.4  2004/09/04 13:23:55  yosizaki
  change to load TABLE-memory with WFSi_Alloc.

  Revision 1.3  2004/09/04 01:45:21  yosizaki
  delete obsolete codes and waste parameters.

  Revision 1.2  2004/09/03 09:58:38  yosizaki
  add wfs_thread.c

  Revision 1.1  2004/09/03 01:10:17  yosizaki
  (none)

  $NoKeywords: $
 *---------------------------------------------------------------------------*/


#include <nitro.h>
#include <nitro/fs.h>
#include <nitro/card.h>

#include "wfs.h"
#include "wfs_private.h"


/********************************************************************/
/* Functions */

/* Read Callback */
static FSResult	WFSi_ReadRomCallback(
	FSArchive *p_arc, void *dst, u32 src, u32 len)
{
	/* Read FNT / FAT as is. */
	(void)p_arc;
	MI_CpuCopy8((const void*)src, dst, len);
	return	FS_RESULT_SUCCESS;
}

/* Write callback */
static FSResult	WFSi_WriteRomCallback(
	FSArchive *p_arc, const void *src, u32 dst, u32 len)
{
	(void)p_arc;
	(void)src;
	(void)dst;
	(void)len;
	return	FS_RESULT_FAILURE;
}

/* User Procedure */
static FSResult	WFSi_RomArchiveProc(FSFile *p_file, FSCommandType cmd)
{
	switch(cmd) {

	case FS_COMMAND_READFILE:
		/* Fail if not ready */
		if(wfsi_work->state != WFS_STATE_READY) {
			return	FS_RESULT_ERROR;
		}
		/* Succeed if size is 0 */
		if(p_file->arg.readfile.len == 0) {
			return	FS_RESULT_SUCCESS;
		}
		/* Start request - GetBlock - End request */
		WFSi_ReadRequest(p_file);
		return	FS_RESULT_PROC_ASYNC;

	case FS_COMMAND_WRITEFILE:
		/* These commands are not supported. */
		return	FS_RESULT_UNSUPPORTED;

	default:
		/* Other commands use default behavior. */
		return	FS_RESULT_PROC_UNKNOWN;

	}

}

enum {
	region_fat,
	region_fnt,
	region_ovt9,
	region_ovt7,
	region_max
} ;

/*
 * Parent loads FNT, FAT, OVT to memory.
 * Prepare this file first so that a parent can accept connection from a child at any time.
 * 
 */
void	WFSi_LoadTables(FSFile *p_rom, BOOL use_parent_fs)
{
	/*
	 * Reads FNT+FAT+OVT information, and registers it as a file.
	 * Can use the following selection forks.
	 *
	 * (1) Normal independent child program: p_rom && !use_parent_fs
	 * (2) Shared file child program: p_rom && use_parent_fs
	 * (3) Parent clone boot: !p_rom.
	 *
	 * To properly support all of these, use the following procedure
	 * to do merge an overlay FAT, etc.
	 *
	 * 1. Select an appropriate ROM header and acquire the size of FAT, FNT, OVT.
	 * 2. Taking all settings into consideration, calculate and allocate the required memory.
	 *    Depending on the settings, appropriately add FAT size used for OVT.
	 * 3. Load OVT/FAT/FNT.
	 * 4. Depending on settings, appropriately revise the OVT File-ID and add FAT.
	 *    (Special process only required for file sharing children.)
	 */

	typedef	struct
	{
		u32	len;
		u32	data[1];
	} TableInfo;

	int	i;

	u8	*mem = NULL;
	u32	size = 0;

	const BOOL	is_own = (p_rom == NULL);
	const BOOL	is_mixed_fat = (!is_own && use_parent_fs);

	u32	child_base_offset;
	u32 rom_base_offset;
	const u8	*rom_header;
	u8	rom_buf[0x60];

	CARDRomRegion	r[region_max];
	u32	align_len[region_max];
	TableInfo	*(table_info[region_max]);


	FSFile	rom_file[1];

	/*
	 * 0. Open a large file that indicates the parent itself.
	 */
	FS_InitFile(rom_file);
	(void)FS_OpenFileDirect(rom_file, FS_FindArchive("rom", 3), 0, 0x7FFFFFFF, (u32)~0);
	/*
	 * 1. Select an appropriate ROM header and acquire the size of FAT, FNT, OVT.
	 */
	{
		/* If p_rom is NULL, acquire the parent itself as a file. */
		if(is_own) {
			rom_base_offset = 0;
			child_base_offset = 0;
			rom_header = (const u8*)HW_ROM_HEADER_BUF;
		}
		/* If not, first acquire only the ROM header. */
		else {
			const u32	bak_pos = FS_GetPosition(p_rom);
			rom_base_offset = bak_pos + FS_GetFileImageTop(p_rom);
			child_base_offset = rom_base_offset;
			rom_header = rom_buf;
			(void)FS_ReadFile(p_rom, rom_buf, sizeof(rom_buf));
			(void)FS_SeekFile(p_rom, bak_pos, FS_SEEK_SET);
		}
		/*
		 * Since the header has been acquired, calculate total memory size.
		 *
		 * (1) : OVT(child) + FNT(child) + FAT(child)
		 * (2) : OVT(child) + FNT(parent) + FAT(parent + OVT(child))
		 * (3) : OVT(parent) + FAT(parent) + FNT(parent)
		 *
		 * (2) is extremely complex because FAT must be converted due to integrating OVT.
		 */
		if(!is_mixed_fat) {
			/* In the case of (1), (3), reference parent / child as they are. */
			r[region_fat] = *(const CARDRomRegion*)(rom_header + 0x48);
			r[region_fnt] = *(const CARDRomRegion*)(rom_header + 0x40);
			r[region_ovt9] = *(const CARDRomRegion*)(rom_header + 0x50);
			r[region_ovt7] = *(const CARDRomRegion*)(rom_header + 0x58);
		} else {
			/*
			 * Case (2)
			 * OVT references the child. However FAT/FNT reference the parent.
			 * Furthermore, the ID for the overlay file must be added to the FAT 
			 * by the amount of the OVT.
			 * When doing this, the file_id member in the OVT must also be revised.
			 */
			r[region_fat] = *(const CARDRomRegion*)(HW_ROM_HEADER_BUF + 0x48);
			r[region_fnt] = *(const CARDRomRegion*)(HW_ROM_HEADER_BUF + 0x40);
			r[region_ovt9].offset = (u32)(*(const u32*)(rom_header + 0x50) + rom_base_offset);
			r[region_ovt9].length = (u32)(*(const u32*)(rom_header + 0x54));
			r[region_ovt7].offset = (u32)(*(const u32*)(rom_header + 0x58) + rom_base_offset);
			r[region_ovt7].length = (u32)(*(const u32*)(rom_header + 0x5C));
			/* The file system is based on the parent. Therefore, the offset goes to the head. */
			child_base_offset = 0;
		}
	}
	/*
	 * 2. Taking overall settings into consideration, calculate and allocate the required memory.
	 *    Depending on the settings, appropriately add the FAT size for OVT.
	 */
	{
		/*
		 * Acquire overall size.
		 * The first 4 bytes hold the child side ROM base offset.
		 */
		size += sizeof(u32);
		for( i = 0 ; i < region_max ; ++i ) {
			align_len[i] = ((r[i].length + 31) & ~31);
			size += (u32)(sizeof(u32) + align_len[i]);
		}
		/* In the case of (2), add an ID for the overlay file to the FAT. */
		if(is_mixed_fat) {
			for( i = region_ovt9 ; i < region_max ; ++i ) {
				const u32	ovl_size = (u32)(
					(((r[i].length / sizeof(FSOverlayInfoHeader)) *
					sizeof(CARDRomRegion)) + 31) & ~31);
				align_len[region_fat] += ovl_size;
				size += ovl_size;
			}
		}
		/* Dynamically allocate the required size, and set the base offset in the head. */
		mem = (u8*)WFSi_Alloc(size);
		SDK_ASSERT(mem);
		*(u32*)mem = child_base_offset;
	}
	/*
	 * 3. Load OVT/FAT/FNT.
	 */
	{
		u8	*dst = mem + sizeof(u32);
		/* Read each region. */
		for( i = 0 ; i < region_max ; ++i ) {
			table_info[i] = (TableInfo*)dst;
			table_info[i]->len = r[i].length;
			(void)FS_SeekFile(rom_file,
				child_base_offset + r[i].offset, FS_SEEK_SET);
			(void)FS_ReadFile(rom_file, table_info[i]->data, table_info[i]->len);
			dst += sizeof(u32) + align_len[i];
		}
	}
	/*
	 * 4. Depending on settings, appropriately revise the OVT File-ID and add FAT.
	 *    (Special process only required for file sharing children.)
	 */
	{
		if(is_mixed_fat) {
			typedef	struct { u32 top, bottom; } FatInfo;
			/* Parent side FAT (in the end used by the child) */
			FatInfo	* const p_fat = (FatInfo*)table_info[region_fat]->data;
			int	fat_num = table_info[region_fat]->len / sizeof(FatInfo);
			/* Child side FAT position (must convert OVT offset) */
			const u32	child_fat_base = (u32)(
				rom_base_offset + ((const CARDRomRegion*)(rom_header + 0x48))->offset);
			/* Convert for ARM9/ARM9 OVT */
			for( i = region_ovt9 ; i < region_max ; ++i ) {
				FSOverlayInfoHeader	* const p_ovt =
					(FSOverlayInfoHeader*)table_info[i]->data;
				const int	ovt_num = table_info[i]->len / sizeof(FSOverlayInfoHeader);
				int	j;
				for( j = 0 ; j < ovt_num ; ++j ) {
					/* First acquire relevant file's FAT from the child OVT. */
					(void)FS_SeekFile(rom_file,
						child_fat_base + p_ovt[j].file_id * sizeof(FatInfo), FS_SEEK_SET);
					(void)FS_ReadFile(rom_file, &p_fat[fat_num], sizeof(FatInfo));
					/* Convert this to the parent based ROM offset. */
					p_fat[fat_num].top += rom_base_offset;
					p_fat[fat_num].bottom += rom_base_offset;
					/* Also revise the File-ID in the OVT to the newly added one. */
					p_ovt[j].file_id = fat_num;
					++fat_num;
				}
			}
			/* Reflect conversion results: the total number of FAT, which was increased due to the integration of the OVT. */
			table_info[region_fat]->len = (u32)(fat_num * sizeof(FatInfo));
		}
	}
	/*
	 * In the above, table creation is completed for each of (1) - (3).
	 * By performing this complex adjustment on the parent side,
	 * the child determines everything other than ReadFile with direct table access
	 * and requests ReadFile as base_offset + position.
	 * The parent side simply evaluates this request as a ROM based ReadCard.
	 */

	DC_FlushRange(mem, size);
	DC_WaitWriteBufferEmpty();
	(void)FS_CloseFile(rom_file);
	wfsi_work->table = mem;
	wfsi_work->table_size = size;
}

/*
 * Restructure child ROM archive, and exchange overlay table.
 * Returns the base offset for the child ROM address that is used by the parent.
 */
u32	WFSi_ReplaceRomArchive(void *p_table)
{
	u32	ret;
	CARDRomRegion	r[region_max];

	FSArchive	* const p_rom = FS_FindArchive("rom", 3);

	int	i;

	/* Since there is no CARD in the first place, unload will complete instantly. */
	(void)FS_UnloadArchive(p_rom);

	/* Exchange user procedure */
	FS_SetArchiveProc(p_rom, WFSi_RomArchiveProc, FS_ARCHIVE_PROC_ALL);

	/* Acquire the base offset. */
	ret = *(u32*)p_table;
	p_table = ((u8*)p_table) + sizeof(u32);

	/* Extract each region information from the provided buffer. */
	for( i = 0 ; i < region_max ; ++i ) {
		r[i].offset = (u32)((u32)p_table + sizeof(u32));
		r[i].length = *(u32*)p_table;
		p_table = (void*)(r[i].offset + (r[i].length + 31 & ~31));
	}

	/* Use extracted region and reload archive. */
	(void)FS_LoadArchive(p_rom, 0,
		r[region_fat].offset, r[region_fat].length,
		r[region_fnt].offset, r[region_fnt].length,
		WFSi_ReadRomCallback, WFSi_WriteRomCallback);

	/* Exchange OVT */
	FS_AttachOverlayTable(MI_PROCESSOR_ARM9,
		(const void*)r[region_ovt9].offset, r[region_ovt9].length);
	FS_AttachOverlayTable(MI_PROCESSOR_ARM7,
		(const void*)r[region_ovt7].offset, r[region_ovt7].length);

	return	ret;
}
