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