diff --git a/common/achievements.cpp b/common/achievements.cpp index d2e1033f053..64e48bb6d16 100644 --- a/common/achievements.cpp +++ b/common/achievements.cpp @@ -23,8 +23,10 @@ #include "common/achievements.h" #include "common/debug.h" +#include "common/stream.h" #include "common/system.h" #include "common/translation.h" +#include "common/unzip.h" namespace Common { @@ -67,11 +69,11 @@ bool AchievementsManager::setActiveDomain(const AchievementsInfo &info) { _iniFile = new Common::INIFile(); _iniFile->loadFromSaveFile(_iniFileName); // missing file is OK - _descriptions = info.descriptions; + loadAchievementsData(platform, info.appId.c_str()); - for (uint32 i = 0; i < info.stats.size(); i++) { - if (!(_iniFile->hasKey(info.stats[i].id, "statistics"))) { - _iniFile->setKey(info.stats[i].id, "statistics", info.stats[i].start); + for (uint32 i = 0; i < _stats.size(); i++) { + if (!(_iniFile->hasKey(_stats[i].id, "statistics"))) { + _iniFile->setKey(_stats[i].id, "statistics", _stats[i].start); } } @@ -82,6 +84,68 @@ bool AchievementsManager::setActiveDomain(const AchievementsInfo &info) { } +bool AchievementsManager::loadAchievementsData(const char *platform, const char *appId) { + Archive *cfgZip = Common::makeZipArchive("achievements.dat"); + if (!cfgZip) { + warning("achievements.dat is not found. Achievements messages are unavailable"); + return false; + } + + String cfgFileName = String::format("%s-%s.ini", platform, appId); + SeekableReadStream *stream = cfgZip->createReadStreamForMember(cfgFileName); + if (!stream) { + delete cfgZip; + warning("%s is not found in achievements.dat. Achievements messages are unavailable", cfgFileName.c_str()); + return false; + } + + INIFile cfgFile; + if (!cfgFile.loadFromStream(*stream)) { + delete stream; + delete cfgZip; + warning("%s is corrupted in achievements.dat. Achievements messages are unavailable", cfgFileName.c_str()); + return false; + } + + _descriptions.clear(); + for (int i = 0; i < 256; i++) { + String prefix = String::format("item_%d", i); + + String id, title, comment, hidden; + cfgFile.getKey(prefix + "_id", "achievements:en", id); + cfgFile.getKey(prefix + "_title", "achievements:en", title); + cfgFile.getKey(prefix + "_comment", "achievements:en", comment); + cfgFile.getKey(prefix + "_hidden", "achievements:en", hidden); + + if (id.empty()) { + break; + } else { + _descriptions.push_back({id, title, comment, !hidden.empty()}); + } + } + + _stats.clear(); + for (int i = 0; i < 256; i++) { + String prefix = String::format("item_%d", i); + + String id, comment, start; + cfgFile.getKey(prefix + "_id", "stats:en", id); + cfgFile.getKey(prefix + "_comment", "stats:en", comment); + cfgFile.getKey(prefix + "_start", "stats:en", start); + + if (id.empty()) { + break; + } else { + _stats.push_back({id, comment, start}); + } + } + + delete stream; + delete cfgZip; + return true; +} + + bool AchievementsManager::unsetActiveDomain() { debug("AchievementsManager::unsetActiveDomain()"); @@ -91,6 +155,7 @@ bool AchievementsManager::unsetActiveDomain() { _iniFile = nullptr; _descriptions.clear(); + _stats.clear(); return true; } @@ -107,7 +172,7 @@ bool AchievementsManager::setAchievement(const String &id) { String displayedMessage = id; for (uint32 i = 0; i < _descriptions.size(); i++) { - if (strcmp(_descriptions[i].id, id.c_str()) == 0) { + if (_descriptions[i].id == id) { displayedMessage = _descriptions[i].title; break; } diff --git a/common/achievements.h b/common/achievements.h index 3512c2ece31..63e6adbbd44 100644 --- a/common/achievements.h +++ b/common/achievements.h @@ -53,19 +53,19 @@ enum AchievementsPlatform { * Information structure for game-specific statistics. */ struct StatDescription { - const char *id; //!< Stat internal ID, such as "ITEMS_THROWN". - const char *comment; //!< Optional stat comment, such as "Items Thrown". - const char *start; //!< Stat default value, such as "0". + String id; //!< Stat internal ID, such as "ITEMS_THROWN". + String comment; //!< Optional stat comment, such as "Items Thrown". + String start; //!< Stat default value, such as "0". }; /** * Information structure for game-specific achievements. */ struct AchievementDescription { - const char *id; //!< Achievement internal ID, such as "ACHIEVEMENT_TIMING". - bool isHidden; //!< Whether the achievement is hidden. - const char *title; //!< Achievement displayed text, such as "Marathon Runner". - const char *comment; //!< Optional achievement hint or comment, such as "Finish the game in less than 4 hours". + String id; //!< Achievement internal ID, such as "ACHIEVEMENT_TIMING". + String title; //!< Achievement displayed text, such as "Marathon Runner". + String comment; //!< Optional achievement hint or comment, such as "Finish the game in less than 4 hours". + bool isHidden; //!< Whether the achievement is hidden. }; /** @@ -74,8 +74,6 @@ struct AchievementDescription { struct AchievementsInfo { Common::AchievementsPlatform platform; //!< Achievements platform, such as "STEAM_ACHIEVEMENTS". Common::String appId; //!< Achievements application ID of the given platform. - Common::Array stats; //!< Descriptions of all game stats. - Common::Array descriptions; //!< Descriptions of all game achievements. AchievementsInfo() { platform = Common::UNK_ACHIEVEMENTS; } }; @@ -91,9 +89,10 @@ public: ~AchievementsManager(); /** - * Set a platform and application ID as active domain, store messages texts. + * Set a game targeted by platform type and application ID as active domain. + * Automaticly loads messages texts from achievements.dat. * - * @param[in] info Achievements platform, application ID and messages information. + * @param[in] info Achievements platform type and application ID. */ bool setActiveDomain(const AchievementsInfo &info); bool unsetActiveDomain(); //!< Unset the current active domain. @@ -213,11 +212,14 @@ public: /** @} */ private: + bool loadAchievementsData(const char *platform, const char *appId); + float getStatFloatEx(const String &id, const String §ion) const; bool setStatFloatEx(const String &id, float value, const String §ion) const; INIFile *_iniFile; String _iniFileName; + Common::Array _stats; Common::Array _descriptions; };