/* Copyright (C) 2019 Mr Goldberg This file is part of the Goldberg Emulator The Goldberg Emulator is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. The Goldberg Emulator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with the Goldberg Emulator; if not, see . */ #include "base.h" // For SteamItemDef_t #include "../json/json.hpp" struct Steam_Inventory_Requests { double timeout = 0.1; bool done = false; bool full_query; SteamInventoryResult_t inventory_result; std::chrono::system_clock::time_point time_created; std::vector instance_ids; bool result_done() { return done; } uint32 timestamp() { return std::chrono::duration_cast>(time_created.time_since_epoch()).count(); } }; class Steam_Inventory : public ISteamInventory001, public ISteamInventory002, public ISteamInventory { public: static constexpr auto items_user_file = "items.json"; static constexpr auto items_default_file = "default_items.json"; private: class Settings *settings; class SteamCallResults *callback_results; class SteamCallBacks *callbacks; class RunEveryRunCB *run_every_runcb; class Local_Storage* local_storage; std::vector inventory_requests; nlohmann::json defined_items; nlohmann::json user_items; bool inventory_loaded; bool call_definition_update; bool item_definitions_loaded; struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0) { static SteamInventoryResult_t result; ++result; struct Steam_Inventory_Requests request; request.inventory_result = result; request.full_query = full_query; if (pInstanceIDs && unCountInstanceIDs) { request.instance_ids.reserve(unCountInstanceIDs); std::copy(pInstanceIDs, pInstanceIDs + unCountInstanceIDs, std::back_inserter(request.instance_ids)); } request.time_created = std::chrono::system_clock::now(); inventory_requests.push_back(request); return &(inventory_requests.back()); } struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t resultHandle) { auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); if (inventory_requests.end() == request) return NULL; return &(*request); } 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()); local_storage->load_json(items_db_path, defined_items); } void read_inventory_db() { // If we havn't got any inventory if (!local_storage->load_json_file("", items_user_file, user_items)) { // 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()); local_storage->load_json(items_db_path, user_items); } } public: static void run_every_runcb_cb(void *object) { PRINT_DEBUG("Steam_Inventory::run_every_runcb\n"); Steam_Inventory *obj = (Steam_Inventory *)object; obj->RunCallbacks(); } Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, class Local_Storage *local_storage): settings(settings), callback_results(callback_results), callbacks(callbacks), run_every_runcb(run_every_runcb), local_storage(local_storage), defined_items(nlohmann::json::object()), user_items(nlohmann::json::object()), inventory_loaded(false), call_definition_update(false), item_definitions_loaded(false) { this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this); } ~Steam_Inventory() { this->run_every_runcb->remove(&Steam_Inventory::run_every_runcb_cb, this); } // INVENTORY ASYNC RESULT MANAGEMENT // // Asynchronous inventory queries always output a result handle which can be used with // GetResultStatus, GetResultItems, etc. A SteamInventoryResultReady_t callback will // be triggered when the asynchronous result becomes ready (or fails). // // Find out the status of an asynchronous inventory result handle. Possible values: // k_EResultPending - still in progress // k_EResultOK - done, result ready // k_EResultExpired - done, result ready, maybe out of date (see DeserializeResult) // k_EResultInvalidParam - ERROR: invalid API call parameters // k_EResultServiceUnavailable - ERROR: service temporarily down, you may retry later // k_EResultLimitExceeded - ERROR: operation would exceed per-user inventory limits // k_EResultFail - ERROR: unknown / generic error STEAM_METHOD_DESC(Find out the status of an asynchronous inventory result handle.) EResult GetResultStatus( SteamInventoryResult_t resultHandle ) { PRINT_DEBUG("GetResultStatus\n"); std::lock_guard lock(global_mutex); struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); if (!request) return k_EResultInvalidParam; if (!request->result_done()) return k_EResultPending; return k_EResultOK; } // Copies the contents of a result set into a flat array. The specific // contents of the result set depend on which query which was used. STEAM_METHOD_DESC(Copies the contents of a result set into a flat array. The specific contents of the result set depend on which query which was used.) bool GetResultItems( SteamInventoryResult_t resultHandle, STEAM_OUT_ARRAY_COUNT( punOutItemsArraySize,Output array) SteamItemDetails_t *pOutItemsArray, uint32 *punOutItemsArraySize ) { PRINT_DEBUG("GetResultItems\n"); std::lock_guard lock(global_mutex); struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); if (!request) return false; if (!request->result_done()) return false; if (!inventory_loaded) return false; if (pOutItemsArray != nullptr) { SteamItemDetails_t *items_array_base = pOutItemsArray; uint32 max_items = *punOutItemsArraySize; if (request->full_query) { // We end if we reached the end of items or the end of buffer for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items ) { pOutItemsArray->m_iDefinition = std::stoi(i.key()); pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition; try { pOutItemsArray->m_unQuantity = i.value().get(); } catch (...) { pOutItemsArray->m_unQuantity = 0; } pOutItemsArray->m_unFlags = k_ESteamItemNoTrade; ++pOutItemsArray; } } else { for (auto &itemid : request->instance_ids) { if (!max_items) break; 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; } } } *punOutItemsArraySize = pOutItemsArray - items_array_base; } else if (punOutItemsArraySize != nullptr) { if (request->full_query) { *punOutItemsArraySize = user_items.size(); } else { *punOutItemsArraySize = std::count_if(request->instance_ids.begin(), request->instance_ids.end(), [this](SteamItemInstanceID_t item_id){ return user_items.find(std::to_string(item_id)) != user_items.end();}); } } PRINT_DEBUG("GetResultItems good\n"); return true; } // In combination with GetResultItems, you can use GetResultItemProperty to retrieve // dynamic string properties for a given item returned in the result set. // // Property names are always composed of ASCII letters, numbers, and/or underscores. // // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available // property names. // // If pchValueBuffer is NULL, *punValueBufferSize will contain the // suggested buffer size. Otherwise it will be the number of bytes actually copied // to pchValueBuffer. If the results do not fit in the given buffer, partial // results may be copied. bool GetResultItemProperty( SteamInventoryResult_t resultHandle, uint32 unItemIndex, const char *pchPropertyName, STEAM_OUT_STRING_COUNT( punValueBufferSizeOut ) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) { PRINT_DEBUG("GetResultItemProperty\n"); //TODO return false; } // Returns the server time at which the result was generated. Compare against // the value of IClientUtils::GetServerRealTime() to determine age. STEAM_METHOD_DESC(Returns the server time at which the result was generated. Compare against the value of IClientUtils::GetServerRealTime() to determine age.) uint32 GetResultTimestamp( SteamInventoryResult_t resultHandle ) { PRINT_DEBUG("GetResultTimestamp\n"); std::lock_guard lock(global_mutex); struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); if (!request || !request->result_done()) return 0; return request->timestamp(); } // Returns true if the result belongs to the target steam ID, false if the // result does not. This is important when using DeserializeResult, to verify // that a remote player is not pretending to have a different user's inventory. STEAM_METHOD_DESC(Returns true if the result belongs to the target steam ID or false if the result does not. This is important when using DeserializeResult to verify that a remote player is not pretending to have a different users inventory.) bool CheckResultSteamID( SteamInventoryResult_t resultHandle, CSteamID steamIDExpected ) { PRINT_DEBUG("CheckResultSteamID\n"); //TODO return true; } // Destroys a result handle and frees all associated memory. STEAM_METHOD_DESC(Destroys a result handle and frees all associated memory.) void DestroyResult( SteamInventoryResult_t resultHandle ) { PRINT_DEBUG("DestroyResult\n"); std::lock_guard lock(global_mutex); auto request = std::find_if(inventory_requests.begin(), inventory_requests.end(), [&resultHandle](struct Steam_Inventory_Requests const& item) { return item.inventory_result == resultHandle; }); if (inventory_requests.end() == request) return; inventory_requests.erase(request); } // INVENTORY ASYNC QUERY // // Captures the entire state of the current user's Steam inventory. // You must call DestroyResult on this handle when you are done with it. // Returns false and sets *pResultHandle to zero if inventory is unavailable. // Note: calls to this function are subject to rate limits and may return // cached results if called too frequently. It is suggested that you call // this function only when you are about to display the user's full inventory, // or if you expect that the inventory may have changed. STEAM_METHOD_DESC(Captures the entire state of the current users Steam inventory.) bool GetAllItems( SteamInventoryResult_t *pResultHandle ) { PRINT_DEBUG("GetAllItems\n"); std::lock_guard lock(global_mutex); struct Steam_Inventory_Requests* request = new_inventory_result(); if (pResultHandle != nullptr) *pResultHandle = request->inventory_result; return true; } // Captures the state of a subset of the current user's Steam inventory, // identified by an array of item instance IDs. The results from this call // can be serialized and passed to other players to "prove" that the current // user owns specific items, without exposing the user's entire inventory. // For example, you could call GetItemsByID with the IDs of the user's // currently equipped cosmetic items and serialize this to a buffer, and // then transmit this buffer to other players upon joining a game. STEAM_METHOD_DESC(Captures the state of a subset of the current users Steam inventory identified by an array of item instance IDs.) bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unCountInstanceIDs ) const SteamItemInstanceID_t *pInstanceIDs, uint32 unCountInstanceIDs ) { PRINT_DEBUG("GetItemsByID\n"); std::lock_guard lock(global_mutex); if (pResultHandle) { struct Steam_Inventory_Requests *request = new_inventory_result(false, pInstanceIDs, unCountInstanceIDs); *pResultHandle = request->inventory_result; return true; } return false; } // RESULT SERIALIZATION AND AUTHENTICATION // // Serialized result sets contain a short signature which can't be forged // or replayed across different game sessions. A result set can be serialized // on the local client, transmitted to other players via your game networking, // and deserialized by the remote players. This is a secure way of preventing // hackers from lying about posessing rare/high-value items. // Serializes a result set with signature bytes to an output buffer. Pass // NULL as an output buffer to get the required size via punOutBufferSize. // The size of a serialized result depends on the number items which are being // serialized. When securely transmitting items to other players, it is // recommended to use "GetItemsByID" first to create a minimal result set. // Results have a built-in timestamp which will be considered "expired" after // an hour has elapsed. See DeserializeResult for expiration handling. bool SerializeResult( SteamInventoryResult_t resultHandle, STEAM_OUT_BUFFER_COUNT(punOutBufferSize) void *pOutBuffer, uint32 *punOutBufferSize ) { PRINT_DEBUG("SerializeResult %i\n", resultHandle); std::lock_guard lock(global_mutex); //TODO struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle); if (!request) return false; if (!request->result_done()) return false; uint8 buffer[8 + 128] = {}; memset(buffer, 0x5F, sizeof(buffer)); if (!punOutBufferSize) return false; PRINT_DEBUG("Size %u\n", *punOutBufferSize); if (!pOutBuffer) { *punOutBufferSize = sizeof(buffer); return true; } if (*punOutBufferSize < sizeof(buffer)) { *punOutBufferSize = sizeof(buffer); return false; //?? } memcpy(pOutBuffer, buffer, sizeof(buffer)); *punOutBufferSize = sizeof(buffer); return true; } // Deserializes a result set and verifies the signature bytes. Returns false // if bRequireFullOnlineVerify is set but Steam is running in Offline mode. // Otherwise returns true and then delivers error codes via GetResultStatus. // // The bRESERVED_MUST_BE_FALSE flag is reserved for future use and should not // be set to true by your game at this time. // // DeserializeResult has a potential soft-failure mode where the handle status // is set to k_EResultExpired. GetResultItems() still succeeds in this mode. // The "expired" result could indicate that the data may be out of date - not // just due to timed expiration (one hour), but also because one of the items // in the result set may have been traded or consumed since the result set was // generated. You could compare the timestamp from GetResultTimestamp() to // ISteamUtils::GetServerRealTime() to determine how old the data is. You could // simply ignore the "expired" result code and continue as normal, or you // could challenge the player with expired data to send an updated result set. bool DeserializeResult( SteamInventoryResult_t *pOutResultHandle, STEAM_BUFFER_COUNT(punOutBufferSize) const void *pBuffer, uint32 unBufferSize, bool bRESERVED_MUST_BE_FALSE) { PRINT_DEBUG("DeserializeResult\n"); std::lock_guard lock(global_mutex); //TODO if (pOutResultHandle) { struct Steam_Inventory_Requests *request = new_inventory_result(false); *pOutResultHandle = request->inventory_result; return true; } return false; } // INVENTORY ASYNC MODIFICATION // // GenerateItems() creates one or more items and then generates a SteamInventoryCallback_t // notification with a matching nCallbackContext parameter. This API is only intended // for prototyping - it is only usable by Steam accounts that belong to the publisher group // for your game. // If punArrayQuantity is not NULL, it should be the same length as pArrayItems and should // describe the quantity of each item to generate. bool GenerateItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) { PRINT_DEBUG("GenerateItems\n"); return false; } // GrantPromoItems() checks the list of promotional items for which the user may be eligible // and grants the items (one time only). On success, the result set will include items which // were granted, if any. If no items were granted because the user isn't eligible for any // promotions, this is still considered a success. STEAM_METHOD_DESC(GrantPromoItems() checks the list of promotional items for which the user may be eligible and grants the items (one time only).) bool GrantPromoItems( SteamInventoryResult_t *pResultHandle ) { PRINT_DEBUG("GrantPromoItems\n"); return true; } // AddPromoItem() / AddPromoItems() are restricted versions of GrantPromoItems(). Instead of // scanning for all eligible promotional items, the check is restricted to a single item // definition or set of item definitions. This can be useful if your game has custom UI for // showing a specific promo item to the user. bool AddPromoItem( SteamInventoryResult_t *pResultHandle, SteamItemDef_t itemDef ) { PRINT_DEBUG("AddPromoItem\n"); return false; } bool AddPromoItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, uint32 unArrayLength ) { PRINT_DEBUG("AddPromoItems\n"); return false; } // ConsumeItem() removes items from the inventory, permanently. They cannot be recovered. // Not for the faint of heart - if your game implements item removal at all, a high-friction // UI confirmation process is highly recommended. STEAM_METHOD_DESC(ConsumeItem() removes items from the inventory permanently.) bool ConsumeItem( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemConsume, uint32 unQuantity ) { PRINT_DEBUG("ConsumeItem\n"); return false; } // ExchangeItems() is an atomic combination of item generation and consumption. // It can be used to implement crafting recipes or transmutations, or items which unpack // themselves into other items (e.g., a chest). // Exchange recipes are defined in the ItemDef, and explicitly list the required item // types and resulting generated type. // Exchange recipes are evaluated atomically by the Inventory Service; if the supplied // components do not match the recipe, or do not contain sufficient quantity, the // exchange will fail. bool ExchangeItems( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT(unArrayGenerateLength) const SteamItemDef_t *pArrayGenerate, STEAM_ARRAY_COUNT(unArrayGenerateLength) const uint32 *punArrayGenerateQuantity, uint32 unArrayGenerateLength, STEAM_ARRAY_COUNT(unArrayDestroyLength) const SteamItemInstanceID_t *pArrayDestroy, STEAM_ARRAY_COUNT(unArrayDestroyLength) const uint32 *punArrayDestroyQuantity, uint32 unArrayDestroyLength ) { PRINT_DEBUG("ExchangeItems\n"); return false; } // TransferItemQuantity() is intended for use with items which are "stackable" (can have // quantity greater than one). It can be used to split a stack into two, or to transfer // quantity from one stack into another stack of identical items. To split one stack into // two, pass k_SteamItemInstanceIDInvalid for itemIdDest and a new item will be generated. bool TransferItemQuantity( SteamInventoryResult_t *pResultHandle, SteamItemInstanceID_t itemIdSource, uint32 unQuantity, SteamItemInstanceID_t itemIdDest ) { PRINT_DEBUG("TransferItemQuantity\n"); return false; } // TIMED DROPS AND PLAYTIME CREDIT // // Deprecated. Calling this method is not required for proper playtime accounting. STEAM_METHOD_DESC( Deprecated method. Playtime accounting is performed on the Steam servers. ) void SendItemDropHeartbeat() { PRINT_DEBUG("SendItemDropHeartbeat\n"); } // Playtime credit must be consumed and turned into item drops by your game. Only item // definitions which are marked as "playtime item generators" can be spawned. The call // will return an empty result set if there is not enough playtime credit for a drop. // Your game should call TriggerItemDrop at an appropriate time for the user to receive // new items, such as between rounds or while the player is dead. Note that players who // hack their clients could modify the value of "dropListDefinition", so do not use it // to directly control rarity. // See your Steamworks configuration to set playtime drop rates for individual itemdefs. // The client library will suppress too-frequent calls to this method. STEAM_METHOD_DESC(Playtime credit must be consumed and turned into item drops by your game.) bool TriggerItemDrop( SteamInventoryResult_t *pResultHandle, SteamItemDef_t dropListDefinition ) { PRINT_DEBUG("TriggerItemDrop %p %i\n", pResultHandle, dropListDefinition); //TODO: if gameserver return false return true; } // Deprecated. This method is not supported. bool TradeItems( SteamInventoryResult_t *pResultHandle, CSteamID steamIDTradePartner, STEAM_ARRAY_COUNT(nArrayGiveLength) const SteamItemInstanceID_t *pArrayGive, STEAM_ARRAY_COUNT(nArrayGiveLength) const uint32 *pArrayGiveQuantity, uint32 nArrayGiveLength, STEAM_ARRAY_COUNT(nArrayGetLength) const SteamItemInstanceID_t *pArrayGet, STEAM_ARRAY_COUNT(nArrayGetLength) const uint32 *pArrayGetQuantity, uint32 nArrayGetLength ) { PRINT_DEBUG("TradeItems\n"); return false; } // ITEM DEFINITIONS // // Item definitions are a mapping of "definition IDs" (integers between 1 and 1000000) // to a set of string properties. Some of these properties are required to display items // on the Steam community web site. Other properties can be defined by applications. // Use of these functions is optional; there is no reason to call LoadItemDefinitions // if your game hardcodes the numeric definition IDs (eg, purple face mask = 20, blue // weapon mod = 55) and does not allow for adding new item types without a client patch. // // LoadItemDefinitions triggers the automatic load and refresh of item definitions. // Every time new item definitions are available (eg, from the dynamic addition of new // item types while players are still in-game), a SteamInventoryDefinitionUpdate_t // callback will be fired. STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of item definitions.) bool LoadItemDefinitions() { PRINT_DEBUG("LoadItemDefinitions\n"); std::lock_guard lock(global_mutex); if (!item_definitions_loaded) { call_definition_update = true; } //real steam launches a SteamInventoryResultReady_t which is why I create a new inventory result new_inventory_result(false); return true; } // GetItemDefinitionIDs returns the set of all defined item definition IDs (which are // defined via Steamworks configuration, and not necessarily contiguous integers). // If pItemDefIDs is null, the call will return true and *punItemDefIDsArraySize will // contain the total size necessary for a subsequent call. Otherwise, the call will // return false if and only if there is not enough space in the output array. bool GetItemDefinitionIDs( STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) { PRINT_DEBUG("GetItemDefinitionIDs %p\n", pItemDefIDs); std::lock_guard lock(global_mutex); if (!punItemDefIDsArraySize) return false; PRINT_DEBUG("array_size %u\n", *punItemDefIDsArraySize); if (pItemDefIDs == nullptr) { *punItemDefIDsArraySize = defined_items.size(); return true; } if (*punItemDefIDsArraySize < defined_items.size()) return false; for (auto i = defined_items.begin(); i != defined_items.end(); ++i) *pItemDefIDs++ = std::stoi(i.key()); return true; } // GetItemDefinitionProperty returns a string property from a given item definition. // Note that some properties (for example, "name") may be localized and will depend // on the current Steam language settings (see ISteamApps::GetCurrentGameLanguage). // Property names are always composed of ASCII letters, numbers, and/or underscores. // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available // property names. If pchValueBuffer is NULL, *punValueBufferSize will contain the // suggested buffer size. Otherwise it will be the number of bytes actually copied // to pchValueBuffer. If the results do not fit in the given buffer, partial // results may be copied. bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPropertyName, STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut ) { PRINT_DEBUG("GetItemDefinitionProperty %i %s\n", iDefinition, pchPropertyName); std::lock_guard lock(global_mutex); auto item = defined_items.find(std::to_string(iDefinition)); if (item != defined_items.end()) { if (pchPropertyName != nullptr) { // Should I check for punValueBufferSizeOut == nullptr ? // Try to get the property auto attr = item.value().find(pchPropertyName); if (attr != item.value().end()) { std::string val; try { val = attr.value().get(); } catch (...) { pchPropertyName = ""; *punValueBufferSizeOut = 0; PRINT_DEBUG("Error, item: %d, attr: %s is not a string!", iDefinition, pchPropertyName); return true; } if (pchValueBuffer != nullptr) { // copy what we can strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut); *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut); } else { // Set punValueBufferSizeOut to the property size *punValueBufferSizeOut = val.length() + 1; } if (pchValueBuffer != nullptr) { // Make sure we have a null terminator pchValueBuffer[*punValueBufferSizeOut-1] = '\0'; } } // Property not found else { *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. { // If pchValueBuffer is NULL, *punValueBufferSize will contain the suggested buffer size if (pchValueBuffer == nullptr) { // Should I check for punValueBufferSizeOut == nullptr ? *punValueBufferSizeOut = 0; for (auto i = item.value().begin(); i != item.value().end(); ++i) *punValueBufferSizeOut += i.key().length() + 1; // Size of key + comma, and the last is not a comma but null char } else { // strncat always add the null terminator, so remove 1 to the string length uint32_t len = *punValueBufferSizeOut-1; *punValueBufferSizeOut = 0; memset(pchValueBuffer, 0, len); for( auto i = item.value().begin(); i != item.value().end() && len > 0; ++i ) { strncat(pchValueBuffer, i.key().c_str(), len); // Count how many chars we copied // Either the string length or the buffer size if its too small uint32 x = std::min(len, static_cast(i.key().length())); *punValueBufferSizeOut += x; len -= x; if (len && std::distance(i, item.value().end()) != 1) // If this is not the last item, add a comma strncat(pchValueBuffer, ",", len--); // Always add 1, its a comma or the null terminator ++*punValueBufferSizeOut; } } } return true; } return false; } // Request the list of "eligible" promo items that can be manually granted to the given // user. These are promo items of type "manual" that won't be granted automatically. // An example usage of this is an item that becomes available every week. STEAM_CALL_RESULT( SteamInventoryEligiblePromoItemDefIDs_t ) SteamAPICall_t RequestEligiblePromoItemDefinitionsIDs( CSteamID steamID ) { PRINT_DEBUG("RequestEligiblePromoItemDefinitionsIDs\n"); return 0; } // After handling a SteamInventoryEligiblePromoItemDefIDs_t call result, use this // function to pull out the list of item definition ids that the user can be // manually granted via the AddPromoItems() call. bool GetEligiblePromoItemDefinitionIDs( CSteamID steamID, STEAM_OUT_ARRAY_COUNT(punItemDefIDsArraySize,List of item definition IDs) SteamItemDef_t *pItemDefIDs, STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize ) { PRINT_DEBUG("GetEligiblePromoItemDefinitionIDs\n"); return false; } // Starts the purchase process for the given item definitions. The callback SteamInventoryStartPurchaseResult_t // will be posted if Steam was able to initialize the transaction. // // Once the purchase has been authorized and completed by the user, the callback SteamInventoryResultReady_t // will be posted. STEAM_CALL_RESULT( SteamInventoryStartPurchaseResult_t ) SteamAPICall_t StartPurchase( STEAM_ARRAY_COUNT(unArrayLength) const SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) const uint32 *punArrayQuantity, uint32 unArrayLength ) { PRINT_DEBUG("StartPurchase\n"); return 0; } // Request current prices for all applicable item definitions STEAM_CALL_RESULT( SteamInventoryRequestPricesResult_t ) SteamAPICall_t RequestPrices() { PRINT_DEBUG("RequestPrices\n"); SteamInventoryRequestPricesResult_t data; data.m_result = k_EResultOK; memcpy(data.m_rgchCurrency, "USD", 4); return callback_results->addCallResult(data.k_iCallback, &data, sizeof(data), 0.2); } // Returns the number of items with prices. Need to call RequestPrices() first. uint32 GetNumItemsWithPrices() { PRINT_DEBUG("GetNumItemsWithPrices\n"); return 0; } bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pCurrentPrices, STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pBasePrices, uint32 unArrayLength ) { PRINT_DEBUG("GetItemsWithPrices\n"); return false; } // Returns item definition ids and their prices in the user's local currency. // Need to call RequestPrices() first. bool GetItemsWithPrices( STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pArrayItemDefs, Items with prices) SteamItemDef_t *pArrayItemDefs, STEAM_ARRAY_COUNT(unArrayLength) STEAM_OUT_ARRAY_COUNT(pPrices, List of prices for the given item defs) uint64 *pPrices, uint32 unArrayLength ) { PRINT_DEBUG("GetItemsWithPrices old\n"); return GetItemsWithPrices(pArrayItemDefs, pPrices, NULL, unArrayLength); } bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pCurrentPrice, uint64 *pBasePrice ) { PRINT_DEBUG("GetItemPrice\n"); return false; } // Retrieves the price for the item definition id // Returns false if there is no price stored for the item definition. bool GetItemPrice( SteamItemDef_t iDefinition, uint64 *pPrice ) { PRINT_DEBUG("GetItemPrice old\n"); return GetItemPrice(iDefinition, pPrice, NULL); } // Create a request to update properties on items SteamInventoryUpdateHandle_t StartUpdateProperties() { PRINT_DEBUG("StartUpdateProperties\n"); return 0; } // Remove the property on the item bool RemoveProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName ) { PRINT_DEBUG("RemoveProperty\n"); return false; } // Accessor methods to set properties on items bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, const char *pchPropertyValue ) { PRINT_DEBUG("SetProperty\n"); return false; } bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, bool bValue ) { PRINT_DEBUG("SetProperty\n"); return false; } bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, int64 nValue ) { PRINT_DEBUG("SetProperty\n"); return false; } bool SetProperty( SteamInventoryUpdateHandle_t handle, SteamItemInstanceID_t nItemID, const char *pchPropertyName, float flValue ) { PRINT_DEBUG("SetProperty\n"); return false; } // Submit the update request by handle bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventoryResult_t * pResultHandle ) { PRINT_DEBUG("SubmitUpdateProperties\n"); return false; } bool InspectItem( SteamInventoryResult_t *pResultHandle, const char *pchItemToken ) { PRINT_DEBUG("InspectItem\n"); return false; } void RunCallbacks() { if (call_definition_update || !inventory_requests.empty()) { 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), 0.05); } call_definition_update = false; } if (!inventory_requests.empty() && !inventory_loaded) { read_inventory_db(); inventory_loaded = true; } 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) { // 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)); } { struct SteamInventoryResultReady_t data; data.m_handle = r.inventory_result; data.m_result = k_EResultOK; callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); } r.done = true; } } } } };