Exemple #1
0
s32 WII_LaunchTitle(u64 titleID)
{
	s32 res;
	u32 numviews;
	STACK_ALIGN(tikview,views,4,32);

	if(!__initialized)
		return WII_ENOTINIT;

	res = ES_GetNumTicketViews(titleID, &numviews);
	if(res < 0) {
		return res;
	}
	if(numviews > 4) {
		return WII_EINTERNAL;
	}
	res = ES_GetTicketViews(titleID, views, numviews);
	if(res < 0)
		return res;

	VIDEO_SetBlack(1);
	VIDEO_Flush();

	res = ES_LaunchTitle(titleID, &views[0]);
	if(res < 0)
		return res;
	return WII_EINTERNAL;
}
Exemple #2
0
s32 Title_GetTicketViews(u64 tid, tikview **outbuf, u32 *outlen)
{
	tikview *views = NULL;

	u32 nb_views;
	s32 ret;

	/* Get number of ticket views */
	ret = ES_GetNumTicketViews(tid, &nb_views);
	if (ret < 0)
		return ret;

	/* Allocate memory */
	views = (tikview *)memalign(32, sizeof(tikview) * nb_views);
	if (!views)
		return -1;

	/* Get ticket views */
	ret = ES_GetTicketViews(tid, views, nb_views);
	if (ret < 0)
		goto err;

	/* Set values */
	*outbuf = views;
	*outlen = nb_views;

	return 0;

err:
	/* Free memory */
	if (views)
		free(views);

	return ret;
}
Exemple #3
0
s32 Uninstall_RemoveTicket(u64 tid)
{
	static tikview viewdata[0x10] ATTRIBUTE_ALIGN(32);

	u32 cnt, views;
	s32 ret;

	//printf("\t\t- Removing tickets...");
	fflush(stdout);

	/* Get number of ticket views */
	ret = ES_GetNumTicketViews(tid, &views);
	if (ret < 0) {
		//printf(" Error! (ret = %d)\n", ret);
		return ret;
	}

	if (!views) {
		//printf(" No tickets found!\n");
		return 1;
	} else if (views > 16) {
		//printf(" Too many ticket views! (views = %d)\n", views);
		return -1;
	}
	
	/* Get ticket views */
	ret = ES_GetTicketViews(tid, viewdata, views);
	if (ret < 0) {
		//printf(" \n\tError! ES_GetTicketViews (ret = %d)\n", ret);
		return ret;
	}

	/* Remove tickets */
	for (cnt = 0; cnt < views; cnt++) {
		ret = ES_DeleteTicket(&viewdata[cnt]);
		if (ret < 0) {
			//printf(" Error! (view = %d, ret = %d)\n", cnt, ret);
			return ret;
		}
	}
	//printf(" OK!\n");

	return ret;
}
Exemple #4
0
s8 GetTitleName(u64 id, u32 app, char* name,u8* _dst_uncode_name) {
	s32 r;
    int lang = 1; //CONF_GetLanguage();
    /*
    languages:
    enum {
	CONF_LANG_JAPANESE = 0,
	CONF_LANG_ENGLISH,
	CONF_LANG_GERMAN,
	CONF_LANG_FRENCH,
	CONF_LANG_SPANISH,
	CONF_LANG_ITALIAN,
	CONF_LANG_DUTCH,
	CONF_LANG_SIMP_CHINESE,
	CONF_LANG_TRAD_CHINESE,
	CONF_LANG_KOREAN
	};
	cause we dont support unicode stuff in font.cpp we will force to use english then(1)
    */
	u8 return_unicode_name = 0;
	if(_dst_uncode_name == NULL)
	{
		return_unicode_name = 0;
	}
	else
	{
		return_unicode_name = 1;
	}
    char file[256] ATTRIBUTE_ALIGN(32);
	memset(file,0,256);
    sprintf(file, "/title/%08x/%08x/content/%08x.app", (u32)(id >> 32), (u32)(id & 0xFFFFFFFF), app);
	gdprintf("GetTitleName : %s\n",file);
	u32 cnt ATTRIBUTE_ALIGN(32);
	cnt = 0;
	IMET *data = (IMET *)mem_align(32, ALIGN32( sizeof(IMET) ) );
	if(data == NULL)
	{
		gprintf("GetTitleName : IMET header align failure\n");
		return -1;
	}
	memset(data,0,sizeof(IMET) );
	r = ES_GetNumTicketViews(id, &cnt);
	if(r < 0)
	{
		gprintf("GetTitleName : GetNumTicketViews error %d!\n",r);
		mem_free(data);
		return -1;
	}
	tikview *views = (tikview *)mem_align( 32, sizeof(tikview)*cnt );
	if(views == NULL)
	{
		mem_free(data);
		return -2;
	}
	r = ES_GetTicketViews(id, views, cnt);
	if (r < 0)
	{
		gprintf("GetTitleName : GetTicketViews error %d \n",r);
		mem_free(data);
		mem_free(views);
		return -3;
	}

	//lets get this party started with the right way to call ES_OpenTitleContent. and not like how libogc < 1.8.3 does it. patch was passed on , and is done correctly in 1.8.3
	//the right way is ES_OpenTitleContent(u64 TitleID,tikview* views,u16 Index); note the views >_>
	s32 fh = ES_OpenTitleContent(id, views, 0);
	if (fh == -106)
	{
		CheckTitleOnSD(id);
		mem_free(data);
		mem_free(views);
		return -106;
	}
	else if(fh < 0)
	{
		//ES method failed. remove tikviews from memory and fall back on ISFS method
		gprintf("GetTitleName : ES_OpenTitleContent error %d\n",fh);
		mem_free(views);
		fh = ISFS_Open(file, ISFS_OPEN_READ);
		// f**k failed. lets check SD & GTFO
		if (fh == -106)
		{
			CheckTitleOnSD(id);
			return -106;
		}
		else if (fh < 0)
		{
			mem_free(data);
			gprintf("open %s error %d\n",file,fh);
			return -5;
		}
		// read the completed IMET header
		r = ISFS_Read(fh, data, sizeof(IMET));
		if (r < 0) {
			gprintf("IMET read error %d\n",r);
			ISFS_Close(fh);
			mem_free(data);
			return -6;
		}
		ISFS_Close(fh);
	}
	else
	{
		//ES method
		r = ES_ReadContent(fh,(u8*)data,sizeof(IMET));
		if (r < 0) {
			gprintf("GetTitleName : ES_ReadContent error %d\n",r);
			ES_CloseContent(fh);
			mem_free(data);
			mem_free(views);
			return -8;
		}
		//free data and let it point to IMET_data so everything else can work just fine
		ES_CloseContent(fh);
		mem_free(views);
	}
	char str[10][84];
	char str_unprocessed[10][84];
	//clear any memory that is in the place of the array cause we dont want any confusion here
	memset(str,0,10*84);
	if(return_unicode_name)
		memset(str_unprocessed,0,10*84);
	if(data->imet == 0x494d4554) // check if its a valid imet header
	{
		for(u8 y =0;y <= 9;y++)
		{
			u8 p = 0;
			u8 up = 0;
			for(u8 j=0;j<83;j++)
			{
				if(data->names[y][j] < 0x20)
					if(return_unicode_name && data->names[y][j] == 0x00)
						str_unprocessed[y][up++] = data->names[y][j];
					else
						continue;
				else if(data->names[y][j] > 0x7E)
					continue;
				else
				{
					str[y][p++] = data->names[y][j];
					str_unprocessed[y][up++] = data->names[y][j];
				}
			}
			str[y][83] = '\0';

		}
		mem_free(data);
	}
	else
	{
		gprintf("invalid IMET header for 0x%08x/0x%08x\n", (u32)(id >> 32), (u32)(id & 0xFFFFFFFF));
		return -9;
	}
	if(str[lang][0] != '\0')
	{
		gdprintf("GetTitleName : title %s\n",str[lang]);
		snprintf(name,255, "%s", str[lang]);
		if (return_unicode_name && str_unprocessed[lang][1] != '\0')
		{
			memcpy(_dst_uncode_name,&str_unprocessed[lang][0],83);
		}
		else if(return_unicode_name)
			gprintf("WARNING : empty unprocessed string\n");
	}
	else
		gprintf("GetTitleName: no name found\n");
	memset(str,0,10*84);
	memset(str_unprocessed,0,10*84);
	return 1;
}
Exemple #5
0
s32 extractChannelContents(u64 titleID, char* location)
{
	u32 TitleIDH=TITLE_UPPER(titleID);
	u32 TitleIDL=TITLE_LOWER(titleID);
	if(ES_SetUID(titleID)<0)
		return ERR_UID;

	u32 tmd_size ATTRIBUTE_ALIGN(32);


	if(ES_GetStoredTMDSize(titleID, &tmd_size)<0)
		return ERR_TMDSIZE;

	signed_blob *TMD = (signed_blob *)memalign( 32, tmd_size );
	memset(TMD, 0, tmd_size);

	if(ES_GetStoredTMD(titleID, TMD, tmd_size)<0)
	{
		free(TMD);
		return ERR_TMD;
	}

	u32 cnt ATTRIBUTE_ALIGN(32);

	if(ES_GetNumTicketViews(titleID, &cnt)<0)
	{
		free(TMD);
		return ERR_TIKCOUNT;
	}

	if( cnt <= 0 )
	{
		free(TMD);
		return ERR_TIKCOUNT;
	}

	tikview *views = (tikview *)memalign( 32, sizeof(tikview)*cnt );
	if(ES_GetTicketViews(titleID, views, cnt)<0)
	{
		free(views);
		free(TMD);
		return ERR_TIK;
	}
	printf("Allocated and filled views.\n");
	sleep(3);

	Identify_SU();

	int z;
	tmd_content *TMDc = TMD_CONTENTS(((tmd*)(SIGNATURE_PAYLOAD(TMD))));		// OH GOD CREDIAR, WTF WAS THAT MESS!
	// List format is "XXXXXXXX = YYYYYYYY" where X is index, and Y is cid.
	char *lookup_list=calloc(21,((tmd*)(SIGNATURE_PAYLOAD(TMD)))->num_contents);
	ClearScreen();
	printf("\nNumber of contents: %d\n",((tmd*)(SIGNATURE_PAYLOAD(TMD)))->num_contents);
	sleep(1);
	for(z=0; z < ((tmd*)(SIGNATURE_PAYLOAD(TMD)))->num_contents; z++)
	{
		/* Get Content */
		char nameDirectory[80];
		sprintf(nameDirectory,"/title/%08x/%08x/content/%08x.app",TitleIDH,TitleIDL,TMDc[z].cid);
		s32 contentFd=ISFS_Open(nameDirectory,ISFS_OPEN_READ);
		u8 *data=calloc(TMDc[z].size,1);
		if(contentFd<0)
		{
			switch(contentFd)
			{
				case ISFS_EINVAL:
					printf("FAILED! (Invalid Argument %s)\n\tQuitting...\n",nameDirectory);
					sleep(5);
					Finish(1);
					break;
			
				case ISFS_ENOMEM:
					printf("FAILED! (Out of memory %s)\n\tQuitting...\n",nameDirectory);
					sleep(5);
					Finish(1);
					break;
			
				default:
					goto skip;
//					Finish(1);
					break;
			}
		}
		int isUnaligned = ((int)data)%32 | TMDc[z].size%32;
		int alignedLen = (TMDc[z].size+31)&0xffffffe0;
		unsigned char* alignedBuf;
		if(isUnaligned) alignedBuf = memalign(32, alignedLen);
		else alignedBuf = data;
		ISFS_Seek(contentFd,0,0);
		ISFS_Read(contentFd, alignedBuf, alignedLen);
		// If it was unaligned, you have to copy it and clean up
		if(isUnaligned){
			memcpy(data, alignedBuf, TMDc[z].size);
			free(alignedBuf);
		}
		ISFS_Close(contentFd);

		// Do copying here.
		// data is the actual content data (use fwrite with it :P).
		// Copy the file with it's index as it's filename
		char* destination=calloc(sizeof(location)+14, 1);
		char lookup_entry[21];
		sprintf(destination, "%s/%08x.app",location,TMDc[z].index);
		sprintf(lookup_entry, "%08x = %08x\n",TMDc[z].index,TMDc[z].cid);
		strcat(lookup_list, lookup_entry);
		printf("Got destination as: %s\n", destination);
		sleep(3);
		FILE *dfd=fopen(destination,"wb+");
		printf("Opened %s\n", destination);
		sleep(3);
//		free(destination);
		fwrite(data, TMDc[z].size, 1, dfd);
		printf("Wrote data to %s\n", destination);
		sleep(3);
		fclose(dfd);
		printf("Closed %s\n", destination);
		sleep(2);
skip:
		_nop();
	}
	// Make a file containing the lookups called files.txt
	char* lookup_file=calloc(sizeof(location)+14, 1);
	sprintf(lookup_file, "%s/files.txt",location);
	printf("Got destination as: %s\n", lookup_file);
	sleep(3);
	FILE* lfd=fopen(lookup_file,"wb+");
	printf("Opened %s\n", lookup_file);
	sleep(3);
//	free(lookup_file);
	fwrite(lookup_list, 21, ((tmd*)SIGNATURE_PAYLOAD(TMD))->num_contents, lfd);
	printf("Wrote lookups to %s\n", lookup_file);
	sleep(3);
	fclose(lfd);
	printf("Closed %s\n", lookup_file);
	sleep(2);
	printf("Freed TMD and views");
	sleep(1);
Exemple #6
0
	return ERR_NONE;
}

