#include "global.h" /* ----------------------------------------------------------------------------- Class: Profile Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "Profile.h" #include "RageUtil.h" #include "PrefsManager.h" #include "XmlFile.h" #include "IniFile.h" #include "GameManager.h" #include "RageLog.h" #include "RageFile.h" #include "song.h" #include "SongManager.h" #include "Steps.h" #include "Course.h" #include #include "ThemeManager.h" #include "CryptManager.h" #include "PrefsManager.h" #include "ProfileHtml.h" #include "ProfileManager.h" #include "RageFileManager.h" // // Old file versions for backward compatibility // #define SM_300_STATISTICS_INI "statistics.ini" #define SM_390A12_CATEGORY_SCORES_DAT "CategoryScores.dat" #define SM_390A12_SONG_SCORES_DAT "SongScores.dat" #define SM_390A12_COURSE_SCORES_DAT "CourseScores.dat" #define SM_390A12_PROFILE_INI "Profile.ini" const int SM_390A12_CATEGORY_RANKING_VERSION = 6; const int SM_390A12_SONG_SCORES_VERSION = 9; const int SM_390A12_COURSE_SCORES_VERSION = 8; void Profile::InitEditableData() { m_sDisplayName = ""; m_sLastUsedHighScoreName = ""; m_fWeightPounds = 0; } void Profile::InitGeneralData() { m_bUsingProfileDefaultModifiers = false; m_sDefaultModifiers = ""; m_iTotalPlays = 0; m_iTotalPlaySeconds = 0; m_iTotalGameplaySeconds = 0; m_iCurrentCombo = 0; m_fCaloriesBurned = 0; m_sLastMachinePlayed = ""; int i; for( i=0; i vpSteps; pSong->GetSteps( vpSteps ); for( unsigned i=0; iGetStepsHighScoreList(pSteps).iNumTimesPlayed; } return iTotalNumTimesPlayed; } // // Steps high scores // void Profile::AddStepsHighScore( const Steps* pSteps, HighScore hs, int &iIndexOut ) { std::map::iterator iter = m_StepsHighScores.find( pSteps ); if( iter == m_StepsHighScores.end() ) m_StepsHighScores[pSteps].hs.AddHighScore( hs, iIndexOut ); // operator[] inserts into map else iter->second.hs.AddHighScore( hs, iIndexOut ); } const HighScoreList& Profile::GetStepsHighScoreList( const Steps* pSteps ) const { /* We're const, but insert a blank entry anyway if the requested pointer doesn't exist. */ return ((Profile *) this)->m_StepsHighScores[pSteps].hs; } HighScoreList& Profile::GetStepsHighScoreList( const Steps* pSteps ) { std::map::iterator iter = m_StepsHighScores.find( pSteps ); if( iter == m_StepsHighScores.end() ) return m_StepsHighScores[pSteps].hs; // operator[] inserts into map else return iter->second.hs; } int Profile::GetStepsNumTimesPlayed( const Steps* pSteps ) const { std::map::const_iterator iter = m_StepsHighScores.find( pSteps ); if( iter == m_StepsHighScores.end() ) return 0; else return iter->second.hs.iNumTimesPlayed; } void Profile::IncrementStepsPlayCount( const Steps* pSteps ) { GetStepsHighScoreList(pSteps).iNumTimesPlayed++; } // // Course high scores // void Profile::AddCourseHighScore( const Course* pCourse, StepsType st, HighScore hs, int &iIndexOut ) { std::map::iterator iter = m_CourseHighScores.find( pCourse ); if( iter == m_CourseHighScores.end() ) m_CourseHighScores[pCourse].hs[st].AddHighScore( hs, iIndexOut ); // operator[] inserts into map else iter->second.hs[st].AddHighScore( hs, iIndexOut ); } const HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, StepsType st ) const { return ((Profile *)this)->m_CourseHighScores[pCourse].hs[st]; } HighScoreList& Profile::GetCourseHighScoreList( const Course* pCourse, StepsType st ) { std::map::iterator iter = m_CourseHighScores.find( pCourse ); if( iter == m_CourseHighScores.end() ) return m_CourseHighScores[pCourse].hs[st]; // operator[] inserts into map else return iter->second.hs[st]; } int Profile::GetCourseNumTimesPlayed( const Course* pCourse ) const { std::map::const_iterator iter = m_CourseHighScores.find( pCourse ); if( iter == m_CourseHighScores.end() ) { return 0; } else { int iTotalNumTimesPlayed = 0; for( unsigned st = 0; st < NUM_STEPS_TYPES; ++st ) iTotalNumTimesPlayed += iter->second.hs[st].iNumTimesPlayed; return iTotalNumTimesPlayed; } } void Profile::IncrementCoursePlayCount( const Course* pCourse, StepsType st ) { GetCourseHighScoreList(pCourse,st).iNumTimesPlayed++; } // // Category high scores // void Profile::AddCategoryHighScore( StepsType st, RankingCategory rc, HighScore hs, int &iIndexOut ) { m_CategoryHighScores[st][rc].AddHighScore( hs, iIndexOut ); } const HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) const { return ((Profile *)this)->m_CategoryHighScores[st][rc]; } HighScoreList& Profile::GetCategoryHighScoreList( StepsType st, RankingCategory rc ) { return m_CategoryHighScores[st][rc]; } int Profile::GetCategoryNumTimesPlayed( StepsType st ) const { int iNumTimesPlayed = 0; FOREACH_RankingCategory( rc ) iNumTimesPlayed += m_CategoryHighScores[st][rc].iNumTimesPlayed; return iNumTimesPlayed; } void Profile::IncrementCategoryPlayCount( StepsType st, RankingCategory rc ) { m_CategoryHighScores[st][rc].iNumTimesPlayed++; } // // Loading and saving // #define WARN LOG->Warn("Error parsing file at %s:%d",__FILE__,__LINE__); #define WARN_AND_RETURN { WARN; return; } #define WARN_AND_CONTINUE { WARN; continue; } #define WARN_AND_BREAK { WARN; break; } #define LOAD_NODE(X) { \ XNode* X = xml.GetChild(#X); \ if( X==NULL ) LOG->Warn("Failed to read section " #X); \ else Load##X##FromNode(X); } int g_iOnceCtr; #define FOR_ONCE for(g_iOnceCtr=0;g_iOnceCtr<1;g_iOnceCtr++) bool Profile::LoadAllFromDir( CString sDir ) { CHECKPOINT; InitAll(); // Only try to load old score formats if we're allowing unsigned data. if( !PREFSMAN->m_bSignProfileData ) { LoadProfileDataFromDirSM390a12( sDir ); LoadSongScoresFromDirSM390a12( sDir ); LoadCourseScoresFromDirSM390a12( sDir ); LoadCategoryScoresFromDirSM390a12( sDir ); } LoadEditableDataFromDir( sDir ); // Read stats.xml FOR_ONCE { CString fn = sDir + STATS_XML; if( !IsAFile(fn) ) break; LOG->Trace( "Reading profile data '%s'", fn.c_str() ); // // Don't unreasonably large stats.xml files. // int iBytes = FILEMAN->GetFileSizeInBytes( fn ); if( iBytes > REASONABLE_STATS_XML_SIZE_BYTES ) { LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); break; } if( PREFSMAN->m_bSignProfileData ) { CString sStatsXmlSigFile = fn+SIGNATURE_APPEND; CString sDontShareFile = sDir + DONT_SHARE_SIG; // verify the stats.xml signature with the "don't share" file if( !CryptManager::VerifyFileWithFile(sStatsXmlSigFile, sDontShareFile) ) { LOG->Warn( "The don't share check for '%s' failed. Data will be ignored.", sStatsXmlSigFile.c_str() ); break; } // verify stats.xml if( !CryptManager::VerifyFileWithFile(fn, sStatsXmlSigFile) ) { LOG->Warn( "The signature check for '%s' failed. Data will be ignored.", fn.c_str() ); break; } } XNode xml; if( !xml.LoadFromFile( fn ) ) { LOG->Warn( "Couldn't open file '%s' for reading.", fn.c_str() ); break; } if( xml.name != "Stats" ) WARN_AND_BREAK; LOAD_NODE( GeneralData ); LOAD_NODE( SongScores ); LOAD_NODE( CourseScores ); LOAD_NODE( CategoryScores ); LOAD_NODE( ScreenshotData ); } return true; // FIXME? Investigate what happens if we always return true. } bool Profile::SaveAllToDir( CString sDir ) const { m_sLastMachinePlayed = PREFSMAN->m_sMachineName; // Save editable.xml SaveEditableDataToDir( sDir ); // Save stats.xml { CString fn = sDir + STATS_XML; XNode xml; xml.name = "Stats"; xml.AppendChild( SaveGeneralDataCreateNode() ); xml.AppendChild( SaveSongScoresCreateNode() ); xml.AppendChild( SaveCourseScoresCreateNode() ); xml.AppendChild( SaveCategoryScoresCreateNode() ); xml.AppendChild( SaveScreenshotDataCreateNode() ); bool bSaved = xml.SaveToFile(fn); if( bSaved && PREFSMAN->m_bSignProfileData ) { CString sStatsXmlSigFile = fn+SIGNATURE_APPEND; CryptManager::SignFileToFile(fn, sStatsXmlSigFile); // Save the "don't share" file CString sDontShareFile = sDir + DONT_SHARE_SIG; CryptManager::SignFileToFile(sStatsXmlSigFile, sDontShareFile); } } // Delete old files after saving new ones so we don't try to load old // files the next time and make duplicate records. if( !PREFSMAN->m_bSignProfileData ) // we tried to read the older formats { DeleteProfileDataFromDirSM390a12( sDir ); DeleteSongScoresFromDirSM390a12( sDir ); DeleteCourseScoresFromDirSM390a12( sDir ); DeleteCategoryScoresFromDirSM390a12( sDir ); } SaveStatsWebPageToDir( sDir ); return true; } void Profile::LoadProfileDataFromDirSM390a12( CString sDir ) { CString fn = sDir + SM_390A12_PROFILE_INI; InitEditableData(); InitGeneralData(); // // read ini // IniFile ini( fn ); if( !ini.ReadFile() ) return; ini.GetValue( "Profile", "DisplayName", m_sDisplayName ); ini.GetValue( "Profile", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); ini.GetValue( "Profile", "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers ); ini.GetValue( "Profile", "DefaultModifiers", m_sDefaultModifiers ); ini.GetValue( "Profile", "TotalPlays", m_iTotalPlays ); ini.GetValue( "Profile", "TotalPlaySeconds", m_iTotalPlaySeconds ); ini.GetValue( "Profile", "TotalGameplaySeconds", m_iTotalGameplaySeconds ); ini.GetValue( "Profile", "CurrentCombo", m_iCurrentCombo ); ini.GetValue( "Profile", "WeightPounds", m_fWeightPounds ); ini.GetValue( "Profile", "CaloriesBurned", m_fCaloriesBurned ); ini.GetValue( "Profile", "LastMachinePlayed", m_sLastMachinePlayed ); unsigned i; for( i=0; iGetGameDefForGame(GAMEMAN->GetStyleDefForStyle((Style)i)->m_Game)->m_szName)+Capitalize(GAMEMAN->GetStyleDefForStyle((Style)i)->m_szName), m_iNumSongsPlayedByStyle[i] ); for( i=0; iname = "GeneralData"; pGeneralDataNode->AppendChild( "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers ); pGeneralDataNode->AppendChild( "DefaultModifiers", m_sDefaultModifiers ); pGeneralDataNode->AppendChild( "TotalPlays", m_iTotalPlays ); pGeneralDataNode->AppendChild( "TotalPlaySeconds", m_iTotalPlaySeconds ); pGeneralDataNode->AppendChild( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); pGeneralDataNode->AppendChild( "CurrentCombo", m_iCurrentCombo ); pGeneralDataNode->AppendChild( "CaloriesBurned", m_fCaloriesBurned ); pGeneralDataNode->AppendChild( "LastMachinePlayed", m_sLastMachinePlayed ); { XNode* pNumSongsPlayedByPlayMode = pGeneralDataNode->AppendChild("NumSongsPlayedByPlayMode"); FOREACH_PlayMode( pm ) { /* Don't save unplayed PlayModes. */ if( !m_iNumSongsPlayedByPlayMode[pm] ) continue; pNumSongsPlayedByPlayMode->AppendChild( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); } } { XNode* pNumSongsPlayedByStyle = pGeneralDataNode->AppendChild("NumSongsPlayedByStyle"); for( int i=0; iAppendChild( "Style", m_iNumSongsPlayedByStyle[i] ); const StyleDef *pStyle = GAMEMAN->GetStyleDefForStyle((Style)i); const GameDef *g = GAMEMAN->GetGameDefForGame( pStyle->m_Game ); ASSERT( g ); pStyleNode->AppendAttr( "Game", g->m_szName ); pStyleNode->AppendAttr( "Style", pStyle->m_szName ); } } { XNode* pNumSongsPlayedByDifficulty = pGeneralDataNode->AppendChild("NumSongsPlayedByDifficulty"); FOREACH_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] ); } } return pGeneralDataNode; } void Profile::LoadEditableDataFromDir( CString sDir ) { CString fn = sDir + EDITABLE_INI; // // Don't load unreasonably large editable.xml files. // int iBytes = FILEMAN->GetFileSizeInBytes( fn ); if( iBytes > REASONABLE_EDITABLE_INI_SIZE_BYTES ) { LOG->Warn( "The file '%s' is unreasonably large. It won't be loaded.", fn.c_str() ); return; } IniFile ini; ini.SetPath( fn ); ini.ReadFile(); ini.GetValue("Editable","DisplayName", m_sDisplayName); ini.GetValue("Editable","LastUsedHighScoreName", m_sLastUsedHighScoreName); ini.GetValue("Editable","WeightPounds", m_fWeightPounds); // This is data that the user can change, so we have to validate it. wstring wstr = CStringToWstring(m_sDisplayName); if( wstr.size() > 12 ) wstr = wstr.substr(0, 12); m_sDisplayName = WStringToCString(wstr); // TODO: strip invalid chars? CLAMP( m_fWeightPounds, 0, 500 ); } void Profile::LoadGeneralDataFromNode( const XNode* pNode ) { ASSERT( pNode->name == "GeneralData" ); pNode->GetChildValue( "UsingProfileDefaultModifiers", m_bUsingProfileDefaultModifiers ); pNode->GetChildValue( "DefaultModifiers", m_sDefaultModifiers ); pNode->GetChildValue( "TotalPlays", m_iTotalPlays ); pNode->GetChildValue( "TotalPlaySeconds", m_iTotalPlaySeconds ); pNode->GetChildValue( "TotalGameplaySeconds", m_iTotalGameplaySeconds ); pNode->GetChildValue( "CurrentCombo", m_iCurrentCombo ); pNode->GetChildValue( "CaloriesBurned", m_fCaloriesBurned ); pNode->GetChildValue( "LastMachinePlayed", m_sLastMachinePlayed ); { XNode* pNumSongsPlayedByPlayMode = pNode->GetChild("NumSongsPlayedByPlayMode"); if( pNumSongsPlayedByPlayMode ) FOREACH_PlayMode( pm ) pNumSongsPlayedByPlayMode->GetChildValue( PlayModeToString(pm), m_iNumSongsPlayedByPlayMode[pm] ); } { XNode* pNumSongsPlayedByStyle = pNode->GetChild("NumSongsPlayedByStyle"); if( pNumSongsPlayedByStyle ) for( XNodes::const_iterator style = pNumSongsPlayedByStyle->childs.begin(); style != pNumSongsPlayedByStyle->childs.end(); style++ ) { if( (*style)->name != "Style" ) continue; XAttr *TypeAttr = (*style)->GetAttr( "Game" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; Game g = GAMEMAN->StringToGameType( TypeAttr->value ); if( g == GAME_INVALID ) WARN_AND_CONTINUE; TypeAttr = (*style)->GetAttr( "Style" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; Style s = GAMEMAN->GameAndStringToStyle( g, TypeAttr->value ); (*style)->GetValue( m_iNumSongsPlayedByStyle[s] ); } } { XNode* pNumSongsPlayedByDifficulty = pNode->GetChild("NumSongsPlayedByDifficulty"); if( pNumSongsPlayedByDifficulty ) FOREACH_Difficulty( dc ) pNumSongsPlayedByDifficulty->GetChildValue( DifficultyToString(dc), m_iNumSongsPlayedByDifficulty[dc] ); } { XNode* pNumSongsPlayedByMeter = pNode->GetChild("NumSongsPlayedByMeter"); if( pNumSongsPlayedByMeter ) for( int i=0; iGetChildValue( ssprintf("Meter%d",i), m_iNumSongsPlayedByMeter[i] ); } } XNode* Profile::SaveSongScoresCreateNode() const { CHECKPOINT; const Profile* pProfile = this; ASSERT( pProfile ); XNode* pNode = new XNode; pNode->name = "SongScores"; const vector &vpSongs = SONGMAN->GetAllSongs(); for( unsigned s=0; sGetSongNumTimesPlayed(pSong) == 0 ) continue; LPXNode pSongNode = pNode->AppendChild( "Song" ); pSongNode->AppendAttr( "Dir", pSong->GetSongDir() ); const vector vSteps = pSong->GetAllSteps(); for( unsigned n=0; nGetStepsHighScoreList(pSteps).iNumTimesPlayed == 0 ) continue; LPXNode pStepsNode = pSongNode->AppendChild( "Steps" ); const HighScoreList &hsl = pProfile->GetStepsHighScoreList( pSteps ); pStepsNode->AppendChild( "StepsType", GameManager::NotesTypeToString(pSteps->m_StepsType) ); pStepsNode->AppendChild( "Difficulty", DifficultyToString(pSteps->GetDifficulty()) ); if( pSteps->GetDifficulty() == DIFFICULTY_EDIT ) pStepsNode->AppendChild( "Description", pSteps->GetDescription() ); pStepsNode->AppendChild( hsl.CreateNode() ); } } return pNode; } void Profile::LoadSongScoresFromNode( const XNode* pNode ) { ASSERT( pNode->name == "SongScores" ); for( XNodes::const_iterator song = pNode->childs.begin(); song != pNode->childs.end(); song++ ) { if( (*song)->name != "Song" ) continue; const LPXAttr TypeAttr = (*song)->GetAttr( "Dir" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; const CString sSongDir = TypeAttr->value; Song* pSong = SONGMAN->GetSongFromDir( sSongDir ); if( pSong == NULL ) WARN_AND_CONTINUE; for( XNodes::iterator steps = (*song)->childs.begin(); steps != (*song)->childs.end(); steps++ ) { if( (*steps)->name != "Steps" ) continue; CString str; if( !(*steps)->GetChildValue("StepsType", str) ) WARN_AND_CONTINUE; const StepsType st = GameManager::StringToNotesType( str ); if( st == STEPS_TYPE_INVALID ) WARN_AND_CONTINUE; if( !(*steps)->GetChildValue("Difficulty", str) ) WARN_AND_CONTINUE; const Difficulty dc = StringToDifficulty( str ); if( dc == DIFFICULTY_INVALID ) WARN_AND_CONTINUE; Steps* pSteps = NULL; if( dc == DIFFICULTY_EDIT ) { CString sDescription; if( !(*steps)->GetChildValue("Description", sDescription) ) WARN_AND_CONTINUE; pSteps = pSong->GetStepsByDescription( st, sDescription ); } else pSteps = pSong->GetStepsByDifficulty( st, dc ); if( pSteps == NULL ) WARN_AND_CONTINUE; XNode *pHighScoreListNode = (*steps)->GetChild("HighScoreList"); if( pHighScoreListNode == NULL ) WARN_AND_CONTINUE; HighScoreList &hsl = this->GetStepsHighScoreList( pSteps ); hsl.LoadFromNode( pHighScoreListNode ); } } } void Profile::LoadSongScoresFromDirSM390a12( CString sDir ) { Profile* pProfile = this; ASSERT( pProfile ); CString fn = sDir + SM_390A12_SONG_SCORES_DAT; if( !IsAFile(fn) ) return; RageFile f; if( !f.Open(fn, RageFile::READ) ) { LOG->Warn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); return; } int version; if( !FileRead(f, version) ) WARN_AND_RETURN; if( version != SM_390A12_SONG_SCORES_VERSION ) WARN_AND_RETURN; int iNumSongs; if( !FileRead(f, iNumSongs) ) WARN_AND_RETURN; for( int s=0; sGetSongFromDir( sSongDir ); int iNumNotes; if( !FileRead(f, iNumNotes) ) WARN_AND_RETURN; for( int n=0; nGetStepsByDescription( st, sDescription ); else pSteps = pSong->GetStepsByDifficulty( st, dc ); } int iNumTimesPlayed; if( !FileRead(f, iNumTimesPlayed) ) WARN_AND_RETURN; HighScoreList &hsl = pProfile->GetStepsHighScoreList(pSteps); if( pSteps ) hsl.iNumTimesPlayed = iNumTimesPlayed; int iNumHighScores; if( !FileRead(f, iNumHighScores) ) WARN_AND_RETURN; if( pSteps ) hsl.vHighScores.resize( iNumHighScores ); int l; for( l=0; lWarn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); return; } int version; if( !FileRead(f, version) ) WARN_AND_RETURN; if( version != SM_390A12_CATEGORY_RANKING_VERSION ) WARN_AND_RETURN; for( int st=0; stGetCategoryHighScoreList( (StepsType)st, (RankingCategory)rc ); hsl.vHighScores.resize( iNumHighScores ); for( int l=0; lWarn( "Couldn't open file \"%s\": %s", fn.c_str(), f.GetError().c_str() ); return; } int version; if( !FileRead(f, version) ) WARN_AND_RETURN; if( version != SM_390A12_COURSE_SCORES_VERSION ) WARN_AND_RETURN; int iNumCourses; if( !FileRead(f, iNumCourses) ) WARN_AND_RETURN; for( int c=0; cGetCourseFromPath( sPath ); if( pCourse == NULL ) pCourse = SONGMAN->GetCourseFromName( sPath ); // even if we don't find the Course*, we still have to read past the input int NumStepsTypesPlayed = 0; if( !FileRead(f, NumStepsTypesPlayed) ) WARN_AND_RETURN; while( NumStepsTypesPlayed-- ) { int st; if( !FileRead(f, st) ) WARN_AND_RETURN; int iNumTimesPlayed; if( !FileRead(f, iNumTimesPlayed) ) WARN_AND_RETURN; HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, (StepsType)st ); if( pCourse ) hsl.iNumTimesPlayed = iNumTimesPlayed; int iNumHighScores; if( !FileRead(f, iNumHighScores) ) WARN_AND_RETURN; if( pCourse ) hsl.vHighScores.resize(iNumHighScores); for( int l=0; lname = "CourseScores"; vector vpCourses; SONGMAN->GetAllCourses( vpCourses, true ); for( unsigned c=0; cGetCourseNumTimesPlayed(pCourse) == 0 ) continue; XNode* pCourseNode = pNode->AppendChild( "Course" ); pCourseNode->AppendAttr( "Name", pCourse->m_bIsAutogen ? pCourse->m_sName : pCourse->m_sPath ); for( StepsType st=(StepsType)0; stGetCourseHighScoreList(pCourse, st).iNumTimesPlayed == 0 ) continue; LPXNode pStepsTypeNode = pCourseNode->AppendChild( "StepsType" ); pStepsTypeNode->AppendAttr( "Type", GameManager::NotesTypeToString(st) ); const HighScoreList &hsl = pProfile->GetCourseHighScoreList( pCourse, st ); pStepsTypeNode->AppendChild( hsl.CreateNode() ); } } return pNode; } void Profile::LoadCourseScoresFromNode( const XNode* pNode ) { CHECKPOINT; ASSERT( pNode->name == "CourseScores" ); for( XNodes::const_iterator course = pNode->childs.begin(); course != pNode->childs.end(); course++ ) { if( (*course)->name != "Course" ) continue; const LPXAttr TypeAttr = (*course)->GetAttr( "Name" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; CString sCourse = TypeAttr->value; Course* pCourse = SONGMAN->GetCourseFromPath( sCourse ); if( pCourse == NULL ) pCourse = SONGMAN->GetCourseFromName( sCourse ); if( pCourse == NULL ) WARN_AND_CONTINUE; for( XNodes::iterator stepsType = (*course)->childs.begin(); stepsType != (*course)->childs.end(); stepsType++ ) { if( (*stepsType)->name != "StepsType" ) continue; const LPXAttr TypeAttr = (*stepsType)->GetAttr( "Type" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; StepsType st = GameManager::StringToNotesType( TypeAttr->value ); if( st == STEPS_TYPE_INVALID ) WARN_AND_CONTINUE; XNode *pHighScoreListNode = (*stepsType)->GetChild("HighScoreList"); if( pHighScoreListNode == NULL ) WARN_AND_CONTINUE; HighScoreList &hsl = this->GetCourseHighScoreList( pCourse, st ); hsl.LoadFromNode( pHighScoreListNode ); } } } XNode* Profile::SaveCategoryScoresCreateNode() const { CHECKPOINT; const Profile* pProfile = this; ASSERT( pProfile ); XNode* pNode = new XNode; pNode->name = "CategoryScores"; FOREACH_StepsType( st ) { // skip steps types that have never been played if( pProfile->GetCategoryNumTimesPlayed( st ) == 0 ) continue; XNode* pStepsTypeNode = pNode->AppendChild( "StepsType" ); pStepsTypeNode->AppendAttr( "Type", GameManager::NotesTypeToString(st) ); FOREACH_RankingCategory( rc ) { // skip steps types/categories that have never been played if( pProfile->GetCategoryHighScoreList(st,rc).iNumTimesPlayed == 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* pNode ) { CHECKPOINT; ASSERT( pNode->name == "CategoryScores" ); for( XNodes::const_iterator stepsType = pNode->childs.begin(); stepsType != pNode->childs.end(); stepsType++ ) { if( (*stepsType)->name != "StepsType" ) continue; LPXAttr TypeAttr = (*stepsType)->GetAttr( "Type" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; StepsType st = GameManager::StringToNotesType( TypeAttr->value ); if( st == STEPS_TYPE_INVALID ) WARN_AND_CONTINUE; for( XNodes::iterator radarCategory = (*stepsType)->childs.begin(); radarCategory != (*stepsType)->childs.end(); radarCategory++ ) { if( (*radarCategory)->name != "RankingCategory" ) continue; const LPXAttr TypeAttr = (*radarCategory)->GetAttr( "Type" ); if( TypeAttr == NULL ) WARN_AND_CONTINUE; RankingCategory rc = StringToRankingCategory( TypeAttr->value ); if( rc == RANKING_INVALID ) WARN_AND_CONTINUE; XNode *pHighScoreListNode = (*radarCategory)->GetChild("HighScoreList"); if( pHighScoreListNode == NULL ) WARN_AND_CONTINUE; HighScoreList &hsl = this->GetCategoryHighScoreList( st, rc ); hsl.LoadFromNode( pHighScoreListNode ); } } } void Profile::DeleteProfileDataFromDirSM390a12( CString sDir ) const { CString fn = sDir + SM_390A12_PROFILE_INI; FILEMAN->Remove( fn ); } void Profile::DeleteSongScoresFromDirSM390a12( CString sDir ) const { CString fn = sDir + SM_390A12_SONG_SCORES_DAT; FILEMAN->Remove( fn ); } void Profile::DeleteCourseScoresFromDirSM390a12( CString sDir ) const { CString fn = sDir + SM_390A12_COURSE_SCORES_DAT; FILEMAN->Remove( fn ); } void Profile::DeleteCategoryScoresFromDirSM390a12( CString sDir ) const { CString fn = sDir + SM_390A12_CATEGORY_SCORES_DAT; FILEMAN->Remove( fn ); } void Profile::SaveStatsWebPageToDir( CString sDir ) const { ASSERT( PROFILEMAN ); // UGLY... bool bThisIsMachineProfile = (this == PROFILEMAN->GetMachineProfile()); // Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); SaveStatsWebPage( sDir, this, PROFILEMAN->GetMachineProfile(), bThisIsMachineProfile ? HTML_TYPE_MACHINE : HTML_TYPE_PLAYER ); if( bThisIsMachineProfile ) SaveStatsWebPage( sDir+"temp/", this, PROFILEMAN->GetMachineProfile(), HTML_TYPE_PLAYER ); } void Profile::SaveMachinePublicKeyToDir( CString sDir ) const { if( PREFSMAN->m_bSignProfileData && IsAFile(CRYPTMAN->GetPublicKeyFileName()) ) FileCopy( CRYPTMAN->GetPublicKeyFileName(), PUBLIC_KEY_FILE ); } void Profile::AddScreenshot( Screenshot screenshot ) { m_vScreenshots.push_back( screenshot ); } void Profile::LoadScreenshotDataFromNode( const XNode* pNode ) { CHECKPOINT; ASSERT( pNode->name == "ScreenshotData" ); for( XNodes::const_iterator screenshot = pNode->childs.begin(); screenshot != pNode->childs.end(); screenshot++ ) { if( (*screenshot)->name != "Screenshot" ) WARN_AND_CONTINUE; Screenshot ss; if( !(*screenshot)->GetChildValue("FileName",ss.sFileName) ) WARN_AND_CONTINUE; if( !(*screenshot)->GetChildValue("MD5",ss.sMD5) ) WARN_AND_CONTINUE; XNode *pHighScoreNode = (*screenshot)->GetChild("HighScore"); if( pHighScoreNode == NULL ) WARN_AND_CONTINUE; HighScore &hs = ss.highScore; hs.LoadFromNode( pHighScoreNode ); m_vScreenshots.push_back( ss ); } } XNode* Profile::SaveScreenshotDataCreateNode() const { CHECKPOINT; const Profile* pProfile = this; ASSERT( pProfile ); XNode* pNode = new XNode; pNode->name = "ScreenshotData"; for( unsigned i=0; iAppendChild( "Screenshot" ); pScreenshotNode->AppendChild( "FileName", ss.sFileName ); pScreenshotNode->AppendChild( "MD5", ss.sMD5); pScreenshotNode->AppendChild( ss.highScore.CreateNode() ); } return pNode; }