diff --git a/dll/dll/steam_client.h b/dll/dll/steam_client.h index eaa4ff09..48377538 100644 --- a/dll/dll/steam_client.h +++ b/dll/dll/steam_client.h @@ -54,6 +54,7 @@ #include "steam_gameserver.h" #include "steam_gameserverstats.h" +#include "steam_gamestats.h" #include "steam_masterserver_updater.h" #include "overlay/steam_overlay.h" @@ -136,6 +137,7 @@ public: Steam_Parties *steam_parties{}; Steam_RemotePlay *steam_remoteplay{}; Steam_TV *steam_tv{}; + Steam_GameStats *steam_gamestats{}; Steam_GameServer *steam_gameserver{}; Steam_Utils *steam_gameserver_utils{}; @@ -231,6 +233,9 @@ public: // user screenshots ISteamScreenshots *GetISteamScreenshots( HSteamUser hSteamuser, HSteamPipe hSteamPipe, const char *pchVersion ); + // game stats + ISteamGameStats *GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ); + // Deprecated. Applications should use SteamAPI_RunCallbacks() or SteamGameServer_RunCallbacks() instead. STEAM_PRIVATE_API( void RunFrame() ); diff --git a/dll/dll/steam_gamestats.h b/dll/dll/steam_gamestats.h new file mode 100644 index 00000000..e233081c --- /dev/null +++ b/dll/dll/steam_gamestats.h @@ -0,0 +1,117 @@ +/* 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 + . */ + +#ifndef __INCLUDED_STEAM_GAMESTATS_H__ +#define __INCLUDED_STEAM_GAMESTATS_H__ + +#include "base.h" + +//----------------------------------------------------------------------------- +// Purpose: Functions for recording game play sessions and details thereof +//----------------------------------------------------------------------------- +class Steam_GameStats : +public ISteamGameStats +{ + enum class AttributeType_t + { + Int, Str, Float, Int64, + }; + + struct Attribute_t + { + AttributeType_t type{}; + union { + int32 n_data; + std::string s_data; + float f_data; + int64 ll_data{}; + }; + + Attribute_t(); + Attribute_t(const Attribute_t &other); + Attribute_t(Attribute_t &&other); + ~Attribute_t(); + }; + + struct Row_t + { + bool committed = false; + std::map attributes{}; + }; + + struct Table_t + { + std::vector rows{}; + }; + + struct Session_t + { + EGameStatsAccountType nAccountType{}; + RTime32 rtTimeStarted{}; + RTime32 rtTimeEnded{}; + int nReasonCode{}; + bool ended = false; + std::map attributes{}; + + std::vector> tables{}; + }; + + class Settings *settings{}; + class Networking *network{}; + class SteamCallResults *callback_results{}; + class SteamCallBacks *callbacks{}; + class RunEveryRunCB *run_every_runcb{}; + + std::vector sessions{}; + + + bool valid_stats_account_type(int8 nAccountType); + 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); + + void steam_run_callback(); + + // user connect/disconnect + void network_callback_low_level(Common_Message *msg); + + static void steam_gamestats_network_low_level(void *object, Common_Message *msg); + static void steam_gamestats_run_every_runcb(void *object); + +public: + Steam_GameStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb); + ~Steam_GameStats(); + + SteamAPICall_t GetNewSession( int8 nAccountType, uint64 ulAccountID, int32 nAppID, RTime32 rtTimeStarted ); + SteamAPICall_t EndSession( uint64 ulSessionID, RTime32 rtTimeEnded, int nReasonCode ); + EResult AddSessionAttributeInt( uint64 ulSessionID, const char* pstrName, int32 nData ); + EResult AddSessionAttributeString( uint64 ulSessionID, const char* pstrName, const char *pstrData ); + EResult AddSessionAttributeFloat( uint64 ulSessionID, const char* pstrName, float fData ); + + EResult AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const char *pstrTableName ); + EResult CommitRow( uint64 ulRowID ); + EResult CommitOutstandingRows( uint64 ulSessionID ); + EResult AddRowAttributeInt( uint64 ulRowID, const char *pstrName, int32 nData ); + EResult AddRowAtributeString( uint64 ulRowID, const char *pstrName, const char *pstrData ); + EResult AddRowAttributeFloat( uint64 ulRowID, const char *pstrName, float fData ); + + EResult AddSessionAttributeInt64( uint64 ulSessionID, const char *pstrName, int64 llData ); + EResult AddRowAttributeInt64( uint64 ulRowID, const char *pstrName, int64 llData ); + +}; + +#endif // __INCLUDED_STEAM_GAMESTATS_H__ diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp index c98c9bd1..d64d0896 100644 --- a/dll/steam_client.cpp +++ b/dll/steam_client.cpp @@ -122,6 +122,7 @@ Steam_Client::Steam_Client() steam_parties = new Steam_Parties(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_remoteplay = new Steam_RemotePlay(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); steam_tv = new Steam_TV(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); + steam_gamestats = new Steam_GameStats(settings_client, network, callback_results_client, callbacks_client, run_every_runcb); // server PRINT_DEBUG("init gameserver"); @@ -200,6 +201,7 @@ Steam_Client::~Steam_Client() DEL_INST(steam_parties); DEL_INST(steam_remoteplay); DEL_INST(steam_tv); + DEL_INST(steam_gamestats); DEL_INST(steam_utils); DEL_INST(steam_friends); diff --git a/dll/steam_client_interface_getter.cpp b/dll/steam_client_interface_getter.cpp index 7d8190dd..a61ca5f8 100644 --- a/dll/steam_client_interface_getter.cpp +++ b/dll/steam_client_interface_getter.cpp @@ -18,6 +18,19 @@ #include "dll/steam_client.h" +// retrieves the ISteamGameStats interface associated with the handle +ISteamGameStats *Steam_Client::GetISteamGameStats( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) +{ + PRINT_DEBUG("%s", pchVersion); + if (!steam_pipes.count(hSteamPipe) || !hSteamUser) return nullptr; + + if (strcmp(pchVersion, STEAMGAMESTATS_INTERFACE_VERSION) == 0) { + return reinterpret_cast(static_cast(steam_gamestats)); + } + + report_missing_impl_and_exit(pchVersion, EMU_FUNC_NAME); +} + // retrieves the ISteamUser interface associated with the handle ISteamUser *Steam_Client::GetISteamUser( HSteamUser hSteamUser, HSteamPipe hSteamPipe, const char *pchVersion ) { @@ -332,6 +345,8 @@ void *Steam_Client::GetISteamGenericInterface( HSteamUser hSteamUser, HSteamPipe return GetISteamGameServerStats(hSteamUser, hSteamPipe, pchVersion); } else if (strstr(pchVersion, "SteamGameServer") == pchVersion) { return GetISteamGameServer(hSteamUser, hSteamPipe, pchVersion); + } else if (strstr(pchVersion, "SteamGameStats") == pchVersion) { + return GetISteamGameStats(hSteamUser, hSteamPipe, pchVersion); } else if (strstr(pchVersion, "SteamMatchMakingServers") == pchVersion) { return GetISteamMatchmakingServers(hSteamUser, hSteamPipe, pchVersion); } else if (strstr(pchVersion, "SteamMatchMaking") == pchVersion) { diff --git a/dll/steam_gamestats.cpp b/dll/steam_gamestats.cpp new file mode 100644 index 00000000..e7b576a5 --- /dev/null +++ b/dll/steam_gamestats.cpp @@ -0,0 +1,474 @@ +/* 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 + . +*/ + +/* + There are no public docs about this interface, this implementation + is an imagination of how it might have been implemented +*/ + +#include "dll/steam_gamestats.h" + + +Steam_GameStats::Attribute_t::Attribute_t() +{ } + +Steam_GameStats::Attribute_t::Attribute_t(const Attribute_t &other) +{ + type = other.type; + switch (other.type) + { + case AttributeType_t::Int: n_data = other.n_data; break; + case AttributeType_t::Str: s_data = other.s_data; break; + case AttributeType_t::Float: f_data = other.f_data; break; + case AttributeType_t::Int64: ll_data = other.ll_data; break; + + default: break; + } +} + +Steam_GameStats::Attribute_t::Attribute_t(Attribute_t &&other) +{ + type = other.type; + switch (other.type) + { + case AttributeType_t::Int: n_data = other.n_data; break; + case AttributeType_t::Str: s_data = std::move(other.s_data); break; + case AttributeType_t::Float: f_data = other.f_data; break; + case AttributeType_t::Int64: ll_data = other.ll_data; break; + + default: break; + } +} + +Steam_GameStats::Attribute_t::~Attribute_t() +{ } + + +void Steam_GameStats::steam_gamestats_network_low_level(void *object, Common_Message *msg) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_GameStats *)object; + inst->network_callback_low_level(msg); +} + +void Steam_GameStats::steam_gamestats_run_every_runcb(void *object) +{ + //PRINT_DEBUG_ENTRY(); + + auto inst = (Steam_GameStats *)object; + inst->steam_run_callback(); +} + +Steam_GameStats::Steam_GameStats(class Settings *settings, class Networking *network, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb) +{ + this->settings = settings; + this->network = network; + this->callback_results = callback_results; + this->callbacks = callbacks; + this->run_every_runcb = run_every_runcb; + + this->network->setCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameStats::steam_gamestats_network_low_level, this); + this->run_every_runcb->add(&Steam_GameStats::steam_gamestats_run_every_runcb, this); + +} + +Steam_GameStats::~Steam_GameStats() +{ + this->network->rmCallback(CALLBACK_ID_USER_STATUS, settings->get_local_steam_id(), &Steam_GameStats::steam_gamestats_network_low_level, this); + this->run_every_runcb->remove(&Steam_GameStats::steam_gamestats_run_every_runcb, this); +} + + +bool Steam_GameStats::valid_stats_account_type(int8 nAccountType) +{ + switch ((EGameStatsAccountType)nAccountType) { + case EGameStatsAccountType::k_EGameStatsAccountType_Steam: + case EGameStatsAccountType::k_EGameStatsAccountType_Xbox: + case EGameStatsAccountType::k_EGameStatsAccountType_SteamGameServer: + return true; + } + + return false; +} + +Steam_GameStats::Table_t *Steam_GameStats::get_or_create_session_table(Session_t &session, const char *table_name) +{ + Table_t *table{}; + { + 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) { + session.tables.emplace_back(std::pair{}); + table = &session.tables.back().second; + } else { + table = &table_it->second; + } + } + + return table; +} + +Steam_GameStats::Attribute_t *Steam_GameStats::get_or_create_session_att(const char *att_name, Session_t &session, AttributeType_t type_if_create) +{ + Attribute_t *att{}; + { + auto att_itr = session.attributes.find(att_name); + if (att_itr != session.attributes.end()) { + att = &att_itr->second; + } else { + att = &session.attributes[att_name]; + att->type = type_if_create; + } + } + + return att; +} + +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) +{ + Attribute_t *att{}; + { + auto &row = table.rows[ulRowID]; + auto att_itr = row.attributes.find(att_name); + if (att_itr != row.attributes.end()) { + att = &att_itr->second; + } else { + att = &row.attributes[att_name]; + att->type = type_if_create; + } + } + + return att; +} + + +SteamAPICall_t Steam_GameStats::GetNewSession( int8 nAccountType, uint64 ulAccountID, int32 nAppID, RTime32 rtTimeStarted ) +{ + PRINT_DEBUG("%i, %llu, %i, %u", (int)nAccountType, ulAccountID, nAppID, rtTimeStarted); + std::lock_guard lock(global_mutex); + + if ((settings->get_local_steam_id().ConvertToUint64() != ulAccountID) || + (nAppID < 0) || + (settings->get_local_game_id().AppID() != (uint32)nAppID) || + !valid_stats_account_type(nAccountType)) { + GameStatsSessionIssued_t data_invalid{}; + data_invalid.m_bCollectingAny = false; + data_invalid.m_bCollectingDetails = false; + data_invalid.m_eResult = EResult::k_EResultInvalidParam; + data_invalid.m_ulSessionID = 0; + + auto ret = callback_results->addCallResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + // the function returns SteamAPICall_t (call result), but in isteamstats.h you can see that a callback is also expected + callbacks->addCBResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + return ret; + } + + Session_t new_session{}; + new_session.nAccountType = (EGameStatsAccountType)nAccountType; + new_session.rtTimeStarted = rtTimeStarted; + sessions.emplace_back(new_session); + + GameStatsSessionIssued_t data{}; + data.m_bCollectingAny = true; // TODO is this correct? + data.m_bCollectingDetails = true; // TODO is this correct? + data.m_eResult = EResult::k_EResultOK; + data.m_ulSessionID = (uint64)sessions.size(); // I don't know if 0 is a bad value, so always send count (index + 1) + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + // the function returns SteamAPICall_t (call result), but in isteamstats.h you can see that a callback is also expected + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + +SteamAPICall_t Steam_GameStats::EndSession( uint64 ulSessionID, RTime32 rtTimeEnded, int nReasonCode ) +{ + PRINT_DEBUG("%llu, %u, %i", ulSessionID, rtTimeEnded, nReasonCode); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size()) { + GameStatsSessionClosed_t data_invalid{}; + data_invalid.m_eResult = EResult::k_EResultInvalidParam; + data_invalid.m_ulSessionID = ulSessionID; + + auto ret = callback_results->addCallResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + // the function returns SteamAPICall_t (call result), but in isteamstats.h you can see that a callback is also expected + callbacks->addCBResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + return ret; + } + + auto &session = sessions[ulSessionID - 1]; + if (session.ended) { + GameStatsSessionClosed_t data_invalid{}; + data_invalid.m_eResult = EResult::k_EResultExpired; // TODO is this correct? + data_invalid.m_ulSessionID = ulSessionID; + + auto ret = callback_results->addCallResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + // the function returns SteamAPICall_t (call result), but in isteamstats.h you can see that a callback is also expected + callbacks->addCBResult(data_invalid.k_iCallback, &data_invalid, sizeof(data_invalid)); + return ret; + } + + session.ended = true; + session.rtTimeEnded = rtTimeEnded; + session.nReasonCode = nReasonCode; + + GameStatsSessionClosed_t data{}; + data.m_eResult = EResult::k_EResultOK; + data.m_ulSessionID = ulSessionID; + + auto ret = callback_results->addCallResult(data.k_iCallback, &data, sizeof(data)); + // the function returns SteamAPICall_t (call result), but in isteamstats.h you can see that a callback is also expected + callbacks->addCBResult(data.k_iCallback, &data, sizeof(data)); + return ret; +} + +EResult Steam_GameStats::AddSessionAttributeInt( uint64 ulSessionID, const char* pstrName, int32 nData ) +{ + PRINT_DEBUG("%llu, '%s', %i", ulSessionID, pstrName, nData); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size() || !pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + 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? + + att->n_data = nData; + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddSessionAttributeString( uint64 ulSessionID, const char* pstrName, const char *pstrData ) +{ + PRINT_DEBUG("%llu, '%s', '%s'", ulSessionID, pstrName, pstrData); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size() || !pstrName || !pstrData) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + 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? + + att->s_data = pstrData; + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddSessionAttributeFloat( uint64 ulSessionID, const char* pstrName, float fData ) +{ + PRINT_DEBUG("%llu, '%s', %f", ulSessionID, pstrName, fData); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size() || !pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + 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? + + att->f_data = fData; + return EResult::k_EResultOK; +} + + +EResult Steam_GameStats::AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const char *pstrTableName ) +{ + PRINT_DEBUG("%p, %llu, '%s'", pulRowID, ulSessionID, pstrTableName); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size() || !pstrTableName) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? + + auto table = get_or_create_session_table(session, pstrTableName); + table->rows.emplace_back(Row_t{}); + if (pulRowID) *pulRowID = (uint64)(table->rows.size() - 1); + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::CommitRow( uint64 ulRowID ) +{ + PRINT_DEBUG("%llu", ulRowID); + std::lock_guard lock(global_mutex); + + auto active_session = std::find_if(sessions.rbegin(), sessions.rend(), [](const Session_t &item){ return !item.ended; }); + if (sessions.rend() == 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? + + // TODO what if it was already committed ? + auto &row = table.rows[ulRowID]; + row.committed = true; + + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::CommitOutstandingRows( uint64 ulSessionID ) +{ + PRINT_DEBUG("%llu", ulSessionID); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size()) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + if (session.ended) return EResult::k_EResultExpired; // TODO is this correct? + + if (session.tables.size()) { + for (auto &row : session.tables.back().second.rows) { + row.committed = true; + } + } + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddRowAttributeInt( uint64 ulRowID, const char *pstrName, int32 nData ) +{ + 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 = std::find_if(sessions.rbegin(), sessions.rend(), [](const Session_t &item){ return !item.ended; }); + if (sessions.rend() == 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? + + 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? + + att->n_data = nData; + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddRowAtributeString( uint64 ulRowID, const char *pstrName, const char *pstrData ) +{ + 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 = std::find_if(sessions.rbegin(), sessions.rend(), [](const Session_t &item){ return !item.ended; }); + if (sessions.rend() == active_session) 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? + + 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? + + att->s_data = pstrData; + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddRowAttributeFloat( uint64 ulRowID, const char *pstrName, float fData ) +{ + 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 = std::find_if(sessions.rbegin(), sessions.rend(), [](const Session_t &item){ return !item.ended; }); + if (sessions.rend() == active_session) 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? + + 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? + + att->f_data = fData; + return EResult::k_EResultOK; +} + + +EResult Steam_GameStats::AddSessionAttributeInt64( uint64 ulSessionID, const char *pstrName, int64 llData ) +{ + PRINT_DEBUG("%llu, '%s', %lli", ulSessionID, pstrName, llData); + std::lock_guard lock(global_mutex); + + if (ulSessionID == 0 || ulSessionID > sessions.size() || !pstrName) return EResult::k_EResultInvalidParam; // TODO is this correct? + + auto &session = sessions[ulSessionID - 1]; + 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? + + att->ll_data = llData; + return EResult::k_EResultOK; +} + +EResult Steam_GameStats::AddRowAttributeInt64( uint64 ulRowID, const char *pstrName, int64 llData ) +{ + 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 = std::find_if(sessions.rbegin(), sessions.rend(), [](const Session_t &item){ return !item.ended; }); + if (sessions.rend() == active_session) 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? + + 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? + + att->ll_data = llData; + return EResult::k_EResultOK; +} + + + +// --- steam callbacks + +void Steam_GameStats::steam_run_callback() +{ + // nothing +} + + + +// --- networking callbacks + +// user connect/disconnect +void Steam_GameStats::network_callback_low_level(Common_Message *msg) +{ + switch (msg->low_level().type()) + { + case Low_Level::CONNECT: + // nothing + break; + + case Low_Level::DISCONNECT: + // nothing + break; + + default: + PRINT_DEBUG("unknown type %i", (int)msg->low_level().type()); + break; + } +} diff --git a/sdk/steam/isteamgamestats.h b/sdk/steam/isteamgamestats.h new file mode 100644 index 00000000..309ea382 --- /dev/null +++ b/sdk/steam/isteamgamestats.h @@ -0,0 +1,75 @@ +//====== Copyright 1996-2008, Valve Corporation, All rights reserved. ======= +// +// Purpose: interface to steam for game play statistics +// +//============================================================================= + +#ifndef ISTEAMGAMESTATS_H +#define ISTEAMGAMESTATS_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// Purpose: Functions for recording game play sessions and details thereof +//----------------------------------------------------------------------------- +class ISteamGameStats +{ +public: + virtual SteamAPICall_t GetNewSession( int8 nAccountType, uint64 ulAccountID, int32 nAppID, RTime32 rtTimeStarted ) = 0; + virtual SteamAPICall_t EndSession( uint64 ulSessionID, RTime32 rtTimeEnded, int nReasonCode ) = 0; + virtual EResult AddSessionAttributeInt( uint64 ulSessionID, const char* pstrName, int32 nData ) = 0; + virtual EResult AddSessionAttributeString( uint64 ulSessionID, const char* pstrName, const char *pstrData ) = 0; + virtual EResult AddSessionAttributeFloat( uint64 ulSessionID, const char* pstrName, float fData ) = 0; + + virtual EResult AddNewRow( uint64 *pulRowID, uint64 ulSessionID, const char *pstrTableName ) = 0; + virtual EResult CommitRow( uint64 ulRowID ) = 0; + virtual EResult CommitOutstandingRows( uint64 ulSessionID ) = 0; + virtual EResult AddRowAttributeInt( uint64 ulRowID, const char *pstrName, int32 nData ) = 0; + virtual EResult AddRowAtributeString( uint64 ulRowID, const char *pstrName, const char *pstrData ) = 0; + virtual EResult AddRowAttributeFloat( uint64 ulRowID, const char *pstrName, float fData ) = 0; + + virtual EResult AddSessionAttributeInt64( uint64 ulSessionID, const char *pstrName, int64 llData ) = 0; + virtual EResult AddRowAttributeInt64( uint64 ulRowID, const char *pstrName, int64 llData ) = 0; +}; + +#define STEAMGAMESTATS_INTERFACE_VERSION "SteamGameStats001" + + +//----------------------------------------------------------------------------- +// Purpose: nAccountType for GetNewSession +//----------------------------------------------------------------------------- +enum EGameStatsAccountType +{ + k_EGameStatsAccountType_Steam = 1, // ullAccountID is a 64-bit SteamID for a player + k_EGameStatsAccountType_Xbox = 2, // ullAccountID is a 64-bit XUID + k_EGameStatsAccountType_SteamGameServer = 3, // ullAccountID is a 64-bit SteamID for a game server +}; + + +//----------------------------------------------------------------------------- +// Purpose: callback for GetNewSession() method +//----------------------------------------------------------------------------- +struct GameStatsSessionIssued_t +{ + enum { k_iCallback = k_iSteamGameStatsCallbacks + 1 }; + + uint64 m_ulSessionID; + EResult m_eResult; + bool m_bCollectingAny; + bool m_bCollectingDetails; +}; + + +//----------------------------------------------------------------------------- +// Purpose: callback for EndSession() method +//----------------------------------------------------------------------------- +struct GameStatsSessionClosed_t +{ + enum { k_iCallback = k_iSteamGameStatsCallbacks + 2 }; + + uint64 m_ulSessionID; + EResult m_eResult; +}; + +#endif // ISTEAMGAMESTATS_H diff --git a/sdk/steam/steam_api.h b/sdk/steam/steam_api.h index cf25ffd7..a41ea8fe 100644 --- a/sdk/steam/steam_api.h +++ b/sdk/steam/steam_api.h @@ -193,6 +193,18 @@ #include "isteamnetworkingutils.h" #include "isteamtv.h" #include "steamnetworkingfakeip.h" +#include "isteamgameserver.h" +#include "isteamgameserver014.h" +#include "isteamgameserver013.h" +#include "isteamgameserver012.h" +#include "isteamgameserver011.h" +#include "isteamgameserver010.h" +#include "isteamgameserver009.h" +#include "isteamgameserver008.h" +#include "isteamgameserver005.h" +#include "isteamgameserver004.h" +#include "isteamgameserverstats.h" +#include "isteamgamestats.h" //----------------------------------------------------------------------------------------------------------------------------------------------------------// diff --git a/sdk/steam/steam_gameserver.h b/sdk/steam/steam_gameserver.h index 82696e58..10e483fd 100644 --- a/sdk/steam/steam_gameserver.h +++ b/sdk/steam/steam_gameserver.h @@ -1,4 +1,4 @@ -//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= +//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= // // Purpose: // @@ -12,15 +12,6 @@ #include "steam_api.h" #include "isteamgameserver.h" -#include "isteamgameserver014.h" -#include "isteamgameserver013.h" -#include "isteamgameserver012.h" -#include "isteamgameserver011.h" -#include "isteamgameserver010.h" -#include "isteamgameserver009.h" -#include "isteamgameserver008.h" -#include "isteamgameserver005.h" -#include "isteamgameserver004.h" #include "isteamgameserverstats.h" enum EServerMode @@ -65,11 +56,11 @@ const uint16 STEAMGAMESERVER_QUERY_PORT_SHARED = 0xffff; // server is out of date. (Only servers with the latest version will be listed.) #ifndef STEAM_API_EXPORTS S_API steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -S_API ESteamAPIInitResult SteamGameServer_InitEx( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, SteamErrMsg *pOutErrMsg ); +S_API ESteamAPIInitResult SteamGameServer_InitEx( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, SteamErrMsg *pOutErrMsg ); #endif - + S_API steam_bool S_CALLTYPE SteamInternal_GameServer_Init( uint32 unIP, uint16 usPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ); -S_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, const char *pszInternalCheckInterfaceVersions, SteamErrMsg *pOutErrMsg ); +S_API ESteamAPIInitResult S_CALLTYPE SteamInternal_GameServer_Init_V2( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, const char *pszInternalCheckInterfaceVersions, SteamErrMsg *pOutErrMsg ); // Shutdown SteamGameSeverXxx interfaces, log out, and free resources. S_API void SteamGameServer_Shutdown(); @@ -123,31 +114,31 @@ inline bool CSteamGameServerAPIContext::Init() } -inline steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) +inline steam_bool SteamGameServer_Init( uint32 unIP, uint16 usSteamPort, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString ) { if ( !SteamInternal_GameServer_Init( unIP, usSteamPort, usGamePort, usQueryPort, eServerMode, pchVersionString ) ) return false; return true; } - -inline ESteamAPIInitResult SteamGameServer_InitEx( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, SteamErrMsg *pOutErrMsg ) -{ - const char *pszInternalCheckInterfaceVersions = - STEAMUTILS_INTERFACE_VERSION "\0" - STEAMNETWORKINGUTILS_INTERFACE_VERSION "\0" - - STEAMGAMESERVER_INTERFACE_VERSION "\0" - STEAMGAMESERVERSTATS_INTERFACE_VERSION "\0" - STEAMHTTP_INTERFACE_VERSION "\0" - STEAMINVENTORY_INTERFACE_VERSION "\0" - STEAMNETWORKING_INTERFACE_VERSION "\0" - STEAMNETWORKINGMESSAGES_INTERFACE_VERSION "\0" - STEAMNETWORKINGSOCKETS_INTERFACE_VERSION "\0" - STEAMUGC_INTERFACE_VERSION "\0" - "\0"; - return SteamInternal_GameServer_Init_V2( unIP, usGamePort, usQueryPort, eServerMode, pchVersionString, pszInternalCheckInterfaceVersions, pOutErrMsg ); -} + +inline ESteamAPIInitResult SteamGameServer_InitEx( uint32 unIP, uint16 usGamePort, uint16 usQueryPort, EServerMode eServerMode, const char *pchVersionString, SteamErrMsg *pOutErrMsg ) +{ + const char *pszInternalCheckInterfaceVersions = + STEAMUTILS_INTERFACE_VERSION "\0" + STEAMNETWORKINGUTILS_INTERFACE_VERSION "\0" + + STEAMGAMESERVER_INTERFACE_VERSION "\0" + STEAMGAMESERVERSTATS_INTERFACE_VERSION "\0" + STEAMHTTP_INTERFACE_VERSION "\0" + STEAMINVENTORY_INTERFACE_VERSION "\0" + STEAMNETWORKING_INTERFACE_VERSION "\0" + STEAMNETWORKINGMESSAGES_INTERFACE_VERSION "\0" + STEAMNETWORKINGSOCKETS_INTERFACE_VERSION "\0" + STEAMUGC_INTERFACE_VERSION "\0" + "\0"; + return SteamInternal_GameServer_Init_V2( unIP, usGamePort, usQueryPort, eServerMode, pchVersionString, pszInternalCheckInterfaceVersions, pOutErrMsg ); +} inline void SteamGameServer_ReleaseCurrentThreadMemory() { SteamAPI_ReleaseCurrentThreadMemory();