void changeChannelName(u64 titleID)
{
	printf("Identifying as 00000001-00000000 (SU)!... ");
	CheckESRetval(ES_Identify(SU_IDENTIFY));
//	if(ES_SetUID(titleID)<0)
//		return;

	u32 tmd_size ATTRIBUTE_ALIGN(32);


	if(CheckESRetval(ES_GetStoredTMDSize(titleID, &tmd_size))!=0)
		return;

	signed_blob *TMD = (signed_blob *)memalign( 32, tmd_size );
	memset(TMD, 0, tmd_size);

	if(CheckESRetval(ES_GetStoredTMD(titleID, TMD, tmd_size))!=0)
	{
		free(TMD);
		return;
	}

	u32 cnt ATTRIBUTE_ALIGN(32);

	if(CheckESRetval(ES_GetNumTicketViews(titleID, &cnt))!=0)
	{
		free(TMD);
		return;
	}

	if( cnt <= 0 )
	{
		free(TMD);
		return;
	}

	tikview *views = (tikview *)memalign( 32, sizeof(tikview)*cnt );

	if(CheckESRetval(ES_GetTicketViews(titleID, views, cnt))!=0)
	{
		free(views);
		free(TMD);
		return;
	}
	char *name=calloc((0x54/2),1);
	int z;
	for(z=0; z < 1; ++z)
	{
		tmd_content *TMDc = TMD_CONTENTS(((tmd*)(SIGNATURE_PAYLOAD(TMD))));		// OH GOD CREDIAR, WTF WAS THAT MESS!!!
		//printf("%d,",TMDc->index);
		s32 cfd = ES_OpenTitleContent( titleID, TMDc->index);
		free(views);
		if(CheckESRetval(cfd)!=0)
		{	
			;
			//printf("ES_OpenContent(%d) failed\n", cfd);
			//sleep(10);
			//exit(0);
		} else {
			u64 sz ATTRIBUTE_ALIGN(32) = 0x140;
			u8 *data = (u8*)memalign(32, sz);

			if( TMDc->size < sz )
				sz = TMDc->size;

			if( data != NULL )
			{
				if(ES_ReadContent(cfd, data, sz)<0)
				{
					free(data);
					return;
				}

				int y;
				int chan_name_offset=(language_setting*0x54);				// Set to WiiMU's language
				for(y=0;y<(0x54/2);y++)
					name[y]=data[(0x9C+chan_name_offset)+(y*2)+1];

			}
			if(CheckESRetval(ES_CloseContent(cfd))!=0)
			{
				;
				//printf("ES_CloseContent failed\n");
				//sleep(10);
				//exit(0);
			}
			free(data);
		}
	}
	int wiistring_size;
reenter:
	wiistring_size=type_string_wiimote(name, 0x54/2);
	if(wiistring_size<=0)
	{
		printf("\n\nPlease enter a name!\n");
		sleep(3);
		goto reenter;
	}
	name[wiistring_size+1]=0;

	/* Get Content */
	char nameDirectory[80];
	tmd_content *TMDc = TMD_CONTENTS(((tmd*)(SIGNATURE_PAYLOAD(TMD))));
	sprintf(nameDirectory,"/title/%08x/%08x/content/%08x.app",TITLE_UPPER(titleID),TITLE_LOWER(titleID),TMDc->cid);
	s32 contentFd=ISFS_Open(nameDirectory,ISFS_OPEN_RW);
	CheckISFSRetval(contentFd);
	ClearScreen();
	printf("\n\nOpened content!\n");
	u8 *data=calloc(TMDc->size,1);
	int isUnaligned = ((int)data)%32 | TMDc->size%32;
	int alignedLen = (TMDc->size+31)&0xffffffe0;
	u8* alignedBuf;
	if(isUnaligned) alignedBuf = memalign(32, alignedLen);
	else alignedBuf = data;
	CheckISFSRetval(ISFS_Seek(contentFd,0,0));
	CheckISFSRetval(ISFS_Read(contentFd, alignedBuf, alignedLen));
	printf("Read content!\n");
	int y;
	int chan_name_offset=(SYSCONF_GetArea()*0x54);		// Edits the one for the Wii's system Menu
	char* nameOut=calloc(96,1);
	for(y=0;y<(0x54/2);y++)
		nameOut[(y*2)+1]=name[y];
	printf("Wrote new name! %s\n", name);
	CheckISFSRetval(ISFS_Seek(contentFd,(0x9C+chan_name_offset),0));
	printf("Seeked to location!\n");
	if(nameOut==NULL)
	{
		printf("FAILED! (Name Out is NULL!)\n");
		sleep(5);
		Finish(1);
	}
	if(((u32)nameOut%32)!=0)
	{
		isUnaligned = ((int)nameOut)%32 | 0x54%32;
		alignedLen = (0x54+31)&0xffffffe0;
		if(isUnaligned){ alignedBuf = memalign(32, alignedLen); memcpy(alignedBuf, nameOut, 0x54); }
		else alignedBuf = (u8*)nameOut;
	}
	CheckISFSRetval(ISFS_Write(contentFd, alignedBuf, 0x54));
	printf("Wrote content name!\nReading new Header Chunk!\n");
	CheckISFSRetval(ISFS_Seek(contentFd,0,0));
	CheckISFSRetval(ISFS_Read(contentFd, alignedBuf, alignedLen));
	printf("Read content!\n");
	u8* header_chunk=calloc(0x640, 1);
	int i;
	for(i=0;i<0x630;i++)
		header_chunk[i]=alignedBuf[i];
	for(i=0x630;i<0x640;i++)
		header_chunk[i]=0;
	u8* hash=calloc(0x10,1);
	md5(header_chunk, 0x640, hash);
	CheckISFSRetval(ISFS_Seek(contentFd,0x630,0));
	printf("Seeked to location!\n");
	if(hash==NULL)
	{
		printf("FAILED! (Hash is NULL!)\n");
		sleep(5);
		Finish(1);
	}
	if(((u32)hash%32)!=0)
	{
		isUnaligned = ((int)hash)%32 | 0x10%32;
		alignedLen = (0x10+31)&0xffffffe0;
		if(isUnaligned){ alignedBuf = memalign(32, alignedLen); memcpy(alignedBuf, hash, 0x10); }
		else alignedBuf = hash;
Exemple #7
0
s32 __IOS_LaunchNewIOS(int version)
{
	u32 numviews;
	s32 res;
	u64 titleID = 0x100000000LL;
	raw_irq_handler_t irq_handler;
	u32 counter;

	STACK_ALIGN(tikview,views,4,32);
#ifdef DEBUG_IOS
	s32 oldversion;
#endif
	s32 newversion;

	if(version < 3 || version > 0xFF) {
		return IOS_EBADVERSION;
	}

#ifdef DEBUG_IOS
	oldversion = IOS_GetVersion();
	if(oldversion>0) printf("Current IOS Version: IOS%d\n",oldversion);
#endif

	titleID |= version;
#ifdef DEBUG_IOS
	printf("Launching IOS TitleID: %016llx\n",titleID);
#endif

	res = ES_GetNumTicketViews(titleID, &numviews);
	if(res < 0) {
#ifdef DEBUG_IOS
		printf(" GetNumTicketViews failed: %d\n",res);
#endif
		return res;
	}
	if(numviews > 4) {
		printf(" GetNumTicketViews too many views: %lu\n",numviews);
		return IOS_ETOOMANYVIEWS;
	}
	res = ES_GetTicketViews(titleID, views, numviews);
	if(res < 0) {
#ifdef DEBUG_IOS
		printf(" GetTicketViews failed: %d\n",res);
#endif
		return res;
	}

	write32(0x80003140, 0);

	res = ES_LaunchTitleBackground(titleID, &views[0]);
	if(res < 0) {
#ifdef DEBUG_IOS
		printf(" LaunchTitleBackground failed: %d\n",res);
#endif
		return res;
	}

	__ES_Reset();

	// Mask IPC IRQ while we're busy reloading
	__MaskIrq(IRQ_PI_ACR);
	irq_handler = IRQ_Free(IRQ_PI_ACR);

#ifdef DEBUG_IOS
	printf("Waiting for IOS ...\n");
#endif
	while ((read32(0x80003140) >> 16) == 0)
		udelay(1000);

#ifdef DEBUG_IOS
	u32 v = read32(0x80003140);
	printf("IOS loaded: IOS%d v%d.%d\n", v >> 16, (v >> 8) & 0xff, v & 0xff);
#endif

#ifdef DEBUG_IOS
	printf("Waiting for IPC ...\n");
#endif
	for (counter = 0; !(read32(0x0d000004) & 2); counter++) {
		udelay(1000);
		
		if (counter >= MAX_IPC_RETRIES)
			break;
	}

#ifdef DEBUG_IOS
	printf("IPC started (%u)\n", counter);
#endif

	IRQ_Request(IRQ_PI_ACR, irq_handler, NULL);
    __UnmaskIrq(IRQ_PI_ACR);

	__IPC_Reinitialize();

	newversion = IOS_GetVersion();

	if(newversion != version) {
#ifdef DEBUG_IOS
		printf(" Version mismatch!\n");
#endif
		return IOS_EMISMATCH;
	}

	return version;
}
Exemple #8
0
s32 __DI_StubLaunch(void)
{
	u64 titleID = DVD_TITLEID;
	static tikview views[4] ATTRIBUTE_ALIGN(32);
	u32 numviews;
	s32 res;
	
	u32 ints;
	
	dprintf("Shutting down IOS subsystems\n");
	res = __IOS_ShutdownSubsystems();
	if(res < 0) {
		dprintf("Shutdown failed: %d\n",res);
	}
	dprintf("Initializing ES\n");
	res = __ES_Init();
	if(res < 0) {
		dprintf("ES init failed: %d\n",res);
		return res;
	}
	
	dprintf("Launching TitleID: %016llx\n",titleID);
	
	res = ES_GetNumTicketViews(titleID, &numviews);
	if(res < 0) {
		dprintf(" GetNumTicketViews failed: %d\n",res);
		return res;
	}
	if(numviews > 4) {
		dprintf(" GetNumTicketViews too many views: %u\n",numviews);
		return IOS_ETOOMANYVIEWS;
	}
	res = ES_GetTicketViews(titleID, views, numviews);
	if(res < 0) {
		dprintf(" GetTicketViews failed: %d\n",res);
		return res;
	}
	dprintf("Ready to launch channel\n");
	res = ES_LaunchTitleBackground(titleID, &views[0]);
	if(res<0) {
		dprintf("Launch failed: %d\n",res);
		return res;
	}
	dprintf("Channel launching in the background\n");
	dprintf("Pre-stub status:\n");
	dumpregs();
	dprintf("ISR Disable...\n");
	_CPU_ISR_Disable( ints );
	dprintf("Saving regs...\n");
	__distub_saveregs();
	dprintf("Taking the plunge...\n");
	__distub_take_plunge(&di_ctx);
	
	dprintf("We're back!\n");
	dprintf("Restoring regs...\n");
	__distub_restregs();
	dprintf("ISR Enable...\n");
	_CPU_ISR_Restore( ints );
	
	dprintf("Post-stub status:\n");
	dumpregs();
	
	__IPC_Reinitialize();
	__ES_Reset();
	
	dprintf("IPC reinitialized\n");
	sleep(1);
	dprintf("Restarting IOS subsystems\n");
	
	res = __IOS_InitializeSubsystems();
	
	dprintf("Subsystems running!\n");
	
	res = ES_GetNumTicketViews(titleID, &numviews);
	if(res < 0) {
		dprintf(" GetNumTicketViews failed: %d\n",res);
		return res;
	}
	dprintf(" GetNumTicketViews: %d",numviews);
	
	return 0;
}