From 72f21e479daee5e1649d72c97267c3e3d4438d9d Mon Sep 17 00:00:00 2001 From: a Date: Fri, 6 Dec 2024 23:20:38 +0200 Subject: [PATCH] fix gamestats + write to json instead of csv --- dll/dll/steam_gamestats.h | 14 ++- dll/steam_gamestats.cpp | 234 ++++++++++++++++++-------------------- 2 files changed, 124 insertions(+), 124 deletions(-) diff --git a/dll/dll/steam_gamestats.h b/dll/dll/steam_gamestats.h index a0c12b8f..19ed1655 100644 --- a/dll/dll/steam_gamestats.h +++ b/dll/dll/steam_gamestats.h @@ -37,7 +37,10 @@ private: struct Attribute_t { - const AttributeType_t type; + private: + AttributeType_t type; + + public: union { int32 n_data; std::string s_data; @@ -45,10 +48,16 @@ private: int64 ll_data; }; + Attribute_t() = delete; Attribute_t(AttributeType_t type); Attribute_t(const Attribute_t &other); Attribute_t(Attribute_t &&other); ~Attribute_t(); + + Attribute_t& operator=(const Attribute_t &other); + Attribute_t& operator=(Attribute_t &&other); + + AttributeType_t get_type() const noexcept; }; struct Row_t @@ -87,13 +96,12 @@ private: uint64 create_session_id() const; - bool valid_stats_account_type(int8 nAccountType); + bool valid_stats_account_type(int8 nAccountType) const noexcept; Table_t *get_or_create_session_table(Session_t &session, const char *table_name); Attribute_t *get_or_create_session_att(const char *att_name, Session_t &session, AttributeType_t type_if_create); Attribute_t *get_or_create_row_att(uint64 ulRowID, const char *att_name, Table_t &table, AttributeType_t type_if_create); Session_t* get_last_active_session(); - std::string sanitize_csv_value(std::string_view value); void save_session_to_disk(Steam_GameStats::Session_t &session, uint64 session_id); void steam_run_callback(); diff --git a/dll/steam_gamestats.cpp b/dll/steam_gamestats.cpp index 297d0ccc..eb796e4c 100644 --- a/dll/steam_gamestats.cpp +++ b/dll/steam_gamestats.cpp @@ -40,7 +40,7 @@ Steam_GameStats::Attribute_t::Attribute_t(AttributeType_t type) } Steam_GameStats::Attribute_t::Attribute_t(const Attribute_t &other) - :type(type) + :type(other.type) { switch (other.type) { @@ -54,7 +54,7 @@ Steam_GameStats::Attribute_t::Attribute_t(const Attribute_t &other) } Steam_GameStats::Attribute_t::Attribute_t(Attribute_t &&other) - :type(type) + :type(other.type) { switch (other.type) { @@ -65,15 +65,46 @@ Steam_GameStats::Attribute_t::Attribute_t(Attribute_t &&other) default: PRINT_DEBUG("[X] invalid type %i", (int)other.type); break; } + + other.type = AttributeType_t::Int; } + +Steam_GameStats::Attribute_t& Steam_GameStats::Attribute_t::operator=(const Attribute_t &other) +{ + if (&other != this) { + this->~Attribute_t(); + new (this) Attribute_t(other); + } + + return *this; +} + +Steam_GameStats::Attribute_t& Steam_GameStats::Attribute_t::operator=(Attribute_t &&other) +{ + if (&other != this) { + this->~Attribute_t(); + new (this) Attribute_t(std::move(other)); + } + + return *this; +} + + + Steam_GameStats::Attribute_t::~Attribute_t() { - if (type == AttributeType_t::Str) { + if (AttributeType_t::Str == type) { s_data.~basic_string(); + type = AttributeType_t::Int; } } +Steam_GameStats::AttributeType_t Steam_GameStats::Attribute_t::get_type() const noexcept +{ + return type; +} + void Steam_GameStats::steam_gamestats_network_low_level(void *object, Common_Message *msg) { @@ -119,7 +150,7 @@ uint64 Steam_GameStats::create_session_id() const return session_id; } -bool Steam_GameStats::valid_stats_account_type(int8 nAccountType) +bool Steam_GameStats::valid_stats_account_type(int8 nAccountType) const noexcept { switch ((EGameStatsAccountType)nAccountType) { case EGameStatsAccountType::k_EGameStatsAccountType_Steam: @@ -137,8 +168,9 @@ Steam_GameStats::Table_t *Steam_GameStats::get_or_create_session_table(Session_t { auto table_it = std::find_if(session.tables.rbegin(), session.tables.rend(), [=](const std::pair &item){ return item.first == table_name; }); if (session.tables.rend() == table_it) { - auto& [key, val] = session.tables.emplace_back(std::pair{}); - table = &val; + auto& [new_name, new_table] = session.tables.emplace_back(std::pair{}); + new_name = table_name && table_name[0] ? table_name : ""; + table = &new_table; } else { table = &table_it->second; } @@ -149,13 +181,19 @@ Steam_GameStats::Table_t *Steam_GameStats::get_or_create_session_table(Session_t Steam_GameStats::Attribute_t *Steam_GameStats::get_or_create_session_att(const char *att_name, Session_t &session, AttributeType_t type_if_create) { + if (!att_name || !att_name[0]) { + att_name = ""; + } auto [ele_it, _] = session.attributes.emplace(att_name, type_if_create); return &ele_it->second; } Steam_GameStats::Attribute_t *Steam_GameStats::get_or_create_row_att(uint64 ulRowID, const char *att_name, Table_t &table, AttributeType_t type_if_create) { - auto &row = table.rows[static_cast(ulRowID)]; + if (!att_name || !att_name[0]) { + att_name = ""; + } + auto& row = table.rows[static_cast(ulRowID)]; auto [ele_it, _] = row.attributes.emplace(att_name, type_if_create); return &ele_it->second; } @@ -270,7 +308,7 @@ EResult Steam_GameStats::AddSessionAttributeInt( uint64 ulSessionID, const char* if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? auto att = get_or_create_session_att(pstrName, session, AttributeType_t::Int); - if (att->type != AttributeType_t::Int) return EResult::k_EResultFail; // TODO is this correct? + if (att->get_type() != AttributeType_t::Int) return EResult::k_EResultFail; // TODO is this correct? att->n_data = nData; PRINT_DEBUG("added successfully"); @@ -289,9 +327,9 @@ EResult Steam_GameStats::AddSessionAttributeString( uint64 ulSessionID, const ch if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? auto att = get_or_create_session_att(pstrName, session, AttributeType_t::Str); - if (att->type != AttributeType_t::Str) return EResult::k_EResultFail; // TODO is this correct? + if (att->get_type() != AttributeType_t::Str) return EResult::k_EResultFail; // TODO is this correct? - att->s_data = pstrData; + att->s_data = pstrData ? pstrData : ""; PRINT_DEBUG("added successfully"); return EResult::k_EResultOK; } @@ -308,7 +346,7 @@ EResult Steam_GameStats::AddSessionAttributeFloat( uint64 ulSessionID, const cha if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? auto att = get_or_create_session_att(pstrName, session, AttributeType_t::Float); - if (att->type != AttributeType_t::Float) return EResult::k_EResultFail; // TODO is this correct? + if (att->get_type() != AttributeType_t::Float) return EResult::k_EResultFail; // TODO is this correct? att->f_data = fData; PRINT_DEBUG("added successfully"); @@ -321,6 +359,8 @@ EResult Steam_GameStats::AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const PRINT_DEBUG("%p, %llu, ['%s']", pulRowID, ulSessionID, pstrTableName); std::lock_guard lock(global_mutex); + if (pulRowID) *pulRowID = 0; + auto session_it = sessions.find(ulSessionID); if (sessions.end() == session_it) return EResult::k_EResultInvalidParam; // TODO is this correct? @@ -329,7 +369,9 @@ EResult Steam_GameStats::AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const auto table = get_or_create_session_table(session, pstrTableName); table->rows.emplace_back(Row_t{}); - if (pulRowID) *pulRowID = (uint64)(table->rows.size() - 1); + // sending row id = 0 is considered a failure + if (pulRowID) *pulRowID = (uint64)table->rows.size(); + PRINT_DEBUG("added successfully"); return EResult::k_EResultOK; } @@ -344,10 +386,10 @@ EResult Steam_GameStats::CommitRow( uint64 ulRowID ) if (active_session->tables.empty()) return EResult::k_EResultFail; // TODO is this correct? auto &table = active_session->tables.back().second; - if (ulRowID >= table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + if (0 == ulRowID || ulRowID > table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? // TODO what if it was already committed ? - auto& row = table.rows[static_cast(ulRowID)]; + auto& row = table.rows[static_cast(ulRowID - 1)]; row.committed = true; PRINT_DEBUG("committed successfully"); @@ -379,17 +421,15 @@ EResult Steam_GameStats::AddRowAttributeInt( uint64 ulRowID, const char *pstrNam PRINT_DEBUG("%llu, '%s'=%i", ulRowID, pstrName, nData); std::lock_guard lock(global_mutex); - if (!pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto active_session = get_last_active_session(); if (!active_session) return EResult::k_EResultFail; // TODO is this correct? if (active_session->tables.empty()) return EResult::k_EResultFail; // TODO is this correct? auto &table = active_session->tables.back().second; - if (ulRowID >= table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + if (0 == ulRowID || ulRowID > table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto att = get_or_create_row_att(ulRowID, pstrName, table, AttributeType_t::Int); - if (att->type != AttributeType_t::Int) return EResult::k_EResultFail; // TODO is this correct? + auto att = get_or_create_row_att(ulRowID - 1, pstrName, table, AttributeType_t::Int); + if (att->get_type() != AttributeType_t::Int) return EResult::k_EResultFail; // TODO is this correct? att->n_data = nData; PRINT_DEBUG("added successfully"); @@ -401,19 +441,17 @@ EResult Steam_GameStats::AddRowAtributeString( uint64 ulRowID, const char *pstrN PRINT_DEBUG("%llu, '%s'='%s'", ulRowID, pstrName, pstrData); std::lock_guard lock(global_mutex); - if (!pstrName || !pstrData) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto active_session = get_last_active_session(); if (!active_session) return EResult::k_EResultFail; // TODO is this correct? if (active_session->tables.empty()) return EResult::k_EResultFail; // TODO is this correct? auto &table = active_session->tables.back().second; - if (ulRowID >= table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + if (0 == ulRowID || ulRowID > table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto att = get_or_create_row_att(ulRowID, pstrName, table, AttributeType_t::Str); - if (att->type != AttributeType_t::Str) return EResult::k_EResultFail; // TODO is this correct? + auto att = get_or_create_row_att(ulRowID - 1, pstrName, table, AttributeType_t::Str); + if (att->get_type() != AttributeType_t::Str) return EResult::k_EResultFail; // TODO is this correct? - att->s_data = pstrData; + att->s_data = pstrData ? pstrData : ""; PRINT_DEBUG("added successfully"); return EResult::k_EResultOK; } @@ -423,17 +461,15 @@ EResult Steam_GameStats::AddRowAttributeFloat( uint64 ulRowID, const char *pstrN PRINT_DEBUG("%llu, '%s'=%f", ulRowID, pstrName, fData); std::lock_guard lock(global_mutex); - if (!pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto active_session = get_last_active_session(); if (!active_session) return EResult::k_EResultFail; // TODO is this correct? if (active_session->tables.empty()) return EResult::k_EResultFail; // TODO is this correct? auto &table = active_session->tables.back().second; - if (ulRowID >= table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + if (0 == ulRowID || ulRowID > table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto att = get_or_create_row_att(ulRowID, pstrName, table, AttributeType_t::Float); - if (att->type != AttributeType_t::Float) return EResult::k_EResultFail; // TODO is this correct? + auto att = get_or_create_row_att(ulRowID - 1, pstrName, table, AttributeType_t::Float); + if (att->get_type() != AttributeType_t::Float) return EResult::k_EResultFail; // TODO is this correct? att->f_data = fData; PRINT_DEBUG("added successfully"); @@ -453,7 +489,7 @@ EResult Steam_GameStats::AddSessionAttributeInt64( uint64 ulSessionID, const cha if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? auto att = get_or_create_session_att(pstrName, session, AttributeType_t::Int64); - if (att->type != AttributeType_t::Int64) return EResult::k_EResultFail; // TODO is this correct? + if (att->get_type() != AttributeType_t::Int64) return EResult::k_EResultFail; // TODO is this correct? att->ll_data = llData; PRINT_DEBUG("added successfully"); @@ -465,17 +501,15 @@ EResult Steam_GameStats::AddRowAttributeInt64( uint64 ulRowID, const char *pstrN PRINT_DEBUG("%llu, '%s'=%lli", ulRowID, pstrName, llData); std::lock_guard lock(global_mutex); - if (!pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto active_session = get_last_active_session(); if (!active_session) return EResult::k_EResultFail; // TODO is this correct? if (active_session->tables.empty()) return EResult::k_EResultFail; // TODO is this correct? auto &table = active_session->tables.back().second; - if (ulRowID >= table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + if (0 == ulRowID || ulRowID > table.rows.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? - auto att = get_or_create_row_att(ulRowID, pstrName, table, AttributeType_t::Int64); - if (att->type != AttributeType_t::Int64) return EResult::k_EResultFail; // TODO is this correct? + auto att = get_or_create_row_att(ulRowID - 1, pstrName, table, AttributeType_t::Int64); + if (att->get_type() != AttributeType_t::Int64) return EResult::k_EResultFail; // TODO is this correct? att->ll_data = llData; PRINT_DEBUG("added successfully"); @@ -486,111 +520,67 @@ EResult Steam_GameStats::AddRowAttributeInt64( uint64 ulRowID, const char *pstrN // --- steam callbacks -std::string Steam_GameStats::sanitize_csv_value(std::string_view value) -{ - // ref: https://en.wikipedia.org/wiki/Comma-separated_values - // double quotes must be represented by a pair of double quotes - auto val_str = common_helpers::str_replace_all(value, "\"", "\"\""); - // multiline values aren't supported by all parsers - val_str = common_helpers::str_replace_all(val_str, "\r\n", "\n"); - val_str = common_helpers::str_replace_all(val_str, "\n", " "); - return val_str; -} - void Steam_GameStats::save_session_to_disk(Steam_GameStats::Session_t &session, uint64 session_id) { - auto folder = std::to_string(session.account_id) + "_" + std::to_string(session.rtTimeStarted) + "_" + std::to_string(session_id); - auto folder_p = std::filesystem::u8path(settings->steam_game_stats_reports_dir) / std::filesystem::u8path(folder); - auto folder_u8_str = folder_p.u8string(); + auto report_file = + "session_[" + std::to_string(session_id) + "]" + + "-started_" + std::to_string(session.rtTimeStarted) + + "-steamid_" + std::to_string(session.account_id) + + "-rnd_" + std::to_string(common_helpers::rand_number(UINT32_MAX)) + + ".json"; + auto fullpath_p = std::filesystem::u8path(settings->steam_game_stats_reports_dir) / std::filesystem::u8path(report_file); + nlohmann::json jout{}; // save session attributes - if (session.attributes.size()) { - std::stringstream ss{}; - ss << "Session attribute,Value\n"; - for (const auto& [name, val] : session.attributes) { - std::string val_str{}; - switch (val.type) { - case AttributeType_t::Int: val_str = std::to_string(val.n_data); break; - case AttributeType_t::Str: val_str = val.s_data; break; - case AttributeType_t::Float: val_str = std::to_string(val.f_data); break; - case AttributeType_t::Int64: val_str = std::to_string(val.ll_data); break; - } + if (!session.attributes.empty()) { + auto &jsession_att = jout["session_attributes"]; - val_str = sanitize_csv_value(val_str); - auto name_str = sanitize_csv_value(name); - ss << "\"" << name_str << "\",\"" << val_str << "\"\n"; + for (const auto& [name, val] : session.attributes) { + auto &jatt_name = jsession_att[name]; + switch (val.get_type()) { + case AttributeType_t::Int: jatt_name = val.n_data; break; + case AttributeType_t::Str: jatt_name = val.s_data; break; + case AttributeType_t::Float: jatt_name = val.f_data; break; + case AttributeType_t::Int64: jatt_name = val.ll_data; break; + } } - auto ss_str = ss.str(); - Local_Storage::store_file_data(folder_u8_str, "session_attributes.csv", ss_str.c_str(), (unsigned int)ss_str.size()); } // save each table for (const auto& [table_name, table_data] : session.tables) { bool rows_has_attributes = std::any_of(table_data.rows.begin(), table_data.rows.end(), [](const Steam_GameStats::Row_t &item) { - return item.attributes.size() > 0; + return !item.attributes.empty(); }); if (!rows_has_attributes) continue; - // convert the data representation to be column oriented - // key=column header/title - // value = list of column values - std::unordered_map> columns{}; + // save rows attributes + auto &jthis_table = jout["tables"][table_name]; for (size_t row_idx = 0; row_idx < table_data.rows.size(); ++row_idx) { const auto &row = table_data.rows[row_idx]; + auto &jthis_row = jthis_table[row_idx]; for (const auto& [att_name, att_val] : row.attributes) { - auto [column_it, new_column] = columns.emplace(att_name, std::vector{}); - auto &column_values = column_it->second; - // when adding new column make sure we have correct rows count - if (new_column) { - column_values.assign(table_data.rows.size(), nullptr); - } - // add the row value in its correct place - column_values[row_idx] = &att_val; - } - } - - std::stringstream ss_table{}; - // write header - bool first_header_atom = true; - for (const auto& [col_name, _] : columns) { - auto csv_col_name = sanitize_csv_value(col_name); - if (first_header_atom) { - first_header_atom = false; - ss_table << "\"" << csv_col_name << "\""; - } else { - ss_table << ",\"" << csv_col_name << "\""; - } - } - ss_table << "\n"; - // write values - for (size_t row_idx = 0; row_idx < table_data.rows.size(); ++row_idx) { - bool first_col_cell = true; - for (const auto& [_, col_values] : columns) { - auto &cell_val = col_values[row_idx]; - - std::string val_str{}; - switch (cell_val->type) { - case AttributeType_t::Int: val_str = std::to_string(cell_val->n_data); break; - case AttributeType_t::Str: val_str = cell_val->s_data; break; - case AttributeType_t::Float: val_str = std::to_string(cell_val->f_data); break; - case AttributeType_t::Int64: val_str = std::to_string(cell_val->ll_data); break; - } - - val_str = sanitize_csv_value(val_str); - if (first_col_cell) { - first_col_cell = false; - ss_table << "\"" << val_str << "\""; - } else { - ss_table << ",\"" << val_str << "\""; + auto &jthis_row_att = jthis_row[att_name]; + switch (att_val.get_type()) { + case AttributeType_t::Int: jthis_row_att = att_val.n_data; break; + case AttributeType_t::Str: jthis_row_att = att_val.s_data; break; + case AttributeType_t::Float: jthis_row_att = att_val.f_data; break; + case AttributeType_t::Int64: jthis_row_att = att_val.ll_data; break; } } } - ss_table << "\n"; - auto ss_str = ss_table.str(); - Local_Storage::store_file_data(folder_u8_str, table_name.c_str(), ss_str.c_str(), (unsigned int)ss_str.size()); - } + + if (!jout.empty()) { + if (std::filesystem::create_directories(fullpath_p.parent_path())) { + auto fwrite = std::ofstream(fullpath_p, std::ios::out | std::ios::binary | std::ios::trunc); + if (fwrite) { + fwrite << std::setw(2) << jout; + fwrite.close(); + } + } + } + } void Steam_GameStats::steam_run_callback() @@ -607,8 +597,10 @@ void Steam_GameStats::steam_run_callback() if (session.ended) { if (!session.saved_to_disk) { session.saved_to_disk = true; - if (settings->steam_game_stats_reports_dir.size()) { - save_session_to_disk(session, session_it->first); + if (!settings->steam_game_stats_reports_dir.empty()) { + try { + save_session_to_disk(session, session_it->first); + } catch(...){} } }