diff --git a/stepmania/Themes/default/Other/Profile Stats.xsl b/stepmania/Themes/default/Other/Profile Stats.xsl index b7a4a19adf..61ec0272ca 100644 --- a/stepmania/Themes/default/Other/Profile Stats.xsl +++ b/stepmania/Themes/default/Other/Profile Stats.xsl @@ -48,7 +48,7 @@ - This file contains all your game profile data. Please read the instructions below before modifying or moving any files on your memory card. Modifying files may result in irreversible loss of your data. + This section explains all the files saved to your memory card. Please read the instructions below before modifying any files on your memory card. Modifying files may result in irreversible loss of your data. @@ -73,10 +73,27 @@ + + LastGood/ + + This directory contains a backup of your last Stats.xml and signatures that were successfully loaded. + + More Info + + The stats on your memory card may fail to load because: + + If your saved statistics fail to load, your can restore from from the "last good" data. Copy all 3 files in the files LastGood directory into the main profile directory (one level up from LastGood). + + + + Screenshots/ - All screenshots that you take are saved in this directory. + All in-game screenshots that you take are saved in this directory. More Info @@ -91,7 +108,7 @@ Catalog.xml - Contains game data used by Stats.xml. + Contains game data used by Stats.xsl for displaying a report of your stats. DontShare.sig diff --git a/stepmania/src/Profile.cpp b/stepmania/src/Profile.cpp index c5fc10ad15..8e521633ba 100644 --- a/stepmania/src/Profile.cpp +++ b/stepmania/src/Profile.cpp @@ -575,81 +575,81 @@ bool Profile::LoadAllFromDir( CString sDir, bool bRequireSignature ) InitAll(); + // Not critical if this fails LoadEditableDataFromDir( sDir ); - // Read stats.xml - FOR_ONCE + // Check for the existance of stats.xml + CString fn = sDir + STATS_XML; + if( !IsAFile(fn) ) + return true; // This is a common case and not a load error, so return success. + + // + // Don't unreasonably large stats.xml files. + // + if( !IsMachine() ) // only check stats coming from the player { - CString fn = sDir + STATS_XML; - if( !IsAFile(fn) ) - break; - - // - // Don't unreasonably large stats.xml files. - // - if( !IsMachine() ) // only check stats coming from the player + int iBytes = FILEMAN->GetFileSizeInBytes( fn ); + if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) { - int iBytes = FILEMAN->GetFileSizeInBytes( fn ); - if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) - { - LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); - break; - } + LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); + return false; // error } + } - if( bRequireSignature ) + if( bRequireSignature ) + { + CString sStatsXmlSigFile = fn+SIGNATURE_APPEND; + CString sDontShareFile = sDir + DONT_SHARE_SIG; + + LOG->Trace( "Verifying don't share signature" ); + // verify the stats.xml signature with the "don't share" file + if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) { - - CString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - CString sDontShareFile = sDir + DONT_SHARE_SIG; - - LOG->Trace( "Verifying don't share signature" ); - // verify the stats.xml signature with the "don't share" file - if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) - { - LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); - break; - } - LOG->Trace( "Done." ); - - // verify stats.xml - LOG->Trace( "Verifying stats.xml signature" ); - if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) - { - LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); - break; - } - LOG->Trace( "Done." ); - } - - LOG->Trace( "Loading %s", fn.c_str() ); - XNode xml; - if( !xml.LoadFromFile( fn ) ) - { - LOG->Warn( "Couldn't open file '%s' for reading.", fn.c_str() ); - break; + LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); + return false; // error } LOG->Trace( "Done." ); - /* The placeholder stats.xml file has an tag. Don't load it, but don't - * warn about it. */ - if( xml.name == "html" ) - break; - - if( xml.name != "Stats" ) - WARN_AND_BREAK_M( xml.name ); - - LOAD_NODE( GeneralData ); - LOAD_NODE( SongScores ); - LOAD_NODE( CourseScores ); - LOAD_NODE( CategoryScores ); - LOAD_NODE( ScreenshotData ); - LOAD_NODE( CalorieData ); - LOAD_NODE( RecentSongScores ); - LOAD_NODE( RecentCourseScores ); + // verify stats.xml + LOG->Trace( "Verifying stats.xml signature" ); + if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) + { + LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); + return false; // error + } + LOG->Trace( "Done." ); } + + LOG->Trace( "Loading %s", fn.c_str() ); + XNode xml; + if( !xml.LoadFromFile( fn ) ) + { + LOG->Warn( "Couldn't open file '%s' for reading.", fn.c_str() ); + return false; // error + } + LOG->Trace( "Done." ); + + /* The placeholder stats.xml file has an tag. Don't load it, but don't + * warn about it. */ + if( xml.name == "html" ) + return true; // success + + if( xml.name != "Stats" ) + { + WARN_M( xml.name ); + return false; // error + } + + LOAD_NODE( GeneralData ); + LOAD_NODE( SongScores ); + LOAD_NODE( CourseScores ); + LOAD_NODE( CategoryScores ); + LOAD_NODE( ScreenshotData ); + LOAD_NODE( CalorieData ); + LOAD_NODE( RecentSongScores ); + LOAD_NODE( RecentCourseScores ); - return true; // FIXME? Investigate what happens if we always return true. + return true; // success } bool Profile::SaveAllToDir( CString sDir, bool bSignData ) const @@ -657,9 +657,22 @@ bool Profile::SaveAllToDir( CString sDir, bool bSignData ) const m_sLastPlayedMachineGuid = PROFILEMAN->GetMachineProfile()->m_sGuid; m_LastPlayedDate = DateTime::GetNowDate(); - // Save editable.xml + // Save editable.ini SaveEditableDataToDir( sDir ); + bool bSaved = SaveStatsXmlToDir( sDir, bSignData ); + + SaveStatsWebPageToDir( sDir ); + + // Empty directories if none exist. + FILEMAN->CreateDir( sDir + EDITS_SUBDIR ); + FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR ); + + return bSaved; +} + +bool Profile::SaveStatsXmlToDir( CString sDir, bool bSignData ) const +{ // Save stats.xml CString fn = sDir + STATS_XML; @@ -697,12 +710,6 @@ bool Profile::SaveAllToDir( CString sDir, bool bSignData ) const CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); } - SaveStatsWebPageToDir( sDir ); - - // Empty directories if none exist. - FILEMAN->CreateDir( sDir + EDITS_SUBDIR ); - FILEMAN->CreateDir( sDir + SCREENSHOTS_SUBDIR ); - return bSaved; } diff --git a/stepmania/src/Profile.h b/stepmania/src/Profile.h index 9ea8d1ec02..7bbab43550 100644 --- a/stepmania/src/Profile.h +++ b/stepmania/src/Profile.h @@ -260,7 +260,7 @@ public: // // Loading and saving // - bool LoadAllFromDir( CString sDir, bool bRequireSignature ); // return false on error + bool LoadAllFromDir( CString sDir, bool bRequireSignature ); // return false on a tamper or corruption error bool SaveAllToDir( CString sDir, bool bSignData ) const; void LoadEditableDataFromDir( CString sDir ); @@ -274,6 +274,7 @@ public: void LoadRecentCourseScoresFromNode( const XNode* pNode ); void SaveEditableDataToDir( CString sDir ) const; + bool SaveStatsXmlToDir( CString sDir, bool bSignData ) const; XNode* SaveGeneralDataCreateNode() const; XNode* SaveSongScoresCreateNode() const; XNode* SaveCourseScoresCreateNode() const; diff --git a/stepmania/src/ProfileManager.cpp b/stepmania/src/ProfileManager.cpp index 96b656e81b..fd8258d38f 100644 --- a/stepmania/src/ProfileManager.cpp +++ b/stepmania/src/ProfileManager.cpp @@ -41,7 +41,10 @@ ProfileManager::~ProfileManager() void ProfileManager::Init() { FOREACH_PlayerNumber( p ) + { m_bWasLoadedFromMemoryCard[p] = false; + m_bLastLoadWasTamperedOrCorrupt[p] = false; + } LoadMachineProfile(); } @@ -79,8 +82,18 @@ bool ProfileManager::LoadProfile( PlayerNumber pn, CString sProfileDir, bool bIs m_sProfileDir[pn] = sProfileDir; m_bWasLoadedFromMemoryCard[pn] = bIsMemCard; + bool bSuccess = m_Profile[pn].LoadAllFromDir( m_sProfileDir[pn], PREFSMAN->m_bSignProfileData ); - m_Profile[pn].LoadAllFromDir( m_sProfileDir[pn], PREFSMAN->m_bSignProfileData ); + m_bLastLoadWasTamperedOrCorrupt[pn] = !bSuccess; + + // Save a backup of the profile now that we've loaded it and know it's good. + // This should be reasonably fast because we're only saving Stats.xml and + // signatures - not all of the files in the Profile. + if( bSuccess ) + { + CString sBackupDir = m_sProfileDir[pn] + "LastGood/"; + m_Profile[pn].SaveStatsXmlToDir( sBackupDir, PREFSMAN->m_bSignProfileData ); + } if( bIsMemCard ) MEMCARDMAN->UnPauseMountingThread(); @@ -206,6 +219,7 @@ void ProfileManager::UnloadProfile( PlayerNumber pn ) { m_sProfileDir[pn] = ""; m_bWasLoadedFromMemoryCard[pn] = false; + m_bLastLoadWasTamperedOrCorrupt[pn] = false; m_Profile[pn].InitAll(); } @@ -320,6 +334,11 @@ bool ProfileManager::ProfileWasLoadedFromMemoryCard( PlayerNumber pn ) const return GetProfile(pn) && m_bWasLoadedFromMemoryCard[pn]; } +bool ProfileManager::LastLoadWasTamperedOrCorrupt( PlayerNumber pn ) const +{ + return GetProfile(pn) && m_bLastLoadWasTamperedOrCorrupt[pn]; +} + CString ProfileManager::GetProfileDir( ProfileSlot slot ) const { switch( slot ) diff --git a/stepmania/src/ProfileManager.h b/stepmania/src/ProfileManager.h index 9d22fe808d..a709ef779e 100644 --- a/stepmania/src/ProfileManager.h +++ b/stepmania/src/ProfileManager.h @@ -57,6 +57,7 @@ public: CString GetPlayerName( PlayerNumber pn ) const; bool ProfileWasLoadedFromMemoryCard( PlayerNumber pn ) const; + bool LastLoadWasTamperedOrCorrupt( PlayerNumber pn ) const; // @@ -81,7 +82,6 @@ public: void IncrementCategoryPlayCount( StepsType st, RankingCategory rc, PlayerNumber pn ); -// void ReadSM300NoteScores(); private: bool CreateMemoryCardProfile( PlayerNumber pn ); @@ -93,6 +93,7 @@ private: CString m_sProfileDir[NUM_PLAYERS]; bool m_bWasLoadedFromMemoryCard[NUM_PLAYERS]; + bool m_bLastLoadWasTamperedOrCorrupt[NUM_PLAYERS]; // true if Stats.xml was present, but failed to load (probably because of a signature failure) // actual loaded profile data Profile m_Profile[NUM_PLAYERS]; diff --git a/stepmania/src/ScreenManager.cpp b/stepmania/src/ScreenManager.cpp index aa01f1e124..35358a5e6d 100644 --- a/stepmania/src/ScreenManager.cpp +++ b/stepmania/src/ScreenManager.cpp @@ -52,6 +52,7 @@ ScreenManager* SCREENMAN = NULL; // global and accessable from anywhere in our p #define CREDITS_FREE_PLAY THEME->GetMetric ("ScreenSystemLayer","CreditsFreePlay") #define CREDITS_CREDITS THEME->GetMetric ("ScreenSystemLayer","CreditsCredits") #define CREDITS_NOT_PRESENT THEME->GetMetric ("ScreenSystemLayer","CreditsNotPresent") +#define CREDITS_LOAD_FAILED THEME->GetMetric ("ScreenSystemLayer","CreditsLoadFailed") #define CREDITS_JOIN_ONLY THEME->GetMetricB("ScreenSystemLayer","CreditsJoinOnly") const int NUM_SKIPS_TO_SHOW = 5; @@ -209,7 +210,12 @@ void ScreenSystemLayer::RefreshCreditsMessages() sCredits = CREDITS_CARD_TOO_LATE; break; case MEMORY_CARD_STATE_READY: - if( !MEMCARDMAN->GetName(p).empty() ) + if( PROFILEMAN->LastLoadWasTamperedOrCorrupt(p) ) + sCredits = CREDITS_LOAD_FAILED; + // Prefer the name of the profile over the name of the card. + else if( pProfile ) + sCredits = pProfile->GetDisplayName(); + else if( !MEMCARDMAN->GetName(p).empty() ) sCredits = MEMCARDMAN->GetName(p); else sCredits = CREDITS_CARD_NO_NAME;