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