/*---------------------------------------------------------------------------* Project: Sample demo program for NAND library File: gamesave.c Programmer: John Cho HIRATSU Daisuke Copyright (C) 2006 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: gamesave.c,v $ Revision 1.4 2006/09/28 03:02:11 hiratsu Memory allocation for NANDReadDir() was wrong. Fixed. Revision 1.3 2006/09/06 11:58:20 hiratsu Removed DeleteTestFiles() because it can delete non-test files. Revision 1.2 2006/06/20 02:47:59 hiratsu Fixed filename mismatch bug. Revision 1.1 2006/06/15 11:19:00 johnc Initial checkin. Simple demo to list/delete files in game save directory *---------------------------------------------------------------------------*/ #define DEMO_USE_MEMLIB // Must define before including demo.h to use MEM library #include #include #include #include // Defines #define NUM_TEST_FILES 2 // Functions static void Initialize(); static char* GetNextName(char *name); static char* GetNameFromDirList(u32 item); static void ReadDir(); static void CreateFile(u32 id); static void PrintFile(u32 id); static BOOL UserConfirm(u16 button); static void DeleteFileConfirm(u32 item); static void DeleteTestFiles(); static void GetPermString(u8 perm, char *permString); static BOOL RenderControls(); static void DrawAllTextToScreen(); static void DrawStatic(char* msg, ...); static void DrawDynamic(char* msg, ...); static void ResetStaticText(); static void ResetDynamicText(); // Variables static u8 Umask = NAND_PERM_OWNER_READ | NAND_PERM_OWNER_WRITE | NAND_PERM_GROUP_READ; static u32 NumFilesInDir = 0; static u32 Current = 0; static char* GameSaveDirList = NULL; static char GameSaveDirPath[NAND_MAX_PATH] ATTRIBUTE_ALIGN(32); /* ================================================================================================ Initialization ================================================================================================ */ static void Initialize() { const GXColor BLACK = {0, 0, 0, 255}; GXRenderModeObj *rmp; s32 rv; // Initialize subsystems using DEMO library DEMOInit(&GXNtsc480IntDf); // Initialize NAND library // Please make sure to check return value rv = NANDInit(); if (rv != NAND_RESULT_OK) { OSReport("NANDInit failed: %d\n", rv); OSHalt("Cannot continue\n"); } // Setup for displaying text using DEMOFont GXSetCopyClear( BLACK, GX_MAX_Z24 ); GXCopyDisp( DEMOGetCurrentBuffer(), GX_TRUE ); rmp = DEMOGetRenderModeObj(); DEMOInitCaption(DM_FT_XLU, (s16)(rmp->fbWidth / 2), (s16)(rmp->efbHeight / 2)); GXSetZMode( GX_ENABLE, GX_ALWAYS, GX_ENABLE ); GXSetBlendMode( GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR ); } /* ================================================================================================ NAND Functions ================================================================================================ */ static char* GetNextName(char *name) { return name + (strlen(name) + 1); } static char* GetNameFromDirList(u32 item) { u32 i; char *fileName; // Names in the directory list buffer are continguous, // only separated by the NULL character. // Here, we want to get the name to the nth name in the list // This shows you how to traverse the directory buffer. fileName = GameSaveDirList; for (i = 0; i < item; i++) { fileName = GetNextName(fileName); } return fileName; } static void ReadDir() { s32 rv; // Get the count of the contents of the directory // - Use NULL to specific that we should get the count first // so we can allocate enough memory rv = NANDReadDir(GameSaveDirPath, NULL, &NumFilesInDir); if (rv != NAND_RESULT_OK) { OSReport("NANDReadDir failed %d\n", rv); return; } // Free the last allocated directory list buffer if (GameSaveDirList) { MEMFreeToExpHeap(DemoAllocator2.pHeap, GameSaveDirList); // MEMFreeToAllocator(&DemoAllocator2, GameSaveDirList); GameSaveDirList = NULL; } // Allocate a new directory list buffer // - In this demo we are using a dynamically allocated buffer. // - This can be done more intelligently and efficiently, depending on your implementation needs // - We could have just used a static variable with a max number supported, but that is up to the game designer // - If you use a statically allocated buffer, don't forget to align it using ATTRIBUTE_ALIGN(32) if (!GameSaveDirList) { // GameSaveDirList must be 32B aligned to NANDReadDir // Using the MEM library, we explicitly assign 32B alignment this way // But for your information, DemoAllocator1 and DemoAllocator2 automatically default to 32B alignment // Calling it this way is also okay: // GameSaveDirList = MEMAllocFromAllocator(&DemoAllocator2, size); u32 size = (NumFilesInDir * (NAND_MAX_NAME + 1) + 31)/32*32; GameSaveDirList = MEMAllocFromExpHeapEx(DemoAllocator2.pHeap, size, 32); } // Check for any files // But in this demo, we need to always leave GameSaveDirList allocated, even if empty if (NumFilesInDir == 0) { // Empty directory GameSaveDirList[0] = '\0'; } else { // Finally read the directory list into the directory buffer // - Notice all of the names are placed in a contiguous buffer, // separated by NULL character. rv = NANDReadDir(GameSaveDirPath, GameSaveDirList, &NumFilesInDir); if (rv != NAND_RESULT_OK) { OSReport("NANDReadDir failed %d\n", rv); } } // Reset the cursor to the beginning Current = 0; // Draw/Print files in the directory ResetStaticText(); } static void CreateFile(u32 id) { NANDFileInfo info; s32 rv; u32 len; char fileName[NAND_MAX_PATH]; char content[64] ATTRIBUTE_ALIGN(32); sprintf(fileName, "test%d.txt", id); // Create a unique test file with the id // If it already exists, don't error // Please be careful of the permissions you set rv = NANDCreate(fileName, Umask, 0); if (rv == NAND_RESULT_EXISTS) return; else if (rv != NAND_RESULT_OK) { OSReport("NANDCreate failed %d\n", rv); return; } else OSReport("Created: '%s'\n", fileName); // Open the file for writing rv = NANDOpen(fileName, &info, NAND_ACCESS_WRITE); if (rv != NAND_RESULT_OK) { OSReport("NANDOpen: Could not open '%s' for writing\n", fileName); return; } // Write unique text to the file // The buffer must be 32B aligned with a size a multiple of 32B snprintf(content, 64, "test%d.txt says Hello World!", id); len = OSRoundUp32B(strlen(content)); rv = NANDWrite(&info, content, len); // Check errors. Notice that NANDWrite returns the number of bytes written if (rv != len) { OSReport("NANDWrite: Could not write rv=%d len=%d\n", rv, len); } NANDClose(&info); // Make sure we call ReadDir to refresh the screen/text after we are finished creating } static void PrintFile(u32 id) { NANDFileInfo info; s32 rv; u32 len; char *fileName; u8 buffer[64] ATTRIBUTE_ALIGN(32); fileName = GetNameFromDirList(id); // Open the file for reading rv = NANDOpen(fileName, &info, NAND_ACCESS_READ); if (rv != NAND_RESULT_OK) { OSReport("NANDOpen: Could not open '%s' for reading\n", fileName); return; } // Get the length of the file rv = NANDGetLength(&info, &len); if (rv != NAND_RESULT_OK) { NANDClose(&info); OSReport("NANDGetLength: Could not get length for '%s'\n", fileName); return; } // Prevent buffer overflow if (len > 64) len = 64; // Notice that even though we got the length of the file, the read length // must be a multiple of 32B len = OSRoundUp32B(len); // Read the contents of the file // The buffer must be 32B aligned with a size a multiple of 32B rv = NANDRead(&info, buffer, len); NANDClose(&info); // Check errors. Notice that NANDRead returns the number bytes read if (rv != len) { OSReport("NANDRead: Could not read rv=%d len=%d\n", rv, len); return; } buffer[len-1] = '\0'; // Doesn't put it right after, but put here for safety DrawStatic("%s\n", buffer); } static BOOL UserConfirm(u16 cancelButton) { #define WAIT_TICKS 40000 s32 tick = -1; u16 buttonDown; // Loop until user confirms with button // If any other button is pressed, exit false // Any GC controller in any port will work do { DEMOPadRead(); buttonDown = (u16)(DEMOPadGetButtonDown(0) | DEMOPadGetButtonDown(1) | DEMOPadGetButtonDown(2) | DEMOPadGetButtonDown(3)); if (buttonDown && !(buttonDown & cancelButton)) { // Button was pressed, and it wasn't the confirm button, so cancel return FALSE; } else { tick++; tick %= 4*WAIT_TICKS; if (tick % WAIT_TICKS == 0) { // Draw the waiting indicator DrawDynamic("\b%c", "/-\\|"[tick/WAIT_TICKS]); DEMOBeforeRender(); DrawAllTextToScreen(); DEMODoneRender(); } } } while (!(buttonDown & cancelButton)); // Confirm button was pressed return TRUE; } static void DeleteFileConfirm(u32 item) { s32 rv; char *ptr; ptr = GetNameFromDirList((u32)item); // Verify Delete DrawDynamic("Are you sure you want to\ndelete '%s'?\n", ptr); DrawDynamic("Press X again to confirm... "); // Leave an extra space for waiting indicator if (!UserConfirm(PAD_BUTTON_X)) { DrawDynamic("\bcancelled\n"); return; } // Finally Delete rv = NANDDelete(ptr); if (rv != NAND_RESULT_OK) { OSReport("\bNANDDelete failed %d\n", rv); return; } DrawDynamic("\bdone\n"); // Refresh the directory list ReadDir(); } /* static void DeleteTestFiles() { s32 rv; u32 i; char* fileName; // Delete all the test files we generate in this demo DrawStatic("Deleting test files..."); fileName = GameSaveDirList; for (i = 0; i < NUM_TEST_FILES; i++) { rv = NANDDelete(fileName); if (rv != NAND_RESULT_OK) OSReport("failed %d...", rv); fileName = GetNextName(fileName); } DrawStatic("done\n"); } */ static void GetPermString(u8 perm, char *permString) { u32 i; // Construct the permission string from the 8-bit permission field for (i = 0; i < 6; i++) permString[i] = '-'; if (perm & NAND_PERM_OWNER_READ) permString[0] = 'r'; if (perm & NAND_PERM_OWNER_WRITE) permString[1] = 'w'; if (perm & NAND_PERM_GROUP_READ) permString[2] = 'r'; if (perm & NAND_PERM_GROUP_WRITE) permString[3] = 'w'; if (perm & NAND_PERM_OTHER_READ) permString[4] = 'r'; if (perm & NAND_PERM_OTHER_WRITE) permString[5] = 'w'; permString[6] = '\0'; } /* ================================================================================================ Main ================================================================================================ */ static BOOL RenderControls() { #define FONT_HEIGHT 8 s16 x = FONT_HEIGHT; s16 y = FONT_HEIGHT; u16 buttonDown; u16 dirsNew; // Be nice and let any GC controller work from any port // Stick or button pad (dpad) will work buttonDown = (u16)(DEMOPadGetButtonDown(0) | DEMOPadGetButtonDown(1) | DEMOPadGetButtonDown(2) | DEMOPadGetButtonDown(3)); dirsNew = (u16)(DEMOPadGetDirsNew(0) | DEMOPadGetDirsNew(1) | DEMOPadGetDirsNew(2) | DEMOPadGetDirsNew(3)); // Draw the controls on the lower part of the screen y = 160; DEMOPrintf( 16, y += FONT_HEIGHT, 0, "---------GC Controller---------"); if (NumFilesInDir) { DEMOPrintf( 16, y += FONT_HEIGHT, 0, " Up: Move cursor up"); DEMOPrintf( 16, y += FONT_HEIGHT, 0, " Down: Move cursor down"); DEMOPrintf( 16, y += FONT_HEIGHT, 0, " A: Print"); DEMOPrintf( 16, y += FONT_HEIGHT, 0, " L: Refresh"); DEMOPrintf( 16, y += FONT_HEIGHT, 0, " X: Delete"); // Move the cursor up or down a file if ((buttonDown & PAD_BUTTON_UP) || (dirsNew & DEMO_STICK_UP)) Current = (Current + NumFilesInDir - 1) % NumFilesInDir; if ((buttonDown & PAD_BUTTON_DOWN) || (dirsNew & DEMO_STICK_DOWN)) Current = (Current + 1) % NumFilesInDir; if (buttonDown & PAD_BUTTON_A) PrintFile(Current); if (buttonDown & PAD_BUTTON_X) { ResetStaticText(); DeleteFileConfirm(Current); } } // Refresh the directory / screen (static buffer) if (buttonDown & PAD_TRIGGER_L) ReadDir(); // Always try to press this button to clean the test files from NAND // Otherwise, test files will remain in your directory!! DEMOPrintf( 16, y += FONT_HEIGHT, 0, "Start: Quit this demo"); if (buttonDown & PAD_BUTTON_MENU) { // DeleteTestFiles(); DrawStatic("Demo Finished\n"); return FALSE; } return TRUE; } int main( void ) { u32 i; BOOL alive = TRUE; Initialize(); // Get the game save directory // - NANDInit will automatically set the game save directory // - For NDEV systems, it is hardcoded and shared among all NDEV applications // - For retail systems, it will be /title/titleIdHi/titleIdLo/data, which is unique to the application if (NANDGetCurrentDir(GameSaveDirPath) != NAND_RESULT_OK) OSReport("NANDGetCurrentDir failed\n"); // Create test files for (i = 0; i < NUM_TEST_FILES; i++) CreateFile(i); // Call initialize the directory list after we create our files ReadDir(); // Main loop while (alive) { DEMOPadRead(); DEMOBeforeRender(); alive = RenderControls(); DrawAllTextToScreen(); DEMODoneRender(); ResetDynamicText(); } return 0; } /* ================================================================================================ Printing ================================================================================================ */ #define SCREEN_LINE_SIZE 256 #define SCREEN_LOG_SIZE 1024*12 #define SCREEN_LOG_NEWLINES 30 char ScreenStaticBuffer[SCREEN_LOG_SIZE]; char *ScreenStatic = ScreenStaticBuffer; u32 ScreenStaticNewlineCount = 0; char ScreenDynamicBuffer[SCREEN_LOG_SIZE]; char *ScreenDynamic = ScreenDynamicBuffer; // These are log-type functions. // StaticBuffer is not reset per frame whereas Dynamic Buffer is reset once per frame static void DrawAllTextToScreen() { // Draw the static logged text DEMOPuts(5, 20, 0, ScreenStaticBuffer); // Draw the cursor if (NumFilesInDir) DEMOPuts(5, (s16)(20+((Current+4)*8)), 0, "*"); // Draw the dynamic logged text (add an extra blank line on screen) DEMOPuts(5, (s16)(20+((NumFilesInDir+4+1)*8)), 0, ScreenDynamicBuffer); } static void DrawStatic(char* msg, ...) { char buf[SCREEN_LINE_SIZE]; u32 len; va_list marker; va_start(marker, msg); vsnprintf(buf, SCREEN_LINE_SIZE, msg, marker); // should check later for truncation len = strlen(buf)+1; // including '\0' // Print it to serial output as well OSReport(buf); fflush(stdout); // Count the number of newlines // Only supports 1 newline per call to DrawStatic for now ScreenStaticNewlineCount++; if ((ScreenStatic + len) >= (ScreenStaticBuffer + SCREEN_LOG_SIZE) || ScreenStaticNewlineCount > SCREEN_LOG_NEWLINES) { // Scroll back to the top of the log ScreenStatic = ScreenStaticBuffer; ScreenStaticNewlineCount = 0; } strncpy((void*) ScreenStatic, (void*)buf, len); ScreenStatic += len-1; // move to '\0' va_end(marker); } static void DrawDynamic(char* msg, ...) { char buf[SCREEN_LINE_SIZE]; char *bufPtr = buf; u32 len; va_list marker; va_start(marker, msg); vsnprintf(buf, SCREEN_LINE_SIZE, msg, marker); // should check later for truncation len = strlen(buf)+1; // including '\0' // Print it to serial output as well OSReport(buf); fflush(stdout); if ((ScreenDynamic + len) < (ScreenDynamicBuffer + SCREEN_LOG_SIZE)) { // Copy only if we have space // We only support \b if it is the first control character in the string if ((buf[0] == '\b') && (ScreenDynamic > ScreenDynamicBuffer)) { ScreenDynamic--; bufPtr++; len--; } strncpy((void*) ScreenDynamic, (void*)bufPtr, len); ScreenDynamic += len-1; // move to '\0' } va_end(marker); } static void ResetStaticText() { u32 i; NANDStatus stat; char permString[8]; char* fileName; ScreenStatic = ScreenStaticBuffer; *ScreenStatic = '\0'; ScreenStaticNewlineCount = 0; // Draw the header OSReport("-------------------------------------\n"); DrawStatic( "Game Save Demo\n"); DrawStatic( "Game Save Directory:\n"); DrawStatic( " %s\n\n", GameSaveDirPath); // If we change the number of newlines, need to change parameters in DrawAllTextToScreen // Currently there are 4 lines drawn to the screen (including one blank) // Draw the files in the directory fileName = GameSaveDirList; for (i = 0; i < NumFilesInDir; i++) { NANDGetStatus(fileName, &stat); GetPermString(stat.permission, permString); DrawStatic(" %s %s\n", permString, fileName); // Leave first character blank for cursor fileName = GetNextName(fileName); // Go to next name in GameSaveDirList } DrawStatic("\n"); } static void ResetDynamicText() { ScreenDynamic = ScreenDynamicBuffer; *ScreenDynamic = '\0'; }