From d1d5416d628945d65f59343f4f709d50c0a804dc Mon Sep 17 00:00:00 2001
From: otavepto <153766569+otavepto@users.noreply.github.com>
Date: Fri, 14 Jun 2024 17:57:04 +0300
Subject: [PATCH] implement isteamgamestats
---
dll/dll/steam_client.h | 5 +
dll/dll/steam_gamestats.h | 117 +++++++
dll/steam_client.cpp | 2 +
dll/steam_client_interface_getter.cpp | 15 +
dll/steam_gamestats.cpp | 474 ++++++++++++++++++++++++++
sdk/steam/isteamgamestats.h | 75 ++++
sdk/steam/steam_api.h | 12 +
sdk/steam/steam_gameserver.h | 55 ++-
8 files changed, 723 insertions(+), 32 deletions(-)
create mode 100644 dll/dll/steam_gamestats.h
create mode 100644 dll/steam_gamestats.cpp
create mode 100644 sdk/steam/isteamgamestats.h
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();