diff --git a/README.md b/README.md
index eec7959a..bd22dc79 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,18 @@ If your game has an original steam_api(64).dll or libsteam_api.so older than may
For more information see: [The Release Readme](Readme_release.txt)
+## How to add items to your steam inventory
+
+Create a folder named steam_settings right beside steam_api.dll if there isn't one already. In that folder, create a file named items.json which will contain every item you want to have in your game.
+
+An example can be found in steam_settings.EXAMPLE that works with Killing Floor 2.
+
+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.
+
## Download Binaries
You can download the latest git builds for Linux and Windows on [the Gitlab pages website](https://mr_goldberg.gitlab.io/goldberg_emulator/) and the stable releases in the [release section](https://gitlab.com/Mr_Goldberg/goldberg_emulator/releases) of this repo.
diff --git a/dll/item_db_loader.cpp b/dll/item_db_loader.cpp
new file mode 100644
index 00000000..4e27e55c
--- /dev/null
+++ b/dll/item_db_loader.cpp
@@ -0,0 +1,60 @@
+/* Copyright (C) 2019 Nemirtingas (Maxime P)
+ 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 "item_db_loader.h"
+
+#include
+#include "../json/json.hpp"
+
+void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loadedb)
+{
+ std::ifstream items_file(items_db);
+ // If there is a file and we opened it
+ if( items_file )
+ {
+ items_file.seekg(0, std::ios::end);
+ size_t size = items_file.tellg();
+ std::string buffer(size, '\0');
+ items_file.seekg(0);
+ // Read it entirely, if the .json file gets too big,
+ // I should look into this and split reads into smaller parts.
+ items_file.read(&buffer[0], size);
+ items_file.close();
+
+ try
+ {
+ std::map> tmp;
+ nlohmann::json json = nlohmann::json::parse(buffer);
+
+ for (auto& i : json.items())
+ {
+ SteamItemDef_t key = std::stoi((*i).key());
+ nlohmann::json& value = (*i).value();
+ for (auto& j : value.items())
+ {
+ tmp[key][(*j).key()] = (*j).value();
+ }
+ }
+
+ items->swap(tmp);
+ }
+ catch (std::exception& e)
+ {
+ PRINT_DEBUG("Error while parsing json: %s", e.what());
+ }
+ }
+ *is_loadedb = true;
+}
\ No newline at end of file
diff --git a/dll/item_db_loader.h b/dll/item_db_loader.h
new file mode 100644
index 00000000..89c09129
--- /dev/null
+++ b/dll/item_db_loader.h
@@ -0,0 +1,24 @@
+/* Copyright (C) 2019 Nemirtingas (Maxime P)
+ 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;*/
+#ifndef __ITEM_DB_LOADER_INCLUDED__
+#define __ITEM_DB_LOADER_INCLUDED__
+
+#include "base.h" // For SteamItemDef_t
+
+#include
+void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loaded);
+
+#endif//__ITEM_DB_LOADER_INCLUDED__
\ No newline at end of file
diff --git a/dll/steam_inventory.cpp b/dll/steam_inventory.cpp
new file mode 100644
index 00000000..03968943
--- /dev/null
+++ b/dll/steam_inventory.cpp
@@ -0,0 +1,5 @@
+#include "steam_inventory.h"
+
+std::once_flag Steam_Inventory::items_loading;
+std::atomic_bool Steam_Inventory::items_loaded(false);
+std::map> Steam_Inventory::items;
\ No newline at end of file
diff --git a/dll/steam_inventory.h b/dll/steam_inventory.h
index 2b4d93e9..1aedef94 100644
--- a/dll/steam_inventory.h
+++ b/dll/steam_inventory.h
@@ -15,7 +15,8 @@
License along with the Goldberg Emulator; if not, see
. */
-#include "base.h"
+#include "item_db_loader.h"
+#include
struct Steam_Inventory_Requests {
double timeout = 0.1;
@@ -34,11 +35,10 @@ struct Steam_Inventory_Requests {
}
};
-
class Steam_Inventory :
-public ISteamInventory001,
-public ISteamInventory002,
-public ISteamInventory
+ public ISteamInventory001,
+ public ISteamInventory002,
+ public ISteamInventory
{
class Settings *settings;
class SteamCallResults *callback_results;
@@ -46,7 +46,21 @@ public ISteamInventory
std::vector inventory_requests;
-struct Steam_Inventory_Requests *new_inventory_result(const SteamItemInstanceID_t *pInstanceIDs=NULL, uint32 unCountInstanceIDs=0)
+ static std::once_flag items_loading;
+ static std::atomic_bool items_loaded;
+ static std::map> items;
+ // Like typedefs
+ using item_iterator = std::map>::iterator;
+ using attr_iterator = std::map::iterator;
+
+ // Set this to false when we have cached everything,
+ // reset to true if something changed in the item db.
+ // Could use inotify on linux
+ // Could use FindFirstChangeNotificationA + WaitForSingleObject + FindNextChangeNotification on Windows to monitor the db file
+ // Or find a server somewhere to hold the data for us then cache on local settings.
+ bool need_load_definitions = true;
+
+struct Steam_Inventory_Requests* new_inventory_result(const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0)
{
static SteamInventoryResult_t result;
++result;
@@ -77,6 +91,14 @@ public:
Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks)
{
+ std::call_once(items_loading, [&]()
+ {
+ std::string items_db_file(Local_Storage::get_game_settings_path() + "items.json");
+ PRINT_DEBUG("Items file path: %s\n", items_db_file.c_str());
+ std::thread items_load_thread(read_items_db, items_db_file, &items, &items_loaded);
+ items_load_thread.detach();
+ });
+
this->settings = settings;
this->callbacks = callbacks;
this->callback_results = callback_results;
@@ -122,7 +144,25 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
if (!request) return false;
if (!request->result_done()) return false;
- if (punOutItemsArraySize) *punOutItemsArraySize = 0;
+ if (pOutItemsArray != nullptr)
+ {
+ uint32 max_items = *punOutItemsArraySize;
+ // We end if we reached the end of items or the end of buffer
+ for( auto i = items.begin(); i != items.end() && max_items; ++i, --max_items )
+ {
+ pOutItemsArray->m_iDefinition = i->first;
+ pOutItemsArray->m_itemId = i->first;
+ pOutItemsArray->m_unQuantity = 1;
+ pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
+ ++pOutItemsArray;
+ }
+ *punOutItemsArraySize = std::min(*punOutItemsArraySize, static_cast(items.size()));
+ }
+ else if (punOutItemsArraySize != nullptr)
+ {
+ *punOutItemsArraySize = items.size();
+ }
+
PRINT_DEBUG("GetResultItems good\n");
return true;
}
@@ -206,8 +246,21 @@ bool GetAllItems( SteamInventoryResult_t *pResultHandle )
{
PRINT_DEBUG("GetAllItems\n");
std::lock_guard lock(global_mutex);
- if (pResultHandle) {
- struct Steam_Inventory_Requests *request = new_inventory_result();
+ struct Steam_Inventory_Requests* request = new_inventory_result();
+
+ // Can't call LoadItemDefinitions because it sends a SteamInventoryResultReady_t.
+ if( need_load_definitions )
+ {
+ if (items_loaded)
+ {
+ need_load_definitions = false;
+ SteamInventoryDefinitionUpdate_t data = {};
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
+ }
+ }
+
+ if (!need_load_definitions)
+ {
{
// SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems
// successfully returns a result which is newer / fresher than the last
@@ -224,11 +277,21 @@ bool GetAllItems( SteamInventoryResult_t *pResultHandle )
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), request->timeout);
}
- *pResultHandle = request->inventory_result;
- return true;
+ if (pResultHandle != nullptr)
+ *pResultHandle = request->inventory_result;
+ }
+ else
+ {
+ struct SteamInventoryResultReady_t data;
+ data.m_handle = request->inventory_result;
+ data.m_result = k_EResultPending;
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data), request->timeout);
+
+ if (pResultHandle != nullptr)
+ *pResultHandle = request->inventory_result;
}
- return false;
+ return true;
}
@@ -488,6 +551,32 @@ STEAM_METHOD_DESC(LoadItemDefinitions triggers the automatic load and refresh of
bool LoadItemDefinitions()
{
PRINT_DEBUG("LoadItemDefinitions\n");
+
+ if (need_load_definitions)
+ {
+ if (!items_loaded)
+ {
+ SteamInventoryResultReady_t data;
+ data.m_result = k_EResultPending;
+ data.m_handle = new_inventory_result()->inventory_result;
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
+ }
+ else
+ {
+ need_load_definitions = false;
+ {
+ SteamInventoryDefinitionUpdate_t data = {};
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
+ }
+ {
+ SteamInventoryResultReady_t data = {};
+ data.m_result = k_EResultOK;
+ data.m_handle = new_inventory_result()->inventory_result;
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
+ }
+ }
+ }
+
return true;
}
@@ -502,18 +591,24 @@ bool GetItemDefinitionIDs(
STEAM_DESC(Size of array is passed in and actual size used is returned in this param) uint32 *punItemDefIDsArraySize )
{
PRINT_DEBUG("GetItemDefinitionIDs\n");
- if (!punItemDefIDsArraySize) {
+ if (!punItemDefIDsArraySize)
return false;
- }
PRINT_DEBUG("array_size %u\n", *punItemDefIDsArraySize);
-/*
- if (pItemDefIDs) {
- *pItemDefIDs = 0;
+
+ if (pItemDefIDs == nullptr)
+ {
+ *punItemDefIDsArraySize = items.size();
+ return true;
}
-*/
- //*punItemDefIDsArraySize = 0;
- return false;
+
+ if (*punItemDefIDsArraySize < items.size())
+ return false;
+
+ for (auto& i : items)
+ *pItemDefIDs++ = i.first;
+
+ return true;
}
@@ -530,6 +625,75 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
STEAM_OUT_STRING_COUNT(punValueBufferSizeOut) char *pchValueBuffer, uint32 *punValueBufferSizeOut )
{
PRINT_DEBUG("GetItemDefinitionProperty\n");
+
+ item_iterator item;
+ if ((item = items.find(iDefinition)) != items.end())
+ {
+ attr_iterator attr;
+ if (pchPropertyName != nullptr)
+ {
+ // Should I check for punValueBufferSizeOut == nullptr ?
+ // Try to get the property
+ if ((attr = item->second.find(pchPropertyName)) != items[iDefinition].end())
+ {
+ std::string const& val = attr->second;
+ if (pchValueBuffer != nullptr)
+ {
+ // copy what we can
+ strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut);
+ }
+
+ // Set punValueBufferSizeOut to the property size
+ *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut);
+
+ 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);
+ }
+ }
+ 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->second)
+ *punValueBufferSizeOut += i.first.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->second.begin(); i != item->second.end() && len > 0; ++i )
+ {
+ strncat(pchValueBuffer, i->first.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->first.length()));
+ *punValueBufferSizeOut += x;
+ len -= x;
+
+ if (len && std::distance(i, item->second.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;
}
@@ -653,4 +817,4 @@ bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventory
PRINT_DEBUG("SubmitUpdateProperties\n");
}
-};
+};
\ No newline at end of file
diff --git a/files_example/steam_settings.EXAMPLE/items.EXAMPLE.json b/files_example/steam_settings.EXAMPLE/items.EXAMPLE.json
new file mode 100644
index 00000000..978255e3
--- /dev/null
+++ b/files_example/steam_settings.EXAMPLE/items.EXAMPLE.json
@@ -0,0 +1,95 @@
+{
+ "2001": {
+ "Timestamp": "2018-01-09T19:30:03Z",
+ "modified": "20180109T193003Z",
+ "date_created": "20180109T193003Z",
+ "type": "bundle",
+ "display_type": "Bundle",
+ "name": "Foster Classic Bundle",
+ "bundle": "2011x1;2012x1;3358x1",
+ "description": "Comes with Foster's Classic Suit Uniform (includes 9 skin styles), Classic Tie Accessory (includes 8 skin styles) and Bow Tie Accessory. Not tradeable or marketable.",
+ "background_color": "000000",
+ "icon_url": "http://art.tripwirecdn.com/TestItemIcons/Bundle_ClassicFoster_96.png",
+ "icon_url_large": "http://art.tripwirecdn.com/TestItemIcons/Bundle_ClassicFoster_360.png",
+ "name_color": "7a0000",
+ "tradable": "false",
+ "marketable": "false",
+ "commodity": "false",
+ "drop_interval": "0",
+ "drop_max_per_window": "0",
+ "workshopid": "0",
+ "tw_unique_to_own": "true",
+ "item_quality": "0",
+ "tw_price": "$4.99",
+ "tw_type": "skc",
+ "tw_client_visible": "1",
+ "tw_icon_small": "CHR_MrFoster_Item_TEX.ClassicSuit.UniformBundle_FostersSuit",
+ "tw_icon_large": "CHR_MrFoster_Item_TEX.ClassicSuit.UniformBundle_FostersSuit",
+ "tw_description": "",
+ "tw_client_name": "",
+ "tw_client_type": "",
+ "tw_rarity": "crate"
+ },
+ "2002": {
+ "Timestamp": "2018-01-09T19:30:03Z",
+ "modified": "20180109T193003Z",
+ "date_created": "20180109T193003Z",
+ "type": "bundle",
+ "display_type": "Bundle",
+ "name": "Briar's Bobby Bundle",
+ "bundle": "2021x1;2022x1",
+ "description": "Comes with Briar's London Uniform (includes 5 skin styles), and Custodian Helmet Cosmetic Accessory (includes 3 skin styles) Not tradeable or marketable.",
+ "background_color": "000000",
+ "icon_url": "http://art.tripwirecdn.com/TestItemIcons/Bundle_BriarBobby_96.png",
+ "icon_url_large": "http://art.tripwirecdn.com/TestItemIcons/Bundle_BriarBobby_360.png",
+ "name_color": "7a0000",
+ "tradable": "false",
+ "marketable": "false",
+ "commodity": "false",
+ "drop_interval": "0",
+ "drop_max_per_window": "0",
+ "workshopid": "0",
+ "tw_unique_to_own": "true",
+ "item_quality": "0",
+ "tw_price": "$4.99",
+ "tw_type": "skc",
+ "tw_client_visible": "1",
+ "tw_icon_small": "CHR_Briar_Item_TEX.BobbyUniform.UniformBundle_BriarBobby",
+ "tw_icon_large": "CHR_Briar_Item_TEX.BobbyUniform.UniformBundle_BriarBobby",
+ "tw_description": "",
+ "tw_client_name": "",
+ "tw_client_type": "",
+ "tw_rarity": "crate"
+ },
+ "2003": {
+ "Timestamp": "2018-01-09T19:30:03Z",
+ "modified": "20180109T193003Z",
+ "date_created": "20180109T193003Z",
+ "type": "bundle",
+ "display_type": "Bundle",
+ "name": "Tanaka's Biker Bundle",
+ "bundle": "2031x1;2032x1",
+ "description": "Comes with Tanaka's Motorcycle Uniform (includes 7 skin styles) and Helmet Cosmetic Accessory (includes 7 skin styles) Not tradeable or marketable.",
+ "background_color": "000000",
+ "icon_url": "http://art.tripwirecdn.com/TestItemIcons/Bundle_BikerTanaka_96.png",
+ "icon_url_large": "http://art.tripwirecdn.com/TestItemIcons/Bundle_BikerTanaka_360.png",
+ "name_color": "7a0000",
+ "tradable": "false",
+ "marketable": "false",
+ "commodity": "false",
+ "drop_interval": "0",
+ "drop_max_per_window": "0",
+ "workshopid": "0",
+ "tw_unique_to_own": "true",
+ "item_quality": "0",
+ "tw_price": "$4.99",
+ "tw_type": "skc",
+ "tw_client_visible": "1",
+ "tw_icon_small": "CHR_Tanaka_01_Item_TEX.BikerUniform.UniformBundle_TanakaBiker",
+ "tw_icon_large": "CHR_Tanaka_01_Item_TEX.BikerUniform.UniformBundle_TanakaBiker",
+ "tw_description": "",
+ "tw_client_name": "",
+ "tw_client_type": "",
+ "tw_rarity": "crate"
+ }
+}
\ No newline at end of file
diff --git a/json/json.hpp b/json/json.hpp
new file mode 100644
index 00000000..e0a2645d
--- /dev/null
+++ b/json/json.hpp
@@ -0,0 +1,20919 @@
+/*
+ __ _____ _____ _____
+ __| | __| | | | JSON for Modern C++
+| | |__ | | | | | | version 3.6.1
+|_____|_____|_____|_|___| https://github.com/nlohmann/json
+
+Licensed under the MIT License .
+SPDX-License-Identifier: MIT
+Copyright (c) 2013-2019 Niels Lohmann .
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef INCLUDE_NLOHMANN_JSON_HPP_
+#define INCLUDE_NLOHMANN_JSON_HPP_
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3
+#define NLOHMANN_JSON_VERSION_MINOR 6
+#define NLOHMANN_JSON_VERSION_PATCH 1
+
+#include // all_of, find, for_each
+#include // assert
+#include // and, not, or
+#include // nullptr_t, ptrdiff_t, size_t
+#include // hash, less
+#include // initializer_list
+#include // istream, ostream
+#include // random_access_iterator_tag
+#include // unique_ptr
+#include // accumulate
+#include // string, stoi, to_string
+#include // declval, forward, move, pair, swap
+#include // vector
+
+// #include
+
+
+#include
+
+// #include
+
+
+#include // transform
+#include // array
+#include // and, not
+#include // forward_list
+#include // inserter, front_inserter, end
+#include