1 /*---------------------------------------------------------------------------*
2   Project:      Sample demo program for NAND library
3   File:         gamesave.c
4   Programmer:   John Cho
5                 HIRATSU Daisuke
6 
7   Copyright (C) 2006 Nintendo.  All rights reserved.
8 
9   These coded instructions, statements, and computer programs contain
10   proprietary information of Nintendo of America Inc. and/or Nintendo
11   Company Ltd., and are protected by Federal copyright law.  They may
12   not be disclosed to third parties or copied or duplicated in any form,
13   in whole or in part, without the prior written consent of Nintendo.
14 
15   $Log: gamesave.c,v $
16   Revision 1.4  2006/09/28 03:02:11  hiratsu
17   Memory allocation for NANDReadDir() was wrong.  Fixed.
18 
19   Revision 1.3  2006/09/06 11:58:20  hiratsu
20   Removed DeleteTestFiles() because it can delete non-test files.
21 
22   Revision 1.2  2006/06/20 02:47:59  hiratsu
23   Fixed filename mismatch bug.
24 
25   Revision 1.1  2006/06/15 11:19:00  johnc
26   Initial checkin.  Simple demo to list/delete files in game save directory
27 
28 
29  *---------------------------------------------------------------------------*/
30 
31 #define DEMO_USE_MEMLIB		// Must define before including demo.h to use MEM library
32 #include <demo.h>
33 #include <revolution.h>
34 #include <revolution/nand.h>
35 #include <string.h>
36 
37 // Defines
38 #define NUM_TEST_FILES	2
39 
40 // Functions
41 static void  Initialize();
42 static char* GetNextName(char *name);
43 static char* GetNameFromDirList(u32 item);
44 static void  ReadDir();
45 static void  CreateFile(u32 id);
46 static void  PrintFile(u32 id);
47 static BOOL  UserConfirm(u16 button);
48 static void  DeleteFileConfirm(u32 item);
49 static void  DeleteTestFiles();
50 static void  GetPermString(u8 perm, char *permString);
51 static BOOL  RenderControls();
52 static void  DrawAllTextToScreen();
53 static void  DrawStatic(char* msg, ...);
54 static void  DrawDynamic(char* msg, ...);
55 static void  ResetStaticText();
56 static void  ResetDynamicText();
57 
58 // Variables
59 static u8	 Umask = NAND_PERM_OWNER_READ | NAND_PERM_OWNER_WRITE | NAND_PERM_GROUP_READ;
60 static u32   NumFilesInDir = 0;
61 static u32   Current = 0;
62 static char* GameSaveDirList = NULL;
63 static char  GameSaveDirPath[NAND_MAX_PATH] ATTRIBUTE_ALIGN(32);
64 
65 
66 /* ================================================================================================
67 
68    Initialization
69 
70    ================================================================================================ */
71 
Initialize()72 static void Initialize()
73 {
74     const GXColor BLACK = {0, 0, 0, 255};
75     GXRenderModeObj *rmp;
76     s32 rv;
77 
78     // Initialize subsystems using DEMO library
79     DEMOInit(&GXNtsc480IntDf);
80 
81     // Initialize NAND library
82     // Please make sure to check return value
83 	rv = NANDInit();
84 	if (rv != NAND_RESULT_OK)
85 	{
86 		OSReport("NANDInit failed: %d\n", rv);
87 		OSHalt("Cannot continue\n");
88 	}
89 
90 	// Setup for displaying text using DEMOFont
91     GXSetCopyClear( BLACK, GX_MAX_Z24 );
92     GXCopyDisp( DEMOGetCurrentBuffer(), GX_TRUE );
93     rmp = DEMOGetRenderModeObj();
94     DEMOInitCaption(DM_FT_XLU, (s16)(rmp->fbWidth / 2), (s16)(rmp->efbHeight / 2));
95     GXSetZMode( GX_ENABLE, GX_ALWAYS, GX_ENABLE );
96     GXSetBlendMode( GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
97 }
98 
99 
100 /* ================================================================================================
101 
102    NAND Functions
103 
104    ================================================================================================ */
105 
GetNextName(char * name)106 static char* GetNextName(char *name)
107 {
108 	return name + (strlen(name) + 1);
109 }
110 
GetNameFromDirList(u32 item)111 static char* GetNameFromDirList(u32 item)
112 {
113 	u32   i;
114 	char *fileName;
115 
116 	// Names in the directory list buffer are continguous,
117 	// only separated by the NULL character.
118 	// Here, we want to get the name to the nth name in the list
119 	// This shows you how to traverse the directory buffer.
120 	fileName = GameSaveDirList;
121     for (i = 0; i < item; i++)
122     {
123 	    fileName = GetNextName(fileName);
124     }
125 
126     return fileName;
127 }
128 
ReadDir()129 static void ReadDir()
130 {
131 	s32 rv;
132 
133     // Get the count of the contents of the directory
134     // - Use NULL to specific that we should get the count first
135     //   so we can allocate enough memory
136     rv = NANDReadDir(GameSaveDirPath, NULL, &NumFilesInDir);
137     if (rv != NAND_RESULT_OK)
138     {
139 		OSReport("NANDReadDir failed %d\n", rv);
140 		return;
141     }
142 
143     // Free the last allocated directory list buffer
144     if (GameSaveDirList)
145     {
146 		MEMFreeToExpHeap(DemoAllocator2.pHeap, GameSaveDirList);
147 		// MEMFreeToAllocator(&DemoAllocator2, GameSaveDirList);
148 		GameSaveDirList = NULL;
149  	}
150 
151  	// Allocate a new directory list buffer
152  	// - In this demo we are using a dynamically allocated buffer.
153  	// - This can be done more intelligently and efficiently, depending on your implementation needs
154  	// - We could have just used a static variable with a max number supported, but that is up to the game designer
155  	// - If you use a statically allocated buffer, don't forget to align it using ATTRIBUTE_ALIGN(32)
156 	if (!GameSaveDirList)
157 	{
158 		// GameSaveDirList must be 32B aligned to NANDReadDir
159 		// Using the MEM library, we explicitly assign 32B alignment this way
160 		// But for your information, DemoAllocator1 and DemoAllocator2 automatically default to 32B alignment
161 		// Calling it this way is also okay:
162 		// GameSaveDirList = MEMAllocFromAllocator(&DemoAllocator2, size);
163         u32 size = (NumFilesInDir * (NAND_MAX_NAME + 1) + 31)/32*32;
164         GameSaveDirList = MEMAllocFromExpHeapEx(DemoAllocator2.pHeap, size, 32);
165     }
166 
167 	// Check for any files
168 	// But in this demo, we need to always leave GameSaveDirList allocated, even if empty
169     if (NumFilesInDir == 0)
170     {
171 	    // Empty directory
172 	 	GameSaveDirList[0] = '\0';
173     }
174     else
175     {
176 	    // Finally read the directory list into the directory buffer
177 	    // - Notice all of the names are placed in a contiguous buffer,
178 	    //   separated by NULL character.
179 	    rv = NANDReadDir(GameSaveDirPath, GameSaveDirList, &NumFilesInDir);
180 	    if (rv != NAND_RESULT_OK)
181 	    {
182 			OSReport("NANDReadDir failed %d\n", rv);
183 	    }
184 	}
185 
186     // Reset the cursor to the beginning
187     Current = 0;
188 
189  	// Draw/Print files in the directory
190  	ResetStaticText();
191 }
192 
CreateFile(u32 id)193 static void CreateFile(u32 id)
194 {
195 	NANDFileInfo info;
196 	s32          rv;
197 	u32          len;
198     char         fileName[NAND_MAX_PATH];
199     char         content[64] ATTRIBUTE_ALIGN(32);
200 
201     sprintf(fileName, "test%d.txt", id);
202 
203     // Create a unique test file with the id
204     // If it already exists, don't error
205     // Please be careful of the permissions you set
206 	rv = NANDCreate(fileName, Umask, 0);
207 	if (rv == NAND_RESULT_EXISTS)
208 		return;
209 	else if (rv != NAND_RESULT_OK)
210 	{
211 		OSReport("NANDCreate failed %d\n", rv);
212 		return;
213 	}
214 	else
215 		OSReport("Created: '%s'\n", fileName);
216 
217 	// Open the file for writing
218     rv = NANDOpen(fileName, &info, NAND_ACCESS_WRITE);
219     if (rv != NAND_RESULT_OK)
220     {
221 		OSReport("NANDOpen: Could not open '%s' for writing\n", fileName);
222     	return;
223 	}
224 
225 	// Write unique text to the file
226 	// The buffer must be 32B aligned with a size a multiple of 32B
227 	snprintf(content, 64, "test%d.txt says Hello World!", id);
228 	len = OSRoundUp32B(strlen(content));
229 	rv = NANDWrite(&info, content, len);
230 
231 	// Check errors.  Notice that NANDWrite returns the number of bytes written
232 	if (rv != len)
233 	{
234 		OSReport("NANDWrite: Could not write rv=%d len=%d\n", rv, len);
235 	}
236 
237     NANDClose(&info);
238 
239 	// Make sure we call ReadDir to refresh the screen/text after we are finished creating
240 }
241 
PrintFile(u32 id)242 static void PrintFile(u32 id)
243 {
244 	NANDFileInfo info;
245 	s32          rv;
246 	u32          len;
247     char        *fileName;
248 	u8           buffer[64] ATTRIBUTE_ALIGN(32);
249     fileName = GetNameFromDirList(id);
250 
251     // Open the file for reading
252     rv = NANDOpen(fileName, &info, NAND_ACCESS_READ);
253     if (rv != NAND_RESULT_OK)
254     {
255 		OSReport("NANDOpen: Could not open '%s' for reading\n", fileName);
256     	return;
257 	}
258 
259 	// Get the length of the file
260 	rv = NANDGetLength(&info, &len);
261 	if (rv != NAND_RESULT_OK)
262 	{
263 		NANDClose(&info);
264 		OSReport("NANDGetLength: Could not get length for '%s'\n", fileName);
265     	return;
266 	}
267 
268 	// Prevent buffer overflow
269 	if (len > 64)
270 		len = 64;
271 
272 	// Notice that even though we got the length of the file, the read length
273 	// must be a multiple of 32B
274 	len = OSRoundUp32B(len);
275 
276 	// Read the contents of the file
277 	// The buffer must be 32B aligned with a size a multiple of 32B
278 	rv = NANDRead(&info, buffer, len);
279     NANDClose(&info);
280 
281     // Check errors.  Notice that NANDRead returns the number bytes read
282     if (rv != len)
283     {
284 		OSReport("NANDRead: Could not read rv=%d len=%d\n", rv, len);
285 		return;
286     }
287 
288     buffer[len-1] = '\0';		// Doesn't put it right after, but put here for safety
289 	DrawStatic("%s\n", buffer);
290 }
291 
UserConfirm(u16 cancelButton)292 static BOOL UserConfirm(u16 cancelButton)
293 {
294 	#define WAIT_TICKS	40000
295 	s32 tick = -1;
296 	u16 buttonDown;
297 
298 	// Loop until user confirms with button
299 	// If any other button is pressed, exit false
300 	// Any GC controller in any port will work
301 	do
302 	{
303 		DEMOPadRead();
304 		buttonDown = (u16)(DEMOPadGetButtonDown(0) | DEMOPadGetButtonDown(1) | DEMOPadGetButtonDown(2) | DEMOPadGetButtonDown(3));
305 		if (buttonDown && !(buttonDown & cancelButton))
306 		{
307 			// Button was pressed, and it wasn't the confirm button, so cancel
308 			return FALSE;
309 		}
310 		else
311 		{
312 		    tick++;
313 		    tick %= 4*WAIT_TICKS;
314 		    if (tick % WAIT_TICKS == 0)
315 		    {
316 			    // Draw the waiting indicator
317 		        DrawDynamic("\b%c", "/-\\|"[tick/WAIT_TICKS]);
318 		        DEMOBeforeRender();
319 		        DrawAllTextToScreen();
320         		DEMODoneRender();
321 	        }
322 		}
323 	}
324 	while (!(buttonDown & cancelButton));
325 
326 	// Confirm button was pressed
327 	return TRUE;
328 }
329 
DeleteFileConfirm(u32 item)330 static void DeleteFileConfirm(u32 item)
331 {
332 	s32   rv;
333 	char *ptr;
334 
335 	ptr = GetNameFromDirList((u32)item);
336 
337 	// Verify Delete
338 	DrawDynamic("Are you sure you want to\ndelete '%s'?\n", ptr);
339 	DrawDynamic("Press X again to confirm... "); // Leave an extra space for waiting indicator
340 	if (!UserConfirm(PAD_BUTTON_X))
341 	{
342 		DrawDynamic("\bcancelled\n");
343 		return;
344 	}
345 
346 	// Finally Delete
347 	rv = NANDDelete(ptr);
348     if (rv != NAND_RESULT_OK)
349     {
350 		OSReport("\bNANDDelete failed %d\n", rv);
351 		return;
352     }
353 	DrawDynamic("\bdone\n");
354 
355 	// Refresh the directory list
356 	ReadDir();
357 }
358 
359 /*
360 static void DeleteTestFiles()
361 {
362 	s32   rv;
363 	u32   i;
364     char* fileName;
365 
366 	// Delete all the test files we generate in this demo
367     DrawStatic("Deleting test files...");
368     fileName = GameSaveDirList;
369     for (i = 0; i < NUM_TEST_FILES; i++)
370     {
371 	    rv = NANDDelete(fileName);
372 	    if (rv != NAND_RESULT_OK)
373 			OSReport("failed %d...", rv);
374 		fileName = GetNextName(fileName);
375     }
376     DrawStatic("done\n");
377 }
378 */
379 
380 static void
GetPermString(u8 perm,char * permString)381 GetPermString(u8 perm, char *permString)
382 {
383 	u32 i;
384 
385 	// Construct the permission string from the 8-bit permission field
386 
387 	for (i = 0; i < 6; i++)
388 		permString[i] = '-';
389 
390 	if (perm & NAND_PERM_OWNER_READ)  permString[0] = 'r';
391 	if (perm & NAND_PERM_OWNER_WRITE) permString[1] = 'w';
392 	if (perm & NAND_PERM_GROUP_READ)  permString[2] = 'r';
393 	if (perm & NAND_PERM_GROUP_WRITE) permString[3] = 'w';
394 	if (perm & NAND_PERM_OTHER_READ)  permString[4] = 'r';
395 	if (perm & NAND_PERM_OTHER_WRITE) permString[5] = 'w';
396 	permString[6] = '\0';
397 }
398 
399 /* ================================================================================================
400 
401    Main
402 
403    ================================================================================================ */
404 
RenderControls()405 static BOOL RenderControls()
406 {
407 	#define FONT_HEIGHT	8
408     s16   x = FONT_HEIGHT;
409     s16   y = FONT_HEIGHT;
410     u16   buttonDown;
411     u16	  dirsNew;
412 
413     // Be nice and let any GC controller work from any port
414     // Stick or button pad (dpad) will work
415 	buttonDown = (u16)(DEMOPadGetButtonDown(0) | DEMOPadGetButtonDown(1) | DEMOPadGetButtonDown(2) | DEMOPadGetButtonDown(3));
416     dirsNew = (u16)(DEMOPadGetDirsNew(0) | DEMOPadGetDirsNew(1) | DEMOPadGetDirsNew(2) | DEMOPadGetDirsNew(3));
417 
418     // Draw the controls on the lower part of the screen
419     y = 160;
420     DEMOPrintf( 16, y += FONT_HEIGHT, 0, "---------GC Controller---------");
421     if (NumFilesInDir)
422     {
423 	    DEMOPrintf( 16, y += FONT_HEIGHT, 0, "   Up: Move cursor up");
424 	    DEMOPrintf( 16, y += FONT_HEIGHT, 0, " Down: Move cursor down");
425 	    DEMOPrintf( 16, y += FONT_HEIGHT, 0, "    A: Print");
426 	    DEMOPrintf( 16, y += FONT_HEIGHT, 0, "    L: Refresh");
427 	    DEMOPrintf( 16, y += FONT_HEIGHT, 0, "    X: Delete");
428 
429 	    // Move the cursor up or down a file
430 	    if ((buttonDown & PAD_BUTTON_UP) || (dirsNew & DEMO_STICK_UP))
431 	    	Current = (Current + NumFilesInDir - 1) % NumFilesInDir;
432 	    if ((buttonDown & PAD_BUTTON_DOWN) || (dirsNew & DEMO_STICK_DOWN))
433 	    	Current = (Current + 1) % NumFilesInDir;
434 	    if (buttonDown & PAD_BUTTON_A)
435 	    	PrintFile(Current);
436         if (buttonDown & PAD_BUTTON_X)
437         {
438             ResetStaticText();
439             DeleteFileConfirm(Current);
440         }
441     }
442 
443     // Refresh the directory / screen (static buffer)
444     if (buttonDown & PAD_TRIGGER_L)
445 	    ReadDir();
446 
447 	// Always try to press this button to clean the test files from NAND
448 	// Otherwise, test files will remain in your directory!!
449     DEMOPrintf( 16, y += FONT_HEIGHT, 0, "Start: Quit this demo");
450     if (buttonDown & PAD_BUTTON_MENU)
451     {
452 //		DeleteTestFiles();
453 	    DrawStatic("Demo Finished\n");
454 	 	return FALSE;
455     }
456 
457     return TRUE;
458 }
459 
main(void)460 int main( void )
461 {
462 	u32  i;
463 	BOOL alive = TRUE;
464 
465     Initialize();
466 
467     // Get the game save directory
468 	// - NANDInit will automatically set the game save directory
469 	// - For NDEV systems, it is hardcoded and shared among all NDEV applications
470 	// - For retail systems, it will be /title/titleIdHi/titleIdLo/data, which is unique to the application
471 	if (NANDGetCurrentDir(GameSaveDirPath) != NAND_RESULT_OK)
472 		OSReport("NANDGetCurrentDir failed\n");
473 
474     // Create test files
475     for (i = 0; i < NUM_TEST_FILES; i++)
476 		CreateFile(i);
477 
478     // Call initialize the directory list after we create our files
479     ReadDir();
480 
481     // Main loop
482     while (alive)
483     {
484 	    DEMOPadRead();
485         DEMOBeforeRender();
486         alive = RenderControls();
487 	    DrawAllTextToScreen();
488         DEMODoneRender();
489 		ResetDynamicText();
490     }
491 
492     return 0;
493 }
494 
495 
496 /* ================================================================================================
497 
498    Printing
499 
500    ================================================================================================ */
501 
502 #define SCREEN_LINE_SIZE 256
503 #define SCREEN_LOG_SIZE  1024*12
504 #define SCREEN_LOG_NEWLINES 30
505 char  ScreenStaticBuffer[SCREEN_LOG_SIZE];
506 char *ScreenStatic = ScreenStaticBuffer;
507 u32   ScreenStaticNewlineCount = 0;
508 char  ScreenDynamicBuffer[SCREEN_LOG_SIZE];
509 char *ScreenDynamic = ScreenDynamicBuffer;
510 
511 // These are log-type functions.
512 // StaticBuffer is not reset per frame whereas Dynamic Buffer is reset once per frame
513 
DrawAllTextToScreen()514 static void DrawAllTextToScreen()
515 {
516     // Draw the static logged text
517     DEMOPuts(5, 20, 0, ScreenStaticBuffer);
518 
519     // Draw the cursor
520 	if (NumFilesInDir)
521     	DEMOPuts(5, (s16)(20+((Current+4)*8)), 0, "*");
522 
523     // Draw the dynamic logged text (add an extra blank line on screen)
524     DEMOPuts(5, (s16)(20+((NumFilesInDir+4+1)*8)), 0, ScreenDynamicBuffer);
525 }
526 
DrawStatic(char * msg,...)527 static void DrawStatic(char* msg, ...)
528 {
529     char 	buf[SCREEN_LINE_SIZE];
530     u32 	len;
531 	va_list marker;
532 
533     va_start(marker, msg);
534 
535     vsnprintf(buf, SCREEN_LINE_SIZE, msg, marker);   // should check later for truncation
536     len = strlen(buf)+1; 				// including '\0'
537 
538     // Print it to serial output as well
539     OSReport(buf);
540     fflush(stdout);
541 
542     // Count the number of newlines
543     // Only supports 1 newline per call to DrawStatic for now
544     ScreenStaticNewlineCount++;
545 
546     if ((ScreenStatic + len) >= (ScreenStaticBuffer + SCREEN_LOG_SIZE) ||
547         ScreenStaticNewlineCount > SCREEN_LOG_NEWLINES)
548     {
549 	    // Scroll back to the top of the log
550         ScreenStatic = ScreenStaticBuffer;
551         ScreenStaticNewlineCount = 0;
552     }
553 
554     strncpy((void*) ScreenStatic, (void*)buf, len);
555     ScreenStatic += len-1;					// move to '\0'
556 
557     va_end(marker);
558 }
559 
DrawDynamic(char * msg,...)560 static void DrawDynamic(char* msg, ...)
561 {
562     char 	buf[SCREEN_LINE_SIZE];
563     char   *bufPtr = buf;
564     u32 	len;
565 	va_list marker;
566 
567     va_start(marker, msg);
568 
569     vsnprintf(buf, SCREEN_LINE_SIZE, msg, marker);	// should check later for truncation
570     len = strlen(buf)+1; 				// including '\0'
571 
572     // Print it to serial output as well
573     OSReport(buf);
574     fflush(stdout);
575 
576     if ((ScreenDynamic + len) < (ScreenDynamicBuffer + SCREEN_LOG_SIZE))
577     {
578 	    // Copy only if we have space
579 
580 	    // We only support \b if it is the first control character in the string
581 	    if ((buf[0] == '\b') && (ScreenDynamic > ScreenDynamicBuffer))
582 	    {
583 	    	ScreenDynamic--;
584 	    	bufPtr++;
585 	    	len--;
586     	}
587 	    strncpy((void*) ScreenDynamic, (void*)bufPtr, len);
588 	    ScreenDynamic += len-1;			// move to '\0'
589     }
590 
591     va_end(marker);
592 }
593 
ResetStaticText()594 static void ResetStaticText()
595 {
596 	u32   i;
597     NANDStatus stat;
598     char  permString[8];
599 	char* fileName;
600 
601 	ScreenStatic = ScreenStaticBuffer;
602 	*ScreenStatic = '\0';
603 	ScreenStaticNewlineCount = 0;
604 
605 	// Draw the header
606 	OSReport("-------------------------------------\n");
607 	DrawStatic( "Game Save Demo\n");
608 	DrawStatic( "Game Save Directory:\n");
609 	DrawStatic( "  %s\n\n", GameSaveDirPath);
610 	// If we change the number of newlines, need to change parameters in DrawAllTextToScreen
611 	// Currently there are 4 lines drawn to the screen (including one blank)
612 
613 	// Draw the files in the directory
614     fileName = GameSaveDirList;
615     for (i = 0; i < NumFilesInDir; i++)
616     {
617 	    NANDGetStatus(fileName, &stat);
618 	    GetPermString(stat.permission, permString);
619 	    DrawStatic("  %s %s\n", permString, fileName);	// Leave first character blank for cursor
620 		fileName = GetNextName(fileName);					// Go to next name in GameSaveDirList
621     }
622     DrawStatic("\n");
623 }
624 
ResetDynamicText()625 static void ResetDynamicText()
626 {
627 	ScreenDynamic = ScreenDynamicBuffer;
628 	*ScreenDynamic = '\0';
629 }