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 }