/*---------------------------------------------------------------------------*
  Project:  NitroSDK - MB - demos - mb_child
  File:     child.c

  Copyright 2003-2006 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.

  $NoKeywords: $
 *---------------------------------------------------------------------------*/
#include <nitro.h>
#include <nitro/wm.h>
#include <nitro/mb.h>
#include <mb_child.h>
#include <mb_private.h>

#include "loader.h"
#include "boot.h"

#include "nitroCrypto/crypto.h"

#include "main.h"
#include "child.h"
#include "state.h"




#ifndef GGID
#error GGID is not defined
#endif

#ifdef SDK_FINALROM
#define SCANNING_TIMEOUT   (60*60)         // 60秒(60*60frame)
#else
#define SCANNING_TIMEOUT   (15*60)         // 15秒(15*60frame)
#endif

/* common */
static u32 cwork[MB_CHILD_SYSTEM_BUF_SIZE / sizeof(u32)];       /* ワーク領域 */
static MBUserInfo myUser;
static u16 tgid = 0;


/* for child */
static BOOL boot_ready_flag = FALSE;
static u32 dl_size = 0;
static OSTick tk_start;
MBDownloadFileInfo dlf_info;

static int childscan_timeout;
static int child_restart;

static void ChildStateCallback(u32 status, void *arg);
static void MakeUserInfo(MBUserInfo *user, u8 favoriteColor, u8 playerNo, u16 *name,
                         u16 name_length);



/*---------------------------------------------------------------------------*
  Name:         ChildStart/ChildRestart

  Description:  子機開始

  Arguments:    None.
  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ChildStart()
{
	prog_state = STATE_MB_CHILDINIT;
	child_restart = 0;
}

void
ChildRestart()
{
	boot_ready_flag = FALSE;
	childscan_timeout = 0;

	prog_state = STATE_MB_CHILDINIT;
	child_restart = 60;
}



/*---------------------------------------------------------------------------*
  Name:         ChildInit

  Description:  子機初期化

  Arguments:    None.
  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ChildInit(void)
{
	int errcode;

	/*
	 * ARM7側で、NVRAMから読み込んだowner情報から（システム領域に格納されている。）
	 * ユーザーネームを登録する。
	 */
#if ( SDK_NVRAM_FORMAT >= 100 )
	OSOwnerInfo MyInfo;

	OS_GetOwnerInfo(&MyInfo);
	MakeUserInfo(&myUser, MyInfo.favoriteColor, 0, (u16 *)MyInfo.nickName, MyInfo.nickNameLength);
#else  // 旧IPL用
	NVRAMConfig *pMyinfo = (NVRAMConfig *)OS_GetSystemWork()->nvramUserInfo;
	MakeUserInfo(&myUser, pMyinfo->ncd.owner.favoriteColor, 0,
	             (u16 *)pMyinfo->ncd.owner.nickname.str, pMyinfo->ncd.owner.nickname.length);
#endif

	/*
	 * 「この関数は内部で StartScan を呼び出します」
	 */
	tgid++;
	errcode = MB_Init((void *)cwork, &myUser, GGID, tgid, 3);
	MB_CommSetChildStateCallback(ChildStateCallback);

	errcode = MB_StartChild();

	boot_ready_flag = FALSE;
	prog_state = STATE_MB_CHILDSCAN;
	childscan_timeout = 0;

	if (errcode != WM_ERRCODE_SUCCESS) {
		prog_state = STATE_MB_TIMEOUT;
	}
}

void
ChildForceError()
{
	switch (prog_state) {
	case STATE_MB_CHILDINIT:
		prog_state = STATE_MB_TIMEOUT;
		break;
	case STATE_MB_CHILDSCAN:
		MB_End();
		prog_state = STATE_MB_TIMEOUT;
		break;
	case STATE_MB_CHILD:
		MB_End();
		prog_state = STATE_MB_DOWNLOAD_ERROR;
		break;
	default:
		break;
	}
}



/*---------------------------------------------------------------------------*
  Name:         ChildScan

  Description:  子機の親機スキャン

  Arguments:    None.
  Returns:      None.
 *---------------------------------------------------------------------------*/
