diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 9ccc9a77e4..3f0a170429 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1353,6 +1353,7 @@ + @@ -1362,6 +1363,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index dfa4404ba3..918eb6ad54 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -4014,6 +4014,9 @@ save yourself some time, copy this for undocumented things: Returns the number of times Song s has been played with the specified ProfileSlot. + + Returns the current stats prefix. + Returns true if player pn's profile is persistent. @@ -4044,6 +4047,9 @@ save yourself some time, copy this for undocumented things: Saves the profile for player pn. + + Sets the current stats prefix. The stats prefix is prepended to the Stats.xml file when loading or saving a profile. SetStatsPrefix will reload all profiles from the Stats.xml that has the given prefix. In general, score entries are the only thing not preserved when changing the stats prefix. Profile::HandleStatsPrefixChange in Profile.cpp lists the fields that are preserved. + diff --git a/src/Profile.cpp b/src/Profile.cpp index 20606e8e68..e24e0aadd2 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -1065,6 +1065,55 @@ void Profile::LoadCustomFunction( RString sDir ) LUA->Release(L); } + +void Profile::HandleStatsPrefixChange(RString dir, bool require_signature) +{ + // Temp variables to preserve stuff across the reload. + // Some stuff intentionally left out because the original reason for the + // stats prefix was to allow scores from different game types to coexist. + ProfileType type= m_Type; + int priority= m_ListPriority; + RString guid= m_sGuid; + map default_mods= m_sDefaultModifiers; + SortOrder sort_order= m_SortOrder; + Difficulty last_diff= m_LastDifficulty; + CourseDifficulty last_course_diff= m_LastCourseDifficulty; + StepsType last_stepstype= m_LastStepsType; + SongID last_song= m_lastSong; + CourseID last_course= m_lastCourse; + int total_sessions= m_iTotalSessions; + int total_session_seconds= m_iTotalSessionSeconds; + int total_gameplay_seconds= m_iTotalGameplaySeconds; + float total_calories_burned= m_fTotalCaloriesBurned; + LuaTable user_table= m_UserTable; + bool need_to_create_file= false; + if(IsAFile(dir + PROFILEMAN->GetStatsPrefix() + STATS_XML)) + { + LoadAllFromDir(dir, require_signature); + } + else + { + ClearStats(); + need_to_create_file= true; + } + m_Type= type; + m_ListPriority= priority; + m_sGuid= guid; + m_sDefaultModifiers= default_mods; + m_SortOrder= sort_order; + m_LastDifficulty= last_diff; + m_LastCourseDifficulty= last_course_diff; + m_LastStepsType= last_stepstype; + m_lastSong= last_song; + m_iTotalSessions= total_sessions; + m_iTotalGameplaySeconds= total_gameplay_seconds; + m_fTotalCaloriesBurned= total_calories_burned; + m_UserTable= user_table; + if(need_to_create_file) + { + SaveAllToDir(dir, require_signature); + } +} ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature ) { @@ -1078,82 +1127,7 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature // Not critical if this fails LoadEditableDataFromDir( sDir ); - // Check for the existance of stats.xml - RString fn = sDir + STATS_XML; - bool bCompressed = false; - if( !IsAFile(fn) ) - { - // Check for the existance of stats.xml.gz - fn = sDir + STATS_XML_GZ; - bCompressed = true; - if( !IsAFile(fn) ) - return ProfileLoadResult_FailedNoProfile; - } - - int iError; - auto_ptr pFile( FILEMAN->Open(fn, RageFile::READ, iError) ); - if( pFile.get() == NULL ) - { - LOG->Trace( "Error opening %s: %s", fn.c_str(), strerror(iError) ); - return ProfileLoadResult_FailedTampered; - } - - if( bCompressed ) - { - RString sError; - uint32_t iCRC32; - RageFileObjInflate *pInflate = GunzipFile( pFile.release(), sError, &iCRC32 ); - if( pInflate == NULL ) - { - LOG->Trace( "Error opening %s: %s", fn.c_str(), sError.c_str() ); - return ProfileLoadResult_FailedTampered; - } - - pFile.reset( pInflate ); - } - - // Don't load unreasonably large stats.xml files. - if( !IsMachine() ) // only check stats coming from the player - { - int iBytes = pFile->GetFileSize(); - if( iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES ) - { - LuaHelpers::ReportScriptErrorFmt( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); - return ProfileLoadResult_FailedTampered; - } - } - - if( bRequireSignature ) - { - RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - RString sDontShareFile = sDir + DONT_SHARE_SIG; - - LOG->Trace( "Verifying don't share signature \"%s\" against \"%s\"", sDontShareFile.c_str(), sStatsXmlSigFile.c_str() ); - // verify the stats.xml signature with the "don't share" file - if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) - { - LuaHelpers::ReportScriptErrorFmt( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); - return ProfileLoadResult_FailedTampered; - } - LOG->Trace( "Done." ); - - // verify stats.xml - LOG->Trace( "Verifying stats.xml signature" ); - if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) - { - LuaHelpers::ReportScriptErrorFmt( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); - return ProfileLoadResult_FailedTampered; - } - LOG->Trace( "Done." ); - } - - LOG->Trace( "Loading %s", fn.c_str() ); - XNode xml; - if( !XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get()) ) - return ProfileLoadResult_FailedTampered; - LOG->Trace( "Done." ); - - ProfileLoadResult ret = LoadStatsXmlFromNode(&xml); + ProfileLoadResult ret= LoadStatsFromDir(sDir, bRequireSignature); if (ret != ProfileLoadResult_Success) return ret; @@ -1162,6 +1136,89 @@ ProfileLoadResult Profile::LoadAllFromDir( RString sDir, bool bRequireSignature return ProfileLoadResult_Success; } +ProfileLoadResult Profile::LoadStatsFromDir(RString dir, bool require_signature) +{ + dir= dir + PROFILEMAN->GetStatsPrefix(); + // Check for the existance of stats.xml + RString fn = dir + STATS_XML; + bool compressed = false; + if(!IsAFile(fn)) + { + // Check for the existance of stats.xml.gz + fn = dir + STATS_XML_GZ; + compressed = true; + if(!IsAFile(fn)) + { + return ProfileLoadResult_FailedNoProfile; + } + } + + int iError; + auto_ptr pFile(FILEMAN->Open(fn, RageFile::READ, iError)); + if(pFile.get() == NULL) + { + LOG->Trace("Error opening %s: %s", fn.c_str(), strerror(iError)); + return ProfileLoadResult_FailedTampered; + } + + if(compressed) + { + RString sError; + uint32_t iCRC32; + RageFileObjInflate *pInflate = GunzipFile(pFile.release(), sError, &iCRC32); + if(pInflate == NULL) + { + LOG->Trace("Error opening %s: %s", fn.c_str(), sError.c_str()); + return ProfileLoadResult_FailedTampered; + } + + pFile.reset(pInflate); + } + + // Don't load unreasonably large stats.xml files. + if(!IsMachine()) // only check stats coming from the player + { + int iBytes = pFile->GetFileSize(); + if(iBytes > MAX_PLAYER_STATS_XML_SIZE_BYTES) + { + LuaHelpers::ReportScriptErrorFmt("The file '%s' is unreasonably large. It won't be loaded.", fn.c_str()); + return ProfileLoadResult_FailedTampered; + } + } + + if(require_signature) + { + RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; + RString sDontShareFile = dir + DONT_SHARE_SIG; + + LOG->Trace("Verifying don't share signature \"%s\" against \"%s\"", sDontShareFile.c_str(), sStatsXmlSigFile.c_str()); + // verify the stats.xml signature with the "don't share" file + if(!CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile)) + { + LuaHelpers::ReportScriptErrorFmt("The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str()); + return ProfileLoadResult_FailedTampered; + } + LOG->Trace("Done."); + + // verify stats.xml + LOG->Trace("Verifying stats.xml signature"); + if(!CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile)) + { + LuaHelpers::ReportScriptErrorFmt("The signature check for '%s' failed. Data will be ignored.", fn.c_str()); + return ProfileLoadResult_FailedTampered; + } + LOG->Trace("Done."); + } + + LOG->Trace("Loading %s", fn.c_str()); + XNode xml; + if(!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) + return ProfileLoadResult_FailedTampered; + LOG->Trace("Done."); + + return LoadStatsXmlFromNode(&xml); +} + void Profile::LoadTypeFromDir(RString dir) { m_Type= ProfileType_Normal; @@ -1299,6 +1356,7 @@ bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const LOG->Trace( "SaveStatsXmlToDir: %s", sDir.c_str() ); auto_ptr xml( SaveStatsXmlCreateNode() ); + sDir= sDir + PROFILEMAN->GetStatsPrefix(); // Save stats.xml RString fn = sDir + (g_bProfileDataCompress? STATS_XML_GZ:STATS_XML); diff --git a/src/Profile.h b/src/Profile.h index d89975d175..45c17bd642 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -390,7 +390,9 @@ public: void swap(Profile& other); // Loading and saving + void HandleStatsPrefixChange(RString dir, bool require_signature); ProfileLoadResult LoadAllFromDir( RString sDir, bool bRequireSignature ); + ProfileLoadResult LoadStatsFromDir(RString dir, bool require_signature); void LoadTypeFromDir(RString dir); void LoadCustomFunction( RString sDir ); bool SaveAllToDir( RString sDir, bool bSignData ) const; diff --git a/src/ProfileManager.cpp b/src/ProfileManager.cpp index 79662cb7ec..c673e61a0e 100644 --- a/src/ProfileManager.cpp +++ b/src/ProfileManager.cpp @@ -73,6 +73,7 @@ static ThemeMetric NUM_FIXED_PROFILES ( "ProfileManager", "NumFixedProfile ProfileManager::ProfileManager() + :m_stats_prefix("") { m_pMachineProfile = new Profile; FOREACH_PlayerNumber(pn) @@ -1004,6 +1005,24 @@ int ProfileManager::GetNumLocalProfiles() const return g_vLocalProfile.size(); } +void ProfileManager::SetStatsPrefix(RString const& prefix) +{ + m_stats_prefix= prefix; + for(size_t i= 0; i < g_vLocalProfile.size(); ++i) + { + g_vLocalProfile[i].profile.HandleStatsPrefixChange(g_vLocalProfile[i].sDir, PREFSMAN->m_bSignProfileData); + } + FOREACH_PlayerNumber(pn) + { + if(ProfileWasLoadedFromMemoryCard(pn)) + { + // This probably runs into a problem if the memory card has been removed. -Kyz + GetProfile(pn)->HandleStatsPrefixChange(m_sProfileDir[pn], PREFSMAN->m_bSignProfileData); + } + } + m_pMachineProfile->HandleStatsPrefixChange(MACHINE_PROFILE_DIR, false); +} + // lua start #include "LuaBinding.h" @@ -1011,6 +1030,17 @@ int ProfileManager::GetNumLocalProfiles() const class LunaProfileManager: public Luna { public: + static int GetStatsPrefix(T* p, lua_State* L) + { + lua_pushstring(L, p->GetStatsPrefix().c_str()); + return 1; + } + static int SetStatsPrefix(T* p, lua_State* L) + { + RString prefix= SArg(1); + p->SetStatsPrefix(prefix); + COMMON_RETURN_SELF; + } static int IsPersistentProfile( T* p, lua_State *L ) { lua_pushboolean(L, p->IsPersistentProfile(Enum::Check(L, 1)) ); return 1; } static int GetProfile( T* p, lua_State *L ) { PlayerNumber pn = Enum::Check(L, 1); Profile* pP = p->GetProfile(pn); ASSERT(pP != NULL); pP->PushSelf(L); return 1; } static int GetMachineProfile( T* p, lua_State *L ) { p->GetMachineProfile()->PushSelf(L); return 1; } @@ -1088,6 +1118,8 @@ public: LunaProfileManager() { + ADD_METHOD(GetStatsPrefix); + ADD_METHOD(SetStatsPrefix); ADD_METHOD( IsPersistentProfile ); ADD_METHOD( GetProfile ); ADD_METHOD( GetMachineProfile ); diff --git a/src/ProfileManager.h b/src/ProfileManager.h index dd457292f6..cf3bf8a4d0 100644 --- a/src/ProfileManager.h +++ b/src/ProfileManager.h @@ -43,6 +43,8 @@ public: int GetLocalProfileIndexFromID( RString sProfileID ) const; int GetNumLocalProfiles() const; + RString GetStatsPrefix() { return m_stats_prefix; } + void SetStatsPrefix(RString const& prefix); bool LoadFirstAvailableProfile( PlayerNumber pn, bool bLoadEdits = true ); // memory card or local profile bool LoadLocalProfileFromMachine( PlayerNumber pn ); @@ -119,6 +121,8 @@ private: // MemoryCardProfileImportSubdirs name, if the profile was imported. RString m_sProfileDirImportedFrom[NUM_PLAYERS]; + RString m_stats_prefix; + 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) bool m_bLastLoadWasFromLastGood[NUM_PLAYERS]; // if true, then m_bLastLoadWasTamperedOrCorrupt is also true