From f1fcdac212d87d692685e5f2631b7aebd5c7527a Mon Sep 17 00:00:00 2001 From: Nickito12 Date: Sun, 17 Sep 2017 20:37:09 -0300 Subject: [PATCH] Move xml profile loading/saving to XMLProfile --- src/Profile.cpp | 1259 +------------------------------------------- src/Profile.h | 48 +- src/XMLProfile.cpp | 1157 ++++++++++++++++++++++++++++++++++++++++ src/XMLProfile.h | 64 +++ 4 files changed, 1234 insertions(+), 1294 deletions(-) create mode 100644 src/XMLProfile.cpp create mode 100644 src/XMLProfile.h diff --git a/src/Profile.cpp b/src/Profile.cpp index 15653d362a..36e513f9da 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -2,7 +2,6 @@ #include "Profile.h" #include "RageUtil.h" #include "PrefsManager.h" -#include "XmlFile.h" #include "IniFile.h" #include "GameManager.h" #include "GameState.h" @@ -17,8 +16,6 @@ #include "RageFileDriverDeflate.h" #include "RageFileManager.h" #include "LuaManager.h" -#include "XmlFile.h" -#include "XmlFileUtil.h" #include "Foreach.h" #include "Game.h" #include "CharacterManager.h" @@ -28,25 +25,20 @@ #include "ScoreManager.h" #include #include "ScreenManager.h" +#include "XMLProfile.h" -const RString STATS_XML = "Stats.xml"; -const RString STATS_XML_GZ = "Stats.xml.gz"; -const RString ETT_XML = "Etterna.xml"; -const RString ETT_XML_GZ = "Etterna.xml.gz"; /** @brief The filename for where one can edit their personal profile information. */ const RString EDITABLE_INI = "Editable.ini"; /** @brief A tiny file containing the type and list priority. */ const RString TYPE_INI = "Type.ini"; /** @brief The filename containing the signature for STATS_XML's signature. */ -const RString DONT_SHARE_SIG = "DontShare.sig"; const RString PUBLIC_KEY_FILE = "public.key"; const RString SCREENSHOTS_SUBDIR = "Screenshots/"; const RString EDIT_STEPS_SUBDIR = "Edits/"; //const RString UPLOAD_SUBDIR = "Upload/"; const RString RIVAL_SUBDIR = "Rivals/"; -ThemeMetric SHOW_COIN_DATA( "Profile", "ShowCoinData" ); -static Preference g_bProfileDataCompress( "ProfileDataCompress", false ); +ThemeMetric SHOW_COIN_DATA( "Profile", "ShowCoinData" );; #define GUID_SIZE_BYTES 8 #define MAX_EDITABLE_INI_SIZE_BYTES 2*1024 // 2KB @@ -715,21 +707,6 @@ void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc ) m_CategoryHighScores[st][rc].IncrementPlayCount( now ); } - -// Loading and saving -#define WARN_PARSER ShowWarningOrTrace( __FILE__, __LINE__, "Error parsing file.", true ) -#define WARN_AND_RETURN { WARN_PARSER; return; } -#define WARN_AND_CONTINUE { WARN_PARSER; continue; } -#define WARN_AND_BREAK { WARN_PARSER; break; } -#define WARN_M(m) ShowWarningOrTrace( __FILE__, __LINE__, RString("Error parsing file: ")+(m), true ) -#define WARN_AND_RETURN_M(m) { WARN_M(m); return; } -#define WARN_AND_CONTINUE_M(m) { WARN_M(m); continue; } -#define WARN_AND_BREAK_M(m) { WARN_M(m); break; } -#define LOAD_NODE(X) { \ - const XNode* X = xml->GetChild(#X); \ - if( X==NULL ) LOG->Warn("Failed to read section " #X); \ - else Load##X##FromNode(X); } - void Profile::LoadCustomFunction( const RString &sDir ) { /* Get the theme's custom load function: @@ -815,7 +792,7 @@ ProfileLoadResult Profile::LoadAllFromDir( const RString &sDir, bool bRequireSig // Not critical if this fails LoadEditableDataFromDir( sDir ); - ProfileLoadResult ret= LoadEttFromDir(sDir); + ProfileLoadResult ret= XMLProf.LoadEttFromDir(sDir); if (ret != ProfileLoadResult_Success) { ret = LoadStatsFromDir(sDir, bRequireSignature); @@ -860,241 +837,7 @@ ProfileLoadResult Profile::LoadAllFromDir( const RString &sDir, bool bRequireSig ProfileLoadResult Profile::LoadStatsFromDir(RString dir, bool require_signature) { - dir += PROFILEMAN->GetStatsPrefix(); - profiledir = dir; - // 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; - unique_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); - } - - 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); -} - - -ProfileLoadResult Profile::LoadEttFromDir(RString dir) { - dir += PROFILEMAN->GetStatsPrefix(); - profiledir = dir; - IsEtternaProfile = true; - RString fn = dir + ETT_XML; - bool compressed = false; - if (!IsAFile(fn)) { - fn = dir + STATS_XML_GZ; - compressed = true; - if (!IsAFile(fn)) - return ProfileLoadResult_FailedNoProfile; - } - - int iError; - unique_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); - } - - LOG->Trace("Loading %s", fn.c_str()); - XNode xml; - if (!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) - return ProfileLoadResult_FailedTampered; - LOG->Trace("Done."); - - return LoadEttXmlFromNode(&xml); -} - -ProfileLoadResult Profile::EoBatchRecalc(const RString &sDir, LoadingWindow* ld) { - LOG->Trace("Profile::LoadAllFromDir( %s )", sDir.c_str()); - ASSERT(sDir.Right(1) == "/"); - - InitAll(); - LoadTypeFromDir(sDir); - // Not critical if this fails - LoadEditableDataFromDir(sDir); - - RString batchdir = sDir; - vector profs; - GetDirListing(sDir, profs, false, true); - - for (auto p : profs) { - if (p.Right(3) != "xml") - continue; - - if (p.Right(11).MakeLower() == "etterna.xml" || p.Right(9).MakeLower() == "stats.xml") - continue; - - RString profid = p.substr(p.find_last_of('/')); - RString newpath = "/BatchRecalc/" + profid; - - // skip recalc unless batchfolder doesnt have an updated file - if (FILEMAN->IsAFile(newpath)) - continue; - - int iError; - unique_ptr pFile(FILEMAN->Open(p, RageFile::READ, iError)); - if (pFile.get() == NULL) { - LOG->Trace("Error opening %s: %s", p.c_str(), strerror(iError)); - continue; - } - - LOG->Trace("Loading %s", p.c_str()); - - XNode xml; - if (!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) - continue; - LOG->Trace("Done."); - - LoadEttXmlFromNodeForBatchRecalc(&xml, p); - CalculateStatsFromScores(ld); - - SaveEttXmlToDirForBatchRecalc(newpath); - InitAll(); - SCOREMAN->PurgeScores(); - } - - return ProfileLoadResult_Success; -} - -ProfileLoadResult Profile::LoadEttXmlFromNodeForBatchRecalc(const XNode *xml, RString p) { - /* The placeholder stats.xml file has an tag. Don't load it, - * but don't warn about it. */ - if (xml->GetName() == "html") - return ProfileLoadResult_FailedNoProfile; - - if (xml->GetName() != "Stats") - { - WARN_M(xml->GetName()); - return ProfileLoadResult_FailedTampered; - } - - const XNode* gen = xml->GetChild("GeneralData"); - if (gen) - LoadEttGeneralDataFromNode(gen); - - const XNode* favs = xml->GetChild("Favorites"); - if (favs) - LoadFavoritesFromNode(favs); - - const XNode* pmir = xml->GetChild("PermaMirror"); - if (pmir) - LoadPermaMirrorFromNode(pmir); - - const XNode* goals = xml->GetChild("ScoreGoals"); - if (goals) - LoadScoreGoalsFromNode(goals); - - const XNode* play = xml->GetChild("Playlists"); - if (play) - LoadPlaylistsFromNode(play); - - const XNode* scores = xml->GetChild("PlayerScores"); - if (scores) - LoadEttScoresFromNode(scores); - - return ProfileLoadResult_Success; -} - -bool Profile::SaveEttXmlToDirForBatchRecalc(RString p) const { - LOG->Trace("Saving Etterna Profile to: %s", p.c_str()); - unique_ptr xml(SaveEttXmlCreateNode()); - // Save Etterna.xml - RString fn = p; - { - RString sError; - RageFile f; - if (!f.Open(fn, RageFile::WRITE)) - { - LuaHelpers::ReportScriptErrorFmt("Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str()); - return false; - } - - if (g_bProfileDataCompress) - { - RageFileObjGzip gzip(&f); - gzip.Start(); - if (!XmlFileUtil::SaveToFile(xml.get(), gzip, "", false)) - return false; - - if (gzip.Finish() == -1) - return false; - } - else - { - if (!XmlFileUtil::SaveToFile(xml.get(), f, "", false)) - return false; - } - } - - return true; + return XMLProf.LoadStatsFromDir(dir, require_signature); } void Profile::LoadTypeFromDir(const RString &dir) @@ -1125,78 +868,6 @@ void Profile::LoadTypeFromDir(const RString &dir) } } -ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreEditable ) -{ - /* The placeholder stats.xml file has an tag. Don't load it, - * but don't warn about it. */ - if( xml->GetName() == "html" ) - return ProfileLoadResult_FailedNoProfile; - - if( xml->GetName() != "Stats" ) - { - WARN_M( xml->GetName() ); - return ProfileLoadResult_FailedTampered; - } - - // These are loaded from Editable, so we usually want to ignore them here. - RString sName = m_sDisplayName; - RString sCharacterID = m_sCharacterID; - RString sLastUsedHighScoreName = m_sLastUsedHighScoreName; - - LOAD_NODE( GeneralData ); - LOAD_NODE( SongScores ); - LOAD_NODE( CategoryScores ); - LOAD_NODE( ScreenshotData ); - - if( bIgnoreEditable ) - { - m_sDisplayName = sName; - m_sCharacterID = sCharacterID; - m_sLastUsedHighScoreName = sLastUsedHighScoreName; - } - - return ProfileLoadResult_Success; -} - -ProfileLoadResult Profile::LoadEttXmlFromNode(const XNode *xml) { - /* The placeholder stats.xml file has an tag. Don't load it, - * but don't warn about it. */ - if (xml->GetName() == "html") - return ProfileLoadResult_FailedNoProfile; - - if (xml->GetName() != "Stats") - { - WARN_M(xml->GetName()); - return ProfileLoadResult_FailedTampered; - } - - const XNode* gen = xml->GetChild("GeneralData"); - if(gen) - LoadEttGeneralDataFromNode(gen); - - const XNode* favs = xml->GetChild("Favorites"); - if(favs) - LoadFavoritesFromNode(favs); - - const XNode* pmir = xml->GetChild("PermaMirror"); - if (pmir) - LoadPermaMirrorFromNode(pmir); - - const XNode* goals = xml->GetChild("ScoreGoals"); - if(goals) - LoadScoreGoalsFromNode(goals); - - const XNode* play = xml->GetChild("Playlists"); - if (play) - LoadPlaylistsFromNode(play); - - const XNode* scores = xml->GetChild("PlayerScores"); - if (scores) - LoadEttScoresFromNode(scores); - - return ProfileLoadResult_Success; -} - void Profile::CalculateStatsFromScores(LoadingWindow* ld) { LOG->Trace("Calculating stats from scores"); vector all = SCOREMAN->GetAllScores(); @@ -1262,7 +933,7 @@ bool Profile::SaveAllToDir( const RString &sDir, bool bSignData ) const // Save editable.ini SaveEditableDataToDir( sDir ); - bool bSaved = SaveEttXmlToDir(sDir); + bool bSaved = XMLProf.SaveEttXmlToDir(sDir); SaveStatsWebPageToDir( sDir ); // Empty directories if none exist. @@ -1271,138 +942,6 @@ bool Profile::SaveAllToDir( const RString &sDir, bool bSignData ) const return bSaved; } -XNode *Profile::SaveStatsXmlCreateNode() const -{ - XNode *xml = new XNode( "Stats" ); - - xml->AppendChild( SaveGeneralDataCreateNode() ); - xml->AppendChild( SaveSongScoresCreateNode() ); - xml->AppendChild( SaveCategoryScoresCreateNode() ); - xml->AppendChild( SaveScreenshotDataCreateNode() ); - - return xml; -} - -XNode *Profile::SaveEttXmlCreateNode() const -{ - XNode *xml = new XNode("Stats"); - xml->AppendChild(SaveEttGeneralDataCreateNode()); - - if(!FavoritedCharts.empty()) - xml->AppendChild(SaveFavoritesCreateNode()); - - if (!PermaMirrorCharts.empty()) - xml->AppendChild(SavePermaMirrorCreateNode()); - - if (!SONGMAN->allplaylists.empty()) - xml->AppendChild(SavePlaylistsCreateNode()); - - if(!goalmap.empty()) - xml->AppendChild(SaveScoreGoalsCreateNode()); - - xml->AppendChild(SaveEttScoresCreateNode()); - return xml; -} - -bool Profile::SaveStatsXmlToDir( RString sDir, bool bSignData ) const -{ - LOG->Trace( "SaveStatsXmlToDir: %s", sDir.c_str() ); - unique_ptr xml( SaveStatsXmlCreateNode() ); - - sDir += PROFILEMAN->GetStatsPrefix(); - // Save stats.xml - RString fn = sDir + (g_bProfileDataCompress? STATS_XML_GZ:STATS_XML); - - { - RString sError; - RageFile f; - if( !f.Open(fn, RageFile::WRITE) ) - { - LuaHelpers::ReportScriptErrorFmt( "Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str() ); - return false; - } - - if( g_bProfileDataCompress ) - { - RageFileObjGzip gzip( &f ); - gzip.Start(); - if( !XmlFileUtil::SaveToFile( xml.get(), gzip, "", false ) ) - return false; - - if( gzip.Finish() == -1 ) - return false; - - /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ - if( FILEMAN->IsAFile(sDir + STATS_XML) ) - FILEMAN->Remove( sDir + STATS_XML ); - } - else - { - if( !XmlFileUtil::SaveToFile( xml.get(), f, "", false ) ) - return false; - - /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ - if( FILEMAN->IsAFile(sDir + STATS_XML_GZ) ) - FILEMAN->Remove( sDir + STATS_XML_GZ ); - } - } - - if( bSignData ) - { - RString sStatsXmlSigFile = fn+SIGNATURE_APPEND; - CryptManager::SignFileToFile(fn, sStatsXmlSigFile); - - // Save the "don't share" file - RString sDontShareFile = sDir + DONT_SHARE_SIG; - CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); - } - - return true; -} - -bool Profile::SaveEttXmlToDir(RString sDir) const { - LOG->Trace("Saving Etterna Profile to: %s", sDir.c_str()); - unique_ptr xml(SaveEttXmlCreateNode()); - sDir += PROFILEMAN->GetStatsPrefix(); - // Save Etterna.xml - RString fn = sDir + ETT_XML; - { - RString sError; - RageFile f; - if (!f.Open(fn, RageFile::WRITE)) - { - LuaHelpers::ReportScriptErrorFmt("Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str()); - return false; - } - - if (g_bProfileDataCompress) - { - RageFileObjGzip gzip(&f); - gzip.Start(); - if (!XmlFileUtil::SaveToFile(xml.get(), gzip, "", false)) - return false; - - if (gzip.Finish() == -1) - return false; - - /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ - if (FILEMAN->IsAFile(sDir + STATS_XML)) - FILEMAN->Remove(sDir + STATS_XML); - } - else - { - if (!XmlFileUtil::SaveToFile(xml.get(), f, "", false)) - return false; - - /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ - if (FILEMAN->IsAFile(sDir + STATS_XML_GZ)) - FILEMAN->Remove(sDir + STATS_XML_GZ); - } - } - - return true; -} - void Profile::SaveTypeToDir(const RString &dir) const { IniFile ini; @@ -1422,330 +961,6 @@ void Profile::SaveEditableDataToDir( const RString &sDir ) const ini.WriteFile( sDir + EDITABLE_INI ); } -XNode* Profile::SaveGeneralDataCreateNode() const -{ - XNode* pGeneralDataNode = new XNode( "GeneralData" ); - - // TRICKY: These are write-only elements that are normally never read again. - // This data is required by other apps (like internet ranking), but is - // redundant to the game app. - pGeneralDataNode->AppendChild( "DisplayName", GetDisplayNameOrHighScoreName() ); - pGeneralDataNode->AppendChild( "CharacterID", m_sCharacterID ); - pGeneralDataNode->AppendChild( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - pGeneralDataNode->AppendChild( "Guid", m_sGuid ); - pGeneralDataNode->AppendChild( "SortOrder", SortOrderToString(m_SortOrder) ); - pGeneralDataNode->AppendChild( "LastDifficulty", DifficultyToString(m_LastDifficulty) ); - if( m_LastStepsType != StepsType_Invalid ) - pGeneralDataNode->AppendChild( "LastStepsType", GAMEMAN->GetStepsTypeInfo(m_LastStepsType).szName ); - pGeneralDataNode->AppendChild( m_lastSong.CreateNode() ); - pGeneralDataNode->AppendChild( "CurrentCombo", m_iCurrentCombo ); - pGeneralDataNode->AppendChild( "TotalSessions", m_iTotalSessions ); - pGeneralDataNode->AppendChild( "TotalSessionSeconds", m_iTotalSessionSeconds ); - pGeneralDataNode->AppendChild( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); - pGeneralDataNode->AppendChild( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); - pGeneralDataNode->AppendChild( "LastPlayedDate", m_LastPlayedDate.GetString() ); - pGeneralDataNode->AppendChild( "TotalDancePoints", m_iTotalDancePoints ); - pGeneralDataNode->AppendChild( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); - pGeneralDataNode->AppendChild( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); - pGeneralDataNode->AppendChild( "NumToasties", m_iNumToasties ); - pGeneralDataNode->AppendChild( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); - pGeneralDataNode->AppendChild( "TotalJumps", m_iTotalJumps ); - pGeneralDataNode->AppendChild( "TotalHolds", m_iTotalHolds ); - pGeneralDataNode->AppendChild( "TotalRolls", m_iTotalRolls ); - pGeneralDataNode->AppendChild( "TotalMines", m_iTotalMines ); - pGeneralDataNode->AppendChild( "TotalHands", m_iTotalHands ); - pGeneralDataNode->AppendChild( "TotalLifts", m_iTotalLifts ); - pGeneralDataNode->AppendChild( "PlayerRating", m_fPlayerRating); - - // Keep declared variables in a very local scope so they aren't - // accidentally used where they're not intended. There's a lot of - // copying and pasting in this code. - - { - XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); - FOREACHM_CONST( RString, RString, m_sDefaultModifiers, it ) - pDefaultModifiers->AppendChild( it->first, it->second ); - } - - { - XNode* pFavorites = pGeneralDataNode->AppendChild("Favorites"); - FOREACHS_CONST(string, FavoritedCharts, it) - pFavorites->AppendChild(*it); - } - - { - XNode* pPlayerSkillsets = pGeneralDataNode->AppendChild("PlayerSkillsets"); - FOREACH_ENUM(Skillset, ss) - pPlayerSkillsets->AppendChild(SkillsetToString(ss), m_fPlayerSkillsets[ss]); - } - - { - XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode"); - FOREACH_ENUM( PlayMode, pm ) - { - // Don't save unplayed PlayModes. - if( !m_iNumSongsPlayedByPlayMode[pm] ) - continue; - pNumSongsPlayedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); - } - } - - { - XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); - FOREACHM_CONST( StyleID, int, m_iNumSongsPlayedByStyle, iter ) - { - const StyleID &s = iter->first; - int iNumPlays = iter->second; - - XNode *pStyleNode = s.CreateNode(); - pStyleNode->AppendAttr(XNode::TEXT_ATTRIBUTE, iNumPlays ); - - pNumSongsPlayedByStyle->AppendChild( pStyleNode ); - } - } - - { - XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty"); - FOREACH_ENUM( Difficulty, dc ) - { - if( !m_iNumSongsPlayedByDifficulty[dc] ) - continue; - pNumSongsPlayedByDifficulty->AppendChild( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); - } - } - - { - XNode* pNumSongsPlayedByMeter = pGeneralDataNode->AppendChild("NumSongsPlayedByMeter"); - for( int i=0; iAppendChild( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); - } - } - - pGeneralDataNode->AppendChild( "NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); - - { - XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); - FOREACH_ENUM( PlayMode, pm ) - { - // Don't save unplayed PlayModes. - if( !m_iNumStagesPassedByPlayMode[pm] ) - continue; - pNumStagesPassedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); - } - } - - { - XNode* pNumStagesPassedByGrade = pGeneralDataNode->AppendChild("NumStagesPassedByGrade"); - FOREACH_ENUM( Grade, g ) - { - if( !m_iNumStagesPassedByGrade[g] ) - continue; - pNumStagesPassedByGrade->AppendChild( GradeToString(g), m_iNumStagesPassedByGrade[g] ); - } - } - - // Load Lua UserTable from profile - if( m_UserTable.IsSet() ) - { - Lua *L = LUA->Get(); - m_UserTable.PushSelf( L ); - XNode* pUserTable = XmlFileUtil::XNodeFromTable( L ); - LUA->Release( L ); - - // XXX: XNodeFromTable returns a root node with the name "Layer". - pUserTable->m_sName = "UserTable"; - pGeneralDataNode->AppendChild( pUserTable ); - } - - return pGeneralDataNode; -} - -XNode* Profile::SaveFavoritesCreateNode() const { - CHECKPOINT_M("Saving the favorites node."); - - XNode* favs = new XNode("Favorites"); - FOREACHS_CONST(string, FavoritedCharts, it) - favs->AppendChild(*it); - return favs; -} - -XNode* Profile::SavePermaMirrorCreateNode() const { - CHECKPOINT_M("Saving the permamirror node."); - - XNode* pmir = new XNode("PermaMirror"); - FOREACHS_CONST(string, PermaMirrorCharts, it) - pmir->AppendChild(*it); - return pmir; -} - -XNode* GoalsForChart::CreateNode() const { - XNode* cg = new XNode("GoalsForChart"); - - if (!goals.empty()) { - cg->AppendAttr("Key", goals[0].chartkey); - FOREACH_CONST(ScoreGoal, goals, sg) - cg->AppendChild(sg->CreateNode()); - } - return cg; -} - -XNode* Profile::SaveScoreGoalsCreateNode() const { - CHECKPOINT_M("Saving the scoregoals node."); - - XNode* goals = new XNode("ScoreGoals"); - FOREACHUM_CONST(string, GoalsForChart, goalmap, i) { - const GoalsForChart& cg = i->second; - goals->AppendChild(cg.CreateNode()); - } - return goals; -} - -XNode* Profile::SavePlaylistsCreateNode() const { - CHECKPOINT_M("Saving the playlists node."); - - XNode* playlists = new XNode("Playlists"); - auto& pls = SONGMAN->allplaylists; - FOREACHM(string, Playlist, pls, i) - if(i->first != "" && i->first != "Favorites") - playlists->AppendChild(i->second.CreateNode()); - return playlists; -} - -void Profile::LoadFavoritesFromNode(const XNode *pNode) { - CHECKPOINT_M("Loading the favorites node."); - - FOREACH_CONST_Child(pNode, ck) - FavoritedCharts.emplace(SONGMAN->ReconcileBustedKeys(ck->GetName())); - - SONGMAN->SetFavoritedStatus(FavoritedCharts); - SONGMAN->MakePlaylistFromFavorites(FavoritedCharts); -} - -void Profile::LoadPermaMirrorFromNode(const XNode *pNode) { - CHECKPOINT_M("Loading the permamirror node."); - - FOREACH_CONST_Child(pNode, ck) - PermaMirrorCharts.emplace(SONGMAN->ReconcileBustedKeys(ck->GetName())); - - SONGMAN->SetPermaMirroredStatus(PermaMirrorCharts); -} - -void GoalsForChart::LoadFromNode(const XNode *pNode) { - FOREACH_CONST_Child(pNode, sg) { - ScoreGoal doot; - doot.LoadFromNode(sg); - Add(doot); - } -} - -void Profile::LoadScoreGoalsFromNode(const XNode *pNode) { - CHECKPOINT_M("Loading the scoregoals node."); - - RString ck; - FOREACH_CONST_Child(pNode, chgoals) { - chgoals->GetAttrValue("Key", ck); - ck = SONGMAN->ReconcileBustedKeys(ck); - goalmap[ck].LoadFromNode(chgoals); - } - SONGMAN->SetHasGoal(goalmap); -} - -void Profile::LoadPlaylistsFromNode(const XNode *pNode) { - CHECKPOINT_M("Loading the playlists node."); - - auto& pls = SONGMAN->allplaylists; - FOREACH_CONST_Child(pNode, pl) { - Playlist tmp; - tmp.LoadFromNode(pl); - pls.emplace(tmp.name, tmp); - SONGMAN->activeplaylist = tmp.name; - } -} - - -XNode* Profile::SaveEttGeneralDataCreateNode() const { - CHECKPOINT_M("Saving the general node."); - - XNode* pGeneralDataNode = new XNode("GeneralData"); - - // TRICKY: These are write-only elements that are normally never read again. - // This data is required by other apps (like internet ranking), but is - // redundant to the game app. - pGeneralDataNode->AppendChild("DisplayName", GetDisplayNameOrHighScoreName()); - pGeneralDataNode->AppendChild("CharacterID", m_sCharacterID); - pGeneralDataNode->AppendChild("Guid", m_sGuid); - pGeneralDataNode->AppendChild("SortOrder", SortOrderToString(m_SortOrder)); - pGeneralDataNode->AppendChild("LastDifficulty", DifficultyToString(Difficulty_Invalid)); - if (m_LastStepsType != StepsType_Invalid) - pGeneralDataNode->AppendChild("LastStepsType", GAMEMAN->GetStepsTypeInfo(m_LastStepsType).szName); - pGeneralDataNode->AppendChild(m_lastSong.CreateNode()); - pGeneralDataNode->AppendChild("TotalSessions", m_iTotalSessions); - pGeneralDataNode->AppendChild("TotalSessionSeconds", m_iTotalSessionSeconds); - pGeneralDataNode->AppendChild("TotalGameplaySeconds", m_iTotalGameplaySeconds); - pGeneralDataNode->AppendChild("LastPlayedMachineGuid", m_sLastPlayedMachineGuid); - pGeneralDataNode->AppendChild("LastPlayedDate", m_LastPlayedDate.GetString()); - pGeneralDataNode->AppendChild("TotalDancePoints", m_iTotalDancePoints); - pGeneralDataNode->AppendChild("NumToasties", m_iNumToasties); - pGeneralDataNode->AppendChild("TotalTapsAndHolds", m_iTotalTapsAndHolds); - pGeneralDataNode->AppendChild("TotalJumps", m_iTotalJumps); - pGeneralDataNode->AppendChild("TotalHolds", m_iTotalHolds); - pGeneralDataNode->AppendChild("TotalRolls", m_iTotalRolls); - pGeneralDataNode->AppendChild("TotalMines", m_iTotalMines); - pGeneralDataNode->AppendChild("TotalHands", m_iTotalHands); - pGeneralDataNode->AppendChild("TotalLifts", m_iTotalLifts); - pGeneralDataNode->AppendChild("PlayerRating", m_fPlayerRating); - - // apparently this got ripped out in the course of streamlining things -mina - GAMESTATE->SaveCurrentSettingsToProfile(PLAYER_1); - - // Keep declared variables in a very local scope so they aren't - // accidentally used where they're not intended. There's a lot of - // copying and pasting in this code. - - { - XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); - FOREACHM_CONST(RString, RString, m_sDefaultModifiers, it) - pDefaultModifiers->AppendChild(it->first, it->second); - } - - { - XNode* pPlayerSkillsets = pGeneralDataNode->AppendChild("PlayerSkillsets"); - FOREACH_ENUM(Skillset, ss) - pPlayerSkillsets->AppendChild(SkillsetToString(ss), m_fPlayerSkillsets[ss]); - } - - pGeneralDataNode->AppendChild("NumTotalSongsPlayed", m_iNumTotalSongsPlayed); - - { - XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); - FOREACH_ENUM(PlayMode, pm) - { - // Don't save unplayed PlayModes. - if (!m_iNumStagesPassedByPlayMode[pm]) - continue; - pNumStagesPassedByPlayMode->AppendChild(PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm]); - } - } - - // Load Lua UserTable from profile - if (m_UserTable.IsSet()) - { - Lua *L = LUA->Get(); - m_UserTable.PushSelf(L); - XNode* pUserTable = XmlFileUtil::XNodeFromTable(L); - LUA->Release(L); - - // XXX: XNodeFromTable returns a root node with the name "Layer". - pUserTable->m_sName = "UserTable"; - pGeneralDataNode->AppendChild(pUserTable); - } - - return pGeneralDataNode; -} - ProfileLoadResult Profile::LoadEditableDataFromDir( const RString &sDir ) { RString fn = sDir + EDITABLE_INI; @@ -1778,214 +993,6 @@ ProfileLoadResult Profile::LoadEditableDataFromDir( const RString &sDir ) return ProfileLoadResult_Success; } -void Profile::LoadGeneralDataFromNode( const XNode* pNode ) -{ - ASSERT( pNode->GetName() == "GeneralData" ); - - RString s; - const XNode* pTemp; - - pNode->GetChildValue( "DisplayName", m_sDisplayName ); - pNode->GetChildValue( "CharacterID", m_sCharacterID ); - pNode->GetChildValue( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); - pNode->GetChildValue( "Guid", m_sGuid ); - pNode->GetChildValue( "SortOrder", s ); m_SortOrder = StringToSortOrder( s ); - pNode->GetChildValue( "LastDifficulty", s ); m_LastDifficulty = StringToDifficulty( s ); - pNode->GetChildValue( "LastStepsType", s ); m_LastStepsType = GAMEMAN->StringToStepsType( s ); - pTemp = pNode->GetChild( "Song" ); if( pTemp ) m_lastSong.LoadFromNode( pTemp ); - pNode->GetChildValue( "CurrentCombo", m_iCurrentCombo ); - pNode->GetChildValue( "TotalSessions", m_iTotalSessions ); - pNode->GetChildValue( "TotalSessionSeconds", m_iTotalSessionSeconds ); - pNode->GetChildValue( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); - pNode->GetChildValue( "LastPlayedMachineGuid", m_sLastPlayedMachineGuid ); - pNode->GetChildValue( "LastPlayedDate", s ); m_LastPlayedDate.FromString( s ); - pNode->GetChildValue( "TotalDancePoints", m_iTotalDancePoints ); - pNode->GetChildValue( "NumExtraStagesPassed", m_iNumExtraStagesPassed ); - pNode->GetChildValue( "NumExtraStagesFailed", m_iNumExtraStagesFailed ); - pNode->GetChildValue( "NumToasties", m_iNumToasties ); - pNode->GetChildValue( "TotalTapsAndHolds", m_iTotalTapsAndHolds ); - pNode->GetChildValue( "TotalJumps", m_iTotalJumps ); - pNode->GetChildValue( "TotalHolds", m_iTotalHolds ); - pNode->GetChildValue( "TotalRolls", m_iTotalRolls ); - pNode->GetChildValue( "TotalMines", m_iTotalMines ); - pNode->GetChildValue( "TotalHands", m_iTotalHands ); - pNode->GetChildValue( "TotalLifts", m_iTotalLifts ); - pNode->GetChildValue( "PlayerRating", m_fPlayerRating); - - { - const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); - if( pDefaultModifiers ) - { - FOREACH_CONST_Child( pDefaultModifiers, game_type ) - { - game_type->GetTextValue( m_sDefaultModifiers[game_type->GetName()] ); - } - } - } - - { - const XNode* pFavorites = pNode->GetChild("Favorites"); - if (pFavorites) { - FOREACH_CONST_Child(pFavorites, ck) { - RString tmp = ck->GetName(); // handle duplicated entries caused by an oversight - mina - bool duplicated = false; - FOREACHS(string, FavoritedCharts, chartkey) - if (*chartkey == tmp) - duplicated = true; - if (!duplicated) - FavoritedCharts.emplace(tmp); - } - SONGMAN->SetFavoritedStatus(FavoritedCharts); - } - } - - { - const XNode* pPlayerSkillsets = pNode->GetChild("PlayerSkillsets"); - if (pPlayerSkillsets) { - FOREACH_ENUM(Skillset, ss) - pPlayerSkillsets->GetChildValue(SkillsetToString(ss), m_fPlayerSkillsets[ss]); - } - } - - { - const XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode"); - if( pNumSongsPlayedByPlayMode ) - FOREACH_ENUM( PlayMode, pm ) - pNumSongsPlayedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); - } - - { - const XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle"); - if( pNumSongsPlayedByStyle ) - { - FOREACH_CONST_Child( pNumSongsPlayedByStyle, style ) - { - if( style->GetName() != "Style" ) - continue; - - StyleID sID; - sID.LoadFromNode( style ); - - if( !sID.IsValid() ) - WARN_AND_CONTINUE; - - style->GetTextValue( m_iNumSongsPlayedByStyle[sID] ); - } - } - } - - { - const XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty"); - if( pNumSongsPlayedByDifficulty ) - FOREACH_ENUM( Difficulty, dc ) - pNumSongsPlayedByDifficulty->GetChildValue( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); - } - - { - const XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter"); - if( pNumSongsPlayedByMeter ) - for( int i=0; iGetChildValue( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); - } - - pNode->GetChildValue("NumTotalSongsPlayed", m_iNumTotalSongsPlayed ); - - { - const XNode* pNumStagesPassedByGrade = pNode->GetChild("NumStagesPassedByGrade"); - if( pNumStagesPassedByGrade ) - FOREACH_ENUM( Grade, g ) - pNumStagesPassedByGrade->GetChildValue( GradeToString(g), m_iNumStagesPassedByGrade[g] ); - } - - { - const XNode* pNumStagesPassedByPlayMode = pNode->GetChild("NumStagesPassedByPlayMode"); - if( pNumStagesPassedByPlayMode ) - FOREACH_ENUM( PlayMode, pm ) - pNumStagesPassedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumStagesPassedByPlayMode[pm] ); - - } - - const XNode *pUserTable = pNode->GetChild("UserTable"); - - Lua *L = LUA->Get(); - - // If we have custom data, load it. Otherwise, make a blank table. - if (pUserTable) - LuaHelpers::CreateTableFromXNode(L, pUserTable); - else - lua_newtable(L); - - m_UserTable.SetFromStack(L); - LUA->Release(L); - -} - - -void Profile::LoadEttGeneralDataFromNode(const XNode* pNode) { - CHECKPOINT_M("Loading the general node."); - ASSERT(pNode->GetName() == "GeneralData"); - - RString s; - const XNode* pTemp; - - pNode->GetChildValue("DisplayName", m_sDisplayName); - pNode->GetChildValue("CharacterID", m_sCharacterID); - pNode->GetChildValue("LastUsedHighScoreName", m_sLastUsedHighScoreName); - pNode->GetChildValue("Guid", m_sGuid); - pNode->GetChildValue("SortOrder", s); m_SortOrder = StringToSortOrder(s); - pNode->GetChildValue("LastDifficulty", s); m_LastDifficulty = StringToDifficulty(s); - pNode->GetChildValue("LastStepsType", s); m_LastStepsType = GAMEMAN->StringToStepsType(s); - pTemp = pNode->GetChild("Song"); if (pTemp) m_lastSong.LoadFromNode(pTemp); - pNode->GetChildValue("CurrentCombo", m_iCurrentCombo); - pNode->GetChildValue("TotalSessions", m_iTotalSessions); - pNode->GetChildValue("TotalSessionSeconds", m_iTotalSessionSeconds); - pNode->GetChildValue("TotalGameplaySeconds", m_iTotalGameplaySeconds); - pNode->GetChildValue("LastPlayedDate", s); m_LastPlayedDate.FromString(s); - pNode->GetChildValue("TotalDancePoints", m_iTotalDancePoints); - pNode->GetChildValue("NumToasties", m_iNumToasties); - pNode->GetChildValue("TotalTapsAndHolds", m_iTotalTapsAndHolds); - pNode->GetChildValue("TotalJumps", m_iTotalJumps); - pNode->GetChildValue("TotalHolds", m_iTotalHolds); - pNode->GetChildValue("TotalRolls", m_iTotalRolls); - pNode->GetChildValue("TotalMines", m_iTotalMines); - pNode->GetChildValue("TotalHands", m_iTotalHands); - pNode->GetChildValue("TotalLifts", m_iTotalLifts); - pNode->GetChildValue("PlayerRating", m_fPlayerRating); - - { - const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); - if (pDefaultModifiers) - { - FOREACH_CONST_Child(pDefaultModifiers, game_type) - { - game_type->GetTextValue(m_sDefaultModifiers[game_type->GetName()]); - } - } - } - - { - const XNode* pPlayerSkillsets = pNode->GetChild("PlayerSkillsets"); - if (pPlayerSkillsets) { - FOREACH_ENUM(Skillset, ss) - pPlayerSkillsets->GetChildValue(SkillsetToString(ss), m_fPlayerSkillsets[ss]); - } - } - - const XNode *pUserTable = pNode->GetChild("UserTable"); - - Lua *L = LUA->Get(); - - // If we have custom data, load it. Otherwise, make a blank table. - if (pUserTable) - LuaHelpers::CreateTableFromXNode(L, pUserTable); - else - lua_newtable(L); - - m_UserTable.SetFromStack(L); - LUA->Release(L); - -} - void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotalHolds, int iTotalRolls, int iTotalMines, int iTotalHands, int iTotalLifts) { m_iTotalTapsAndHolds += iTotalTapsAndHolds; @@ -1998,49 +1005,6 @@ void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotal } -XNode* Profile::SaveSongScoresCreateNode() const -{ - CHECKPOINT_M("Getting the node to save song scores."); - - const Profile* pProfile = this; - ASSERT(pProfile != NULL); - - XNode* pNode = new XNode("SongScores"); - - FOREACHM_CONST(SongID, HighScoresForASong, m_SongHighScores, i) - { - const SongID &songID = i->first; - const HighScoresForASong &hsSong = i->second; - - // skip songs that have never been played - if (pProfile->GetSongNumTimesPlayed(songID) == 0) - continue; - - XNode* pSongNode = pNode->AppendChild(songID.CreateNode()); - - int jCheck2 = hsSong.m_StepsHighScores.size(); - int jCheck1 = 0; - FOREACHM_CONST(StepsID, HighScoresForASteps, hsSong.m_StepsHighScores, j) - { - jCheck1++; - ASSERT(jCheck1 <= jCheck2); - const StepsID &stepsID = j->first; - const HighScoresForASteps &hsSteps = j->second; - - const HighScoreList &hsl = hsSteps.hsl; - - // skip steps that have never been played - if (hsl.GetNumTimesPlayed() == 0) - continue; - - XNode* pStepsNode = pSongNode->AppendChild(stepsID.CreateNode()); - - pStepsNode->AppendChild(hsl.CreateNode()); - } - } - - return pNode; -} void Profile::RemoveFromFavorites(const string& ck) { FavoritedCharts.erase(ck); @@ -2050,83 +1014,10 @@ void Profile::RemoveFromPermaMirror(const string& ck) { PermaMirrorCharts.erase(ck); } -void Profile::LoadSongScoresFromNode(const XNode* pSongScores) -{ - CHECKPOINT_M("Loading the node that contains song scores."); - - ASSERT(pSongScores->GetName() == "SongScores"); - - FOREACH_CONST_Child(pSongScores, pSong) - { - if (pSong->GetName() != "Song") - continue; - - SongID songID; - songID.LoadFromNode(pSong); - // Allow invalid songs so that scores aren't deleted for people that use - // AdditionalSongsFolders and change it frequently. -Kyz - //if( !songID.IsValid() ) - // continue; - - FOREACH_CONST_Child(pSong, pSteps) - { - if (pSteps->GetName() != "Steps") - continue; - - StepsID stepsID; - stepsID.LoadFromNode(pSteps); - if (!stepsID.IsValid()) - WARN_AND_CONTINUE; - - const XNode *pHighScoreListNode = pSteps->GetChild("HighScoreList"); - if (pHighScoreListNode == NULL) - WARN_AND_CONTINUE; - - HighScoreList &hsl = m_SongHighScores[songID].m_StepsHighScores[stepsID].hsl; - hsl.LoadFromNode(pHighScoreListNode); - } - } -} - -void Profile::LoadStatsXmlForConversion() { - string dir = profiledir; - RString fn = dir + STATS_XML; - bool compressed = false; - if (!IsAFile(fn)) { - fn = dir + STATS_XML_GZ; - compressed = true; - if (!IsAFile(fn)) { - return ; - } - } - - int iError; - unique_ptr pFile(FILEMAN->Open(fn, RageFile::READ, iError)); - if (pFile.get() == NULL) - return; - - if (compressed) { - RString sError; - uint32_t iCRC32; - RageFileObjInflate *pInflate = GunzipFile(pFile.release(), sError, &iCRC32); - if (pInflate == NULL) - return; - - pFile.reset(pInflate); - } - - XNode xml; - if (!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) - return; - - XNode* scores = xml.GetChild("SongScores"); - LoadSongScoresFromNode(scores); -} - void Profile::ImportScoresToEtterna() { // load stats.xml if(IsEtternaProfile) - LoadStatsXmlForConversion(); + XMLProf.LoadStatsXmlForConversion(); int loaded = 0; int notloaded = 0; @@ -2281,22 +1172,6 @@ void Profile::ImportScoresToEtterna() { PROFILEMAN->SaveProfile(PLAYER_1); } -XNode* Profile::SaveEttScoresCreateNode() const { - CHECKPOINT_M("Saving the player scores node."); - - const Profile* pProfile = this; - ASSERT(pProfile != NULL); - - //SCOREMAN->SetAllTopScores(); if this isn't already settled before you save the xml what the fuck are you doing -lurker - XNode* pNode = SCOREMAN->CreateNode(); - return pNode; -} - -void Profile::LoadEttScoresFromNode(const XNode* pSongScores) { - CHECKPOINT_M("Loading the player scores node."); - SCOREMAN->LoadFromNode(pSongScores); -} - // more future goalman stuff void Profile::CreateGoal(const string& ck) { ScoreGoal goal; @@ -2398,81 +1273,6 @@ void Profile::DeleteGoal(const string& ck, DateTime assigned) { } } -XNode* Profile::SaveCategoryScoresCreateNode() const -{ - CHECKPOINT_M("Getting the node that saves category scores."); - - const Profile* pProfile = this; - ASSERT( pProfile != NULL ); - - XNode* pNode = new XNode( "CategoryScores" ); - - FOREACH_ENUM( StepsType,st ) - { - // skip steps types that have never been played - if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 ) - continue; - - XNode* pStepsTypeNode = pNode->AppendChild( "StepsType" ); - pStepsTypeNode->AppendAttr( "Type", GAMEMAN->GetStepsTypeInfo(st).szName ); - - FOREACH_ENUM( RankingCategory,rc ) - { - // skip steps types/categories that have never been played - if( pProfile->GetCategoryHighScoreList(st,rc).GetNumTimesPlayed() == 0 ) - continue; - - XNode* pRankingCategoryNode = pStepsTypeNode->AppendChild( "RankingCategory" ); - pRankingCategoryNode->AppendAttr( "Type", RankingCategoryToString(rc) ); - - const HighScoreList &hsl = pProfile->GetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); - - pRankingCategoryNode->AppendChild( hsl.CreateNode() ); - } - } - - return pNode; -} - -void Profile::LoadCategoryScoresFromNode( const XNode* pCategoryScores ) -{ - CHECKPOINT_M("Loading the node that contains category scores."); - - ASSERT( pCategoryScores->GetName() == "CategoryScores" ); - - FOREACH_CONST_Child( pCategoryScores, pStepsType ) - { - if( pStepsType->GetName() != "StepsType" ) - continue; - - RString str; - if( !pStepsType->GetAttrValue( "Type", str ) ) - WARN_AND_CONTINUE; - StepsType st = GAMEMAN->StringToStepsType( str ); - if( st == StepsType_Invalid ) - WARN_AND_CONTINUE_M( str ); - - FOREACH_CONST_Child( pStepsType, pRadarCategory ) - { - if( pRadarCategory->GetName() != "RankingCategory" ) - continue; - - if( !pRadarCategory->GetAttrValue( "Type", str ) ) - WARN_AND_CONTINUE; - RankingCategory rc = StringToRankingCategory( str ); - if( rc == RankingCategory_Invalid ) - WARN_AND_CONTINUE_M( str ); - - const XNode *pHighScoreListNode = pRadarCategory->GetChild("HighScoreList"); - if( pHighScoreListNode == NULL ) - WARN_AND_CONTINUE; - - HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc ); - hsl.LoadFromNode( pHighScoreListNode ); - } - } -} - void Profile::SaveStatsWebPageToDir( const RString &sDir) const { ASSERT( PROFILEMAN != NULL ); @@ -2489,40 +1289,6 @@ void Profile::AddScreenshot( const Screenshot &screenshot ) m_vScreenshots.push_back( screenshot ); } -void Profile::LoadScreenshotDataFromNode( const XNode* pScreenshotData ) -{ - CHECKPOINT_M("Loading the node containing screenshot data."); - - ASSERT( pScreenshotData->GetName() == "ScreenshotData" ); - FOREACH_CONST_Child( pScreenshotData, pScreenshot ) - { - if( pScreenshot->GetName() != "Screenshot" ) - WARN_AND_CONTINUE_M( pScreenshot->GetName() ); - - Screenshot ss; - ss.LoadFromNode( pScreenshot ); - - m_vScreenshots.push_back( ss ); - } -} - -XNode* Profile::SaveScreenshotDataCreateNode() const -{ - CHECKPOINT_M("Getting the node containing screenshot data."); - - const Profile* pProfile = this; - ASSERT( pProfile != NULL ); - - XNode* pNode = new XNode( "ScreenshotData" ); - - FOREACH_CONST( Screenshot, m_vScreenshots, ss ) - { - pNode->AppendChild( ss->CreateNode() ); - } - - return pNode; -} - const Profile::HighScoresForASong *Profile::GetHighScoresForASong( const SongID& songID ) const { map::const_iterator it; @@ -2534,18 +1300,7 @@ const Profile::HighScoresForASong *Profile::GetHighScoresForASong( const SongID& void Profile::MoveBackupToDir( const RString &sFromDir, const RString &sToDir ) { - if( FILEMAN->IsAFile(sFromDir + STATS_XML) && - FILEMAN->IsAFile(sFromDir+STATS_XML+SIGNATURE_APPEND) ) - { - FILEMAN->Move( sFromDir+STATS_XML, sToDir+STATS_XML ); - FILEMAN->Move( sFromDir+STATS_XML+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); - } - else if( FILEMAN->IsAFile(sFromDir + STATS_XML_GZ) && - FILEMAN->IsAFile(sFromDir+STATS_XML_GZ+SIGNATURE_APPEND) ) - { - FILEMAN->Move( sFromDir+STATS_XML_GZ, sToDir+STATS_XML ); - FILEMAN->Move( sFromDir+STATS_XML_GZ+SIGNATURE_APPEND, sToDir+STATS_XML+SIGNATURE_APPEND ); - } + XMLProfile::MoveBackupToDir(sFromDir, sToDir); if( FILEMAN->IsAFile(sFromDir + EDITABLE_INI) ) FILEMAN->Move( sFromDir+EDITABLE_INI, sToDir+EDITABLE_INI ); diff --git a/src/Profile.h b/src/Profile.h index 095c89125f..8f08d8ee6b 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -12,6 +12,7 @@ #include "StepsUtil.h" // for StepsID #include "StyleUtil.h" // for StyleID #include "LuaReference.h" +#include "XMLProfile.h" #include "arch/LoadingWindow/LoadingWindow.h" #include @@ -162,6 +163,7 @@ class Profile FOREACH_ENUM( StepsType,st ) FOREACH_ENUM( RankingCategory,rc ) m_CategoryHighScores[st][rc].Init(); + XMLProf.profile = this; } // smart accessors @@ -258,14 +260,6 @@ class Profile set FavoritedCharts; set PermaMirrorCharts; - XNode* SaveFavoritesCreateNode() const; - XNode* SavePermaMirrorCreateNode() const; - XNode* SaveScoreGoalsCreateNode() const; - XNode* SavePlaylistsCreateNode() const; - void LoadFavoritesFromNode(const XNode *pNode); - void LoadPermaMirrorFromNode(const XNode *pNode); - void LoadScoreGoalsFromNode(const XNode *pNode); - void LoadPlaylistsFromNode(const XNode *pNode); // more future goalman stuff -mina void CreateGoal(const string& ck); @@ -351,49 +345,17 @@ class Profile bool SaveAllToDir( const RString &sDir, bool bSignData ) const; ProfileLoadResult LoadEditableDataFromDir( const RString &sDir ); - ProfileLoadResult LoadStatsXmlFromNode( const XNode* pNode, bool bIgnoreEditable = true ); - void LoadGeneralDataFromNode( const XNode* pNode ); - void LoadSongScoresFromNode( const XNode* pNode ); - void LoadCategoryScoresFromNode( const XNode* pNode ); - void LoadScreenshotDataFromNode( const XNode* pNode ); + + void ImportScoresToEtterna(); void SaveTypeToDir(const RString &dir) const; void SaveEditableDataToDir( const RString &sDir ) const; - bool SaveStatsXmlToDir( RString sDir, bool bSignData ) const; - - XNode* SaveStatsXmlCreateNode() const; - - XNode* SaveGeneralDataCreateNode() const; - XNode* SaveSongScoresCreateNode() const; - - XNode* SaveCategoryScoresCreateNode() const; - XNode* SaveScreenshotDataCreateNode() const; - XNode* SaveCoinDataCreateNode() const; - - // Etterna profile - ProfileLoadResult LoadEttFromDir(RString dir); - ProfileLoadResult LoadEttXmlFromNode(const XNode* pNode); - void LoadEttGeneralDataFromNode(const XNode* pNode); - void LoadEttScoresFromNode(const XNode* pNode); - - // Batch recalc stuff - ProfileLoadResult EoBatchRecalc(const RString & sDir, LoadingWindow * ld); - ProfileLoadResult LoadEttXmlFromNodeForBatchRecalc(const XNode * xml, RString p); - bool SaveEttXmlToDirForBatchRecalc(RString p) const; - - bool SaveEttXmlToDir(RString sDir) const; - XNode* SaveEttGeneralDataCreateNode() const; - XNode* SaveEttScoresCreateNode() const; - XNode* SaveEttXmlCreateNode() const; void CalculateStatsFromScores(LoadingWindow* ld); void CalculateStatsFromScores(); - // For converting to etterna from stats.xml - void LoadStatsXmlForConversion(); - void ImportScoresToEtterna(); void SaveStatsWebPageToDir( const RString &sDir ) const; void SaveMachinePublicKeyToDir( const RString &sDir ) const; @@ -402,6 +364,8 @@ class Profile static RString MakeUniqueFileNameNoExtension( const RString &sDir, const RString &sFileNameBeginning ); static RString MakeFileNameNoExtension( const RString &sFileNameBeginning, int iIndex ); + XMLProfile XMLProf; + // Lua void PushSelf( lua_State *L ); diff --git a/src/XMLProfile.cpp b/src/XMLProfile.cpp new file mode 100644 index 0000000000..5f10d7a9e2 --- /dev/null +++ b/src/XMLProfile.cpp @@ -0,0 +1,1157 @@ + +#include "global.h" +#include "XmlFile.h" +#include "XmlFileUtil.h" +#include "Profile.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "ProfileManager.h" +#include "NoteData.h" +#include "XMLProfile.h" +#include "RageFile.h" +#include "RageFileDriverDeflate.h" +#include "GameState.h" +#include "GameManager.h" +#include "LuaManager.h" +#include "NoteData.h" +#include "RageFileManager.h" + +#include "ScoreManager.h" +#include "CryptManager.h" +#include "Song.h" +#include "SongManager.h" +#include "Steps.h" + +const RString STATS_XML = "Stats.xml"; +const RString STATS_XML_GZ = "Stats.xml.gz"; +const RString ETT_XML = "Etterna.xml"; +const RString ETT_XML_GZ = "Etterna.xml.gz"; +/** @brief The filename containing the signature for STATS_XML's signature. */ +const RString DONT_SHARE_SIG = "DontShare.sig"; +static Preference g_bProfileDataCompress("ProfileDataCompress", false); + + +// Loading and saving +#define WARN_PARSER ShowWarningOrTrace( __FILE__, __LINE__, "Error parsing file.", true ) +#define WARN_AND_RETURN { WARN_PARSER; return; } +#define WARN_AND_CONTINUE { WARN_PARSER; continue; } +#define WARN_AND_BREAK { WARN_PARSER; break; } +#define WARN_M(m) ShowWarningOrTrace( __FILE__, __LINE__, RString("Error parsing file: ")+(m), true ) +#define WARN_AND_RETURN_M(m) { WARN_M(m); return; } +#define WARN_AND_CONTINUE_M(m) { WARN_M(m); continue; } +#define WARN_AND_BREAK_M(m) { WARN_M(m); break; } + + +#define LOAD_NODE(X) { \ + const XNode* X = xml->GetChild(#X); \ + if( X==NULL ) LOG->Warn("Failed to read section " #X); \ + else Load##X##FromNode(X); } +ProfileLoadResult XMLProfile::LoadStatsFromDir(RString dir, bool require_signature) +{ + dir += PROFILEMAN->GetStatsPrefix(); + this->profiledir = dir; + // 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; + unique_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); + } + + 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); +} + + +ProfileLoadResult XMLProfile::LoadEttFromDir(RString dir) { + dir += PROFILEMAN->GetStatsPrefix(); + profiledir = dir; + profile->IsEtternaProfile = true; + RString fn = dir + ETT_XML; + bool compressed = false; + if (!IsAFile(fn)) { + fn = dir + STATS_XML_GZ; + compressed = true; + if (!IsAFile(fn)) + return ProfileLoadResult_FailedNoProfile; + } + + int iError; + unique_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); + } + + LOG->Trace("Loading %s", fn.c_str()); + XNode xml; + if (!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) + return ProfileLoadResult_FailedTampered; + LOG->Trace("Done."); + + return LoadEttXmlFromNode(&xml); +} + +bool XMLProfile::SaveStatsXmlToDir(RString sDir, bool bSignData) +{ + LOG->Trace("SaveStatsXmlToDir: %s", sDir.c_str()); + unique_ptr xml(SaveStatsXmlCreateNode()); + + sDir += PROFILEMAN->GetStatsPrefix(); + // Save stats.xml + RString fn = sDir + (g_bProfileDataCompress ? STATS_XML_GZ : STATS_XML); + + { + RString sError; + RageFile f; + if (!f.Open(fn, RageFile::WRITE)) + { + LuaHelpers::ReportScriptErrorFmt("Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str()); + return false; + } + + if (g_bProfileDataCompress) + { + RageFileObjGzip gzip(&f); + gzip.Start(); + if (!XmlFileUtil::SaveToFile(xml.get(), gzip, "", false)) + return false; + + if (gzip.Finish() == -1) + return false; + + /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ + if (FILEMAN->IsAFile(sDir + STATS_XML)) + FILEMAN->Remove(sDir + STATS_XML); + } + else + { + if (!XmlFileUtil::SaveToFile(xml.get(), f, "", false)) + return false; + + /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ + if (FILEMAN->IsAFile(sDir + STATS_XML_GZ)) + FILEMAN->Remove(sDir + STATS_XML_GZ); + } + } + + if (bSignData) + { + RString sStatsXmlSigFile = fn + SIGNATURE_APPEND; + CryptManager::SignFileToFile(fn, sStatsXmlSigFile); + + // Save the "don't share" file + RString sDontShareFile = sDir + DONT_SHARE_SIG; + CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); + } + + return true; +} + +bool XMLProfile::SaveEttXmlToDir(RString sDir) const { + LOG->Trace("Saving Etterna Profile to: %s", sDir.c_str()); + unique_ptr xml(SaveEttXmlCreateNode()); + sDir += PROFILEMAN->GetStatsPrefix(); + // Save Etterna.xml + RString fn = sDir + ETT_XML; + { + RString sError; + RageFile f; + if (!f.Open(fn, RageFile::WRITE)) + { + LuaHelpers::ReportScriptErrorFmt("Couldn't open %s for writing: %s", fn.c_str(), f.GetError().c_str()); + return false; + } + + if (g_bProfileDataCompress) + { + RageFileObjGzip gzip(&f); + gzip.Start(); + if (!XmlFileUtil::SaveToFile(xml.get(), gzip, "", false)) + return false; + + if (gzip.Finish() == -1) + return false; + + /* After successfully saving STATS_XML_GZ, remove any stray STATS_XML. */ + if (FILEMAN->IsAFile(sDir + STATS_XML)) + FILEMAN->Remove(sDir + STATS_XML); + } + else + { + if (!XmlFileUtil::SaveToFile(xml.get(), f, "", false)) + return false; + + /* After successfully saving STATS_XML, remove any stray STATS_XML_GZ. */ + if (FILEMAN->IsAFile(sDir + STATS_XML_GZ)) + FILEMAN->Remove(sDir + STATS_XML_GZ); + } + } + + return true; +} + +XNode* XMLProfile::SaveGeneralDataCreateNode() const +{ + XNode* pGeneralDataNode = new XNode("GeneralData"); + + // TRICKY: These are write-only elements that are normally never read again. + // This data is required by other apps (like internet ranking), but is + // redundant to the game app. + pGeneralDataNode->AppendChild("DisplayName", profile->GetDisplayNameOrHighScoreName()); + pGeneralDataNode->AppendChild("CharacterID", profile->m_sCharacterID); + pGeneralDataNode->AppendChild("LastUsedHighScoreName", profile->m_sLastUsedHighScoreName); + pGeneralDataNode->AppendChild("Guid", profile->m_sGuid); + pGeneralDataNode->AppendChild("SortOrder", SortOrderToString(profile->m_SortOrder)); + pGeneralDataNode->AppendChild("LastDifficulty", DifficultyToString(profile->m_LastDifficulty)); + if (profile->m_LastStepsType != StepsType_Invalid) + pGeneralDataNode->AppendChild("LastStepsType", GAMEMAN->GetStepsTypeInfo(profile->m_LastStepsType).szName); + pGeneralDataNode->AppendChild(profile->m_lastSong.CreateNode()); + pGeneralDataNode->AppendChild("CurrentCombo", profile->m_iCurrentCombo); + pGeneralDataNode->AppendChild("TotalSessions", profile->m_iTotalSessions); + pGeneralDataNode->AppendChild("TotalSessionSeconds", profile->m_iTotalSessionSeconds); + pGeneralDataNode->AppendChild("TotalGameplaySeconds", profile->m_iTotalGameplaySeconds); + pGeneralDataNode->AppendChild("LastPlayedMachineGuid", profile->m_sLastPlayedMachineGuid); + pGeneralDataNode->AppendChild("LastPlayedDate", profile->m_LastPlayedDate.GetString()); + pGeneralDataNode->AppendChild("TotalDancePoints", profile->m_iTotalDancePoints); + pGeneralDataNode->AppendChild("NumExtraStagesPassed", profile->m_iNumExtraStagesPassed); + pGeneralDataNode->AppendChild("NumExtraStagesFailed", profile->m_iNumExtraStagesFailed); + pGeneralDataNode->AppendChild("NumToasties", profile->m_iNumToasties); + pGeneralDataNode->AppendChild("TotalTapsAndHolds", profile->m_iTotalTapsAndHolds); + pGeneralDataNode->AppendChild("TotalJumps", profile->m_iTotalJumps); + pGeneralDataNode->AppendChild("TotalHolds", profile->m_iTotalHolds); + pGeneralDataNode->AppendChild("TotalRolls", profile->m_iTotalRolls); + pGeneralDataNode->AppendChild("TotalMines", profile->m_iTotalMines); + pGeneralDataNode->AppendChild("TotalHands", profile->m_iTotalHands); + pGeneralDataNode->AppendChild("TotalLifts", profile->m_iTotalLifts); + pGeneralDataNode->AppendChild("PlayerRating", profile->m_fPlayerRating); + + // Keep declared variables in a very local scope so they aren't + // accidentally used where they're not intended. There's a lot of + // copying and pasting in this code. + + { + XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); + FOREACHM_CONST(RString, RString, profile->m_sDefaultModifiers, it) + pDefaultModifiers->AppendChild(it->first, it->second); + } + + { + XNode* pFavorites = pGeneralDataNode->AppendChild("Favorites"); + FOREACHS_CONST(string, profile->FavoritedCharts, it) + pFavorites->AppendChild(*it); + } + + { + XNode* pPlayerSkillsets = pGeneralDataNode->AppendChild("PlayerSkillsets"); + FOREACH_ENUM(Skillset, ss) + pPlayerSkillsets->AppendChild(SkillsetToString(ss), profile->m_fPlayerSkillsets[ss]); + } + + { + XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode"); + FOREACH_ENUM(PlayMode, pm) + { + // Don't save unplayed PlayModes. + if (!profile->m_iNumSongsPlayedByPlayMode[pm]) + continue; + pNumSongsPlayedByPlayMode->AppendChild(PlayModeToString(pm), profile->m_iNumSongsPlayedByPlayMode[pm]); + } + } + + { + XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); + FOREACHM_CONST(StyleID, int, profile->m_iNumSongsPlayedByStyle, iter) + { + const StyleID &s = iter->first; + int iNumPlays = iter->second; + + XNode *pStyleNode = s.CreateNode(); + pStyleNode->AppendAttr(XNode::TEXT_ATTRIBUTE, iNumPlays); + + pNumSongsPlayedByStyle->AppendChild(pStyleNode); + } + } + + { + XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty"); + FOREACH_ENUM(Difficulty, dc) + { + if (!profile->m_iNumSongsPlayedByDifficulty[dc]) + continue; + pNumSongsPlayedByDifficulty->AppendChild(DifficultyToString(dc), profile->m_iNumSongsPlayedByDifficulty[dc]); + } + } + + { + XNode* pNumSongsPlayedByMeter = pGeneralDataNode->AppendChild("NumSongsPlayedByMeter"); + for (int i = 0; im_iNumSongsPlayedByMeter[i]) + continue; + pNumSongsPlayedByMeter->AppendChild(ssprintf("Meter%d", i), profile->m_iNumSongsPlayedByMeter[i]); + } + } + + pGeneralDataNode->AppendChild("NumTotalSongsPlayed", profile->m_iNumTotalSongsPlayed); + + { + XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); + FOREACH_ENUM(PlayMode, pm) + { + // Don't save unplayed PlayModes. + if (!profile->m_iNumStagesPassedByPlayMode[pm]) + continue; + pNumStagesPassedByPlayMode->AppendChild(PlayModeToString(pm), profile->m_iNumStagesPassedByPlayMode[pm]); + } + } + + { + XNode* pNumStagesPassedByGrade = pGeneralDataNode->AppendChild("NumStagesPassedByGrade"); + FOREACH_ENUM(Grade, g) + { + if (!profile->m_iNumStagesPassedByGrade[g]) + continue; + pNumStagesPassedByGrade->AppendChild(GradeToString(g), profile->m_iNumStagesPassedByGrade[g]); + } + } + + // Load Lua UserTable from profile + if (profile->m_UserTable.IsSet()) + { + Lua *L = LUA->Get(); + profile->m_UserTable.PushSelf(L); + XNode* pUserTable = XmlFileUtil::XNodeFromTable(L); + LUA->Release(L); + + // XXX: XNodeFromTable returns a root node with the name "Layer". + pUserTable->m_sName = "UserTable"; + pGeneralDataNode->AppendChild(pUserTable); + } + + return pGeneralDataNode; +} + +XNode* XMLProfile::SaveFavoritesCreateNode() const { + CHECKPOINT_M("Saving the favorites node."); + + XNode* favs = new XNode("Favorites"); + FOREACHS_CONST(string, profile->FavoritedCharts, it) + favs->AppendChild(*it); + return favs; +} + +XNode* XMLProfile::SavePermaMirrorCreateNode() const { + CHECKPOINT_M("Saving the permamirror node."); + + XNode* pmir = new XNode("PermaMirror"); + FOREACHS_CONST(string, profile->PermaMirrorCharts, it) + pmir->AppendChild(*it); + return pmir; +} + +XNode* GoalsForChart::CreateNode() const { + XNode* cg = new XNode("GoalsForChart"); + + if (!goals.empty()) { + cg->AppendAttr("Key", goals[0].chartkey); + FOREACH_CONST(ScoreGoal, goals, sg) + cg->AppendChild(sg->CreateNode()); + } + return cg; +} + +XNode* XMLProfile::SaveScoreGoalsCreateNode() const { + CHECKPOINT_M("Saving the scoregoals node."); + + XNode* goals = new XNode("ScoreGoals"); + FOREACHUM_CONST(string, GoalsForChart, profile->goalmap, i) { + const GoalsForChart& cg = i->second; + goals->AppendChild(cg.CreateNode()); + } + return goals; +} + +XNode* XMLProfile::SavePlaylistsCreateNode() const { + CHECKPOINT_M("Saving the playlists node."); + + XNode* playlists = new XNode("Playlists"); + auto& pls = SONGMAN->allplaylists; + FOREACHM(string, Playlist, pls, i) + if (i->first != "" && i->first != "Favorites") + playlists->AppendChild(i->second.CreateNode()); + return playlists; +} + +void XMLProfile::LoadFavoritesFromNode(const XNode *pNode) { + CHECKPOINT_M("Loading the favorites node."); + + FOREACH_CONST_Child(pNode, ck) + profile->FavoritedCharts.emplace(SONGMAN->ReconcileBustedKeys(ck->GetName())); + + SONGMAN->SetFavoritedStatus(profile->FavoritedCharts); + SONGMAN->MakePlaylistFromFavorites(profile->FavoritedCharts); +} + +void XMLProfile::LoadPermaMirrorFromNode(const XNode *pNode) { + CHECKPOINT_M("Loading the permamirror node."); + + FOREACH_CONST_Child(pNode, ck) + profile->PermaMirrorCharts.emplace(SONGMAN->ReconcileBustedKeys(ck->GetName())); + + SONGMAN->SetPermaMirroredStatus(profile->PermaMirrorCharts); +} + +void GoalsForChart::LoadFromNode(const XNode *pNode) { + FOREACH_CONST_Child(pNode, sg) { + ScoreGoal doot; + doot.LoadFromNode(sg); + Add(doot); + } +} + +void XMLProfile::LoadScoreGoalsFromNode(const XNode *pNode) { + CHECKPOINT_M("Loading the scoregoals node."); + + RString ck; + FOREACH_CONST_Child(pNode, chgoals) { + chgoals->GetAttrValue("Key", ck); + ck = SONGMAN->ReconcileBustedKeys(ck); + profile->goalmap[ck].LoadFromNode(chgoals); + } + SONGMAN->SetHasGoal(profile->goalmap); +} + +void XMLProfile::LoadPlaylistsFromNode(const XNode *pNode) { + CHECKPOINT_M("Loading the playlists node."); + + auto& pls = SONGMAN->allplaylists; + FOREACH_CONST_Child(pNode, pl) { + Playlist tmp; + tmp.LoadFromNode(pl); + pls.emplace(tmp.name, tmp); + SONGMAN->activeplaylist = tmp.name; + } +} + + +XNode* XMLProfile::SaveEttGeneralDataCreateNode() const { + CHECKPOINT_M("Saving the general node."); + + XNode* pGeneralDataNode = new XNode("GeneralData"); + + // TRICKY: These are write-only elements that are normally never read again. + // This data is required by other apps (like internet ranking), but is + // redundant to the game app. + pGeneralDataNode->AppendChild("DisplayName", profile->GetDisplayNameOrHighScoreName()); + pGeneralDataNode->AppendChild("CharacterID", profile->m_sCharacterID); + pGeneralDataNode->AppendChild("Guid", profile->m_sGuid); + pGeneralDataNode->AppendChild("SortOrder", SortOrderToString(profile->m_SortOrder)); + pGeneralDataNode->AppendChild("LastDifficulty", DifficultyToString(profile->m_LastDifficulty)); + if (profile->m_LastStepsType != StepsType_Invalid) + pGeneralDataNode->AppendChild("LastStepsType", GAMEMAN->GetStepsTypeInfo(profile->m_LastStepsType).szName); + pGeneralDataNode->AppendChild(profile->m_lastSong.CreateNode()); + pGeneralDataNode->AppendChild("TotalSessions", profile->m_iTotalSessions); + pGeneralDataNode->AppendChild("TotalSessionSeconds", profile->m_iTotalSessionSeconds); + pGeneralDataNode->AppendChild("TotalGameplaySeconds", profile->m_iTotalGameplaySeconds); + pGeneralDataNode->AppendChild("LastPlayedMachineGuid", profile->m_sLastPlayedMachineGuid); + pGeneralDataNode->AppendChild("LastPlayedDate", profile->m_LastPlayedDate.GetString()); + pGeneralDataNode->AppendChild("TotalDancePoints", profile->m_iTotalDancePoints); + pGeneralDataNode->AppendChild("NumToasties", profile->m_iNumToasties); + pGeneralDataNode->AppendChild("TotalTapsAndHolds", profile->m_iTotalTapsAndHolds); + pGeneralDataNode->AppendChild("TotalJumps", profile->m_iTotalJumps); + pGeneralDataNode->AppendChild("TotalHolds", profile->m_iTotalHolds); + pGeneralDataNode->AppendChild("TotalRolls", profile->m_iTotalRolls); + pGeneralDataNode->AppendChild("TotalMines", profile->m_iTotalMines); + pGeneralDataNode->AppendChild("TotalHands", profile->m_iTotalHands); + pGeneralDataNode->AppendChild("TotalLifts", profile->m_iTotalLifts); + pGeneralDataNode->AppendChild("PlayerRating", profile->m_fPlayerRating); + + // apparently this got ripped out in the course of streamlining things -mina + GAMESTATE->SaveCurrentSettingsToProfile(PLAYER_1); + + // Keep declared variables in a very local scope so they aren't + // accidentally used where they're not intended. There's a lot of + // copying and pasting in this code. + + { + XNode* pDefaultModifiers = pGeneralDataNode->AppendChild("DefaultModifiers"); + FOREACHM_CONST(RString, RString, profile->m_sDefaultModifiers, it) + pDefaultModifiers->AppendChild(it->first, it->second); + } + + { + XNode* pPlayerSkillsets = pGeneralDataNode->AppendChild("PlayerSkillsets"); + FOREACH_ENUM(Skillset, ss) + pPlayerSkillsets->AppendChild(SkillsetToString(ss), profile->m_fPlayerSkillsets[ss]); + } + + pGeneralDataNode->AppendChild("NumTotalSongsPlayed", profile->m_iNumTotalSongsPlayed); + + { + XNode* pNumStagesPassedByPlayMode = pGeneralDataNode->AppendChild("NumStagesPassedByPlayMode"); + FOREACH_ENUM(PlayMode, pm) + { + // Don't save unplayed PlayModes. + if (!profile->m_iNumStagesPassedByPlayMode[pm]) + continue; + pNumStagesPassedByPlayMode->AppendChild(PlayModeToString(pm), profile->m_iNumStagesPassedByPlayMode[pm]); + } + } + + // Load Lua UserTable from profile + if (profile->m_UserTable.IsSet()) + { + Lua *L = LUA->Get(); + profile->m_UserTable.PushSelf(L); + XNode* pUserTable = XmlFileUtil::XNodeFromTable(L); + LUA->Release(L); + + // XXX: XNodeFromTable returns a root node with the name "Layer". + pUserTable->m_sName = "UserTable"; + pGeneralDataNode->AppendChild(pUserTable); + } + + return pGeneralDataNode; +} + +void XMLProfile::LoadStatsXmlForConversion() { + string dir = profiledir; + RString fn = dir + STATS_XML; + bool compressed = false; + if (!IsAFile(fn)) { + fn = dir + STATS_XML_GZ; + compressed = true; + if (!IsAFile(fn)) { + return; + } + } + + int iError; + unique_ptr pFile(FILEMAN->Open(fn, RageFile::READ, iError)); + if (pFile.get() == NULL) + return; + + if (compressed) { + RString sError; + uint32_t iCRC32; + RageFileObjInflate *pInflate = GunzipFile(pFile.release(), sError, &iCRC32); + if (pInflate == NULL) + return; + + pFile.reset(pInflate); + } + + XNode xml; + if (!XmlFileUtil::LoadFromFileShowErrors(xml, *pFile.get())) + return; + + XNode* scores = xml.GetChild("SongScores"); + LoadSongScoresFromNode(scores); +} + + +void XMLProfile::MoveBackupToDir(const RString &sFromDir, const RString &sToDir) +{ + if (FILEMAN->IsAFile(sFromDir + STATS_XML) && + FILEMAN->IsAFile(sFromDir + STATS_XML + SIGNATURE_APPEND)) + { + FILEMAN->Move(sFromDir + STATS_XML, sToDir + STATS_XML); + FILEMAN->Move(sFromDir + STATS_XML + SIGNATURE_APPEND, sToDir + STATS_XML + SIGNATURE_APPEND); + } + else if (FILEMAN->IsAFile(sFromDir + STATS_XML_GZ) && + FILEMAN->IsAFile(sFromDir + STATS_XML_GZ + SIGNATURE_APPEND)) + { + FILEMAN->Move(sFromDir + STATS_XML_GZ, sToDir + STATS_XML); + FILEMAN->Move(sFromDir + STATS_XML_GZ + SIGNATURE_APPEND, sToDir + STATS_XML + SIGNATURE_APPEND); + } +} + + +void XMLProfile::LoadGeneralDataFromNode(const XNode* pNode) +{ + ASSERT(pNode->GetName() == "GeneralData"); + + RString s; + const XNode* pTemp; + + pNode->GetChildValue("DisplayName", profile->m_sDisplayName); + pNode->GetChildValue("CharacterID", profile->m_sCharacterID); + pNode->GetChildValue("LastUsedHighScoreName", profile->m_sLastUsedHighScoreName); + pNode->GetChildValue("Guid", profile->m_sGuid); + pNode->GetChildValue("SortOrder", s); profile->m_SortOrder = StringToSortOrder(s); + pNode->GetChildValue("LastDifficulty", s); profile->m_LastDifficulty = StringToDifficulty(s); + pNode->GetChildValue("LastStepsType", s); profile->m_LastStepsType = GAMEMAN->StringToStepsType(s); + pTemp = pNode->GetChild("Song"); if (pTemp) profile->m_lastSong.LoadFromNode(pTemp); + pNode->GetChildValue("CurrentCombo", profile->m_iCurrentCombo); + pNode->GetChildValue("TotalSessions", profile->m_iTotalSessions); + pNode->GetChildValue("TotalSessionSeconds", profile->m_iTotalSessionSeconds); + pNode->GetChildValue("TotalGameplaySeconds", profile->m_iTotalGameplaySeconds); + pNode->GetChildValue("LastPlayedMachineGuid", profile->m_sLastPlayedMachineGuid); + pNode->GetChildValue("LastPlayedDate", s); profile->m_LastPlayedDate.FromString(s); + pNode->GetChildValue("TotalDancePoints", profile->m_iTotalDancePoints); + pNode->GetChildValue("NumExtraStagesPassed", profile->m_iNumExtraStagesPassed); + pNode->GetChildValue("NumExtraStagesFailed", profile->m_iNumExtraStagesFailed); + pNode->GetChildValue("NumToasties", profile->m_iNumToasties); + pNode->GetChildValue("TotalTapsAndHolds", profile->m_iTotalTapsAndHolds); + pNode->GetChildValue("TotalJumps", profile->m_iTotalJumps); + pNode->GetChildValue("TotalHolds", profile->m_iTotalHolds); + pNode->GetChildValue("TotalRolls", profile->m_iTotalRolls); + pNode->GetChildValue("TotalMines", profile->m_iTotalMines); + pNode->GetChildValue("TotalHands", profile->m_iTotalHands); + pNode->GetChildValue("TotalLifts", profile->m_iTotalLifts); + pNode->GetChildValue("PlayerRating", profile->m_fPlayerRating); + + { + const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); + if (pDefaultModifiers) + { + FOREACH_CONST_Child(pDefaultModifiers, game_type) + { + game_type->GetTextValue(profile->m_sDefaultModifiers[game_type->GetName()]); + } + } + } + + { + const XNode* pFavorites = pNode->GetChild("Favorites"); + if (pFavorites) { + FOREACH_CONST_Child(pFavorites, ck) { + RString tmp = ck->GetName(); // handle duplicated entries caused by an oversight - mina + bool duplicated = false; + FOREACHS(string, profile->FavoritedCharts, chartkey) + if (*chartkey == tmp) + duplicated = true; + if (!duplicated) + profile->FavoritedCharts.emplace(tmp); + } + SONGMAN->SetFavoritedStatus(profile->FavoritedCharts); + } + } + + { + const XNode* pPlayerSkillsets = pNode->GetChild("PlayerSkillsets"); + if (pPlayerSkillsets) { + FOREACH_ENUM(Skillset, ss) + pPlayerSkillsets->GetChildValue(SkillsetToString(ss), profile->m_fPlayerSkillsets[ss]); + } + } + + { + const XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode"); + if (pNumSongsPlayedByPlayMode) + FOREACH_ENUM(PlayMode, pm) + pNumSongsPlayedByPlayMode->GetChildValue(PlayModeToString(pm), profile->m_iNumSongsPlayedByPlayMode[pm]); + } + + { + const XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle"); + if (pNumSongsPlayedByStyle) + { + FOREACH_CONST_Child(pNumSongsPlayedByStyle, style) + { + if (style->GetName() != "Style") + continue; + + StyleID sID; + sID.LoadFromNode(style); + + if (!sID.IsValid()) + WARN_AND_CONTINUE; + + style->GetTextValue(profile->m_iNumSongsPlayedByStyle[sID]); + } + } + } + + { + const XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty"); + if (pNumSongsPlayedByDifficulty) + FOREACH_ENUM(Difficulty, dc) + pNumSongsPlayedByDifficulty->GetChildValue(DifficultyToString(dc), profile->m_iNumSongsPlayedByDifficulty[dc]); + } + + { + const XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter"); + if (pNumSongsPlayedByMeter) + for (int i = 0; iGetChildValue(ssprintf("Meter%d", i), profile->m_iNumSongsPlayedByMeter[i]); + } + + pNode->GetChildValue("NumTotalSongsPlayed", profile->m_iNumTotalSongsPlayed); + + { + const XNode* pNumStagesPassedByGrade = pNode->GetChild("NumStagesPassedByGrade"); + if (pNumStagesPassedByGrade) + FOREACH_ENUM(Grade, g) + pNumStagesPassedByGrade->GetChildValue(GradeToString(g), profile->m_iNumStagesPassedByGrade[g]); + } + + { + const XNode* pNumStagesPassedByPlayMode = pNode->GetChild("NumStagesPassedByPlayMode"); + if (pNumStagesPassedByPlayMode) + FOREACH_ENUM(PlayMode, pm) + pNumStagesPassedByPlayMode->GetChildValue(PlayModeToString(pm), profile->m_iNumStagesPassedByPlayMode[pm]); + + } + + const XNode *pUserTable = pNode->GetChild("UserTable"); + + Lua *L = LUA->Get(); + + // If we have custom data, load it. Otherwise, make a blank table. + if (pUserTable) + LuaHelpers::CreateTableFromXNode(L, pUserTable); + else + lua_newtable(L); + + profile->m_UserTable.SetFromStack(L); + LUA->Release(L); + +} + + +void XMLProfile::LoadEttGeneralDataFromNode(const XNode* pNode) { + CHECKPOINT_M("Loading the general node."); + ASSERT(pNode->GetName() == "GeneralData"); + + RString s; + const XNode* pTemp; + + pNode->GetChildValue("DisplayName", profile->m_sDisplayName); + pNode->GetChildValue("CharacterID", profile->m_sCharacterID); + pNode->GetChildValue("LastUsedHighScoreName", profile->m_sLastUsedHighScoreName); + pNode->GetChildValue("Guid", profile->m_sGuid); + pNode->GetChildValue("SortOrder", s); profile->m_SortOrder = StringToSortOrder(s); + pNode->GetChildValue("LastDifficulty", s); profile->m_LastDifficulty = StringToDifficulty(s); + pNode->GetChildValue("LastStepsType", s); profile->m_LastStepsType = GAMEMAN->StringToStepsType(s); + pTemp = pNode->GetChild("Song"); if (pTemp) profile->m_lastSong.LoadFromNode(pTemp); + pNode->GetChildValue("CurrentCombo", profile->m_iCurrentCombo); + pNode->GetChildValue("TotalSessions", profile->m_iTotalSessions); + pNode->GetChildValue("TotalSessionSeconds", profile->m_iTotalSessionSeconds); + pNode->GetChildValue("TotalGameplaySeconds", profile->m_iTotalGameplaySeconds); + pNode->GetChildValue("LastPlayedDate", s); profile->m_LastPlayedDate.FromString(s); + pNode->GetChildValue("TotalDancePoints", profile->m_iTotalDancePoints); + pNode->GetChildValue("NumToasties", profile->m_iNumToasties); + pNode->GetChildValue("TotalTapsAndHolds", profile->m_iTotalTapsAndHolds); + pNode->GetChildValue("TotalJumps", profile->m_iTotalJumps); + pNode->GetChildValue("TotalHolds", profile->m_iTotalHolds); + pNode->GetChildValue("TotalRolls", profile->m_iTotalRolls); + pNode->GetChildValue("TotalMines", profile->m_iTotalMines); + pNode->GetChildValue("TotalHands", profile->m_iTotalHands); + pNode->GetChildValue("TotalLifts", profile->m_iTotalLifts); + pNode->GetChildValue("PlayerRating", profile->m_fPlayerRating); + + { + const XNode* pDefaultModifiers = pNode->GetChild("DefaultModifiers"); + if (pDefaultModifiers) + { + FOREACH_CONST_Child(pDefaultModifiers, game_type) + { + game_type->GetTextValue(profile->m_sDefaultModifiers[game_type->GetName()]); + } + } + } + + { + const XNode* pPlayerSkillsets = pNode->GetChild("PlayerSkillsets"); + if (pPlayerSkillsets) { + FOREACH_ENUM(Skillset, ss) + pPlayerSkillsets->GetChildValue(SkillsetToString(ss), profile->m_fPlayerSkillsets[ss]); + } + } + + const XNode *pUserTable = pNode->GetChild("UserTable"); + + Lua *L = LUA->Get(); + + // If we have custom data, load it. Otherwise, make a blank table. + if (pUserTable) + LuaHelpers::CreateTableFromXNode(L, pUserTable); + else + lua_newtable(L); + + profile->m_UserTable.SetFromStack(L); + LUA->Release(L); + +} + +XNode* XMLProfile::SaveSongScoresCreateNode() const +{ + CHECKPOINT_M("Getting the node to save song scores."); + + ASSERT(profile != NULL); + + XNode* pNode = new XNode("SongScores"); + + FOREACHM_CONST(SongID, Profile::HighScoresForASong, profile->m_SongHighScores, i) + { + const SongID &songID = i->first; + const Profile::HighScoresForASong &hsSong = i->second; + + // skip songs that have never been played + if (profile->GetSongNumTimesPlayed(songID) == 0) + continue; + + XNode* pSongNode = pNode->AppendChild(songID.CreateNode()); + + int jCheck2 = hsSong.m_StepsHighScores.size(); + int jCheck1 = 0; + FOREACHM_CONST(StepsID, Profile::HighScoresForASteps, hsSong.m_StepsHighScores, j) + { + jCheck1++; + ASSERT(jCheck1 <= jCheck2); + const StepsID &stepsID = j->first; + const Profile::HighScoresForASteps &hsSteps = j->second; + + const HighScoreList &hsl = hsSteps.hsl; + + // skip steps that have never been played + if (hsl.GetNumTimesPlayed() == 0) + continue; + + XNode* pStepsNode = pSongNode->AppendChild(stepsID.CreateNode()); + + pStepsNode->AppendChild(hsl.CreateNode()); + } + } + + return pNode; +} + + +XNode* XMLProfile::SaveEttScoresCreateNode() const { + CHECKPOINT_M("Saving the player scores node."); + + ASSERT(profile != NULL); + XNode* pNode = SCOREMAN->CreateNode(); + return pNode; +} + +void XMLProfile::LoadEttScoresFromNode(const XNode* pSongScores) { + CHECKPOINT_M("Loading the player scores node."); + SCOREMAN->LoadFromNode(pSongScores); +} + + +void XMLProfile::LoadScreenshotDataFromNode(const XNode* pScreenshotData) +{ + CHECKPOINT_M("Loading the node containing screenshot data."); + + ASSERT(pScreenshotData->GetName() == "ScreenshotData"); + FOREACH_CONST_Child(pScreenshotData, pScreenshot) + { + if (pScreenshot->GetName() != "Screenshot") + WARN_AND_CONTINUE_M(pScreenshot->GetName()); + + Screenshot ss; + ss.LoadFromNode(pScreenshot); + + profile->m_vScreenshots.push_back(ss); + } +} + +XNode* XMLProfile::SaveScreenshotDataCreateNode() const +{ + CHECKPOINT_M("Getting the node containing screenshot data."); + + ASSERT(profile != NULL); + + XNode* pNode = new XNode("ScreenshotData"); + + FOREACH_CONST(Screenshot, profile->m_vScreenshots, ss) + { + pNode->AppendChild(ss->CreateNode()); + } + + return pNode; +} + +void XMLProfile::LoadCategoryScoresFromNode(const XNode* pCategoryScores) +{ + CHECKPOINT_M("Loading the node that contains category scores."); + + ASSERT(pCategoryScores->GetName() == "CategoryScores"); + + FOREACH_CONST_Child(pCategoryScores, pStepsType) + { + if (pStepsType->GetName() != "StepsType") + continue; + + RString str; + if (!pStepsType->GetAttrValue("Type", str)) + WARN_AND_CONTINUE; + StepsType st = GAMEMAN->StringToStepsType(str); + if (st == StepsType_Invalid) + WARN_AND_CONTINUE_M(str); + + FOREACH_CONST_Child(pStepsType, pRadarCategory) + { + if (pRadarCategory->GetName() != "RankingCategory") + continue; + + if (!pRadarCategory->GetAttrValue("Type", str)) + WARN_AND_CONTINUE; + RankingCategory rc = StringToRankingCategory(str); + if (rc == RankingCategory_Invalid) + WARN_AND_CONTINUE_M(str); + + const XNode *pHighScoreListNode = pRadarCategory->GetChild("HighScoreList"); + if (pHighScoreListNode == NULL) + WARN_AND_CONTINUE; + + HighScoreList &hsl = profile->GetCategoryHighScoreList(st, rc); + hsl.LoadFromNode(pHighScoreListNode); + } + } +} + + +XNode* XMLProfile::SaveCategoryScoresCreateNode() const +{ + CHECKPOINT_M("Getting the node that saves category scores."); + + ASSERT(profile != NULL); + + XNode* pNode = new XNode("CategoryScores"); + + FOREACH_ENUM(StepsType, st) + { + // skip steps types that have never been played + if (profile->GetCategoryNumTimesPlayed(st) == 0) + continue; + + XNode* pStepsTypeNode = pNode->AppendChild("StepsType"); + pStepsTypeNode->AppendAttr("Type", GAMEMAN->GetStepsTypeInfo(st).szName); + + FOREACH_ENUM(RankingCategory, rc) + { + // skip steps types/categories that have never been played + if (profile->GetCategoryHighScoreList(st, rc).GetNumTimesPlayed() == 0) + continue; + + XNode* pRankingCategoryNode = pStepsTypeNode->AppendChild("RankingCategory"); + pRankingCategoryNode->AppendAttr("Type", RankingCategoryToString(rc)); + + const HighScoreList &hsl = profile->GetCategoryHighScoreList((StepsType)st, (RankingCategory)rc); + + pRankingCategoryNode->AppendChild(hsl.CreateNode()); + } + } + + return pNode; +} + + +ProfileLoadResult XMLProfile::LoadEttXmlFromNode(const XNode *xml) { + /* The placeholder stats.xml file has an tag. Don't load it, + * but don't warn about it. */ + if (xml->GetName() == "html") + return ProfileLoadResult_FailedNoProfile; + + if (xml->GetName() != "Stats") + { + WARN_M(xml->GetName()); + return ProfileLoadResult_FailedTampered; + } + + const XNode* gen = xml->GetChild("GeneralData"); + if (gen) + LoadEttGeneralDataFromNode(gen); + + const XNode* favs = xml->GetChild("Favorites"); + if (favs) + LoadFavoritesFromNode(favs); + + const XNode* pmir = xml->GetChild("PermaMirror"); + if (pmir) + LoadPermaMirrorFromNode(pmir); + + const XNode* goals = xml->GetChild("ScoreGoals"); + if (goals) + LoadScoreGoalsFromNode(goals); + + const XNode* play = xml->GetChild("Playlists"); + if (play) + LoadPlaylistsFromNode(play); + + const XNode* scores = xml->GetChild("PlayerScores"); + if (scores) + LoadEttScoresFromNode(scores); + + return ProfileLoadResult_Success; +} + +ProfileLoadResult XMLProfile::LoadStatsXmlFromNode(const XNode *xml, bool bIgnoreEditable) +{ + /* The placeholder stats.xml file has an tag. Don't load it, + * but don't warn about it. */ + if (xml->GetName() == "html") + return ProfileLoadResult_FailedNoProfile; + + if (xml->GetName() != "Stats") + { + WARN_M(xml->GetName()); + return ProfileLoadResult_FailedTampered; + } + + // These are loaded from Editable, so we usually want to ignore them here. + RString sName = profile->m_sDisplayName; + RString sCharacterID = profile->m_sCharacterID; + RString sLastUsedHighScoreName = profile->m_sLastUsedHighScoreName; + + LOAD_NODE(GeneralData); + LOAD_NODE(SongScores); + LOAD_NODE(CategoryScores); + LOAD_NODE(ScreenshotData); + + if (bIgnoreEditable) + { + profile->m_sDisplayName = sName; + profile->m_sCharacterID = sCharacterID; + profile->m_sLastUsedHighScoreName = sLastUsedHighScoreName; + } + + return ProfileLoadResult_Success; +} + + +void XMLProfile::LoadSongScoresFromNode(const XNode* pSongScores) +{ + CHECKPOINT_M("Loading the node that contains song scores."); + + ASSERT(pSongScores->GetName() == "SongScores"); + + FOREACH_CONST_Child(pSongScores, pSong) + { + if (pSong->GetName() != "Song") + continue; + + SongID songID; + songID.LoadFromNode(pSong); + // Allow invalid songs so that scores aren't deleted for people that use + // AdditionalSongsFolders and change it frequently. -Kyz + //if( !songID.IsValid() ) + // continue; + + FOREACH_CONST_Child(pSong, pSteps) + { + if (pSteps->GetName() != "Steps") + continue; + + StepsID stepsID; + stepsID.LoadFromNode(pSteps); + if (!stepsID.IsValid()) + WARN_AND_CONTINUE; + + const XNode *pHighScoreListNode = pSteps->GetChild("HighScoreList"); + if (pHighScoreListNode == NULL) + WARN_AND_CONTINUE; + + HighScoreList &hsl = profile->m_SongHighScores[songID].m_StepsHighScores[stepsID].hsl; + hsl.LoadFromNode(pHighScoreListNode); + } + } +} + + +XNode *XMLProfile::SaveStatsXmlCreateNode() const +{ + XNode *xml = new XNode("Stats"); + + xml->AppendChild(SaveGeneralDataCreateNode()); + xml->AppendChild(SaveSongScoresCreateNode()); + xml->AppendChild(SaveCategoryScoresCreateNode()); + xml->AppendChild(SaveScreenshotDataCreateNode()); + + return xml; +} + +XNode *XMLProfile::SaveEttXmlCreateNode() const +{ + XNode *xml = new XNode("Stats"); + xml->AppendChild(SaveEttGeneralDataCreateNode()); + + if (!profile->FavoritedCharts.empty()) + xml->AppendChild(SaveFavoritesCreateNode()); + + if (!profile->PermaMirrorCharts.empty()) + xml->AppendChild(SavePermaMirrorCreateNode()); + + if (!SONGMAN->allplaylists.empty()) + xml->AppendChild(SavePlaylistsCreateNode()); + + if (!profile->goalmap.empty()) + xml->AppendChild(SaveScoreGoalsCreateNode()); + + xml->AppendChild(SaveEttScoresCreateNode()); + return xml; +} diff --git a/src/XMLProfile.h b/src/XMLProfile.h new file mode 100644 index 0000000000..d4ff4bba14 --- /dev/null +++ b/src/XMLProfile.h @@ -0,0 +1,64 @@ +#ifndef Profile_XML +#define Profile_XML + +#include "global.h" +#include "GameConstantsAndTypes.h" +#include "HighScore.h" +#include "XmlFile.h" +#include "Profile.h" +#include "XmlFileUtil.h" + + +class XNode; + +class XMLProfile { +public: + static void MoveBackupToDir(const RString &sFromDir, const RString &sToDir); + // For converting to etterna from stats.xml + void LoadStatsXmlForConversion(); + ProfileLoadResult LoadStatsXmlFromNode(const XNode* pNode, bool bIgnoreEditable = true); + // Etterna profile + ProfileLoadResult LoadEttFromDir(RString dir); + ProfileLoadResult LoadEttXmlFromNode(const XNode* pNode); + + void LoadEttGeneralDataFromNode(const XNode* pNode); + void LoadEttScoresFromNode(const XNode* pNode); + void LoadFavoritesFromNode(const XNode *pNode); + void LoadPermaMirrorFromNode(const XNode *pNode); + void LoadScoreGoalsFromNode(const XNode *pNode); + void LoadPlaylistsFromNode(const XNode *pNode); + void LoadGeneralDataFromNode(const XNode* pNode); + void LoadSongScoresFromNode(const XNode* pNode); + void LoadCategoryScoresFromNode(const XNode* pNode); + void LoadScreenshotDataFromNode(const XNode* pNode); + ProfileLoadResult LoadStatsFromDir(RString dir, bool require_signature); + + bool SaveStatsXmlToDir(RString sDir, bool bSignData); + bool SaveEttXmlToDir(RString sDir) const; + bool SaveStatsXmlToDir(RString sDir, bool bSignData) const; + + XNode* SaveEttGeneralDataCreateNode() const; + XNode* SaveEttScoresCreateNode() const; + XNode* SaveEttXmlCreateNode() const; + + Profile* profile; + + XNode* SaveFavoritesCreateNode() const; + XNode* SavePermaMirrorCreateNode() const; + XNode* SaveScoreGoalsCreateNode() const; + XNode* SavePlaylistsCreateNode() const; + + XNode* SaveStatsXmlCreateNode() const; + + XNode* SaveGeneralDataCreateNode() const; + XNode* SaveSongScoresCreateNode() const; + + XNode* SaveCategoryScoresCreateNode() const; + XNode* SaveScreenshotDataCreateNode() const; + + XNode* SaveCoinDataCreateNode() const; + RString profiledir; +}; + + +#endif \ No newline at end of file