void
ChildScan(void)
{
	int errcode;
	MbBeaconRecvStatus *p_recv = (MbBeaconRecvStatus *)MB_GetBeaconRecvStatus();
	int n;


	// 最後に呼んだ MB_End() が終了するまである程度待つ
	if (child_restart > 0) {
		OS_WaitVBlankIntr();
		child_restart--;
		return;
	}

	child_restart = 0;


	for (n=0; n<MB_GAME_INFO_RECV_LIST_NUM; n++) {
		// n番目の親を調べて、接続してみる？


		// 親機情報が存在しなければ次へ
		if (!(p_recv->usefulGameInfoFlag & (0x0001 << n)))
			continue;


		// GGIDが一致しなかったら、リストから消す
		if (p_recv->list[n].bssDesc.gameInfo.ggid != GGID) {

			/*
			 * GGIDが違ったらリストから消去しておく
			 * またすぐに見つかるのであまり意味はない。
			 * リストが溢れて本来のGGIDを見つけられなくなるのを防ぐ程度
			 */
			OSIntrMode bak_cpsr;

			bak_cpsr = OS_DisableInterrupts();
			p_recv->list[n].lifetimeCount = 0;
			p_recv->usingGameInfoFlag &= ~(1<<n);
			MB_DeleteRecvGameInfo(n);
			(void)OS_RestoreInterrupts(bak_cpsr);

			continue;
		}


		/*
		 * 親機情報が見つかり、GGIDが一致した
		 */


		// 人数が溢れていても、「親は見つけたが接続できない」ことを
		// 子(プレイヤー)に把握させるため接続を試す
		OS_TPrintf("try to conenct %02x:%02x:%02x:%02x:%02x:%02x (%d/%d)\n",
		  p_recv->list[n].bssDesc.bssid[0], p_recv->list[n].bssDesc.bssid[1],
		  p_recv->list[n].bssDesc.bssid[2], p_recv->list[n].bssDesc.bssid[3],
		  p_recv->list[n].bssDesc.bssid[4], p_recv->list[n].bssDesc.bssid[5],
		  p_recv->list[n].gameInfo.volat.nowPlayerNum,
		  p_recv->list[n].gameInfo.fixed.maxPlayerNum);


		// GameInfoのインデックス番号を指定して、リクエストする。
		errcode = MB_CommDownloadRequest(n);
		if (errcode == WM_ERRCODE_SUCCESS) {
			OS_TPrintf("Try to request (File No %2d)\n",
			           p_recv->list[n].gameInfo.fileNo);
			OS_TPrintf("GGID=%08x\n", p_recv->list[n].gameInfo.ggid);
			prog_state = STATE_MB_CHILD;
			return;
		}
	}

	if (childscan_timeout >= SCANNING_TIMEOUT)
		childscan_timeout = 0;

	// タイムアウトしたら終了処理を行う
	if (++childscan_timeout >= SCANNING_TIMEOUT) {
		OS_TPrintf("multiboot timeout\n");

		MB_End();

		childscan_timeout = 0;
		prog_state = STATE_MB_TIMEOUT;
	}
}




/*---------------------------------------------------------------------------*
  Name:         ChildMain

  Description:  子機メイン

  Arguments:    None.
  Returns:      None.
 *---------------------------------------------------------------------------*/

// IPLと同じ方法でdigestを求める。
static int
hash_make_for_rms(void *digest, void *romh_ptr, u32 romh_len, void *mbin_ptr, u32 mbin_len,
                  void *sbin_ptr, u32 sbin_len, u32 serial_num)
{
	unsigned char aDigest[ MATH_SHA1_DIGEST_SIZE * 3 + sizeof(u32) ];

	if ( !romh_ptr || !romh_len )   return 0;
	if ( !mbin_ptr || !mbin_len )   return 0;
	if ( !sbin_ptr || !sbin_len )   return 0;

	MATH_CalcSHA1( aDigest + MATH_SHA1_DIGEST_SIZE * 0, romh_ptr, romh_len);
	MATH_CalcSHA1( aDigest + MATH_SHA1_DIGEST_SIZE * 1, mbin_ptr, mbin_len);
	MATH_CalcSHA1( aDigest + MATH_SHA1_DIGEST_SIZE * 2, sbin_ptr, sbin_len);
	*(u32 *)(aDigest + MATH_SHA1_DIGEST_SIZE * 3) = serial_num;
	MATH_CalcSHA1( digest, aDigest, MATH_SHA1_DIGEST_SIZE * 3 + sizeof(u32) );
	return 1;
}

