diff --git a/Readme_release.txt b/Readme_release.txt index 46561b3..a2c1236 100644 --- a/Readme_release.txt +++ b/Readme_release.txt @@ -75,6 +75,7 @@ An example can be found in steam_settings.EXAMPLE that works with Killing Floor The items.json syntax is simple, you SHOULD validate your .json file before trying to run your game or you won't have any item in your inventory. Just look for "online json validator" on your web brower to valide your file. You can use https://steamdb.info/ to list items and attributes they have and put them into your .json. Keep in mind that some item are not valid to have in your inventory. For example, in PayDay2 all items below item_id 50000 will make your game crash. +items.json should contain all the item definitions for the game, default_items.json is the quantity of each item that you want a user to have initially in their inventory. By default the user will have no items. Leaderboards: By default the emulator assumes all leaderboards queried by the game (FindLeaderboard()) exist and creates them with the most common options (sort method descending, display type numeric) diff --git a/dll/local_storage.cpp b/dll/local_storage.cpp index 33ead80..f1b3d45 100644 --- a/dll/local_storage.cpp +++ b/dll/local_storage.cpp @@ -128,14 +128,19 @@ bool Local_Storage::update_save_filenames(std::string folder) return true; } +bool Local_Storage::load_json(std::string full_path, nlohmann::json& json) +{ + return false; +} + bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json) { - return true; + return false; } bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json) { - return true; + return false; } std::vector Local_Storage::get_filenames_path(std::string path) @@ -691,16 +696,8 @@ bool Local_Storage::update_save_filenames(std::string folder) return true; } -bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json) +bool Local_Storage::load_json(std::string full_path, nlohmann::json& json) { - if (!folder.empty() && folder.back() != *PATH_SEPARATOR) { - folder.append(PATH_SEPARATOR); - } - std::string inv_path = std::move(save_directory + appid + folder); - std::string full_path = inv_path + file; - - create_directory(inv_path); - std::ifstream inventory_file(full_path); // If there is a file and we opened it if (inventory_file) @@ -730,6 +727,17 @@ bool Local_Storage::load_json_file(std::string folder, std::string const&file, n return false; } +bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json) +{ + if (!folder.empty() && folder.back() != *PATH_SEPARATOR) { + folder.append(PATH_SEPARATOR); + } + std::string inv_path = std::move(save_directory + appid + folder); + std::string full_path = inv_path + file; + + return load_json(full_path, json); +} + bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json) { if (!folder.empty() && folder.back() != *PATH_SEPARATOR) { diff --git a/dll/local_storage.h b/dll/local_storage.h index 3c550bd..394fae2 100644 --- a/dll/local_storage.h +++ b/dll/local_storage.h @@ -63,6 +63,7 @@ public: bool update_save_filenames(std::string folder); + bool load_json(std::string full_path, nlohmann::json& json); bool load_json_file(std::string folder, std::string const& file, nlohmann::json& json); bool write_json_file(std::string folder, std::string const& file, nlohmann::json const& json); }; diff --git a/dll/steam_inventory.h b/dll/steam_inventory.h index f92585b..de0a9bc 100644 --- a/dll/steam_inventory.h +++ b/dll/steam_inventory.h @@ -60,9 +60,7 @@ private: bool inventory_loaded; bool call_definition_update; - bool call_inventory_update; - bool definition_update_called; - bool full_update_called; + bool item_definitions_loaded; struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0) { @@ -96,29 +94,7 @@ void read_items_db() { std::string items_db_path = Local_Storage::get_game_settings_path() + items_user_file; PRINT_DEBUG("Items file path: %s\n", items_db_path.c_str()); - std::ifstream inventory_file(items_db_path); - // If there is a file and we opened it - if (inventory_file) - { - inventory_file.seekg(0, std::ios::end); - size_t size = inventory_file.tellg(); - std::string buffer(size, '\0'); - inventory_file.seekg(0); - // Read it entirely, if the .json file gets too big, - // I should look into this and split reads into smaller parts. - inventory_file.read(&buffer[0], size); - inventory_file.close(); - - try - { - defined_items = std::move(nlohmann::json::parse(buffer)); - PRINT_DEBUG("Loaded inventory. Loaded %u items.\n", defined_items.size()); - } - catch (std::exception& e) - { - PRINT_DEBUG("Error while parsing inventory json: %s\n", e.what()); - } - } + local_storage->load_json(items_db_path, defined_items); } void read_inventory_db() @@ -129,29 +105,7 @@ void read_inventory_db() // Try to load a default one std::string items_db_path = Local_Storage::get_game_settings_path() + items_default_file; PRINT_DEBUG("Default items file path: %s\n", items_db_path.c_str()); - std::ifstream inventory_file(items_db_path); - // If there is a file and we opened it - if (inventory_file) - { - inventory_file.seekg(0, std::ios::end); - size_t size = inventory_file.tellg(); - std::string buffer(size, '\0'); - inventory_file.seekg(0); - // Read it entirely, if the .json file gets too big, - // I should look into this and split reads into smaller parts. - inventory_file.read(&buffer[0], size); - inventory_file.close(); - - try - { - user_items = std::move(nlohmann::json::parse(buffer)); - PRINT_DEBUG("Loaded default inventory. Loaded %u items.\n", user_items.size()); - } - catch (std::exception& e) - { - PRINT_DEBUG("Error while parsing inventory json: %s\n", e.what()); - } - } + local_storage->load_json(items_db_path, user_items); } } @@ -175,9 +129,7 @@ Steam_Inventory(class Settings *settings, class SteamCallResults *callback_resul user_items(nlohmann::json::object()), inventory_loaded(false), call_definition_update(false), - call_inventory_update(false), - definition_update_called(false), - full_update_called(false) + item_definitions_loaded(false) { this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this); } @@ -230,6 +182,7 @@ bool GetResultItems( SteamInventoryResult_t resultHandle, if (pOutItemsArray != nullptr) { + SteamItemDetails_t *items_array_base = pOutItemsArray; uint32 max_items = *punOutItemsArraySize; if (request->full_query) { @@ -249,29 +202,38 @@ bool GetResultItems( SteamInventoryResult_t resultHandle, pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; } - *punOutItemsArraySize = std::min(*punOutItemsArraySize, static_cast(user_items.size())); } else { for (auto &itemid : request->instance_ids) { if (!max_items) break; - pOutItemsArray->m_iDefinition = itemid; - pOutItemsArray->m_itemId = itemid; - try - { - pOutItemsArray->m_unQuantity = user_items[itemid].get(); + auto it = user_items.find(std::to_string(itemid)); + if (it != user_items.end()) { + pOutItemsArray->m_iDefinition = itemid; + pOutItemsArray->m_itemId = itemid; + + try + { + pOutItemsArray->m_unQuantity = it->get(); + } + catch (...) + { + pOutItemsArray->m_unQuantity = 0; + } + pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; + ++pOutItemsArray; + --max_items; } - catch (...) - { - pOutItemsArray->m_unQuantity = 0; - } - pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; - ++pOutItemsArray; - --max_items; } } + + *punOutItemsArraySize = pOutItemsArray - items_array_base; } else if (punOutItemsArraySize != nullptr) { - *punOutItemsArraySize = user_items.size(); + if (request->full_query) { + *punOutItemsArraySize = user_items.size(); + } else { + *punOutItemsArraySize = std::count_if(request->instance_ids.begin(), request->instance_ids.end(), [this](auto item_id){ return user_items.find(std::to_string(item_id)) != user_items.end();}); + } } PRINT_DEBUG("GetResultItems good\n"); @@ -359,8 +321,6 @@ bool GetAllItems( SteamInventoryResult_t *pResultHandle ) std::lock_guard lock(global_mutex); struct Steam_Inventory_Requests* request = new_inventory_result(); - call_inventory_update = true; - if (pResultHandle != nullptr) *pResultHandle = request->inventory_result; @@ -383,7 +343,6 @@ bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unC std::lock_guard lock(global_mutex); if (pResultHandle) { struct Steam_Inventory_Requests *request = new_inventory_result(false, pInstanceIDs, unCountInstanceIDs); - //call_inventory_update = true; *pResultHandle = request->inventory_result; return true; } @@ -613,7 +572,7 @@ bool LoadItemDefinitions() PRINT_DEBUG("LoadItemDefinitions\n"); std::lock_guard lock(global_mutex); - if (!definition_update_called) { + if (!item_definitions_loaded) { call_definition_update = true; } @@ -715,6 +674,7 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope { *punValueBufferSizeOut = 0; PRINT_DEBUG("Attr %s not found for item %d\n", pchPropertyName, iDefinition); + return false; } } else // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available property names. @@ -750,8 +710,11 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope } } } + + return true; } - return true; + + return false; } @@ -877,39 +840,37 @@ bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventory void RunCallbacks() { - if (call_definition_update && !definition_update_called) { - definition_update_called = true; - read_items_db(); + if (call_definition_update || inventory_requests.size()) { + if (!item_definitions_loaded) { + read_items_db(); + item_definitions_loaded = true; + + //only gets called once + //also gets called when getting items + SteamInventoryDefinitionUpdate_t data = {}; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + } - SteamInventoryDefinitionUpdate_t data = {}; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); call_definition_update = false; } - if (call_inventory_update) { + if (inventory_requests.size() && !inventory_loaded) { read_inventory_db(); inventory_loaded = true; - - call_definition_update = true; - call_inventory_update = false; } - if (definition_update_called && inventory_loaded) + if (inventory_loaded) { std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); for (auto& r : inventory_requests) { if (!r.done && std::chrono::duration_cast>(now - r.time_created).count() > r.timeout) { if (r.full_query) { - if (!full_update_called) { - // SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems - // successfully returns a result which is newer / fresher than the last - // known result. - //TODO: should this always be returned for each get all item calls? - struct SteamInventoryFullUpdate_t data; - data.m_handle = r.inventory_result; - callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); - full_update_called = true; - } + // SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems + // successfully returns a result which is newer / fresher than the last + // known result. + struct SteamInventoryFullUpdate_t data; + data.m_handle = r.inventory_result; + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } { diff --git a/dll/steam_user_stats.h b/dll/steam_user_stats.h index 98337c3..2a0e951 100644 --- a/dll/steam_user_stats.h +++ b/dll/steam_user_stats.h @@ -66,29 +66,7 @@ unsigned int find_leaderboard(std::string name) void load_achievements_db() { std::string file_path = Local_Storage::get_game_settings_path() + achievements_user_file; - std::ifstream achs_file(file_path); - - if (achs_file) - { - achs_file.seekg(0, std::ios::end); - size_t size = achs_file.tellg(); - std::string buffer(size, '\0'); - achs_file.seekg(0); - // Read it entirely, if the .json file gets too big, - // I should look into this and split reads into smaller parts. - achs_file.read(&buffer[0], size); - achs_file.close(); - - try { - defined_achievements = std::move(nlohmann::json::parse(buffer)); - } catch (std::exception &e) { - PRINT_DEBUG("Error while parsing json: \"%s\" : %s\n", file_path.c_str(), e.what()); - } - } - else - { - PRINT_DEBUG("Couldn't open file \"%s\" to read achievements definition\n", file_path.c_str()); - } + local_storage->load_json(file_path, defined_achievements); } void load_achievements() @@ -96,6 +74,11 @@ void load_achievements() local_storage->load_json_file("", achievements_user_file, user_achievements); } +void save_achievements() +{ + local_storage->write_json_file("", achievements_user_file, user_achievements); +} + public: Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks): settings(settings), @@ -223,6 +206,7 @@ bool GetAchievement( const char *pchName, bool *pbAchieved ) { PRINT_DEBUG("GetAchievement %s\n", pchName); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); try { auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName]( nlohmann::json &item ) { @@ -244,16 +228,19 @@ bool SetAchievement( const char *pchName ) { PRINT_DEBUG("SetAchievement %s\n", pchName); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); try { auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { return item["name"].get() == pchName; }); if (it != defined_achievements.end()) { - if (user_achievements[pchName]["earned"] == false) { + if (user_achievements.find(pchName) == user_achievements.end() || user_achievements[pchName].value("earned", false) == false) { user_achievements[pchName]["earned"] = true; - user_achievements[pchName]["earned_time"] = static_cast(std::time(nullptr)); + user_achievements[pchName]["earned_time"] = std::chrono::duration_cast>(std::chrono::system_clock::now().time_since_epoch()).count(); + save_achievements(); } + return true; } } catch (...) {} @@ -265,6 +252,7 @@ bool ClearAchievement( const char *pchName ) { PRINT_DEBUG("ClearAchievement %s\n", pchName); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); try { auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { @@ -273,6 +261,7 @@ bool ClearAchievement( const char *pchName ) if (it != defined_achievements.end()) { user_achievements[pchName]["earned"] = false; user_achievements[pchName]["earned_time"] = static_cast(0); + save_achievements(); return true; } } catch (...) {} @@ -288,6 +277,7 @@ bool GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 { PRINT_DEBUG("GetAchievementAndUnlockTime\n"); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); try { auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { @@ -319,8 +309,6 @@ bool StoreStats() PRINT_DEBUG("StoreStats\n"); std::lock_guard lock(global_mutex); - local_storage->write_json_file("", achievements_user_file, user_achievements); - UserStatsStored_t data; data.m_nGameID = settings->get_local_game_id().ToUint64(); data.m_eResult = k_EResultOK; @@ -339,6 +327,7 @@ int GetAchievementIcon( const char *pchName ) { PRINT_DEBUG("GetAchievementIcon\n"); if (pchName == nullptr) return 0; + std::lock_guard lock(global_mutex); return 0; } @@ -353,13 +342,17 @@ const char * GetAchievementDisplayAttribute( const char *pchName, const char *pc if (pchName == nullptr) return ""; if (pchKey == nullptr) return ""; + std::lock_guard lock(global_mutex); + if (strcmp (pchKey, "name") == 0) { try { auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { return static_cast(item["name"]) == pchName; }); if (it != defined_achievements.end()) { - return it.value()["displayName"].get().c_str(); + static std::string display_name; + display_name = it.value()["displayName"].get(); + return display_name.c_str(); } } catch (...) {} } @@ -370,7 +363,9 @@ const char * GetAchievementDisplayAttribute( const char *pchName, const char *pc return static_cast(item["name"]) == pchName; }); if (it != defined_achievements.end()) { - return it.value()["description"].get().c_str(); + static std::string description; + description = it.value()["description"].get(); + return description.c_str(); } } catch (...) {} } @@ -381,7 +376,9 @@ const char * GetAchievementDisplayAttribute( const char *pchName, const char *pc return static_cast(item["name"]) == pchName; }); if (it != defined_achievements.end()) { - return it.value()["description"].get().c_str(); + static std::string hidden; + hidden = it.value()["hidden"].get(); + return hidden.c_str(); } } catch (...) {} } @@ -396,7 +393,41 @@ bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint { PRINT_DEBUG("IndicateAchievementProgress\n"); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); + try { + auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) { + return static_cast(item["name"]) == pchName; + }); + auto ach = user_achievements.find(pchName); + if (it != defined_achievements.end()) { + bool achieved = false; + if ( ach != user_achievements.end()) { + bool achieved = ach->value("earned", false); + } + + UserAchievementStored_t data = {}; + data.m_nGameID = settings->get_local_game_id().ToUint64(); + data.m_bGroupAchievement = false; + strncpy(data.m_rgchAchievementName, pchName, k_cchStatNameMax); + + if (achieved) { + data.m_nCurProgress = 0; + data.m_nMaxProgress = 0; + } else { + user_achievements[pchName]["progress"] = nCurProgress; + user_achievements[pchName]["max_progress"] = nMaxProgress; + data.m_nCurProgress = nCurProgress; + data.m_nMaxProgress = nMaxProgress; + } + + save_achievements(); + callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + return true; + } + } catch (...) {} + + return false; } @@ -405,6 +436,7 @@ bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint uint32 GetNumAchievements() { PRINT_DEBUG("GetNumAchievements\n"); + std::lock_guard lock(global_mutex); return defined_achievements.size(); } @@ -413,7 +445,9 @@ const char * GetAchievementName( uint32 iAchievement ) { PRINT_DEBUG("GetAchievementName\n"); try { - return defined_achievements[iAchievement]["name"].get().c_str(); + static std::string achievement_name; + achievement_name = defined_achievements[iAchievement]["name"].get(); + return achievement_name.c_str(); } catch (...) {} return ""; } @@ -482,6 +516,7 @@ bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchi { PRINT_DEBUG("GetUserAchievement %s\n", pchName); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); if (steamIDUser == settings->get_local_steam_id()) { return GetAchievement(pchName, pbAchieved); @@ -495,6 +530,7 @@ bool GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, { PRINT_DEBUG("GetUserAchievementAndUnlockTime %s\n", pchName); if (pchName == nullptr) return false; + std::lock_guard lock(global_mutex); if (steamIDUser == settings->get_local_steam_id()) { return GetAchievementAndUnlockTime(pchName, pbAchieved, punUnlockTime); @@ -507,6 +543,7 @@ bool GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool ResetAllStats( bool bAchievementsToo ) { PRINT_DEBUG("ResetAllStats\n"); + std::lock_guard lock(global_mutex); //TODO if (bAchievementsToo) { std::for_each(user_achievements.begin(), user_achievements.end(), [](nlohmann::json& v) {