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