// IPLで使用している公開キー。
static const u8 Modulus[] = {
	0x9E,0xC1,0xCC,0xC0,0x4A,0x6B,0xD0,0xA0,0x6D,0x62,0xED,0x5F,0x15,0x67,0x87,0x12,
	0xE6,0xF4,0x77,0x1F,0xD8,0x5C,0x81,0xCE,0x0C,0xD0,0x22,0x31,0xF5,0x89,0x08,0xF5,
	0xBE,0x04,0xCB,0xC1,0x4F,0x63,0xD9,0x5A,0x98,0xFF,0xEB,0x36,0x0F,0x9C,0x5D,0xAD,
	0x15,0xB9,0x99,0xFB,0xC6,0x86,0x2C,0x0A,0x0C,0xFC,0xE6,0x86,0x03,0x60,0xD4,0x87,
	0x28,0xD5,0x66,0x42,0x9C,0xF7,0x04,0x14,0x4E,0x6F,0x73,0x20,0xC3,0x3E,0x3F,0xF5,
	0x82,0x2E,0x78,0x18,0xD6,0xCD,0xD5,0xC2,0xDC,0xAA,0x1D,0x34,0x91,0xEC,0x99,0xC9,
	0xF7,0xBF,0xBF,0xA0,0x0E,0x1E,0xF0,0x25,0xF8,0x66,0x17,0x54,0x34,0x28,0x2D,0x28,
	0xA3,0xAE,0xF0,0xA9,0xFA,0x3A,0x70,0x56,0xD2,0x34,0xA9,0xC5,0x9E,0x5D,0xF5,0xE1
};

void
ChildMain(void)
{
	static int main_counter = 0;
	main_counter++;

	switch (MB_CommGetChildState()) {
	case MB_COMM_CSTATE_RECV_PROCEED:
		OS_TPrintf("Downloading... progress %2d%\n", MB_GetChildProgressPercentage());
		break;

	case MB_COMM_CSTATE_RECV_COMPLETE:
		OS_TPrintf("Download has been completed.\n");
		OS_TPrintf("Wait until PARENT is ready.\n");
		break;

	case MB_COMM_CSTATE_CONNECT:
		OS_TPrintf("Connecting to PARENT\n");
		break;

	case MB_COMM_CSTATE_CONNECT_FAILED:
		OS_TPrintf("Connection failed.\n");
		break;

	case MB_COMM_CSTATE_DISCONNECTED_BY_PARENT:
		OS_TPrintf("Disconnected by parent.\n");
		break;

	case MB_COMM_CSTATE_REQ_REFUSED:
		OS_TPrintf("Entry has been refused.\n");
		break;

	case MB_COMM_CSTATE_MEMBER_FULL:
		OS_TPrintf("Entry to the game reached the capacity.\n");
		break;

	case MB_COMM_CSTATE_REQ_ENABLE:
		OS_TPrintf("Requesting download...\n");
		break;

	case MB_COMM_CSTATE_DLINFO_ACCEPTED:
		OS_TPrintf("Entry has been completed.\n");
		OS_TPrintf("Wait until PARENT is ready.\n");
		break;
	}


	if (boot_ready_flag == TRUE) {

		OS_TPrintf("BOOT START!\n");

		{
			/*
			 * ここで専用コンポーネントと同期を取り,
			 * 再配置ののちに _start() へジャンプ.
			 */

// [for pokemon_dp download] ---
			// digestを求めて、Cryptoで署名チェック。
			// 不正srlでない限り、OKとなる。
			MBDownloadFileInfo *mdfi = (MBDownloadFileInfo *) MB_DOWNLOAD_FILEINFO_ADDRESS;
			int res;
			u8 digest[MATH_SHA1_DIGEST_SIZE];

			res = hash_make_for_rms(digest,
			                        (void *)mdfi->seg[0].recv_addr, mdfi->seg[0].size,
			                        (void *)mdfi->seg[1].recv_addr, mdfi->seg[1].size,
			                        (void *)mdfi->seg[2].recv_addr, mdfi->seg[2].size,
			                        mdfi->auth_code[33]);
			if (!res) {
				OS_TPrintf("error: hash_make_for_rms\n");
#ifdef SDK_FINALROM
				prog_state = STATE_MB_DOWNLOAD_ERROR;
				return;
#endif
			}
			res = CRYPTO_VerifySignatureWithHash(digest, &mdfi->auth_code[1], Modulus);
			if (!res) {
				OS_TPrintf("error: VerifySignature\n");
#ifdef SDK_FINALROM
				prog_state = STATE_MB_DOWNLOAD_ERROR;
				return;
#endif
			}


			{
				// フェードアウト
				int i;

				OS_WaitVBlankIntr();
				for (i=0; i<=16; i++) {
					GX_SetMasterBrightness(i);
					GXS_SetMasterBrightness(i);
					OS_WaitVBlankIntr();
				}
			}

// --- [for pokemon_dp download]
			OS_TPrintf("Loading new program ..\n");
			(void)LOADER_Start();
			OS_TPrintf("Loading done! Now booting ..\n");
			(void)BOOT_Start();
			/* no return */
		}

	}
}




