Merge branch 'master' into my_master
parent
c8288d6bf3
commit
a318e04959
|
@ -71,6 +71,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.
|
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.
|
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.
|
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:
|
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)
|
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)
|
||||||
|
|
|
@ -158,17 +158,17 @@ std::string get_lib_path() {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
struct dirent *ep;
|
struct dirent *ep;
|
||||||
dp = opendir (dir.c_str());
|
dp = opendir (dir.c_str());
|
||||||
unsigned long long int p = (unsigned long long int)&get_lib_path;
|
uintptr_t p = (uintptr_t)&get_lib_path;
|
||||||
|
|
||||||
if (dp != NULL)
|
if (dp != NULL)
|
||||||
{
|
{
|
||||||
while ((ep = readdir (dp))) {
|
while ((ep = readdir (dp))) {
|
||||||
if (memcmp(ep->d_name, ".", 2) != 0 && memcmp(ep->d_name, "..", 3) != 0) {
|
if (memcmp(ep->d_name, ".", 2) != 0 && memcmp(ep->d_name, "..", 3) != 0) {
|
||||||
char *upper = NULL;
|
char *upper = NULL;
|
||||||
unsigned long long int lower_bound = strtoull(ep->d_name, &upper, 16);
|
uintptr_t lower_bound = strtoull(ep->d_name, &upper, 16);
|
||||||
if (lower_bound) {
|
if (lower_bound) {
|
||||||
++upper;
|
++upper;
|
||||||
unsigned long long int upper_bound = strtoull(upper, &upper, 16);
|
uintptr_t upper_bound = strtoull(upper, &upper, 16);
|
||||||
if (upper_bound && (lower_bound < p && p < upper_bound)) {
|
if (upper_bound && (lower_bound < p && p < upper_bound)) {
|
||||||
std::string path = dir + PATH_SEPARATOR + ep->d_name;
|
std::string path = dir + PATH_SEPARATOR + ep->d_name;
|
||||||
char link[PATH_MAX] = {};
|
char link[PATH_MAX] = {};
|
||||||
|
|
|
@ -127,6 +127,21 @@ bool Local_Storage::update_save_filenames(std::string folder)
|
||||||
return true;
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> Local_Storage::get_filenames_path(std::string path)
|
std::vector<std::string> Local_Storage::get_filenames_path(std::string path)
|
||||||
{
|
{
|
||||||
return std::vector<std::string>();
|
return std::vector<std::string>();
|
||||||
|
@ -680,4 +695,68 @@ bool Local_Storage::update_save_filenames(std::string folder)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Local_Storage::load_json(std::string full_path, nlohmann::json& json)
|
||||||
|
{
|
||||||
|
std::ifstream inventory_file(full_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 {
|
||||||
|
json = std::move(nlohmann::json::parse(buffer));
|
||||||
|
PRINT_DEBUG("Loaded json \"%s\". Loaded %u items.\n", full_path.c_str(), json.size());
|
||||||
|
return true;
|
||||||
|
} catch (std::exception& e) {
|
||||||
|
PRINT_DEBUG("Error while parsing \"%s\" json: %s\n", full_path.c_str(), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PRINT_DEBUG("Couldn't open file \"%s\" to read json\n", full_path.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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::ofstream inventory_file(full_path, std::ios::trunc | std::ios::out);
|
||||||
|
if (inventory_file)
|
||||||
|
{
|
||||||
|
inventory_file << std::setw(2) << json;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PRINT_DEBUG("Couldn't open file \"%s\" to write json\n", full_path.c_str());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -59,6 +59,10 @@ public:
|
||||||
std::string get_path(std::string folder);
|
std::string get_path(std::string folder);
|
||||||
|
|
||||||
bool update_save_filenames(std::string folder);
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -58,8 +58,7 @@ class Steam_Inventory :
|
||||||
std::string items_db_file;
|
std::string items_db_file;
|
||||||
std::once_flag load_items_flag;
|
std::once_flag load_items_flag;
|
||||||
bool call_definition_update;
|
bool call_definition_update;
|
||||||
bool definition_update_called;
|
bool item_definitions_loaded;
|
||||||
bool full_update_called;
|
|
||||||
|
|
||||||
struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0)
|
struct Steam_Inventory_Requests* new_inventory_result(bool full_query=true, const SteamItemInstanceID_t* pInstanceIDs = NULL, uint32 unCountInstanceIDs = 0)
|
||||||
{
|
{
|
||||||
|
@ -89,6 +88,25 @@ struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t res
|
||||||
return &(*request);
|
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:
|
public:
|
||||||
|
|
||||||
static void run_every_runcb_cb(void *object)
|
static void run_every_runcb_cb(void *object)
|
||||||
|
@ -99,7 +117,17 @@ static void run_every_runcb_cb(void *object)
|
||||||
obj->RunCallbacks();
|
obj->RunCallbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, std::string items_db_file_path)
|
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)
|
||||||
{
|
{
|
||||||
items_db_file = items_db_file_path;
|
items_db_file = items_db_file_path;
|
||||||
PRINT_DEBUG("Items file path: %s\n", items_db_file.c_str());
|
PRINT_DEBUG("Items file path: %s\n", items_db_file.c_str());
|
||||||
|
@ -163,6 +191,7 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
|
||||||
|
|
||||||
if (pOutItemsArray != nullptr)
|
if (pOutItemsArray != nullptr)
|
||||||
{
|
{
|
||||||
|
SteamItemDetails_t *items_array_base = pOutItemsArray;
|
||||||
uint32 max_items = *punOutItemsArraySize;
|
uint32 max_items = *punOutItemsArraySize;
|
||||||
|
|
||||||
if (request->full_query) {
|
if (request->full_query) {
|
||||||
|
@ -175,22 +204,38 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
|
||||||
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
|
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
|
||||||
++pOutItemsArray;
|
++pOutItemsArray;
|
||||||
}
|
}
|
||||||
*punOutItemsArraySize = std::min(*punOutItemsArraySize, static_cast<uint32>(items.size()));
|
|
||||||
} else {
|
} else {
|
||||||
for (auto &itemid : request->instance_ids) {
|
for (auto &itemid : request->instance_ids) {
|
||||||
if (!max_items) break;
|
if (!max_items) break;
|
||||||
pOutItemsArray->m_iDefinition = itemid;
|
auto it = user_items.find(std::to_string(itemid));
|
||||||
pOutItemsArray->m_itemId = itemid;
|
if (it != user_items.end()) {
|
||||||
pOutItemsArray->m_unQuantity = 1;
|
pOutItemsArray->m_iDefinition = itemid;
|
||||||
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
|
pOutItemsArray->m_itemId = itemid;
|
||||||
++pOutItemsArray;
|
|
||||||
--max_items;
|
try
|
||||||
|
{
|
||||||
|
pOutItemsArray->m_unQuantity = it->get<int>();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
pOutItemsArray->m_unQuantity = 0;
|
||||||
|
}
|
||||||
|
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
|
||||||
|
++pOutItemsArray;
|
||||||
|
--max_items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*punOutItemsArraySize = pOutItemsArray - items_array_base;
|
||||||
}
|
}
|
||||||
else if (punOutItemsArraySize != nullptr)
|
else if (punOutItemsArraySize != nullptr)
|
||||||
{
|
{
|
||||||
*punOutItemsArraySize = items.size();
|
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");
|
PRINT_DEBUG("GetResultItems good\n");
|
||||||
|
@ -278,8 +323,6 @@ bool GetAllItems( SteamInventoryResult_t *pResultHandle )
|
||||||
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
||||||
struct Steam_Inventory_Requests* request = new_inventory_result();
|
struct Steam_Inventory_Requests* request = new_inventory_result();
|
||||||
|
|
||||||
if (!definition_update_called) call_definition_update = true;
|
|
||||||
|
|
||||||
if (pResultHandle != nullptr)
|
if (pResultHandle != nullptr)
|
||||||
*pResultHandle = request->inventory_result;
|
*pResultHandle = request->inventory_result;
|
||||||
|
|
||||||
|
@ -531,7 +574,7 @@ bool LoadItemDefinitions()
|
||||||
PRINT_DEBUG("LoadItemDefinitions\n");
|
PRINT_DEBUG("LoadItemDefinitions\n");
|
||||||
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
||||||
|
|
||||||
if (!definition_update_called) {
|
if (!item_definitions_loaded) {
|
||||||
call_definition_update = true;
|
call_definition_update = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,6 +662,7 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
|
||||||
{
|
{
|
||||||
*punValueBufferSizeOut = 0;
|
*punValueBufferSizeOut = 0;
|
||||||
PRINT_DEBUG("Attr %s not found for item %d\n", pchPropertyName, iDefinition);
|
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.
|
else // Pass a NULL pointer for pchPropertyName to get a comma - separated list of available property names.
|
||||||
|
@ -654,8 +698,11 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -782,35 +829,37 @@ bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventory
|
||||||
void RunCallbacks()
|
void RunCallbacks()
|
||||||
{
|
{
|
||||||
if (call_definition_update || inventory_requests.size()) {
|
if (call_definition_update || inventory_requests.size()) {
|
||||||
std::call_once(load_items_flag, [&]() {
|
if (!item_definitions_loaded) {
|
||||||
std::thread items_load_thread(read_items_db, items_db_file, &items, &items_loaded);
|
read_items_db();
|
||||||
items_load_thread.detach();
|
item_definitions_loaded = true;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items_loaded) {
|
//only gets called once
|
||||||
if (call_definition_update) {
|
//also gets called when getting items
|
||||||
SteamInventoryDefinitionUpdate_t data = {};
|
SteamInventoryDefinitionUpdate_t data = {};
|
||||||
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
|
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
|
||||||
call_definition_update = false;
|
|
||||||
definition_update_called = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
call_definition_update = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory_requests.size() && !inventory_loaded) {
|
||||||
|
read_inventory_db();
|
||||||
|
inventory_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inventory_loaded)
|
||||||
|
{
|
||||||
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
for (auto & r : inventory_requests) {
|
for (auto & r : inventory_requests) {
|
||||||
if (!r.done && std::chrono::duration_cast<std::chrono::duration<double>>(now - r.time_created).count() > r.timeout) {
|
if (!r.done && std::chrono::duration_cast<std::chrono::duration<double>>(now - r.time_created).count() > r.timeout) {
|
||||||
if (r.full_query) {
|
if (r.full_query) {
|
||||||
if (!full_update_called) {
|
// SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems
|
||||||
// SteamInventoryFullUpdate_t callbacks are triggered when GetAllItems
|
// successfully returns a result which is newer / fresher than the last
|
||||||
// successfully returns a result which is newer / fresher than the last
|
// known result.
|
||||||
// known result.
|
struct SteamInventoryFullUpdate_t data;
|
||||||
//TODO: should this always be returned for each get all item calls?
|
data.m_handle = r.inventory_result;
|
||||||
struct SteamInventoryFullUpdate_t data;
|
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
|
||||||
data.m_handle = r.inventory_result;
|
|
||||||
callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
|
|
||||||
full_update_called = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,6 @@ unsigned int Steam_User_Stats::find_leaderboard(std::string name)
|
||||||
|
|
||||||
void Steam_User_Stats::load_achievements_db()
|
void Steam_User_Stats::load_achievements_db()
|
||||||
{
|
{
|
||||||
std::string file_path = Local_Storage::get_game_settings_path() + achievements_user_file;
|
|
||||||
local_storage->load_json(file_path, defined_achievements);
|
local_storage->load_json(file_path, defined_achievements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +39,12 @@ void Steam_User_Stats::load_achievements()
|
||||||
local_storage->load_json_file("", achievements_user_file, user_achievements);
|
local_storage->load_json_file("", achievements_user_file, user_achievements);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Steam_User_Stats::save_achievements()
|
void save_achievements()
|
||||||
{
|
{
|
||||||
local_storage->write_json_file("", achievements_user_file, user_achievements);
|
local_storage->write_json_file("", achievements_user_file, user_achievements);
|
||||||
}
|
}
|
||||||
|
|
||||||
Steam_User_Stats::Steam_User_Stats(Settings* settings, Local_Storage* local_storage, class SteamCallResults* callback_results, class SteamCallBacks* callbacks) :
|
Steam_User_Stats::Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks):
|
||||||
settings(settings),
|
settings(settings),
|
||||||
local_storage(local_storage),
|
local_storage(local_storage),
|
||||||
callback_results(callback_results),
|
callback_results(callback_results),
|
||||||
|
@ -317,7 +316,7 @@ const char* Steam_User_Stats::GetAchievementDisplayAttribute(const char* pchName
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
std::lock_guard<std::recursive_mutex> lock(global_mutex);
|
||||||
|
|
||||||
if (strcmp(pchKey, "name") == 0) {
|
if (strcmp (pchKey, "name") == 0) {
|
||||||
try {
|
try {
|
||||||
auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
|
auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
|
||||||
return static_cast<std::string const&>(item["name"]) == pchName;
|
return static_cast<std::string const&>(item["name"]) == pchName;
|
||||||
|
@ -368,7 +367,7 @@ bool Steam_User_Stats::IndicateAchievementProgress(const char* pchName, uint32 n
|
||||||
try {
|
try {
|
||||||
auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
|
auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
|
||||||
return static_cast<std::string const&>(item["name"]) == pchName;
|
return static_cast<std::string const&>(item["name"]) == pchName;
|
||||||
});
|
});
|
||||||
auto ach = user_achievements.find(pchName);
|
auto ach = user_achievements.find(pchName);
|
||||||
if (it != defined_achievements.end()) {
|
if (it != defined_achievements.end()) {
|
||||||
bool achieved = false;
|
bool achieved = false;
|
||||||
|
|
|
@ -0,0 +1,462 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <json/json.hpp>
|
||||||
|
|
||||||
|
class CurlGlobal
|
||||||
|
{
|
||||||
|
bool _init;
|
||||||
|
|
||||||
|
CurlGlobal() :_init(false) {}
|
||||||
|
|
||||||
|
~CurlGlobal() { cleanup(); }
|
||||||
|
|
||||||
|
public:
|
||||||
|
static CurlGlobal& Inst()
|
||||||
|
{
|
||||||
|
static CurlGlobal _this;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode init(long flags = CURL_GLOBAL_DEFAULT) { return curl_global_init(flags); }
|
||||||
|
void cleanup()
|
||||||
|
{
|
||||||
|
if (_init)
|
||||||
|
{
|
||||||
|
curl_global_cleanup();
|
||||||
|
_init = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CurlEasy
|
||||||
|
{
|
||||||
|
CURL* _me;
|
||||||
|
bool _init;
|
||||||
|
std::string _buffer;
|
||||||
|
|
||||||
|
static int writer(char* data, size_t size, size_t nmemb,
|
||||||
|
CurlEasy *_this)
|
||||||
|
{
|
||||||
|
if (_this == nullptr)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
_this->_buffer.append(data, size * nmemb);
|
||||||
|
|
||||||
|
return size * nmemb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
CurlEasy() :_me(nullptr), _init(false) {}
|
||||||
|
~CurlEasy() { cleanup(); }
|
||||||
|
|
||||||
|
bool init()
|
||||||
|
{
|
||||||
|
_init = (_me = curl_easy_init()) != nullptr;
|
||||||
|
if (_init)
|
||||||
|
{
|
||||||
|
if (curl_easy_setopt(_me, CURLOPT_WRITEFUNCTION, writer) != CURLE_OK)
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curl_easy_setopt(_me, CURLOPT_WRITEDATA, this) != CURLE_OK)
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _init;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup()
|
||||||
|
{
|
||||||
|
if (_init)
|
||||||
|
{
|
||||||
|
curl_easy_cleanup(_me);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode set_url(const std::string& url)
|
||||||
|
{
|
||||||
|
return curl_easy_setopt(_me, CURLOPT_URL, url.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode skip_verifypeer(bool skip = true)
|
||||||
|
{
|
||||||
|
return curl_easy_setopt(_me, CURLOPT_SSL_VERIFYPEER, skip ? 0L : 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode skip_verifhost(bool skip = true)
|
||||||
|
{
|
||||||
|
return curl_easy_setopt(_me, CURLOPT_SSL_VERIFYHOST, skip ? 0L : 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode connect_only(bool connect = true)
|
||||||
|
{
|
||||||
|
return curl_easy_setopt(_me, CURLOPT_CONNECT_ONLY, connect ? 1L : 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode perform()
|
||||||
|
{
|
||||||
|
_buffer.clear();
|
||||||
|
return curl_easy_perform(_me);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode recv(void *buffer, size_t buflen, size_t* read_len)
|
||||||
|
{
|
||||||
|
return curl_easy_recv(_me, buffer, buflen, read_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
CURLcode get_html_code(long &code)
|
||||||
|
{
|
||||||
|
return curl_easy_getinfo(_me, CURLINFO_RESPONSE_CODE, &code);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const& get_answer() const { return _buffer; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all steam appid with their name: http://api.steampowered.com/ISteamApps/GetAppList/v2/
|
||||||
|
// Steam storefront webapi: https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI
|
||||||
|
// http://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/?key=<key>&appid=<appid>
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"game" : {
|
||||||
|
"gameName" : "<name>",
|
||||||
|
"availableGameStats" : {
|
||||||
|
"achievements" : {
|
||||||
|
("<id>" : {
|
||||||
|
"name" : "achievement_name",
|
||||||
|
"displayName" : "achievement name on screen",
|
||||||
|
"hidden" : (0|1),
|
||||||
|
["description" : "<desc>",]
|
||||||
|
"icon" : "<url to icon when achievement is earned>",
|
||||||
|
"icongray" : "<url to icon when achievement is not earned>"
|
||||||
|
},
|
||||||
|
...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Get appid infos: http://store.steampowered.com/api/appdetails/?appids=218620
|
||||||
|
/*
|
||||||
|
"appid" : {
|
||||||
|
"success" : (true|false),
|
||||||
|
(success == true "data" : {
|
||||||
|
...
|
||||||
|
"name" : "<name>",
|
||||||
|
"steam_appid" : <appid>,
|
||||||
|
(OPT "dlc" : [<dlc id>, <dlc id>]),
|
||||||
|
"header_image" : "<miniature url>" <-- Use this in the overlay ?
|
||||||
|
(OPT "achievements" : {
|
||||||
|
"total" : <num of achievements>
|
||||||
|
}),
|
||||||
|
"background" : "<background url>" <-- Use this as the overlay background ?
|
||||||
|
(OPT "packages" : [<package id>, <package id>])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// ---------------------------------
|
||||||
|
// -- Special thanks to psychonic --
|
||||||
|
// ---------------------------------
|
||||||
|
// Get game items definition digest (Phase1): https://api.steampowered.com/IInventoryService/GetItemDefMeta/v1?key=<webapi_key>&appid=218620
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"modified": 1566848385,
|
||||||
|
"digest": "3CDFC1CC1AC2B0D55D12C1C130F4294BDD6DF8D0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get game items definition: https://api.steampowered.com/IGameInventory/GetItemDefArchive/v0001?appid=218620&digest=<digest>
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"appid":"218620",
|
||||||
|
"itemdefid":"0",
|
||||||
|
"Timestamp":"2016-04-08T18:00:21.3643085Z",
|
||||||
|
"modified":"20160408T180021Z",
|
||||||
|
"date_created":"20160408T180021Z",
|
||||||
|
"type":"",
|
||||||
|
"display_type":"",
|
||||||
|
"name":"",
|
||||||
|
"quantity":0,
|
||||||
|
"description":"",
|
||||||
|
"tradable":false,
|
||||||
|
"marketable":false,
|
||||||
|
"commodity":false,
|
||||||
|
"drop_interval":0,
|
||||||
|
"drop_max_per_window":0,
|
||||||
|
"workshopid":"0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appid":"218620",
|
||||||
|
"itemdefid":"50002",
|
||||||
|
"Timestamp":"2015-11-13T16:01:18.0338618Z",
|
||||||
|
"modified":"20151113T160117Z",
|
||||||
|
"date_created":"20151113T160117Z",
|
||||||
|
"type":"item",
|
||||||
|
"display_type":"",
|
||||||
|
"name":"Sputnik Safe",
|
||||||
|
"quantity":0,
|
||||||
|
"description":"[color=#2360D8]THE JUDGE SHOTGUN | Pixel [/color]\n[color=#2360D8]KOBUS 90 SUBMACHINE GUN | Red Stars[/color]\n[color=#2360D8]PLAINSRIDER BOW | Arctic Plains[/color]\n[color=#2360D8]GRUBER KURZ PISTOL | Little Leopard[/color]\n[color=#2360D8]HRL-7 ROCKET LAUNCHER | Headline[/color]\n[color=#2360D8]LOCOMOTIVE 12G SHOTGUN | Cosmonaut[/color]\n[color=#9900FF]FLAMETHROWER | St. Basil[/color]\n[color=#9900FF]JP36 RIFLE | Ice Leopard [/color]\n[color=#9900FF]CAR-4 RIFLE | Stripe On[/color]\n[color=#9900FF]BRONCO .44 REVOLVER | Black Bull[/color]\n[color=#FF00FF]BERNETTI 9 PISTOL | Angry Bear[/color]\n[color=#FF00FF]THANATOS .50 CAL SNIPER RIFLE | Matrjoschka[/color]\n[color=#FF00FF]M308 RIFLE | Helmet Space Program[/color]\n[color=#FF0000]CLARION RIFLE | Breaching Owl[/color]\n[color=#FF0000]MOSCONI 12G SHOTGUN | Bullet Bear Gun[/color]\n[color=#FFAA00]or an exceedingly rare special item![/color]",
|
||||||
|
"icon_url":"http://media.overkillsoftware.com/economy42gF2Y/safes_weapon_01.png",
|
||||||
|
"icon_url_large":"http://media.overkillsoftware.com/economy42gF2Y/safes_weapon_01.png",
|
||||||
|
"store_tags":"safe;sputnik safe;",
|
||||||
|
"tradable":true,
|
||||||
|
"marketable":true,
|
||||||
|
"commodity":false,
|
||||||
|
"drop_interval":0,
|
||||||
|
"drop_max_per_window":0,
|
||||||
|
"workshopid":"0",
|
||||||
|
"dsl_bonus":"false",
|
||||||
|
"item_name":"weapon_01",
|
||||||
|
"item_slot":"safes"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::string steam_apikey;
|
||||||
|
std::string app_id;
|
||||||
|
|
||||||
|
#if defined(WIN32) || defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
static bool create_directory(std::string const& strPath)
|
||||||
|
{
|
||||||
|
DWORD dwAttrib = GetFileAttributesA(strPath.c_str());
|
||||||
|
|
||||||
|
if (dwAttrib != INVALID_FILE_ATTRIBUTES && dwAttrib & FILE_ATTRIBUTE_DIRECTORY)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return CreateDirectoryA(strPath.c_str(), NULL);
|
||||||
|
}
|
||||||
|
#elif defined(__linux__)
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
static bool create_directory(std::string const& strPath)
|
||||||
|
{
|
||||||
|
struct stat sb;
|
||||||
|
|
||||||
|
if (stat(strPath.c_str(), &sb) != 0)
|
||||||
|
{
|
||||||
|
return mkdir(strPath.c_str(), 0755) == 0;
|
||||||
|
}
|
||||||
|
if (S_ISDIR(sb.st_mode))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void generate_achievements(CurlEasy &easy)
|
||||||
|
{
|
||||||
|
std::string url = "http://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/?key=";
|
||||||
|
url += steam_apikey;
|
||||||
|
url += "&appid=";
|
||||||
|
url += app_id;
|
||||||
|
easy.set_url(url);
|
||||||
|
easy.perform();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::ofstream ach_file("achievements.json", std::ios::trunc | std::ios::out);
|
||||||
|
nlohmann::json json = nlohmann::json::parse(easy.get_answer());
|
||||||
|
nlohmann::json output_json = nlohmann::json::array();
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
int i = 0;
|
||||||
|
for (auto& item : json["game"]["availableGameStats"]["achievements"].items())
|
||||||
|
{
|
||||||
|
output_json[i]["name"] = item.value()["name"];
|
||||||
|
output_json[i]["displayName"] = item.value()["displayName"];
|
||||||
|
output_json[i]["hidden"] = std::to_string(item.value()["hidden"].get<int>());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if( !item.value()["description"].is_null() )
|
||||||
|
output_json[i]["description"] = item.value()["description"];
|
||||||
|
else
|
||||||
|
output_json[i]["description"] = "";
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
output_json[i]["description"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string icon_path = "images/" + item.value()["name"].get<std::string>() + ".jpg";
|
||||||
|
std::ofstream achievement_icon(icon_path, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||||
|
if (!achievement_icon)
|
||||||
|
{
|
||||||
|
std::cerr << "Cannot create achievement icon \"" << icon_path << "\"" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
easy.set_url(item.value()["icon"]);
|
||||||
|
easy.perform();
|
||||||
|
|
||||||
|
std::string picture = easy.get_answer();
|
||||||
|
achievement_icon.write(picture.c_str(), picture.length());
|
||||||
|
|
||||||
|
output_json[i]["icon"] = icon_path;
|
||||||
|
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::string icon_path = "images/" + item.value()["name"].get<std::string>() + "_gray.jpg";
|
||||||
|
std::ofstream achievement_icon(icon_path, std::ios::out | std::ios::trunc | std::ios::binary);
|
||||||
|
if (!achievement_icon)
|
||||||
|
{
|
||||||
|
std::cerr << "Cannot create achievement icon \"" << icon_path << "\"" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
easy.set_url(item.value()["icongray"]);
|
||||||
|
easy.perform();
|
||||||
|
|
||||||
|
std::string picture = easy.get_answer();
|
||||||
|
achievement_icon.write(picture.c_str(), picture.length());
|
||||||
|
|
||||||
|
output_json[i]["icongray"] = icon_path;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
ach_file << std::setw(2) << output_json;
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get infos: ";
|
||||||
|
long code;
|
||||||
|
if (easy.get_html_code(code) == CURLE_OK && code == 403)
|
||||||
|
{
|
||||||
|
std::cerr << "Error in webapi key";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Error while parsing json. Try to go at " << url << " and see what you can do to build your achivements.json";
|
||||||
|
}
|
||||||
|
std::cerr << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_items(CurlEasy& easy)
|
||||||
|
{
|
||||||
|
std::string url = "http://api.steampowered.com/IInventoryService/GetItemDefMeta/v1?key=";
|
||||||
|
url += steam_apikey;
|
||||||
|
url += "&appid=";
|
||||||
|
url += app_id;
|
||||||
|
|
||||||
|
easy.set_url(url);
|
||||||
|
easy.perform();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nlohmann::json json = nlohmann::json::parse(easy.get_answer());
|
||||||
|
std::string digest = json["response"]["digest"];
|
||||||
|
|
||||||
|
url = "http://api.steampowered.com/IGameInventory/GetItemDefArchive/v0001?appid=";
|
||||||
|
url += app_id;
|
||||||
|
url += "&digest=";
|
||||||
|
url += digest;
|
||||||
|
|
||||||
|
easy.set_url(url);
|
||||||
|
easy.perform();
|
||||||
|
|
||||||
|
nlohmann::json item_json = nlohmann::json::object();
|
||||||
|
nlohmann::json default_item_json = nlohmann::json::object();
|
||||||
|
|
||||||
|
json = nlohmann::json::parse(easy.get_answer());
|
||||||
|
std::ofstream items_file("items.json", std::ios::trunc | std::ios::out);
|
||||||
|
std::ofstream default_items_file("default_items.json", std::ios::trunc | std::ios::out);
|
||||||
|
|
||||||
|
for (auto &i : json)
|
||||||
|
{
|
||||||
|
for (auto j = i.begin(); j != i.end(); ++j)
|
||||||
|
{
|
||||||
|
//if (j.key() == "itemdefid")
|
||||||
|
//{
|
||||||
|
// j.value() = std::stoll(j.value().get<std::string>());
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
{
|
||||||
|
nlohmann::json& v = j.value();
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case nlohmann::json::value_t::boolean:
|
||||||
|
v = (v.get<bool>() ? "true" : "false");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nlohmann::json::value_t::number_float:
|
||||||
|
v = std::to_string(v.get<double>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nlohmann::json::value_t::number_integer:
|
||||||
|
v = std::to_string(v.get<int64_t>());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case nlohmann::json::value_t::number_unsigned:
|
||||||
|
v = std::to_string(v.get<uint64_t>());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item_json[i["itemdefid"].get<std::string>()] = i;
|
||||||
|
default_item_json[i["itemdefid"].get<std::string>()] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
items_file << std::setw(2) << item_json;
|
||||||
|
default_items_file << std::setw(2) << default_item_json;
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << "Failed to get infos: ";
|
||||||
|
long code;
|
||||||
|
if (easy.get_html_code(code) == CURLE_OK && code == 403)
|
||||||
|
{
|
||||||
|
std::cerr << "Error in webapi key";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "Error while parsing json. Try to go at " << url << " and see what you can do to build your items.json";
|
||||||
|
}
|
||||||
|
std::cerr << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
if (!create_directory("images"))
|
||||||
|
{
|
||||||
|
std::cerr << "Cannot create directory \"images\"" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlGlobal& cglobal = CurlGlobal::Inst();
|
||||||
|
cglobal.init();
|
||||||
|
|
||||||
|
CurlEasy easy;
|
||||||
|
if (easy.init())
|
||||||
|
{
|
||||||
|
std::cout << "Enter the game appid: ";
|
||||||
|
std::cin >> app_id;
|
||||||
|
std::cout << "Enter your webapi key: ";
|
||||||
|
std::cin.clear();
|
||||||
|
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||||
|
std::cin >> steam_apikey;
|
||||||
|
|
||||||
|
generate_achievements(easy);
|
||||||
|
generate_items(easy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue