diff --git a/arm9/source/display.cpp b/arm9/source/display.cpp index be9687d..7c77c8d 100644 --- a/arm9/source/display.cpp +++ b/arm9/source/display.cpp @@ -38,6 +38,7 @@ #include "auxspi_core.cpp" #include "globals.h" +#include "strings.h" PrintConsole upperScreen; PrintConsole lowerScreen; @@ -59,7 +60,7 @@ void displayInit() consoleInit(&lowerScreen, 3,BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true); consoleSelect(&upperScreen); - iprintf("\n\n\n\n\nDS savegame manager\nVersion 0.2.3 Beta\nBy Pokedoc"); + iprintf("\n\n\n\n\nDS savegame manager\nVersion 0.2.4 Beta\nBy Pokedoc"); displayPrintState("Press (B) to continue"); while (!(keysCurrent() & KEY_B)); @@ -134,7 +135,7 @@ void displayPrintUpper() sprintf(&name[0], "GBA"); break; case 2: - sprintf(&name[0], "3-in-1"); + sprintf(&name[0], "3in1"); break; case 3: sprintf(&name[0], "DSi/SD"); @@ -338,6 +339,35 @@ void displayMessage(const char *msg) iprintf("%s", msg); } +void displayMessageA(int id) +{ + displayMessage(stringsGetMessageString(id)); +} + +void displayMessage2(const char *msg, bool warn) +{ + consoleSelect(&lowerScreen); + consoleSetWindow(&lowerScreen, 0, 0, 32, 24); + consoleClear(); + + if (warn) { + iprintf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + for (int i = 0; i < 22; i++) + iprintf("! !"); + iprintf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + } + + consoleSetWindow(&lowerScreen, 2, 2, 28, 20); + consoleClear(); + + iprintf("%s", msg); +} + +void displayMessage2A(int id, bool warn) +{ + displayMessage2(stringsGetMessageString(id), warn); +} + void displayPrintState(const char *txt) { swiWaitForVBlank(); diff --git a/arm9/source/display.h b/arm9/source/display.h index 78bcd8c..caad1eb 100644 --- a/arm9/source/display.h +++ b/arm9/source/display.h @@ -38,7 +38,13 @@ void displayInit(); void displayPrintUpper(); void displayPrintLower(); -void displayMessage(const char *msg); +void displayMessage(const char *msg);// upper screen +void displayMessage2(const char *msg, bool warn); // lower screen + +// ftplib includes this file, and it is pure c, so no operator overloading for now. +void displayMessageA(int id);// upper screen, string table version +void displayMessage2A(int id, bool warn); // lower screen, string table version + void displayProgressBar(int cur, int max0); void displayPrintState(const char *txt); diff --git a/arm9/source/dsCard.cpp b/arm9/source/dsCard.cpp index 45b620a..c0d6b41 100644 --- a/arm9/source/dsCard.cpp +++ b/arm9/source/dsCard.cpp @@ -103,7 +103,7 @@ void SetSerialMode() } uint32 ReadNorFlashID() { - // This function was damaged on the origonal sample, "ID mode" was never left. It is fixed now. + // This function was damaged on the original sample, "ID mode" was never left. It is fixed now. vuint16 id1,id2,id3,id4; ID=0; //check intel 512M 3in1 card diff --git a/arm9/source/ftplib.c b/arm9/source/ftplib.c index d7a9609..25a64ad 100644 --- a/arm9/source/ftplib.c +++ b/arm9/source/ftplib.c @@ -75,7 +75,8 @@ #endif #include "display.h" -#define perror iprintf +//#define perror iprintf +#define perror // struct NetBuf { char *cput,*cget; diff --git a/arm9/source/hardware.cpp b/arm9/source/hardware.cpp index 377ecbe..afdcd6f 100644 --- a/arm9/source/hardware.cpp +++ b/arm9/source/hardware.cpp @@ -46,6 +46,7 @@ #include "fileselect.h" #include "auxspi.h" +#include "strings.h" using namespace std; @@ -68,7 +69,7 @@ bool swap_cart() nds.gameTitle[0] = 0; while (!nds.gameTitle[0]) { - displayMessage("Please take out Slot 1 flash\ncart and insert a game\n\nPress A when done."); + displayMessage2A(STR_HW_SWAP_CARD, false); bool swap = false; while (!swap) { @@ -125,7 +126,7 @@ void hwFormatNor(uint32 page, uint32 count) { uint32 ime = hwGrab3in1(); SetSerialMode(); - displayPrintState("Formating NOR memory"); + //displayPrintState("Formating NOR memory"); displayProgressBar(0, count); for (uint32 i = page; i < page+count; i++) { Block_Erase(i << 18); @@ -162,12 +163,10 @@ void hwBackup3in1() int size_blocks = 1 << max(0, (int8(size) - 18)); // ... in units of 0x40000 bytes - that's 256 kB uint8 type = auxspi_save_type(); - displayPrintState("Format NOR"); - hwFormatNor(1, size_blocks); + displayMessage2A(STR_HW_3IN1_FORMAT_NOR, false); + hwFormatNor(0, size_blocks+1); - // Dump save and write it to NOR - displayPrintState("Writing save to NOR"); - // TODO: test smaller write size! + displayMessage2A(STR_HW_3IN1_WRITE_NOR, false); if (size < 15) size_blocks = 1; else @@ -178,7 +177,6 @@ void hwBackup3in1() for (int i = 0; i < size_blocks; i++) { displayProgressBar(i+1, size_blocks); auxspi_read_data(i << 15, data, LEN, type); - // TODO: pull out "setserialmode" uint32 ime = hwGrab3in1(); SetSerialMode(); WriteNorFlash((i << 15) + pitch, data, LEN); @@ -201,6 +199,9 @@ void hwBackup3in1() // Write a flag to tell the app what happens on restart. // This is necessary, since some DLDI drivers cease working after swapping a card. + // Fix for dead batteries: don't write "reboot" values to SRAM, but to NOR. + displayMessage2A(STR_HW_3IN1_PREPARE_REBOOT, false); + // sNDSHeader nds; cardReadHeader((u8*)&nds); // on a Cyclops Evolution, this call *will* mess up your DLDI driver! dsCardData data2; @@ -209,12 +210,23 @@ void hwBackup3in1() data2.data[1] = 0; data2.data[2] = size; data2.data[3] = 0xffff00ff; + // We need to write the reboot flags to NOR, and we can only write in 32kB-blocks, at aligned offsets. + // Therefore we prepare a bigger block and copy lots of data now. memcpy(&data2.name[0], &nds.gameTitle[0], 12); - WriteSram(0x0a000000, (u8*)&data2, sizeof(data2)); - char txt[128]; - sprintf(txt, "%.12s", &nds.gameTitle[0]); + memset(data, 0, 0x8000); + memcpy(&data[0x1000], (u8*)&data2, sizeof(data2)); + uint32 ime = hwGrab3in1(); + WriteNorFlash(0, data, 0x8000); // this should work - but it does not! + hwRelease3in1(ime); - displayMessage("Save has been written to 3in1.\nPlease power off and restart\nthis tool."); + displayMessage2A(STR_HW_3IN1_PLEASE_REBOOT, false); + + ime = hwGrab3in1(); + ReadNorFlash((u8*)&data2, 0x1000, sizeof(data2)); // this should work - but it does not! + hwRelease3in1(ime); + char txt[128]; + sprintf(txt, "%.12s", &data2.name[0]); + displayMessage(txt); while(1) {}; } @@ -223,27 +235,33 @@ void hwDump3in1(uint32 size, const char *gamename) { u32 size_blocks = 1 << max(0, (uint8(size) - 18)); + displayMessageA(STR_HW_SELECT_FILE_OW); + char path[256]; char fname[256] = ""; fileSelect("/", path, fname, 0, true, false); if (!fname[0]) { uint32 cnt = 0; sprintf(fname, "/%s.%i.sav", gamename, cnt); + char txt[256]; + sprintf(txt, stringsGetMessageString(STR_HW_SEEK_UNUSED_FNAME), fname); + displayMessage2(txt, false); while (fileExists(fname)) { if (cnt < 65536) cnt++; else { - displayMessage("Unable to get a filename!\nThis means that you have more\nthan 65536 saves! (wow!)\nOops!"); + displayMessage2A(STR_ERR_NO_FNAME, true); while(1); } - sprintf(fname, "/%s.%i.sav", gamename, cnt); + sprintf(fname, "%s.%i.sav", gamename, cnt); } } - char fullpath[512]; + char fullpath[256]; sprintf(fullpath, "%s/%s", path, fname); - displayMessage(fname); + char txt[512]; + sprintf(txt, stringsGetMessageString(STR_HW_3IN1_DUMP), fullpath); + displayMessage2(txt, false); - displayPrintState("Writing save to flash card"); FILE *file = fopen(fullpath, "wb"); if (size < 15) size_blocks = 1; @@ -259,7 +277,8 @@ void hwDump3in1(uint32 size, const char *gamename) } fclose(file); - displayPrintState("Done! Please power off..."); + sprintf(txt, stringsGetMessageString(STR_HW_3IN1_DONE_DUMP), fullpath); + displayMessage2(txt, false); while (1); } @@ -268,24 +287,26 @@ void hwRestore3in1() char path[256]; char fname[256]; memset(fname, 0, 256); - displayMessage("Please select a .sav file."); + displayMessageA(STR_HW_SELECT_FILE); // lower screen is used for file browser fileSelect("/", path, fname, 0, false, false); - displayMessage(""); char msg[256]; - sprintf(msg, "%s%s", path, fname); + sprintf(msg, "%s/%s", path, fname); + displayMessage(msg); FILE *file = fopen(msg, "rb"); uint32 size0 = fileSize(msg); + uint8 size = 1; + // crude log2 function while (size0 > (1 << size)) { size++; } int size_blocks = 1 << max(0, int8(size) - 18); // ... in units of 0x40000 bytes - that's 256 kB + displayMessage2A(STR_HW_3IN1_FORMAT_NOR, false); hwFormatNor(1, size_blocks); - displayPrintState("Writing save to NOR"); - // Read save and write it to NOR + displayMessage2A(STR_HW_3IN1_WRITE_NOR, false); if (size < 15) size_blocks = 1; else @@ -312,6 +333,7 @@ void hwRestore3in1() while(1); } } + displayProgressBar(1, 1); fclose(file); free(test); @@ -323,6 +345,7 @@ void hwRestore3in1_b(uint32 size_file) // Third, swap in a new game uint32 size = auxspi_save_size_log_2(); while ((size_file < size) || flash_card) { + // TODO: make THIS error message more meaningful! displayPrintState("File too small or no save chip!"); swap_cart(); size = auxspi_save_size_log_2(); @@ -331,12 +354,12 @@ void hwRestore3in1_b(uint32 size_file) uint8 type = auxspi_save_type(); if (type == 3) { - displayPrintState("Formating Flash chip"); + displayMessage2A(STR_HW_FORMAT_GAME, false); auxspi_erase(); } // And finally, write the save! - displayPrintState("Restoring save"); + displayMessage2A(STR_HW_WRITE_GAME, false); u32 LEN = 0, num_blocks = 0, shift = 0; switch (type) { case 1: @@ -364,8 +387,7 @@ void hwRestore3in1_b(uint32 size_file) } displayProgressBar(1, 1); - - displayPrintState("Done! Please turn your DS off..."); + displayMessage2A(STR_HW_3IN1_RESTORE, false); while (1) {}; } @@ -373,10 +395,10 @@ void hwRestore3in1_b(uint32 size_file) // ------------------------------------------------------------ void hwErase() { - displayMessage("This will WIPE OUT your entire\nsave! ARE YOU SURE?\n\nPress R+up+Y to confim!"); + displayMessage2A(STR_HW_WARN_DELETE, true); while (!(keysCurrent() & (KEY_UP | KEY_R | KEY_Y))) {}; auxspi_erase(); - displayMessage("Done! Your save is gone!"); + displayMessage2A(STR_HW_DID_DELETE, true); while(1); } @@ -431,7 +453,7 @@ void hwBackupFTP() char fdir[256] = ""; char fname[256] =""; memset(fname, 0, 256); - displayMessage("Please select a file name to\noverwrite, or press L+R in a\nfolder to create a new file."); + displayMessageA(STR_HW_SELECT_FILE_OW); displayPrintState("FTP: dir"); fileSelect("/", fdir, fname, buf, true, false); bool newfile; @@ -443,17 +465,19 @@ void hwBackupFTP() // Third: get a new target filename FtpChdir(fdir, buf); if (!fname[0]) { - displayMessage("Looking for an unused filename"); sNDSHeader nds; cardReadHeader((u8*)&nds); uint32 cnt = 0; int tsize = 0; sprintf(fname, "%.12s.%i.sav", nds.gameTitle, cnt); while (FtpSize(fname, &tsize, FTPLIB_IMAGE, buf) != 0) { + char txt[256]; + sprintf(txt, stringsGetMessageString(STR_HW_SEEK_UNUSED_FNAME), fname); + displayMessage2(txt, false); if (cnt < 65536) cnt++; else { - displayMessage("Unable to get a filename!\nThis means that you have more\nthan 65536 saves! (wow!)\nOops!"); + displayMessage2A(STR_ERR_NO_FNAME, true); while(1); } sprintf(fname, "%.12s.%i.sav", nds.gameTitle, cnt); @@ -473,29 +497,30 @@ void hwBackupFTP() displayProgressBar(i+1, (size_blocks << 6)); auxspi_read_data(i << 9, (u8*)&data[0], length, type); int out = 0; + int debug = 0; while (out < 512) { out += FtpWrite((u8*)&data[out], 512-out, ndata); - if (out < 512) - displayPrintState("!!!"); - else + if (out < 512) { + displayPrintState(stringsGetMessageString(STR_HW_FTP_SLOW)); + /* + debug++; + if (debug >= 2048) { + debug = 0; + displayMessage2("Unable to reach FTP server. Make sure that you have WRITE ACCESS!" + } + */ + } else { displayPrintState(""); + } } - /* - if ((out = FtpWrite((u8*)&data[0], length, ndata)) < length) { - char dings[512]; - sprintf(dings, "Error: wrote %i, got %i", length, out); - displayPrintState(dings); - while(1); - } - */ } FtpClose(ndata); FtpQuit(ndata); Wifi_DisconnectAP(); - displayMessage("Done! Please turn off your DS."); - while(1); + //displayMessage("Done! Please turn off your DS."); + //while(1); } void hwRestoreFTP() @@ -595,7 +620,7 @@ void hwRestoreFTP() while (in < 512) { in += FtpRead((u8*)&pdata[in], 512-in, ndata); if (in < 512) - displayPrintState("!!!"); + displayPrintState(stringsGetMessageString(STR_HW_FTP_SLOW)); else displayPrintState(""); } @@ -613,7 +638,6 @@ void hwRestoreFTP() //char bums[512]; //sprintf(bums, "addr = %x", (i << 9)+(j << shift)); displayPrintState("Writing save (insecure!)"); - //displayPrintState(bums); for (int j = 0; j < 1 << (9-shift); j++) { auxspi_write_data((i << 9)+(j << shift), ((u8*)pdata)+(j< 8*0) && (touchXY.py < 8*8)) { hwBackupFTP(); + displayPrintLower(); } // restore if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) { hwRestoreFTP(); + displayPrintLower(); } // erase @@ -251,6 +250,7 @@ void mode_wifi() swap_cart(); displayPrintUpper(); hwErase(); + displayPrintLower(); } } } @@ -269,16 +269,24 @@ int main(int argc, char* argv[]) while (1) {}; } + // test if our flash card supports argv at all. some R4 clones seem to be very picky! + // (untested due to lack of an R4 myself, but I hope it works) + bool has_argv = false; + if (argc) + has_argv = true; + // load ini file... ini_fd_t ini = 0; char inipath[256]; - if (argv[0]) { - char *last = strrchr(argv[0], '/'); - int len = (last - argv[0])+1; - strncpy(bootdir, argv[0], len); - sprintf(inipath, "%s/savegame_manager.ini", bootdir); - if (fileExists(inipath)) - ini = ini_open(inipath, "r", ""); + if (has_argv) { + if (argv[0]) { + char *last = strrchr(argv[0], '/'); + int len = (last - argv[0])+1; + strncpy(bootdir, argv[0], len); + sprintf(inipath, "%s/savegame_manager.ini", bootdir); + if (fileExists(inipath)) + ini = ini_open(inipath, "r", ""); + } } if (!ini) { sprintf(inipath, "/savegame_manager.ini"); @@ -308,6 +316,9 @@ int main(int argc, char* argv[]) // delete temp file (which is a remnant of inilib) remove("/tmpfile"); + // load strings + stringsLoadFile(0); + // Identify hardware and branch to corresponding mode //displayMessage("Identifying hardware..."); diff --git a/arm9/source/strings.cpp b/arm9/source/strings.cpp new file mode 100644 index 0000000..e99e856 --- /dev/null +++ b/arm9/source/strings.cpp @@ -0,0 +1,109 @@ +/* + * savegame_manager: a tool to backup and restore savegames from Nintendo + * DS cartridges. Nintendo DS and all derivative names are trademarks + * by Nintendo. EZFlash 3-in-1 is a trademark by EZFlash. + * + * strings.cpp: Localised message strings + * + * Copyright (C) Pokedoc (2010) + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + This file implements the functions and data structures required for localised messages. + */ + + +#include "strings.h" +#include "libini.h" +#include +#include "fileselect.h" + +using namespace std; + +// a global string array +char **message_strings; +char txt[512]; + +#define ADD_STRING(id,text) strcpy(txt,text);\ + message_strings[id] = new char[strlen(txt)+1];\ + memcpy(message_strings[id], txt, strlen(txt)+1); + + +bool stringsLoadFile(const char *fname) +{ +#define NOT_FINISHED + +#ifndef NOT_FINISHED + // load ini file... + ini_fd_t ini = 0; + if (fileExists(fname)) + ini = ini_open(fname, "r", ""); + else + return false; +#endif + + message_strings = new char*[STR_LAST]; +#ifndef NOT_FINISHED + ini_locateHeading(ini, ""); + char txt[512]; + for (int i = 0; i < STR_LAST; i++) { + sprintf(txt, "%i", i); + ini_locateKey(ini, txt); + ini_readString(ini, txt, 512); + message_strings[i] = new char[strlen(txt)+1]; + memcpy(message_strings[i], txt, strlen(txt)+1); + } + + // delete temp file (which is a remnant of inilib) + remove("/tmpfile"); +#else + ADD_STRING(STR_HW_SWAP_CARD,"Please take out Slot 1\nflash card and insert a game\n\nPress A when done."); + // + ADD_STRING(STR_HW_SELECT_FILE,"Please select a .sav file."); + ADD_STRING(STR_HW_SELECT_FILE_OW,"Please select a file to\noverwrite, or press L+R in afolder to create a new file."); + ADD_STRING(STR_HW_SEEK_UNUSED_FNAME,"Please wait... searching for\nan unused filename.\n\nTrying: %s"); + ADD_STRING(STR_ERR_NO_FNAME,"ERROR: Unable to get an\nunused nfilename! This means that you have more than\n65536 saves!\n\n(wow!)"); + // + ADD_STRING(STR_HW_FORMAT_GAME,"Preparing to write to your\ngame. Please wait..."); + ADD_STRING(STR_HW_WRITE_GAME,"Writing the save to your\ngame. Please wait..."); + // + ADD_STRING(STR_HW_3IN1_FORMAT_NOR,"Preparing to write to the\n3in1. Please wait..."); + ADD_STRING(STR_HW_3IN1_WRITE_NOR,"Writing save data to the\n3in1. Please wait..."); + ADD_STRING(STR_HW_3IN1_PREPARE_REBOOT,"Preparing reboot..."); + ADD_STRING(STR_HW_3IN1_PLEASE_REBOOT,"Save has been written to\nthe 3in1. Please power off\nand restart this tool."); + ADD_STRING(STR_HW_3IN1_CLEAR_FLAG,"Preparing to dump your\nsave... Please wait..."); + ADD_STRING(STR_HW_3IN1_DUMP,"Dumping the save from the\n3in1 to your flash card.\nFilename:\n%s"); + ADD_STRING(STR_HW_3IN1_DONE_DUMP,"Done. Your game save has\nbeen dumped using your\n3in1. Filename:\n%s\n\nPlease restart your DS."); + ADD_STRING(STR_HW_3IN1_RESTORE,"Done. Your game save has\nbeen restored using your\n3in1.\n\nPlease restart your DS."); + // + ADD_STRING(STR_HW_FTP_SLOW,"FTP is slow, please wait..."); + // + ADD_STRING(STR_HW_WARN_DELETE,"This will WIPE OUT your\nentire save! ARE YOU SURE?\n\nPress R+up+Y to confim!"); + ADD_STRING(STR_HW_DID_DELETE,"Done. Your game save has\nbeen PERMANENTLY deleted.\n\nPlease restart your DS."); + // +#endif + + return true; +} + +const char *stringsGetMessageString(int id) +{ + if (id > STR_LAST) + return 0; + else + return message_strings[id]; +} diff --git a/arm9/source/strings.h b/arm9/source/strings.h new file mode 100644 index 0000000..56b9598 --- /dev/null +++ b/arm9/source/strings.h @@ -0,0 +1,66 @@ +/* + * savegame_manager: a tool to backup and restore savegames from Nintendo + * DS cartridges. Nintendo DS and all derivative names are trademarks + * by Nintendo. EZFlash 3-in-1 is a trademark by EZFlash. + * + * strings.h: Header for strings.cpp + * + * Copyright (C) Pokedoc (2010) + */ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + This files is used to allow translations of the program. It defines many + string IDs which may be used to overwrite default messages with an INI file. + */ + +#ifndef STRINGS_H +#define STRINGS_H + +enum { + STR_TITLE_MSG, + // + STR_HW_SWAP_CARD, + // + STR_HW_SELECT_FILE, + STR_HW_SELECT_FILE_OW, + STR_HW_SEEK_UNUSED_FNAME, + STR_ERR_NO_FNAME, + // + STR_HW_FORMAT_GAME, + STR_HW_WRITE_GAME, + // + STR_HW_3IN1_FORMAT_NOR, + STR_HW_3IN1_WRITE_NOR, + STR_HW_3IN1_PREPARE_REBOOT, + STR_HW_3IN1_PLEASE_REBOOT, + STR_HW_3IN1_CLEAR_FLAG, + STR_HW_3IN1_DUMP, + STR_HW_3IN1_DONE_DUMP, + STR_HW_3IN1_RESTORE, + // + STR_HW_FTP_SLOW, + // + STR_HW_WARN_DELETE, + STR_HW_DID_DELETE, + // + STR_LAST +}; + +bool stringsLoadFile(const char *fname); +const char *stringsGetMessageString(int id = 0); + +#endif // STRINGS_H diff --git a/savegame_manager.odt b/savegame_manager.odt new file mode 100644 index 0000000..25d10c6 Binary files /dev/null and b/savegame_manager.odt differ diff --git a/savegame_manager.pnproj b/savegame_manager.pnproj index ce5efc0..534ed6c 100644 --- a/savegame_manager.pnproj +++ b/savegame_manager.pnproj @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/savegame_manager.pnps b/savegame_manager.pnps index daaa261..60a5214 100644 --- a/savegame_manager.pnps +++ b/savegame_manager.pnps @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file