/*---------------------------------------------------------------------------*
  Name:         ChildStateCallback

  Description:  子機ファイルダウンロード状態通知コールバック

  Arguments:    status
  Returns:      None.
 *---------------------------------------------------------------------------*/
static void
ChildStateCallback(u32 status, void *arg)
{
	switch (status) {
	case MB_COMM_CSTATE_INIT_COMPLETE:
		OS_TPrintf("CHILD Init Complete\n");
		break;
	case MB_COMM_CSTATE_CONNECT:
		{
			WMStartConnectCallback *pscc = (WMStartConnectCallback *)arg;
			OS_TPrintf("Connected To Parent AID %2d\n", pscc->aid);
		}
		break;
	case MB_COMM_CSTATE_CONNECT_FAILED:
		OS_TPrintf("Connect Failed\n");
		break;
	case MB_COMM_CSTATE_DISCONNECTED_BY_PARENT:
		OS_TPrintf("Disconnected by Parent\n");
		break;
	case MB_COMM_CSTATE_REQ_ENABLE:
		OS_TPrintf("Sent Download Request\n");
		break;
	case MB_COMM_CSTATE_REQ_REFUSED:
		OS_TPrintf("Req Refused from PARENT\n");
		MB_End();
		prog_state = STATE_MB_CONNECT_ERROR;
		break;
	case MB_COMM_CSTATE_DLINFO_ACCEPTED:
		OS_TPrintf("Got DLINFO from PARENT\n");
		(void)MB_CommStartDownload();
		OS_TPrintf("Download Ready\n");
		/* 総ダウンロードサイズの取得 */
		if (arg) {
			MBDownloadFileInfo *mdfi = (MBDownloadFileInfo *) arg;
			int seg;

			MI_CpuCopy8(mdfi, &dlf_info, MB_DOWNLOAD_FILEINFO_SIZE);

			dl_size = 0;

			for (seg = 0; seg < MB_DL_SEGMENT_NUM; seg++) {
				MbSegmentInfo *seg_inf = MBi_GetSegmentInfo(&mdfi->header, seg);
				dl_size += seg_inf->size;
			}

			OS_TPrintf("*** DL Game info ***\n");
			OS_TPrintf("ARM9 Entry           %08x\n", dlf_info.header.arm9EntryAddr);
			OS_TPrintf("ARM7 Entry           %08x\n", dlf_info.header.arm7EntryAddr);

			OS_TPrintf("ARM9 Static recv adr %08x - %08x\n",
			  dlf_info.seg[1].recv_addr,
			  dlf_info.seg[1].recv_addr + dlf_info.seg[1].size);

			OS_TPrintf("ARM9 Static load adr %08x - %08x\n",
			  dlf_info.seg[1].load_addr,
			  dlf_info.seg[1].load_addr + dlf_info.seg[1].size);

			OS_TPrintf("ARM7 Static recv adr %08x - %08x\n",
			  dlf_info.seg[2].recv_addr,
			  dlf_info.seg[2].recv_addr + dlf_info.seg[2].size);

			OS_TPrintf("ARM7 Static load adr %08x - %08x\n",
			  dlf_info.seg[2].load_addr,
			  dlf_info.seg[2].load_addr + dlf_info.seg[2].size);
		}
		break;
	case MB_COMM_CSTATE_RECV_PROCEED:
		tk_start = OS_GetTick();	/* 時間計測開始 */
		OS_TPrintf("Start Downloading\n");
		break;
	case MB_COMM_CSTATE_RECV_COMPLETE:
		{
			int msec;
			msec = (int)OS_TicksToMilliSeconds(OS_GetTick() - tk_start);
			OS_TPrintf("Download Completed\n");
			OS_TPrintf("%d msec\n", msec);
			if (msec)
				OS_TPrintf("%7d kbps\n", (int)(8 * dl_size / msec));
		}
		break;

	case MB_COMM_CSTATE_BOOTREQ_ACCEPTED:
		OS_TPrintf("Got Boot Req from PARENT\n");
		break;

	case MB_COMM_CSTATE_CANCELLED:
		switch (prog_state) {
		case STATE_MB_TIMEOUT:
		case STATE_MB_CONNECT_ERROR:
		case STATE_QUERY_CONTINUE:
		case STATE_QUERY_POWEROFF:
			break;
		default:
			OS_TPrintf("multiboot Cancelled\n");
			prog_state = STATE_MB_DOWNLOAD_ERROR;
		}

		break;

	case MB_COMM_CSTATE_BOOT_READY:
		OS_TPrintf("Ready to Boot\n");
		boot_ready_flag = TRUE;
		break;

	case MB_COMM_CSTATE_MEMBER_FULL:
		OS_TPrintf("Req. Game-member full\n");
		MB_End();
		prog_state = STATE_MB_CONNECT_ERROR;
		break;

	case MB_COMM_CSTATE_GAMEINFO_VALIDATED:
		OS_TPrintf("Get Validated GameInfo\n");
		break;

	case MB_COMM_CSTATE_GAMEINFO_INVALIDATED:
		OS_TPrintf("GameInfo Invalidated\n");
		break;

	case MB_COMM_CSTATE_GAMEINFO_LOST:
		OS_TPrintf("Lost GameInfo\n");
		break;

	case MB_COMM_CSTATE_GAMEINFO_LIST_FULL:
		OS_TPrintf("GameInfo List Full\n");
		break;

	case MB_COMM_CSTATE_ERROR:
		{
			MBErrorStatus *err_stat = (MBErrorStatus *)arg;

			switch (err_stat->errcode) {
			case MB_ERRCODE_INVALID_DLFILEINFO:
				OS_TPrintf("Download Fileinfo Err.\n");
				break;
			case MB_ERRCODE_INVALID_BLOCK_NO:
				OS_TPrintf("Block No. Err.\n");
				break;
			case MB_ERRCODE_INVALID_BLOCK_NUM:
				OS_TPrintf("Block NUM. Err.\n");
				break;
			case MB_ERRCODE_INVALID_FILE:
				OS_TPrintf("File ID Err.\n");
				break;
			case MB_ERRCODE_INVALID_RECV_ADDR:
				OS_TPrintf("Receive Address Err.\n");
				break;

			case MB_ERRCODE_WM_FAILURE:
				OS_TPrintf("WM Err.\n");
				break;

			case MB_ERRCODE_FATAL:
				OS_TPrintf("Fatal Err.\n");
				MB_End();
				break;
			}
		}
		break;

	}
}

/*---------------------------------------------------------------------------*
  Name:         MakeUserInfo

  Description:  MBUserInfoの生成

  Arguments:    user          - MBUserInfo buffer
                favoriteColor - my color palette
                playerNo      - player No.
                name          - username (UNICODE)
                name_length   - username length (UNICODE)
  Returns:      None.
 *---------------------------------------------------------------------------*/

static void
MakeUserInfo(MBUserInfo *user, u8 favoriteColor, u8 playerNo, u16 *name,
             u16 name_length)
{
	SDK_ASSERT(user != NULL);
	user->favoriteColor = favoriteColor;
	user->nameLength = (u8)(name_length);
	MI_CpuCopy8((char *)name, (char *)user->name, (u32)(name_length * 2));
	user->playerNo = playerNo;
}
